Merge from vendor branch NTPD:
[dragonfly.git] / crypto / heimdal-0.6.3 / lib / hdb / hdb-ldap.c
1 /*
2  * Copyright (c) 1999-2001, PADL Software Pty Ltd.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of PADL Software  nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32
33 #include "hdb_locl.h"
34
35 RCSID("$Id: hdb-ldap.c,v 1.10.4.1 2003/09/18 20:49:09 lha Exp $");
36
37 #ifdef OPENLDAP
38
39 #include <lber.h>
40 #include <ldap.h>
41 #include <ctype.h>
42 #include <sys/un.h>
43
44 static krb5_error_code LDAP__connect(krb5_context context, HDB * db);
45
46 static krb5_error_code
47 LDAP_message2entry(krb5_context context, HDB * db, LDAPMessage * msg,
48                    hdb_entry * ent);
49
50 static char *krb5kdcentry_attrs[] =
51     { "krb5PrincipalName", "cn", "krb5PrincipalRealm",
52     "krb5KeyVersionNumber", "krb5Key",
53     "krb5ValidStart", "krb5ValidEnd", "krb5PasswordEnd",
54     "krb5MaxLife", "krb5MaxRenew", "krb5KDCFlags", "krb5EncryptionType",
55     "modifiersName", "modifyTimestamp", "creatorsName", "createTimestamp",
56     NULL
57 };
58
59 static char *krb5principal_attrs[] =
60     { "krb5PrincipalName", "cn", "krb5PrincipalRealm",
61     "modifiersName", "modifyTimestamp", "creatorsName", "createTimestamp",
62     NULL
63 };
64
65 static krb5_error_code
66 LDAP__setmod(LDAPMod *** modlist, int modop, const char *attribute,
67         int *pIndex)
68 {
69     int cMods;
70
71     if (*modlist == NULL) {
72         *modlist = (LDAPMod **)ber_memcalloc(1, sizeof(LDAPMod *));
73         if (*modlist == NULL) {
74             return ENOMEM;
75         }
76     }
77
78     for (cMods = 0; (*modlist)[cMods] != NULL; cMods++) {
79         if ((*modlist)[cMods]->mod_op == modop &&
80             strcasecmp((*modlist)[cMods]->mod_type, attribute) == 0) {
81             break;
82         }
83     }
84
85     *pIndex = cMods;
86
87     if ((*modlist)[cMods] == NULL) {
88         LDAPMod *mod;
89
90         *modlist = (LDAPMod **)ber_memrealloc(*modlist,
91                                               (cMods + 2) * sizeof(LDAPMod *));
92         if (*modlist == NULL) {
93             return ENOMEM;
94         }
95         (*modlist)[cMods] = (LDAPMod *)ber_memalloc(sizeof(LDAPMod));
96         if ((*modlist)[cMods] == NULL) {
97             return ENOMEM;
98         }
99
100         mod = (*modlist)[cMods];
101         mod->mod_op = modop;
102         mod->mod_type = ber_strdup(attribute);
103         if (mod->mod_type == NULL) {
104             ber_memfree(mod);
105             (*modlist)[cMods] = NULL;
106             return ENOMEM;
107         }
108
109         if (modop & LDAP_MOD_BVALUES) {
110             mod->mod_bvalues = NULL;
111         } else {
112             mod->mod_values = NULL;
113         }
114
115         (*modlist)[cMods + 1] = NULL;
116     }
117
118     return 0;
119 }
120
121 static krb5_error_code
122 LDAP_addmod_len(LDAPMod *** modlist, int modop, const char *attribute,
123                 unsigned char *value, size_t len)
124 {
125     int cMods, cValues = 0;
126     krb5_error_code ret;
127
128     ret = LDAP__setmod(modlist, modop | LDAP_MOD_BVALUES, attribute, &cMods);
129     if (ret != 0) {
130         return ret;
131     }
132
133     if (value != NULL) {
134         struct berval *bValue;
135         struct berval ***pbValues = &((*modlist)[cMods]->mod_bvalues);
136
137         if (*pbValues != NULL) {
138             for (cValues = 0; (*pbValues)[cValues] != NULL; cValues++)
139                 ;
140             *pbValues = (struct berval **)ber_memrealloc(*pbValues, (cValues + 2)
141                                                          * sizeof(struct berval *));
142         } else {
143             *pbValues = (struct berval **)ber_memalloc(2 * sizeof(struct berval *));
144         }
145         if (*pbValues == NULL) {
146             return ENOMEM;
147         }
148         (*pbValues)[cValues] = (struct berval *)ber_memalloc(sizeof(struct berval));;
149         if ((*pbValues)[cValues] == NULL) {
150             return ENOMEM;
151         }
152
153         bValue = (*pbValues)[cValues];
154         bValue->bv_val = value;
155         bValue->bv_len = len;
156
157         (*pbValues)[cValues + 1] = NULL;
158     }
159
160     return 0;
161 }
162
163 static krb5_error_code
164 LDAP_addmod(LDAPMod *** modlist, int modop, const char *attribute,
165             const char *value)
166 {
167     int cMods, cValues = 0;
168     krb5_error_code ret;
169
170     ret = LDAP__setmod(modlist, modop, attribute, &cMods);
171     if (ret != 0) {
172         return ret;
173     }
174
175     if (value != NULL) {
176         char ***pValues = &((*modlist)[cMods]->mod_values);
177
178         if (*pValues != NULL) {
179             for (cValues = 0; (*pValues)[cValues] != NULL; cValues++)
180                 ;
181             *pValues = (char **)ber_memrealloc(*pValues, (cValues + 2) * sizeof(char *));
182         } else {
183             *pValues = (char **)ber_memalloc(2 * sizeof(char *));
184         }
185         if (*pValues == NULL) {
186             return ENOMEM;
187         }
188         (*pValues)[cValues] = ber_strdup(value);
189         if ((*pValues)[cValues] == NULL) {
190             return ENOMEM;
191         }
192         (*pValues)[cValues + 1] = NULL;
193     }
194
195     return 0;
196 }
197
198 static krb5_error_code
199 LDAP_addmod_generalized_time(LDAPMod *** mods, int modop,
200                              const char *attribute, KerberosTime * time)
201 {
202     char buf[22];
203     struct tm *tm;
204
205     /* XXX not threadsafe */
206     tm = gmtime(time);
207     strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", tm);
208
209     return LDAP_addmod(mods, modop, attribute, buf);
210 }
211
212 static krb5_error_code
213 LDAP_get_string_value(HDB * db, LDAPMessage * entry,
214                       const char *attribute, char **ptr)
215 {
216     char **vals;
217     int ret;
218
219     vals = ldap_get_values((LDAP *) db->db, entry, (char *) attribute);
220     if (vals == NULL) {
221         return HDB_ERR_NOENTRY;
222     }
223     *ptr = strdup(vals[0]);
224     if (*ptr == NULL) {
225         ret = ENOMEM;
226     } else {
227         ret = 0;
228     }
229
230     ldap_value_free(vals);
231
232     return ret;
233 }
234
235 static krb5_error_code
236 LDAP_get_integer_value(HDB * db, LDAPMessage * entry,
237                        const char *attribute, int *ptr)
238 {
239     char **vals;
240
241     vals = ldap_get_values((LDAP *) db->db, entry, (char *) attribute);
242     if (vals == NULL) {
243         return HDB_ERR_NOENTRY;
244     }
245     *ptr = atoi(vals[0]);
246     ldap_value_free(vals);
247     return 0;
248 }
249
250 static krb5_error_code
251 LDAP_get_generalized_time_value(HDB * db, LDAPMessage * entry,
252                                 const char *attribute, KerberosTime * kt)
253 {
254     char *tmp, *gentime;
255     struct tm tm;
256     int ret;
257
258     *kt = 0;
259
260     ret = LDAP_get_string_value(db, entry, attribute, &gentime);
261     if (ret != 0) {
262         return ret;
263     }
264
265     tmp = strptime(gentime, "%Y%m%d%H%M%SZ", &tm);
266     if (tmp == NULL) {
267         free(gentime);
268         return HDB_ERR_NOENTRY;
269     }
270
271     free(gentime);
272
273     *kt = timegm(&tm);
274
275     return 0;
276 }
277
278 static krb5_error_code
279 LDAP_entry2mods(krb5_context context, HDB * db, hdb_entry * ent,
280                 LDAPMessage * msg, LDAPMod *** pmods)
281 {
282     krb5_error_code ret;
283     krb5_boolean is_new_entry;
284     int rc, i;
285     char *tmp = NULL;
286     LDAPMod **mods = NULL;
287     hdb_entry orig;
288     unsigned long oflags, nflags;
289
290     if (msg != NULL) {
291         ret = LDAP_message2entry(context, db, msg, &orig);
292         if (ret != 0) {
293             goto out;
294         }
295         is_new_entry = FALSE;
296     } else {
297         /* to make it perfectly obvious we're depending on
298          * orig being intiialized to zero */
299         memset(&orig, 0, sizeof(orig));
300         is_new_entry = TRUE;
301     }
302
303     if (is_new_entry) {
304         ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass", "top");
305         if (ret != 0) {
306             goto out;
307         }
308         /* person is the structural object class */
309         ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass", "person");
310         if (ret != 0) {
311             goto out;
312         }
313         ret =
314             LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass",
315                         "krb5Principal");
316         if (ret != 0) {
317             goto out;
318         }
319         ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass",
320                           "krb5KDCEntry");
321         if (ret != 0) {
322             goto out;
323         }
324     }
325
326     if (is_new_entry ||
327         krb5_principal_compare(context, ent->principal, orig.principal) ==
328         FALSE) {
329         ret = krb5_unparse_name(context, ent->principal, &tmp);
330         if (ret != 0) {
331             goto out;
332         }
333         ret =
334             LDAP_addmod(&mods, LDAP_MOD_REPLACE, "krb5PrincipalName", tmp);
335         if (ret != 0) {
336             free(tmp);
337             goto out;
338         }
339         free(tmp);
340     }
341
342     if (ent->kvno != orig.kvno) {
343         rc = asprintf(&tmp, "%d", ent->kvno);
344         if (rc < 0) {
345             krb5_set_error_string(context, "asprintf: out of memory");
346             ret = ENOMEM;
347             goto out;
348         }
349         ret =
350             LDAP_addmod(&mods, LDAP_MOD_REPLACE, "krb5KeyVersionNumber",
351                         tmp);
352         free(tmp);
353         if (ret != 0) {
354             goto out;
355         }
356     }
357
358     if (ent->valid_start) {
359         if (orig.valid_end == NULL
360             || (*(ent->valid_start) != *(orig.valid_start))) {
361             ret =
362                 LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
363                                              "krb5ValidStart",
364                                              ent->valid_start);
365             if (ret != 0) {
366                 goto out;
367             }
368         }
369     }
370
371     if (ent->valid_end) {
372         if (orig.valid_end == NULL
373             || (*(ent->valid_end) != *(orig.valid_end))) {
374             ret =
375                 LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
376                                              "krb5ValidEnd",
377                                              ent->valid_end);
378             if (ret != 0) {
379                 goto out;
380             }
381         }
382     }
383
384     if (ent->pw_end) {
385         if (orig.pw_end == NULL || (*(ent->pw_end) != *(orig.pw_end))) {
386             ret =
387                 LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
388                                              "krb5PasswordEnd",
389                                              ent->pw_end);
390             if (ret != 0) {
391                 goto out;
392             }
393         }
394     }
395
396     if (ent->max_life) {
397         if (orig.max_life == NULL
398             || (*(ent->max_life) != *(orig.max_life))) {
399             rc = asprintf(&tmp, "%d", *(ent->max_life));
400             if (rc < 0) {
401                 krb5_set_error_string(context, "asprintf: out of memory");
402                 ret = ENOMEM;
403                 goto out;
404             }
405             ret = LDAP_addmod(&mods, LDAP_MOD_REPLACE, "krb5MaxLife", tmp);
406             free(tmp);
407             if (ret != 0) {
408                 goto out;
409             }
410         }
411     }
412
413     if (ent->max_renew) {
414         if (orig.max_renew == NULL
415             || (*(ent->max_renew) != *(orig.max_renew))) {
416             rc = asprintf(&tmp, "%d", *(ent->max_renew));
417             if (rc < 0) {
418                 krb5_set_error_string(context, "asprintf: out of memory");
419                 ret = ENOMEM;
420                 goto out;
421             }
422             ret =
423                 LDAP_addmod(&mods, LDAP_MOD_REPLACE, "krb5MaxRenew", tmp);
424             free(tmp);
425             if (ret != 0) {
426                 goto out;
427             }
428         }
429     }
430
431     oflags = HDBFlags2int(orig.flags);
432     nflags = HDBFlags2int(ent->flags);
433
434     if (oflags != nflags) {
435         rc = asprintf(&tmp, "%lu", nflags);
436         if (rc < 0) {
437             krb5_set_error_string(context, "asprintf: out of memory");
438             ret = ENOMEM;
439             goto out;
440         }
441         ret = LDAP_addmod(&mods, LDAP_MOD_REPLACE, "krb5KDCFlags", tmp);
442         free(tmp);
443         if (ret != 0) {
444             goto out;
445         }
446     }
447
448     if (is_new_entry == FALSE && orig.keys.len > 0) {
449         /* for the moment, clobber and replace keys. */
450         ret = LDAP_addmod(&mods, LDAP_MOD_DELETE, "krb5Key", NULL);
451         if (ret != 0) {
452             goto out;
453         }
454     }
455
456     for (i = 0; i < ent->keys.len; i++) {
457         unsigned char *buf;
458         size_t len;
459
460         ASN1_MALLOC_ENCODE(Key, buf, len, &ent->keys.val[i], &len, ret);
461         if (ret != 0)
462             goto out;
463
464         /* addmod_len _owns_ the key, doesn't need to copy it */
465         ret = LDAP_addmod_len(&mods, LDAP_MOD_ADD, "krb5Key", buf, len);
466         if (ret != 0) {
467             goto out;
468         }
469     }
470
471     if (ent->etypes) {
472         /* clobber and replace encryption types. */
473         if (is_new_entry == FALSE) {
474             ret =
475                 LDAP_addmod(&mods, LDAP_MOD_DELETE, "krb5EncryptionType",
476                             NULL);
477         }
478         for (i = 0; i < ent->etypes->len; i++) {
479             rc = asprintf(&tmp, "%d", ent->etypes->val[i]);
480             if (rc < 0) {
481                 krb5_set_error_string(context, "asprintf: out of memory");
482                 ret = ENOMEM;
483                 goto out;
484             }
485             free(tmp);
486             ret =
487                 LDAP_addmod(&mods, LDAP_MOD_ADD, "krb5EncryptionType",
488                             tmp);
489             if (ret != 0) {
490                 goto out;
491             }
492         }
493     }
494
495     /* for clarity */
496     ret = 0;
497
498   out:
499
500     if (ret == 0) {
501         *pmods = mods;
502     } else if (mods != NULL) {
503         ldap_mods_free(mods, 1);
504         *pmods = NULL;
505     }
506
507     if (msg != NULL) {
508         hdb_free_entry(context, &orig);
509     }
510
511     return ret;
512 }
513
514 static krb5_error_code
515 LDAP_dn2principal(krb5_context context, HDB * db, const char *dn,
516                   krb5_principal * principal)
517 {
518     krb5_error_code ret;
519     int rc, limit = 1;
520     char **values;
521     LDAPMessage *res = NULL, *e;
522
523     rc = ldap_set_option((LDAP *) db->db, LDAP_OPT_SIZELIMIT, (const void *)&limit);
524     if (rc != LDAP_SUCCESS) {
525         krb5_set_error_string(context, "ldap_set_option: %s", ldap_err2string(rc));
526         ret = HDB_ERR_BADVERSION;
527         goto out;
528      }
529
530     rc = ldap_search_s((LDAP *) db->db, dn, LDAP_SCOPE_BASE,
531                        "(objectclass=krb5Principal)", krb5principal_attrs,
532                        0, &res);
533     if (rc != LDAP_SUCCESS) {
534         krb5_set_error_string(context, "ldap_search_s: %s", ldap_err2string(rc));
535         ret = HDB_ERR_NOENTRY;
536         goto out;
537     }
538
539     e = ldap_first_entry((LDAP *) db->db, res);
540     if (e == NULL) {
541         ret = HDB_ERR_NOENTRY;
542         goto out;
543     }
544
545     values = ldap_get_values((LDAP *) db->db, e, "krb5PrincipalName");
546     if (values == NULL) {
547         ret = HDB_ERR_NOENTRY;
548         goto out;
549     }
550
551     ret = krb5_parse_name(context, values[0], principal);
552     ldap_value_free(values);
553
554   out:
555     if (res != NULL) {
556         ldap_msgfree(res);
557     }
558     return ret;
559 }
560
561 static krb5_error_code
562 LDAP__lookup_princ(krb5_context context, HDB * db, const char *princname,
563                    LDAPMessage ** msg)
564 {
565     krb5_error_code ret;
566     int rc, limit = 1;
567     char *filter = NULL;
568
569     (void) LDAP__connect(context, db);
570
571     rc =
572         asprintf(&filter,
573                  "(&(objectclass=krb5KDCEntry)(krb5PrincipalName=%s))",
574                  princname);
575     if (rc < 0) {
576         krb5_set_error_string(context, "asprintf: out of memory");
577         ret = ENOMEM;
578         goto out;
579     }
580
581     rc = ldap_set_option((LDAP *) db->db, LDAP_OPT_SIZELIMIT, (const void *)&limit);
582     if (rc != LDAP_SUCCESS) {
583         krb5_set_error_string(context, "ldap_set_option: %s", ldap_err2string(rc));
584         ret = HDB_ERR_BADVERSION;
585         goto out;
586     }
587
588     rc = ldap_search_s((LDAP *) db->db, db->name, LDAP_SCOPE_ONELEVEL, filter, 
589                        krb5kdcentry_attrs, 0, msg);
590     if (rc != LDAP_SUCCESS) {
591         krb5_set_error_string(context, "ldap_search_s: %s", ldap_err2string(rc));
592         ret = HDB_ERR_NOENTRY;
593         goto out;
594     }
595
596     ret = 0;
597
598   out:
599     if (filter != NULL) {
600         free(filter);
601     }
602     return ret;
603 }
604
605 static krb5_error_code
606 LDAP_principal2message(krb5_context context, HDB * db,
607                        krb5_principal princ, LDAPMessage ** msg)
608 {
609     char *princname = NULL;
610     krb5_error_code ret;
611
612     ret = krb5_unparse_name(context, princ, &princname);
613     if (ret != 0) {
614         return ret;
615     }
616
617     ret = LDAP__lookup_princ(context, db, princname, msg);
618     free(princname);
619
620     return ret;
621 }
622
623 /*
624  * Construct an hdb_entry from a directory entry.
625  */
626 static krb5_error_code
627 LDAP_message2entry(krb5_context context, HDB * db, LDAPMessage * msg,
628                    hdb_entry * ent)
629 {
630     char *unparsed_name = NULL, *dn = NULL;
631     int ret;
632     unsigned long tmp;
633     struct berval **keys;
634     char **values;
635
636     memset(ent, 0, sizeof(*ent));
637     ent->flags = int2HDBFlags(0);
638
639     ret =
640         LDAP_get_string_value(db, msg, "krb5PrincipalName",
641                               &unparsed_name);
642     if (ret != 0) {
643         return ret;
644     }
645
646     ret = krb5_parse_name(context, unparsed_name, &ent->principal);
647     if (ret != 0) {
648         goto out;
649     }
650
651     ret =
652         LDAP_get_integer_value(db, msg, "krb5KeyVersionNumber",
653                                &ent->kvno);
654     if (ret != 0) {
655         ent->kvno = 0;
656     }
657
658     keys = ldap_get_values_len((LDAP *) db->db, msg, "krb5Key");
659     if (keys != NULL) {
660         int i;
661         size_t l;
662
663         ent->keys.len = ldap_count_values_len(keys);
664         ent->keys.val = (Key *) calloc(ent->keys.len, sizeof(Key));
665         if (ent->keys.val == NULL) {
666             krb5_set_error_string(context, "calloc: out of memory");
667             ret = ENOMEM;
668             goto out;
669         }
670         for (i = 0; i < ent->keys.len; i++) {
671             decode_Key((unsigned char *) keys[i]->bv_val,
672                        (size_t) keys[i]->bv_len, &ent->keys.val[i], &l);
673         }
674         ber_bvecfree(keys);
675     } else {
676 #if 1
677         /*
678          * This violates the ASN1 but it allows a principal to
679          * be related to a general directory entry without creating
680          * the keys. Hopefully it's OK.
681          */
682         ent->keys.len = 0;
683         ent->keys.val = NULL;
684 #else
685         ret = HDB_ERR_NOENTRY;
686         goto out;
687 #endif
688     }
689
690     ret =
691         LDAP_get_generalized_time_value(db, msg, "createTimestamp",
692                                         &ent->created_by.time);
693     if (ret != 0) {
694         ent->created_by.time = time(NULL);
695     }
696
697     ent->created_by.principal = NULL;
698
699     ret = LDAP_get_string_value(db, msg, "creatorsName", &dn);
700     if (ret == 0) {
701         if (LDAP_dn2principal(context, db, dn, &ent->created_by.principal)
702             != 0) {
703             ent->created_by.principal = NULL;
704         }
705         free(dn);
706     }
707
708     ent->modified_by = (Event *) malloc(sizeof(Event));
709     if (ent->modified_by == NULL) {
710         krb5_set_error_string(context, "malloc: out of memory");
711         ret = ENOMEM;
712         goto out;
713     }
714     ret =
715         LDAP_get_generalized_time_value(db, msg, "modifyTimestamp",
716                                         &ent->modified_by->time);
717     if (ret == 0) {
718         ret = LDAP_get_string_value(db, msg, "modifiersName", &dn);
719         if (LDAP_dn2principal
720             (context, db, dn, &ent->modified_by->principal) != 0) {
721             ent->modified_by->principal = NULL;
722         }
723         free(dn);
724     } else {
725         free(ent->modified_by);
726         ent->modified_by = NULL;
727     }
728
729     if ((ent->valid_start = (KerberosTime *) malloc(sizeof(KerberosTime)))
730         == NULL) {
731         krb5_set_error_string(context, "malloc: out of memory");
732         ret = ENOMEM;
733         goto out;
734     }
735     ret =
736         LDAP_get_generalized_time_value(db, msg, "krb5ValidStart",
737                                         ent->valid_start);
738     if (ret != 0) {
739         /* OPTIONAL */
740         free(ent->valid_start);
741         ent->valid_start = NULL;
742     }
743
744     if ((ent->valid_end = (KerberosTime *) malloc(sizeof(KerberosTime))) ==
745         NULL) {
746         krb5_set_error_string(context, "malloc: out of memory");
747         ret = ENOMEM;
748         goto out;
749     }
750     ret =
751         LDAP_get_generalized_time_value(db, msg, "krb5ValidEnd",
752                                         ent->valid_end);
753     if (ret != 0) {
754         /* OPTIONAL */
755         free(ent->valid_end);
756         ent->valid_end = NULL;
757     }
758
759     if ((ent->pw_end = (KerberosTime *) malloc(sizeof(KerberosTime))) ==
760         NULL) {
761         krb5_set_error_string(context, "malloc: out of memory");
762         ret = ENOMEM;
763         goto out;
764     }
765     ret =
766         LDAP_get_generalized_time_value(db, msg, "krb5PasswordEnd",
767                                         ent->pw_end);
768     if (ret != 0) {
769         /* OPTIONAL */
770         free(ent->pw_end);
771         ent->pw_end = NULL;
772     }
773
774     ent->max_life = (int *) malloc(sizeof(int));
775     if (ent->max_life == NULL) {
776         krb5_set_error_string(context, "malloc: out of memory");
777         ret = ENOMEM;
778         goto out;
779     }
780     ret = LDAP_get_integer_value(db, msg, "krb5MaxLife", ent->max_life);
781     if (ret != 0) {
782         free(ent->max_life);
783         ent->max_life = NULL;
784     }
785
786     ent->max_renew = (int *) malloc(sizeof(int));
787     if (ent->max_renew == NULL) {
788         krb5_set_error_string(context, "malloc: out of memory");
789         ret = ENOMEM;
790         goto out;
791     }
792     ret = LDAP_get_integer_value(db, msg, "krb5MaxRenew", ent->max_renew);
793     if (ret != 0) {
794         free(ent->max_renew);
795         ent->max_renew = NULL;
796     }
797
798     values = ldap_get_values((LDAP *) db->db, msg, "krb5KDCFlags");
799     if (values != NULL) {
800         tmp = strtoul(values[0], (char **) NULL, 10);
801         if (tmp == ULONG_MAX && errno == ERANGE) {
802             krb5_set_error_string(context, "strtoul: could not convert flag");
803             ret = ERANGE;
804             goto out;
805         }
806     } else {
807         tmp = 0;
808     }
809     ent->flags = int2HDBFlags(tmp);
810
811     values = ldap_get_values((LDAP *) db->db, msg, "krb5EncryptionType");
812     if (values != NULL) {
813         int i;
814
815         ent->etypes = malloc(sizeof(*(ent->etypes)));
816         if (ent->etypes == NULL) {
817             krb5_set_error_string(context, "malloc: out of memory");
818             ret = ENOMEM;
819             goto out;
820         }
821         ent->etypes->len = ldap_count_values(values);
822         ent->etypes->val = calloc(ent->etypes->len, sizeof(int));
823         for (i = 0; i < ent->etypes->len; i++) {
824             ent->etypes->val[i] = atoi(values[i]);
825         }
826         ldap_value_free(values);
827     }
828
829     ret = 0;
830
831   out:
832     if (unparsed_name != NULL) {
833         free(unparsed_name);
834     }
835
836     if (ret != 0) {
837         /* I don't think this frees ent itself. */
838         hdb_free_entry(context, ent);
839     }
840
841     return ret;
842 }
843
844 static krb5_error_code LDAP_close(krb5_context context, HDB * db)
845 {
846     ldap_unbind_ext((LDAP *) db->db, NULL, NULL);
847     db->db = NULL;
848
849     return 0;
850 }
851
852 static krb5_error_code
853 LDAP_lock(krb5_context context, HDB * db, int operation)
854 {
855     return 0;
856 }
857
858 static krb5_error_code LDAP_unlock(krb5_context context, HDB * db)
859 {
860     return 0;
861 }
862
863 static krb5_error_code
864 LDAP_seq(krb5_context context, HDB * db, unsigned flags, hdb_entry * entry)
865 {
866     int msgid, rc, parserc;
867     krb5_error_code ret;
868     LDAPMessage *e;
869
870     msgid = db->openp;          /* BOGUS OVERLOADING */
871     if (msgid < 0) {
872         return HDB_ERR_NOENTRY;
873     }
874
875     do {
876         rc = ldap_result((LDAP *) db->db, msgid, LDAP_MSG_ONE, NULL, &e);
877         switch (rc) {
878         case LDAP_RES_SEARCH_ENTRY:
879             /* We have an entry. Parse it. */
880             ret = LDAP_message2entry(context, db, e, entry);
881             ldap_msgfree(e);
882             break;
883         case LDAP_RES_SEARCH_RESULT:
884             /* We're probably at the end of the results. If not, abandon. */
885             parserc =
886                 ldap_parse_result((LDAP *) db->db, e, NULL, NULL, NULL,
887                                   NULL, NULL, 1);
888             if (parserc != LDAP_SUCCESS
889                 && parserc != LDAP_MORE_RESULTS_TO_RETURN) {
890                 krb5_set_error_string(context, "ldap_parse_result: %s", ldap_err2string(parserc));
891                 ldap_abandon((LDAP *) db->db, msgid);
892             }
893             ret = HDB_ERR_NOENTRY;
894             db->openp = -1;
895             break;
896         case 0:
897         case -1:
898         default:
899             /* Some unspecified error (timeout?). Abandon. */
900             ldap_msgfree(e);
901             ldap_abandon((LDAP *) db->db, msgid);
902             ret = HDB_ERR_NOENTRY;
903             db->openp = -1;
904             break;
905         }
906     } while (rc == LDAP_RES_SEARCH_REFERENCE);
907
908     if (ret == 0) {
909         if (db->master_key_set && (flags & HDB_F_DECRYPT)) {
910             ret = hdb_unseal_keys(context, db, entry);
911             if (ret)
912                 hdb_free_entry(context,entry);
913         }
914     }
915
916     return ret;
917 }
918
919 static krb5_error_code
920 LDAP_firstkey(krb5_context context, HDB * db, unsigned flags,
921               hdb_entry * entry)
922 {
923     int msgid, limit = LDAP_NO_LIMIT, rc;
924
925     (void) LDAP__connect(context, db);
926
927     rc = ldap_set_option((LDAP *) db->db, LDAP_OPT_SIZELIMIT, (const void *)&limit);
928     if (rc != LDAP_SUCCESS) {
929         krb5_set_error_string(context, "ldap_set_option: %s", ldap_err2string(rc));
930         return HDB_ERR_BADVERSION;
931     }
932
933     msgid = ldap_search((LDAP *) db->db, db->name,
934                         LDAP_SCOPE_ONELEVEL, "(objectclass=krb5KDCEntry)",
935                         krb5kdcentry_attrs, 0);
936     if (msgid < 0) {
937         return HDB_ERR_NOENTRY;
938     }
939
940     db->openp = msgid;
941
942     return LDAP_seq(context, db, flags, entry);
943 }
944
945 static krb5_error_code
946 LDAP_nextkey(krb5_context context, HDB * db, unsigned flags,
947              hdb_entry * entry)
948 {
949     return LDAP_seq(context, db, flags, entry);
950 }
951
952 static krb5_error_code
953 LDAP_rename(krb5_context context, HDB * db, const char *new_name)
954 {
955     return HDB_ERR_DB_INUSE;
956 }
957
958 static krb5_error_code LDAP__connect(krb5_context context, HDB * db)
959 {
960     int rc, version = LDAP_VERSION3;
961     /*
962      * Empty credentials to do a SASL bind with LDAP. Note that empty
963      * different from NULL credentials. If you provide NULL
964      * credentials instead of empty credentials you will get a SASL
965      * bind in progress message.
966      */
967     struct berval bv = { 0, "" };
968
969     if (db->db != NULL) {
970         /* connection has been opened. ping server. */
971         struct sockaddr_un addr;
972         socklen_t len;
973         int sd;
974
975         if (ldap_get_option((LDAP *) db->db, LDAP_OPT_DESC, &sd) == 0 &&
976             getpeername(sd, (struct sockaddr *) &addr, &len) < 0) {
977             /* the other end has died. reopen. */
978             LDAP_close(context, db);
979         }
980     }
981
982     if (db->db != NULL) {
983         /* server is UP */
984         return 0;
985     }
986
987     rc = ldap_initialize((LDAP **) & db->db, "ldapi:///");
988     if (rc != LDAP_SUCCESS) {
989         krb5_set_error_string(context, "ldap_initialize: %s", ldap_err2string(rc));
990         return HDB_ERR_NOENTRY;
991     }
992
993     rc = ldap_set_option((LDAP *) db->db, LDAP_OPT_PROTOCOL_VERSION, (const void *)&version);
994     if (rc != LDAP_SUCCESS) {
995         krb5_set_error_string(context, "ldap_set_option: %s", ldap_err2string(rc));
996         ldap_unbind_ext((LDAP *) db->db, NULL, NULL);
997         db->db = NULL;
998         return HDB_ERR_BADVERSION;
999     }
1000
1001     rc = ldap_sasl_bind_s((LDAP *) db->db, NULL, "EXTERNAL", &bv, NULL, NULL, NULL);
1002     if (rc != LDAP_SUCCESS) {
1003         krb5_set_error_string(context, "ldap_sasl_bind_s: %s", ldap_err2string(rc));
1004         ldap_unbind_ext((LDAP *) db->db, NULL, NULL);
1005         db->db = NULL;
1006         return HDB_ERR_BADVERSION;
1007     }
1008
1009     return 0;
1010 }
1011
1012 static krb5_error_code
1013 LDAP_open(krb5_context context, HDB * db, int flags, mode_t mode)
1014 {
1015     /* Not the right place for this. */
1016 #ifdef HAVE_SIGACTION
1017     struct sigaction sa;
1018
1019     sa.sa_flags = 0;
1020     sa.sa_handler = SIG_IGN;
1021     sigemptyset(&sa.sa_mask);
1022
1023     sigaction(SIGPIPE, &sa, NULL);
1024 #else
1025     signal(SIGPIPE, SIG_IGN);
1026 #endif /* HAVE_SIGACTION */
1027
1028     return LDAP__connect(context, db);
1029 }
1030
1031 static krb5_error_code
1032 LDAP_fetch(krb5_context context, HDB * db, unsigned flags,
1033            hdb_entry * entry)
1034 {
1035     LDAPMessage *msg, *e;
1036     krb5_error_code ret;
1037
1038     ret = LDAP_principal2message(context, db, entry->principal, &msg);
1039     if (ret != 0) {
1040         return ret;
1041     }
1042
1043     e = ldap_first_entry((LDAP *) db->db, msg);
1044     if (e == NULL) {
1045         ret = HDB_ERR_NOENTRY;
1046         goto out;
1047     }
1048
1049     ret = LDAP_message2entry(context, db, e, entry);
1050     if (ret == 0) {
1051         if (db->master_key_set && (flags & HDB_F_DECRYPT)) {
1052             ret = hdb_unseal_keys(context, db, entry);
1053             if (ret)
1054                 hdb_free_entry(context,entry);
1055         }
1056     }
1057
1058   out:
1059     ldap_msgfree(msg);
1060
1061     return ret;
1062 }
1063
1064 static krb5_error_code
1065 LDAP_store(krb5_context context, HDB * db, unsigned flags,
1066            hdb_entry * entry)
1067 {
1068     LDAPMod **mods = NULL;
1069     krb5_error_code ret;
1070     const char *errfn;
1071     int rc;
1072     LDAPMessage *msg = NULL, *e = NULL;
1073     char *dn = NULL, *name = NULL;
1074
1075     ret = krb5_unparse_name(context, entry->principal, &name);
1076     if (ret != 0) {
1077         goto out;
1078     }
1079
1080     ret = LDAP__lookup_princ(context, db, name, &msg);
1081     if (ret == 0) {
1082         e = ldap_first_entry((LDAP *) db->db, msg);
1083     }
1084
1085     ret = hdb_seal_keys(context, db, entry);
1086     if (ret != 0) {
1087         goto out;
1088     }
1089
1090     /* turn new entry into LDAPMod array */
1091     ret = LDAP_entry2mods(context, db, entry, e, &mods);
1092     if (ret != 0) {
1093         goto out;
1094     }
1095
1096     if (e == NULL) {
1097         /* Doesn't exist yet. */
1098         char *p;
1099
1100         e = NULL;
1101
1102         /* normalize the naming attribute */
1103         for (p = name; *p != '\0'; p++) {
1104             *p = (char) tolower((int) *p);
1105         }
1106
1107         /*
1108          * We could do getpwnam() on the local component of
1109          * the principal to find cn/sn but that's probably
1110          * bad thing to do from inside a KDC. Better leave
1111          * it to management tools.
1112          */
1113         ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "cn", name);
1114         if (ret < 0) {
1115             goto out;
1116         }
1117
1118         ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "sn", name);
1119         if (ret < 0) {
1120             goto out;
1121         }
1122
1123         if (db->name != NULL) {
1124             ret = asprintf(&dn, "cn=%s,%s", name, db->name);
1125         } else {
1126             /* A bit bogus, but we don't have a search base */
1127             ret = asprintf(&dn, "cn=%s", name);
1128         }
1129         if (ret < 0) {
1130             krb5_set_error_string(context, "asprintf: out of memory");
1131             ret = ENOMEM;
1132             goto out;
1133         }
1134     } else if (flags & HDB_F_REPLACE) {
1135         /* Entry exists, and we're allowed to replace it. */
1136         dn = ldap_get_dn((LDAP *) db->db, e);
1137     } else {
1138         /* Entry exists, but we're not allowed to replace it. Bail. */
1139         ret = HDB_ERR_EXISTS;
1140         goto out;
1141     }
1142
1143     /* write entry into directory */
1144     if (e == NULL) {
1145         /* didn't exist before */
1146         rc = ldap_add_s((LDAP *) db->db, dn, mods);
1147         errfn = "ldap_add_s";
1148     } else {
1149         /* already existed, send deltas only */
1150         rc = ldap_modify_s((LDAP *) db->db, dn, mods);
1151         errfn = "ldap_modify_s";
1152     }
1153
1154     if (rc == LDAP_SUCCESS) {
1155         ret = 0;
1156     } else {
1157         krb5_set_error_string(context, "%s: %s (dn=%s) %s", 
1158                               errfn, name, dn, ldap_err2string(rc));
1159         ret = HDB_ERR_CANT_LOCK_DB;
1160     }
1161
1162   out:
1163     /* free stuff */
1164     if (dn != NULL) {
1165         free(dn);
1166     }
1167
1168     if (msg != NULL) {
1169         ldap_msgfree(msg);
1170     }
1171
1172     if (mods != NULL) {
1173         ldap_mods_free(mods, 1);
1174     }
1175
1176     if (name != NULL) {
1177         free(name);
1178     }
1179
1180     return ret;
1181 }
1182
1183 static krb5_error_code
1184 LDAP_remove(krb5_context context, HDB * db, hdb_entry * entry)
1185 {
1186     krb5_error_code ret;
1187     LDAPMessage *msg, *e;
1188     char *dn = NULL;
1189     int rc, limit = LDAP_NO_LIMIT;
1190
1191     ret = LDAP_principal2message(context, db, entry->principal, &msg);
1192     if (ret != 0) {
1193         goto out;
1194     }
1195
1196     e = ldap_first_entry((LDAP *) db->db, msg);
1197     if (e == NULL) {
1198         ret = HDB_ERR_NOENTRY;
1199         goto out;
1200     }
1201
1202     dn = ldap_get_dn((LDAP *) db->db, e);
1203     if (dn == NULL) {
1204         ret = HDB_ERR_NOENTRY;
1205         goto out;
1206     }
1207
1208     rc = ldap_set_option((LDAP *) db->db, LDAP_OPT_SIZELIMIT, (const void *)&limit);
1209     if (rc != LDAP_SUCCESS) {
1210         krb5_set_error_string(context, "ldap_set_option: %s", ldap_err2string(rc));
1211         ret = HDB_ERR_BADVERSION;
1212         goto out;
1213     }
1214
1215     rc = ldap_delete_s((LDAP *) db->db, dn);
1216     if (rc == LDAP_SUCCESS) {
1217         ret = 0;
1218     } else {
1219         krb5_set_error_string(context, "ldap_delete_s: %s", ldap_err2string(rc));
1220         ret = HDB_ERR_CANT_LOCK_DB;
1221     }
1222
1223   out:
1224     if (dn != NULL) {
1225         free(dn);
1226     }
1227
1228     if (msg != NULL) {
1229         ldap_msgfree(msg);
1230     }
1231
1232     return ret;
1233 }
1234
1235 static krb5_error_code
1236 LDAP__get(krb5_context context, HDB * db, krb5_data key, krb5_data * reply)
1237 {
1238     fprintf(stderr, "LDAP__get not implemented\n");
1239     abort();
1240     return 0;
1241 }
1242
1243 static krb5_error_code
1244 LDAP__put(krb5_context context, HDB * db, int replace,
1245           krb5_data key, krb5_data value)
1246 {
1247     fprintf(stderr, "LDAP__put not implemented\n");
1248     abort();
1249     return 0;
1250 }
1251
1252 static krb5_error_code
1253 LDAP__del(krb5_context context, HDB * db, krb5_data key)
1254 {
1255     fprintf(stderr, "LDAP__del not implemented\n");
1256     abort();
1257     return 0;
1258 }
1259
1260 static krb5_error_code LDAP_destroy(krb5_context context, HDB * db)
1261 {
1262     krb5_error_code ret;
1263
1264     ret = hdb_clear_master_key(context, db);
1265     if (db->name != NULL) {
1266         free(db->name);
1267     }
1268     free(db);
1269
1270     return ret;
1271 }
1272
1273 krb5_error_code
1274 hdb_ldap_create(krb5_context context, HDB ** db, const char *arg)
1275 {
1276     *db = malloc(sizeof(**db));
1277     if (*db == NULL) {
1278         krb5_set_error_string(context, "malloc: out of memory");
1279         return ENOMEM;
1280     }
1281
1282     (*db)->db = NULL;
1283
1284     if (arg == NULL || arg[0] == '\0') {
1285         /*
1286          * if no argument specified in the configuration file
1287          * then use NULL, which tells OpenLDAP to look in
1288          * the ldap.conf file. This doesn't work for
1289          * writing entries because we don't know where to
1290          * put new principals.
1291          */
1292         (*db)->name = NULL;
1293     } else {
1294         (*db)->name = strdup(arg); 
1295         if ((*db)->name == NULL) {
1296             krb5_set_error_string(context, "strdup: out of memory");
1297             free(*db);
1298             *db = NULL;
1299             return ENOMEM;
1300         }
1301     }
1302
1303     (*db)->master_key_set = 0;
1304     (*db)->openp = 0;
1305     (*db)->open = LDAP_open;
1306     (*db)->close = LDAP_close;
1307     (*db)->fetch = LDAP_fetch;
1308     (*db)->store = LDAP_store;
1309     (*db)->remove = LDAP_remove;
1310     (*db)->firstkey = LDAP_firstkey;
1311     (*db)->nextkey = LDAP_nextkey;
1312     (*db)->lock = LDAP_lock;
1313     (*db)->unlock = LDAP_unlock;
1314     (*db)->rename = LDAP_rename;
1315     /* can we ditch these? */
1316     (*db)->_get = LDAP__get;
1317     (*db)->_put = LDAP__put;
1318     (*db)->_del = LDAP__del;
1319     (*db)->destroy = LDAP_destroy;
1320
1321     return 0;
1322 }
1323
1324 #endif                          /* OPENLDAP */