]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - crypto/heimdal/lib/hdb/hdb-ldap.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / crypto / heimdal / lib / hdb / hdb-ldap.c
1 /*
2  * Copyright (c) 1999-2001, 2003, PADL Software Pty Ltd.
3  * Copyright (c) 2004, Andrew Bartlett.
4  * Copyright (c) 2003 - 2008, Kungliga Tekniska Högskolan.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * 3. Neither the name of PADL Software  nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34
35 #include "hdb_locl.h"
36
37 #ifdef OPENLDAP
38
39 #include <lber.h>
40 #include <ldap.h>
41 #include <sys/un.h>
42 #include <hex.h>
43
44 static krb5_error_code LDAP__connect(krb5_context context, HDB *);
45 static krb5_error_code LDAP_close(krb5_context context, HDB *);
46
47 static krb5_error_code
48 LDAP_message2entry(krb5_context context, HDB * db, LDAPMessage * msg,
49                    int flags, hdb_entry_ex * ent);
50
51 static const char *default_structural_object = "account";
52 static char *structural_object;
53 static krb5_boolean samba_forwardable;
54
55 struct hdbldapdb {
56     LDAP *h_lp;
57     int   h_msgid;
58     char *h_base;
59     char *h_url;
60     char *h_createbase;
61 };
62
63 #define HDB2LDAP(db) (((struct hdbldapdb *)(db)->hdb_db)->h_lp)
64 #define HDB2MSGID(db) (((struct hdbldapdb *)(db)->hdb_db)->h_msgid)
65 #define HDBSETMSGID(db,msgid) \
66         do { ((struct hdbldapdb *)(db)->hdb_db)->h_msgid = msgid; } while(0)
67 #define HDB2BASE(dn) (((struct hdbldapdb *)(db)->hdb_db)->h_base)
68 #define HDB2URL(dn) (((struct hdbldapdb *)(db)->hdb_db)->h_url)
69 #define HDB2CREATE(db) (((struct hdbldapdb *)(db)->hdb_db)->h_createbase)
70
71 /*
72  *
73  */
74
75 static char * krb5kdcentry_attrs[] = {
76     "cn",
77     "createTimestamp",
78     "creatorsName",
79     "krb5EncryptionType",
80     "krb5KDCFlags",
81     "krb5Key",
82     "krb5KeyVersionNumber",
83     "krb5MaxLife",
84     "krb5MaxRenew",
85     "krb5PasswordEnd",
86     "krb5PrincipalName",
87     "krb5PrincipalRealm",
88     "krb5ValidEnd",
89     "krb5ValidStart",
90     "modifiersName",
91     "modifyTimestamp",
92     "objectClass",
93     "sambaAcctFlags",
94     "sambaKickoffTime",
95     "sambaNTPassword",
96     "sambaPwdLastSet",
97     "sambaPwdMustChange",
98     "uid",
99     NULL
100 };
101
102 static char *krb5principal_attrs[] = {
103     "cn",
104     "createTimestamp",
105     "creatorsName",
106     "krb5PrincipalName",
107     "krb5PrincipalRealm",
108     "modifiersName",
109     "modifyTimestamp",
110     "objectClass",
111     "uid",
112     NULL
113 };
114
115 static int
116 LDAP_no_size_limit(krb5_context context, LDAP *lp)
117 {
118     int ret, limit = LDAP_NO_LIMIT;
119
120     ret = ldap_set_option(lp, LDAP_OPT_SIZELIMIT, (const void *)&limit);
121     if (ret != LDAP_SUCCESS) {
122         krb5_set_error_message(context, HDB_ERR_BADVERSION,
123                                "ldap_set_option: %s",
124                                ldap_err2string(ret));
125         return HDB_ERR_BADVERSION;
126     }
127     return 0;
128 }
129
130 static int
131 check_ldap(krb5_context context, HDB *db, int ret)
132 {
133     switch (ret) {
134     case LDAP_SUCCESS:
135         return 0;
136     case LDAP_SERVER_DOWN:
137         LDAP_close(context, db);
138         return 1;
139     default:
140         return 1;
141     }
142 }
143
144 static krb5_error_code
145 LDAP__setmod(LDAPMod *** modlist, int modop, const char *attribute,
146              int *pIndex)
147 {
148     int cMods;
149
150     if (*modlist == NULL) {
151         *modlist = (LDAPMod **)ber_memcalloc(1, sizeof(LDAPMod *));
152         if (*modlist == NULL)
153             return ENOMEM;
154     }
155
156     for (cMods = 0; (*modlist)[cMods] != NULL; cMods++) {
157         if ((*modlist)[cMods]->mod_op == modop &&
158             strcasecmp((*modlist)[cMods]->mod_type, attribute) == 0) {
159             break;
160         }
161     }
162
163     *pIndex = cMods;
164
165     if ((*modlist)[cMods] == NULL) {
166         LDAPMod *mod;
167
168         *modlist = (LDAPMod **)ber_memrealloc(*modlist,
169                                               (cMods + 2) * sizeof(LDAPMod *));
170         if (*modlist == NULL)
171             return ENOMEM;
172
173         (*modlist)[cMods] = (LDAPMod *)ber_memalloc(sizeof(LDAPMod));
174         if ((*modlist)[cMods] == NULL)
175             return ENOMEM;
176
177         mod = (*modlist)[cMods];
178         mod->mod_op = modop;
179         mod->mod_type = ber_strdup(attribute);
180         if (mod->mod_type == NULL) {
181             ber_memfree(mod);
182             (*modlist)[cMods] = NULL;
183             return ENOMEM;
184         }
185
186         if (modop & LDAP_MOD_BVALUES) {
187             mod->mod_bvalues = NULL;
188         } else {
189             mod->mod_values = NULL;
190         }
191
192         (*modlist)[cMods + 1] = NULL;
193     }
194
195     return 0;
196 }
197
198 static krb5_error_code
199 LDAP_addmod_len(LDAPMod *** modlist, int modop, const char *attribute,
200                 unsigned char *value, size_t len)
201 {
202     krb5_error_code ret;
203     int cMods, i = 0;
204
205     ret = LDAP__setmod(modlist, modop | LDAP_MOD_BVALUES, attribute, &cMods);
206     if (ret)
207         return ret;
208
209     if (value != NULL) {
210         struct berval **bv;
211
212         bv = (*modlist)[cMods]->mod_bvalues;
213         if (bv != NULL) {
214             for (i = 0; bv[i] != NULL; i++)
215                 ;
216             bv = ber_memrealloc(bv, (i + 2) * sizeof(*bv));
217         } else
218             bv = ber_memalloc(2 * sizeof(*bv));
219         if (bv == NULL)
220             return ENOMEM;
221
222         (*modlist)[cMods]->mod_bvalues = bv;
223
224         bv[i] = ber_memalloc(sizeof(**bv));;
225         if (bv[i] == NULL)
226             return ENOMEM;
227
228         bv[i]->bv_val = (void *)value;
229         bv[i]->bv_len = len;
230
231         bv[i + 1] = NULL;
232     }
233
234     return 0;
235 }
236
237 static krb5_error_code
238 LDAP_addmod(LDAPMod *** modlist, int modop, const char *attribute,
239             const char *value)
240 {
241     int cMods, i = 0;
242     krb5_error_code ret;
243
244     ret = LDAP__setmod(modlist, modop, attribute, &cMods);
245     if (ret)
246         return ret;
247
248     if (value != NULL) {
249         char **bv;
250
251         bv = (*modlist)[cMods]->mod_values;
252         if (bv != NULL) {
253             for (i = 0; bv[i] != NULL; i++)
254                 ;
255             bv = ber_memrealloc(bv, (i + 2) * sizeof(*bv));
256         } else
257             bv = ber_memalloc(2 * sizeof(*bv));
258         if (bv == NULL)
259             return ENOMEM;
260
261         (*modlist)[cMods]->mod_values = bv;
262
263         bv[i] = ber_strdup(value);
264         if (bv[i] == NULL)
265             return ENOMEM;
266
267         bv[i + 1] = NULL;
268     }
269
270     return 0;
271 }
272
273 static krb5_error_code
274 LDAP_addmod_generalized_time(LDAPMod *** mods, int modop,
275                              const char *attribute, KerberosTime * time)
276 {
277     char buf[22];
278     struct tm *tm;
279
280     /* XXX not threadsafe */
281     tm = gmtime(time);
282     strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", tm);
283
284     return LDAP_addmod(mods, modop, attribute, buf);
285 }
286
287 static krb5_error_code
288 LDAP_addmod_integer(krb5_context context,
289                     LDAPMod *** mods, int modop,
290                     const char *attribute, unsigned long l)
291 {
292     krb5_error_code ret;
293     char *buf;
294
295     ret = asprintf(&buf, "%ld", l);
296     if (ret < 0) {
297         krb5_set_error_message(context, ENOMEM,
298                                "asprintf: out of memory:");
299         return ENOMEM;
300     }
301     ret = LDAP_addmod(mods, modop, attribute, buf);
302     free (buf);
303     return ret;
304 }
305
306 static krb5_error_code
307 LDAP_get_string_value(HDB * db, LDAPMessage * entry,
308                       const char *attribute, char **ptr)
309 {
310     struct berval **vals;
311
312     vals = ldap_get_values_len(HDB2LDAP(db), entry, attribute);
313     if (vals == NULL || vals[0] == NULL) {
314         *ptr = NULL;
315         return HDB_ERR_NOENTRY;
316     }
317
318     *ptr = malloc(vals[0]->bv_len + 1);
319     if (*ptr == NULL) {
320         ldap_value_free_len(vals);
321         return ENOMEM;
322     }
323
324     memcpy(*ptr, vals[0]->bv_val, vals[0]->bv_len);
325     (*ptr)[vals[0]->bv_len] = 0;
326
327     ldap_value_free_len(vals);
328
329     return 0;
330 }
331
332 static krb5_error_code
333 LDAP_get_integer_value(HDB * db, LDAPMessage * entry,
334                        const char *attribute, int *ptr)
335 {
336     krb5_error_code ret;
337     char *val;
338
339     ret = LDAP_get_string_value(db, entry, attribute, &val);
340     if (ret)
341         return ret;
342     *ptr = atoi(val);
343     free(val);
344     return 0;
345 }
346
347 static krb5_error_code
348 LDAP_get_generalized_time_value(HDB * db, LDAPMessage * entry,
349                                 const char *attribute, KerberosTime * kt)
350 {
351     char *tmp, *gentime;
352     struct tm tm;
353     int ret;
354
355     *kt = 0;
356
357     ret = LDAP_get_string_value(db, entry, attribute, &gentime);
358     if (ret)
359         return ret;
360
361     tmp = strptime(gentime, "%Y%m%d%H%M%SZ", &tm);
362     if (tmp == NULL) {
363         free(gentime);
364         return HDB_ERR_NOENTRY;
365     }
366
367     free(gentime);
368
369     *kt = timegm(&tm);
370
371     return 0;
372 }
373
374 static int
375 bervalstrcmp(struct berval *v, const char *str)
376 {
377     size_t len = strlen(str);
378     return (v->bv_len == len) && strncasecmp(str, (char *)v->bv_val, len) == 0;
379 }
380
381
382 static krb5_error_code
383 LDAP_entry2mods(krb5_context context, HDB * db, hdb_entry_ex * ent,
384                 LDAPMessage * msg, LDAPMod *** pmods)
385 {
386     krb5_error_code ret;
387     krb5_boolean is_new_entry;
388     char *tmp = NULL;
389     LDAPMod **mods = NULL;
390     hdb_entry_ex orig;
391     unsigned long oflags, nflags;
392     int i;
393
394     krb5_boolean is_samba_account = FALSE;
395     krb5_boolean is_account = FALSE;
396     krb5_boolean is_heimdal_entry = FALSE;
397     krb5_boolean is_heimdal_principal = FALSE;
398
399     struct berval **vals;
400
401     *pmods = NULL;
402
403     if (msg != NULL) {
404
405         ret = LDAP_message2entry(context, db, msg, 0, &orig);
406         if (ret)
407             goto out;
408
409         is_new_entry = FALSE;
410
411         vals = ldap_get_values_len(HDB2LDAP(db), msg, "objectClass");
412         if (vals) {
413             int num_objectclasses = ldap_count_values_len(vals);
414             for (i=0; i < num_objectclasses; i++) {
415                 if (bervalstrcmp(vals[i], "sambaSamAccount"))
416                     is_samba_account = TRUE;
417                 else if (bervalstrcmp(vals[i], structural_object))
418                     is_account = TRUE;
419                 else if (bervalstrcmp(vals[i], "krb5Principal"))
420                     is_heimdal_principal = TRUE;
421                 else if (bervalstrcmp(vals[i], "krb5KDCEntry"))
422                     is_heimdal_entry = TRUE;
423             }
424             ldap_value_free_len(vals);
425         }
426
427         /*
428          * If this is just a "account" entry and no other objectclass
429          * is hanging on this entry, it's really a new entry.
430          */
431         if (is_samba_account == FALSE && is_heimdal_principal == FALSE &&
432             is_heimdal_entry == FALSE) {
433             if (is_account == TRUE) {
434                 is_new_entry = TRUE;
435             } else {
436                 ret = HDB_ERR_NOENTRY;
437                 goto out;
438             }
439         }
440     } else
441         is_new_entry = TRUE;
442
443     if (is_new_entry) {
444
445         /* to make it perfectly obvious we're depending on
446          * orig being intiialized to zero */
447         memset(&orig, 0, sizeof(orig));
448
449         ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass", "top");
450         if (ret)
451             goto out;
452
453         /* account is the structural object class */
454         if (is_account == FALSE) {
455             ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass",
456                               structural_object);
457             is_account = TRUE;
458             if (ret)
459                 goto out;
460         }
461
462         ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass", "krb5Principal");
463         is_heimdal_principal = TRUE;
464         if (ret)
465             goto out;
466
467         ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass", "krb5KDCEntry");
468         is_heimdal_entry = TRUE;
469         if (ret)
470             goto out;
471     }
472
473     if (is_new_entry ||
474         krb5_principal_compare(context, ent->entry.principal, orig.entry.principal)
475         == FALSE)
476     {
477         if (is_heimdal_principal || is_heimdal_entry) {
478
479             ret = krb5_unparse_name(context, ent->entry.principal, &tmp);
480             if (ret)
481                 goto out;
482
483             ret = LDAP_addmod(&mods, LDAP_MOD_REPLACE,
484                               "krb5PrincipalName", tmp);
485             if (ret) {
486                 free(tmp);
487                 goto out;
488             }
489             free(tmp);
490         }
491
492         if (is_account || is_samba_account) {
493             ret = krb5_unparse_name_short(context, ent->entry.principal, &tmp);
494             if (ret)
495                 goto out;
496             ret = LDAP_addmod(&mods, LDAP_MOD_REPLACE, "uid", tmp);
497             if (ret) {
498                 free(tmp);
499                 goto out;
500             }
501             free(tmp);
502         }
503     }
504
505     if (is_heimdal_entry && (ent->entry.kvno != orig.entry.kvno || is_new_entry)) {
506         ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
507                             "krb5KeyVersionNumber",
508                             ent->entry.kvno);
509         if (ret)
510             goto out;
511     }
512
513     if (is_heimdal_entry && ent->entry.valid_start) {
514         if (orig.entry.valid_end == NULL
515             || (*(ent->entry.valid_start) != *(orig.entry.valid_start))) {
516             ret = LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
517                                                "krb5ValidStart",
518                                                ent->entry.valid_start);
519             if (ret)
520                 goto out;
521         }
522     }
523
524     if (ent->entry.valid_end) {
525         if (orig.entry.valid_end == NULL || (*(ent->entry.valid_end) != *(orig.entry.valid_end))) {
526             if (is_heimdal_entry) {
527                 ret = LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
528                                                    "krb5ValidEnd",
529                                                    ent->entry.valid_end);
530                 if (ret)
531                     goto out;
532             }
533             if (is_samba_account) {
534                 ret = LDAP_addmod_integer(context, &mods,  LDAP_MOD_REPLACE,
535                                           "sambaKickoffTime",
536                                           *(ent->entry.valid_end));
537                 if (ret)
538                     goto out;
539             }
540         }
541     }
542
543     if (ent->entry.pw_end) {
544         if (orig.entry.pw_end == NULL || (*(ent->entry.pw_end) != *(orig.entry.pw_end))) {
545             if (is_heimdal_entry) {
546                 ret = LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
547                                                    "krb5PasswordEnd",
548                                                    ent->entry.pw_end);
549                 if (ret)
550                     goto out;
551             }
552
553             if (is_samba_account) {
554                 ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
555                                           "sambaPwdMustChange",
556                                           *(ent->entry.pw_end));
557                 if (ret)
558                     goto out;
559             }
560         }
561     }
562
563
564 #if 0 /* we we have last_pw_change */
565     if (is_samba_account && ent->entry.last_pw_change) {
566         if (orig.entry.last_pw_change == NULL || (*(ent->entry.last_pw_change) != *(orig.entry.last_pw_change))) {
567             ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
568                                       "sambaPwdLastSet",
569                                       *(ent->entry.last_pw_change));
570             if (ret)
571                 goto out;
572         }
573     }
574 #endif
575
576     if (is_heimdal_entry && ent->entry.max_life) {
577         if (orig.entry.max_life == NULL
578             || (*(ent->entry.max_life) != *(orig.entry.max_life))) {
579
580             ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
581                                       "krb5MaxLife",
582                                       *(ent->entry.max_life));
583             if (ret)
584                 goto out;
585         }
586     }
587
588     if (is_heimdal_entry && ent->entry.max_renew) {
589         if (orig.entry.max_renew == NULL
590             || (*(ent->entry.max_renew) != *(orig.entry.max_renew))) {
591
592             ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
593                                       "krb5MaxRenew",
594                                       *(ent->entry.max_renew));
595             if (ret)
596                 goto out;
597         }
598     }
599
600     oflags = HDBFlags2int(orig.entry.flags);
601     nflags = HDBFlags2int(ent->entry.flags);
602
603     if (is_heimdal_entry && oflags != nflags) {
604
605         ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
606                                   "krb5KDCFlags",
607                                   nflags);
608         if (ret)
609             goto out;
610     }
611
612     /* Remove keys if they exists, and then replace keys. */
613     if (!is_new_entry && orig.entry.keys.len > 0) {
614         vals = ldap_get_values_len(HDB2LDAP(db), msg, "krb5Key");
615         if (vals) {
616             ldap_value_free_len(vals);
617
618             ret = LDAP_addmod(&mods, LDAP_MOD_DELETE, "krb5Key", NULL);
619             if (ret)
620                 goto out;
621         }
622     }
623
624     for (i = 0; i < ent->entry.keys.len; i++) {
625
626         if (is_samba_account
627             && ent->entry.keys.val[i].key.keytype == ETYPE_ARCFOUR_HMAC_MD5) {
628             char *ntHexPassword;
629             char *nt;
630             time_t now = time(NULL);
631
632             /* the key might have been 'sealed', but samba passwords
633                are clear in the directory */
634             ret = hdb_unseal_key(context, db, &ent->entry.keys.val[i]);
635             if (ret)
636                 goto out;
637
638             nt = ent->entry.keys.val[i].key.keyvalue.data;
639             /* store in ntPassword, not krb5key */
640             ret = hex_encode(nt, 16, &ntHexPassword);
641             if (ret < 0) {
642                 ret = ENOMEM;
643                 krb5_set_error_message(context, ret, "hdb-ldap: failed to "
644                                       "hex encode key");
645                 goto out;
646             }
647             ret = LDAP_addmod(&mods, LDAP_MOD_REPLACE, "sambaNTPassword",
648                               ntHexPassword);
649             free(ntHexPassword);
650             if (ret)
651                 goto out;
652             ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
653                                       "sambaPwdLastSet", now);
654             if (ret)
655                 goto out;
656
657             /* have to kill the LM passwod if it exists */
658             vals = ldap_get_values_len(HDB2LDAP(db), msg, "sambaLMPassword");
659             if (vals) {
660                 ldap_value_free_len(vals);
661                 ret = LDAP_addmod(&mods, LDAP_MOD_DELETE,
662                                   "sambaLMPassword", NULL);
663                 if (ret)
664                     goto out;
665             }
666
667         } else if (is_heimdal_entry) {
668             unsigned char *buf;
669             size_t len, buf_size;
670
671             ASN1_MALLOC_ENCODE(Key, buf, buf_size, &ent->entry.keys.val[i], &len, ret);
672             if (ret)
673                 goto out;
674             if(buf_size != len)
675                 krb5_abortx(context, "internal error in ASN.1 encoder");
676
677             /* addmod_len _owns_ the key, doesn't need to copy it */
678             ret = LDAP_addmod_len(&mods, LDAP_MOD_ADD, "krb5Key", buf, len);
679             if (ret)
680                 goto out;
681         }
682     }
683
684     if (ent->entry.etypes) {
685         int add_krb5EncryptionType = 0;
686
687         /*
688          * Only add/modify krb5EncryptionType if it's a new heimdal
689          * entry or krb5EncryptionType already exists on the entry.
690          */
691
692         if (!is_new_entry) {
693             vals = ldap_get_values_len(HDB2LDAP(db), msg, "krb5EncryptionType");
694             if (vals) {
695                 ldap_value_free_len(vals);
696                 ret = LDAP_addmod(&mods, LDAP_MOD_DELETE, "krb5EncryptionType",
697                                   NULL);
698                 if (ret)
699                     goto out;
700                 add_krb5EncryptionType = 1;
701             }
702         } else if (is_heimdal_entry)
703             add_krb5EncryptionType = 1;
704
705         if (add_krb5EncryptionType) {
706             for (i = 0; i < ent->entry.etypes->len; i++) {
707                 if (is_samba_account &&
708                     ent->entry.keys.val[i].key.keytype == ETYPE_ARCFOUR_HMAC_MD5)
709                 {
710                     ;
711                 } else if (is_heimdal_entry) {
712                     ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_ADD,
713                                               "krb5EncryptionType",
714                                               ent->entry.etypes->val[i]);
715                     if (ret)
716                         goto out;
717                 }
718             }
719         }
720     }
721
722     /* for clarity */
723     ret = 0;
724
725  out:
726
727     if (ret == 0)
728         *pmods = mods;
729     else if (mods != NULL) {
730         ldap_mods_free(mods, 1);
731         *pmods = NULL;
732     }
733
734     if (msg)
735         hdb_free_entry(context, &orig);
736
737     return ret;
738 }
739
740 static krb5_error_code
741 LDAP_dn2principal(krb5_context context, HDB * db, const char *dn,
742                   krb5_principal * principal)
743 {
744     krb5_error_code ret;
745     int rc;
746     const char *filter = "(objectClass=krb5Principal)";
747     LDAPMessage *res = NULL, *e;
748     char *p;
749
750     ret = LDAP_no_size_limit(context, HDB2LDAP(db));
751     if (ret)
752         goto out;
753
754     rc = ldap_search_ext_s(HDB2LDAP(db), dn, LDAP_SCOPE_SUBTREE,
755                            filter, krb5principal_attrs, 0,
756                            NULL, NULL, NULL,
757                            0, &res);
758     if (check_ldap(context, db, rc)) {
759         ret = HDB_ERR_NOENTRY;
760         krb5_set_error_message(context, ret, "ldap_search_ext_s: "
761                                "filter: %s error: %s",
762                                filter, ldap_err2string(rc));
763         goto out;
764     }
765
766     e = ldap_first_entry(HDB2LDAP(db), res);
767     if (e == NULL) {
768         ret = HDB_ERR_NOENTRY;
769         goto out;
770     }
771
772     ret = LDAP_get_string_value(db, e, "krb5PrincipalName", &p);
773     if (ret) {
774         ret = HDB_ERR_NOENTRY;
775         goto out;
776     }
777
778     ret = krb5_parse_name(context, p, principal);
779     free(p);
780
781   out:
782     if (res)
783         ldap_msgfree(res);
784
785     return ret;
786 }
787
788 static int
789 need_quote(unsigned char c)
790 {
791     return (c & 0x80) ||
792         (c < 32) ||
793         (c == '(') ||
794         (c == ')') ||
795         (c == '*') ||
796         (c == '\\') ||
797         (c == 0x7f);
798 }
799
800 const static char hexchar[] = "0123456789ABCDEF";
801
802 static krb5_error_code
803 escape_value(krb5_context context, const unsigned char *unquoted, char **quoted)
804 {
805     size_t i, len;
806
807     for (i = 0, len = 0; unquoted[i] != '\0'; i++, len++) {
808         if (need_quote((unsigned char)unquoted[i]))
809             len += 2;
810     }
811
812     *quoted = malloc(len + 1);
813     if (*quoted == NULL) {
814         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
815         return ENOMEM;
816     }
817
818     for (i = 0; unquoted[0] ; unquoted++) {
819         if (need_quote((unsigned char *)unquoted[0])) {
820             (*quoted)[i++] = '\\';
821             (*quoted)[i++] = hexchar[(unquoted[0] >> 4) & 0xf];
822             (*quoted)[i++] = hexchar[(unquoted[0]     ) & 0xf];
823         } else
824             (*quoted)[i++] = (char)unquoted[0];
825     }
826     (*quoted)[i] = '\0';
827     return 0;
828 }
829
830
831 static krb5_error_code
832 LDAP__lookup_princ(krb5_context context,
833                    HDB *db,
834                    const char *princname,
835                    const char *userid,
836                    LDAPMessage **msg)
837 {
838     krb5_error_code ret;
839     int rc;
840     char *quote, *filter = NULL;
841
842     ret = LDAP__connect(context, db);
843     if (ret)
844         return ret;
845
846     /*
847      * Quote searches that contain filter language, this quote
848      * searches for *@REALM, which takes very long time.
849      */
850
851     ret = escape_value(context, princname, &quote);
852     if (ret)
853         goto out;
854
855     rc = asprintf(&filter,
856                   "(&(objectClass=krb5Principal)(krb5PrincipalName=%s))",
857                   quote);
858     free(quote);
859
860     if (rc < 0) {
861         ret = ENOMEM;
862         krb5_set_error_message(context, ret, "malloc: out of memory");
863         goto out;
864     }
865
866     ret = LDAP_no_size_limit(context, HDB2LDAP(db));
867     if (ret)
868         goto out;
869
870     rc = ldap_search_ext_s(HDB2LDAP(db), HDB2BASE(db),
871                            LDAP_SCOPE_SUBTREE, filter,
872                            krb5kdcentry_attrs, 0,
873                            NULL, NULL, NULL,
874                            0, msg);
875     if (check_ldap(context, db, rc)) {
876         ret = HDB_ERR_NOENTRY;
877         krb5_set_error_message(context, ret, "ldap_search_ext_s: "
878                               "filter: %s - error: %s",
879                               filter, ldap_err2string(rc));
880         goto out;
881     }
882
883     if (userid && ldap_count_entries(HDB2LDAP(db), *msg) == 0) {
884         free(filter);
885         filter = NULL;
886         ldap_msgfree(*msg);
887         *msg = NULL;
888
889         ret = escape_value(context, userid, &quote);
890         if (ret)
891             goto out;
892
893         rc = asprintf(&filter,
894             "(&(|(objectClass=sambaSamAccount)(objectClass=%s))(uid=%s))",
895                       structural_object, quote);
896         free(quote);
897         if (rc < 0) {
898             ret = ENOMEM;
899             krb5_set_error_message(context, ret, "asprintf: out of memory");
900             goto out;
901         }
902
903         ret = LDAP_no_size_limit(context, HDB2LDAP(db));
904         if (ret)
905             goto out;
906
907         rc = ldap_search_ext_s(HDB2LDAP(db), HDB2BASE(db), LDAP_SCOPE_SUBTREE,
908                                filter, krb5kdcentry_attrs, 0,
909                                NULL, NULL, NULL,
910                                0, msg);
911         if (check_ldap(context, db, rc)) {
912             ret = HDB_ERR_NOENTRY;
913             krb5_set_error_message(context, ret,
914                                    "ldap_search_ext_s: filter: %s error: %s",
915                                    filter, ldap_err2string(rc));
916             goto out;
917         }
918     }
919
920     ret = 0;
921
922   out:
923     if (filter)
924         free(filter);
925
926     return ret;
927 }
928
929 static krb5_error_code
930 LDAP_principal2message(krb5_context context, HDB * db,
931                        krb5_const_principal princ, LDAPMessage ** msg)
932 {
933     char *name, *name_short = NULL;
934     krb5_error_code ret;
935     krb5_realm *r, *r0;
936
937     *msg = NULL;
938
939     ret = krb5_unparse_name(context, princ, &name);
940     if (ret)
941         return ret;
942
943     ret = krb5_get_default_realms(context, &r0);
944     if(ret) {
945         free(name);
946         return ret;
947     }
948     for (r = r0; *r != NULL; r++) {
949         if(strcmp(krb5_principal_get_realm(context, princ), *r) == 0) {
950             ret = krb5_unparse_name_short(context, princ, &name_short);
951             if (ret) {
952                 krb5_free_host_realm(context, r0);
953                 free(name);
954                 return ret;
955             }
956             break;
957         }
958     }
959     krb5_free_host_realm(context, r0);
960
961     ret = LDAP__lookup_princ(context, db, name, name_short, msg);
962     free(name);
963     free(name_short);
964
965     return ret;
966 }
967
968 /*
969  * Construct an hdb_entry from a directory entry.
970  */
971 static krb5_error_code
972 LDAP_message2entry(krb5_context context, HDB * db, LDAPMessage * msg,
973                    int flags, hdb_entry_ex * ent)
974 {
975     char *unparsed_name = NULL, *dn = NULL, *ntPasswordIN = NULL;
976     char *samba_acct_flags = NULL;
977     struct berval **keys;
978     struct berval **vals;
979     int tmp, tmp_time, i, ret, have_arcfour = 0;
980
981     memset(ent, 0, sizeof(*ent));
982     ent->entry.flags = int2HDBFlags(0);
983
984     ret = LDAP_get_string_value(db, msg, "krb5PrincipalName", &unparsed_name);
985     if (ret == 0) {
986         ret = krb5_parse_name(context, unparsed_name, &ent->entry.principal);
987         if (ret)
988             goto out;
989     } else {
990         ret = LDAP_get_string_value(db, msg, "uid",
991                                     &unparsed_name);
992         if (ret == 0) {
993             ret = krb5_parse_name(context, unparsed_name, &ent->entry.principal);
994             if (ret)
995                 goto out;
996         } else {
997             krb5_set_error_message(context, HDB_ERR_NOENTRY,
998                                    "hdb-ldap: ldap entry missing"
999                                   "principal name");
1000             return HDB_ERR_NOENTRY;
1001         }
1002     }
1003
1004     {
1005         int integer;
1006         ret = LDAP_get_integer_value(db, msg, "krb5KeyVersionNumber",
1007                                      &integer);
1008         if (ret)
1009             ent->entry.kvno = 0;
1010         else
1011             ent->entry.kvno = integer;
1012     }
1013
1014     keys = ldap_get_values_len(HDB2LDAP(db), msg, "krb5Key");
1015     if (keys != NULL) {
1016         int i;
1017         size_t l;
1018
1019         ent->entry.keys.len = ldap_count_values_len(keys);
1020         ent->entry.keys.val = (Key *) calloc(ent->entry.keys.len, sizeof(Key));
1021         if (ent->entry.keys.val == NULL) {
1022             ret = ENOMEM;
1023             krb5_set_error_message(context, ret, "calloc: out of memory");
1024             goto out;
1025         }
1026         for (i = 0; i < ent->entry.keys.len; i++) {
1027             decode_Key((unsigned char *) keys[i]->bv_val,
1028                        (size_t) keys[i]->bv_len, &ent->entry.keys.val[i], &l);
1029         }
1030         ber_bvecfree(keys);
1031     } else {
1032 #if 1
1033         /*
1034          * This violates the ASN1 but it allows a principal to
1035          * be related to a general directory entry without creating
1036          * the keys. Hopefully it's OK.
1037          */
1038         ent->entry.keys.len = 0;
1039         ent->entry.keys.val = NULL;
1040 #else
1041         ret = HDB_ERR_NOENTRY;
1042         goto out;
1043 #endif
1044     }
1045
1046     vals = ldap_get_values_len(HDB2LDAP(db), msg, "krb5EncryptionType");
1047     if (vals != NULL) {
1048         int i;
1049
1050         ent->entry.etypes = malloc(sizeof(*(ent->entry.etypes)));
1051         if (ent->entry.etypes == NULL) {
1052             ret = ENOMEM;
1053             krb5_set_error_message(context, ret,"malloc: out of memory");
1054             goto out;
1055         }
1056         ent->entry.etypes->len = ldap_count_values_len(vals);
1057         ent->entry.etypes->val = calloc(ent->entry.etypes->len, sizeof(int));
1058         if (ent->entry.etypes->val == NULL) {
1059             ret = ENOMEM;
1060             krb5_set_error_message(context, ret, "malloc: out of memory");
1061             ent->entry.etypes->len = 0;
1062             goto out;
1063         }
1064         for (i = 0; i < ent->entry.etypes->len; i++) {
1065             char *buf;
1066
1067             buf = malloc(vals[i]->bv_len + 1);
1068             if (buf == NULL) {
1069                 ret = ENOMEM;
1070                 krb5_set_error_message(context, ret, "malloc: out of memory");
1071                 goto out;
1072             }
1073             memcpy(buf, vals[i]->bv_val, vals[i]->bv_len);
1074             buf[vals[i]->bv_len] = '\0';
1075             ent->entry.etypes->val[i] = atoi(buf);
1076             free(buf);
1077         }
1078         ldap_value_free_len(vals);
1079     }
1080
1081     for (i = 0; i < ent->entry.keys.len; i++) {
1082         if (ent->entry.keys.val[i].key.keytype == ETYPE_ARCFOUR_HMAC_MD5) {
1083             have_arcfour = 1;
1084             break;
1085         }
1086     }
1087
1088     /* manually construct the NT (type 23) key */
1089     ret = LDAP_get_string_value(db, msg, "sambaNTPassword", &ntPasswordIN);
1090     if (ret == 0 && have_arcfour == 0) {
1091         unsigned *etypes;
1092         Key *keys;
1093         int i;
1094
1095         keys = realloc(ent->entry.keys.val,
1096                        (ent->entry.keys.len + 1) * sizeof(ent->entry.keys.val[0]));
1097         if (keys == NULL) {
1098             free(ntPasswordIN);
1099             ret = ENOMEM;
1100             krb5_set_error_message(context, ret, "malloc: out of memory");
1101             goto out;
1102         }
1103         ent->entry.keys.val = keys;
1104         memset(&ent->entry.keys.val[ent->entry.keys.len], 0, sizeof(Key));
1105         ent->entry.keys.val[ent->entry.keys.len].key.keytype = ETYPE_ARCFOUR_HMAC_MD5;
1106         ret = krb5_data_alloc (&ent->entry.keys.val[ent->entry.keys.len].key.keyvalue, 16);
1107         if (ret) {
1108             krb5_set_error_message(context, ret, "malloc: out of memory");
1109             free(ntPasswordIN);
1110             ret = ENOMEM;
1111             goto out;
1112         }
1113         ret = hex_decode(ntPasswordIN,
1114                          ent->entry.keys.val[ent->entry.keys.len].key.keyvalue.data, 16);
1115         ent->entry.keys.len++;
1116
1117         if (ent->entry.etypes == NULL) {
1118             ent->entry.etypes = malloc(sizeof(*(ent->entry.etypes)));
1119             if (ent->entry.etypes == NULL) {
1120                 ret = ENOMEM;
1121                 krb5_set_error_message(context, ret, "malloc: out of memory");
1122                 goto out;
1123             }
1124             ent->entry.etypes->val = NULL;
1125             ent->entry.etypes->len = 0;
1126         }
1127
1128         for (i = 0; i < ent->entry.etypes->len; i++)
1129             if (ent->entry.etypes->val[i] == ETYPE_ARCFOUR_HMAC_MD5)
1130                 break;
1131         /* If there is no ARCFOUR enctype, add one */
1132         if (i == ent->entry.etypes->len) {
1133             etypes = realloc(ent->entry.etypes->val,
1134                              (ent->entry.etypes->len + 1) *
1135                              sizeof(ent->entry.etypes->val[0]));
1136             if (etypes == NULL) {
1137                 ret = ENOMEM;
1138                 krb5_set_error_message(context, ret, "malloc: out of memory");
1139                 goto out;
1140             }
1141             ent->entry.etypes->val = etypes;
1142             ent->entry.etypes->val[ent->entry.etypes->len] =
1143                 ETYPE_ARCFOUR_HMAC_MD5;
1144             ent->entry.etypes->len++;
1145         }
1146     }
1147
1148     ret = LDAP_get_generalized_time_value(db, msg, "createTimestamp",
1149                                           &ent->entry.created_by.time);
1150     if (ret)
1151         ent->entry.created_by.time = time(NULL);
1152
1153     ent->entry.created_by.principal = NULL;
1154
1155     if (flags & HDB_F_ADMIN_DATA) {
1156         ret = LDAP_get_string_value(db, msg, "creatorsName", &dn);
1157         if (ret == 0) {
1158             LDAP_dn2principal(context, db, dn, &ent->entry.created_by.principal);
1159             free(dn);
1160         }
1161
1162         ent->entry.modified_by = calloc(1, sizeof(*ent->entry.modified_by));
1163         if (ent->entry.modified_by == NULL) {
1164             ret = ENOMEM;
1165             krb5_set_error_message(context, ret, "malloc: out of memory");
1166             goto out;
1167         }
1168
1169         ret = LDAP_get_generalized_time_value(db, msg, "modifyTimestamp",
1170                                               &ent->entry.modified_by->time);
1171         if (ret == 0) {
1172             ret = LDAP_get_string_value(db, msg, "modifiersName", &dn);
1173             if (ret == 0) {
1174                 LDAP_dn2principal(context, db, dn, &ent->entry.modified_by->principal);
1175                 free(dn);
1176             } else {
1177                 free(ent->entry.modified_by);
1178                 ent->entry.modified_by = NULL;
1179             }
1180         }
1181     }
1182
1183     ent->entry.valid_start = malloc(sizeof(*ent->entry.valid_start));
1184     if (ent->entry.valid_start == NULL) {
1185         ret = ENOMEM;
1186         krb5_set_error_message(context, ret, "malloc: out of memory");
1187         goto out;
1188     }
1189     ret = LDAP_get_generalized_time_value(db, msg, "krb5ValidStart",
1190                                           ent->entry.valid_start);
1191     if (ret) {
1192         /* OPTIONAL */
1193         free(ent->entry.valid_start);
1194         ent->entry.valid_start = NULL;
1195     }
1196
1197     ent->entry.valid_end = malloc(sizeof(*ent->entry.valid_end));
1198     if (ent->entry.valid_end == NULL) {
1199         ret = ENOMEM;
1200         krb5_set_error_message(context, ret, "malloc: out of memory");
1201         goto out;
1202     }
1203     ret = LDAP_get_generalized_time_value(db, msg, "krb5ValidEnd",
1204                                           ent->entry.valid_end);
1205     if (ret) {
1206         /* OPTIONAL */
1207         free(ent->entry.valid_end);
1208         ent->entry.valid_end = NULL;
1209     }
1210
1211     ret = LDAP_get_integer_value(db, msg, "sambaKickoffTime", &tmp_time);
1212     if (ret == 0) {
1213         if (ent->entry.valid_end == NULL) {
1214             ent->entry.valid_end = malloc(sizeof(*ent->entry.valid_end));
1215             if (ent->entry.valid_end == NULL) {
1216                 ret = ENOMEM;
1217                 krb5_set_error_message(context, ret, "malloc: out of memory");
1218                 goto out;
1219             }
1220         }
1221         *ent->entry.valid_end = tmp_time;
1222     }
1223
1224     ent->entry.pw_end = malloc(sizeof(*ent->entry.pw_end));
1225     if (ent->entry.pw_end == NULL) {
1226         ret = ENOMEM;
1227         krb5_set_error_message(context, ret, "malloc: out of memory");
1228         goto out;
1229     }
1230     ret = LDAP_get_generalized_time_value(db, msg, "krb5PasswordEnd",
1231                                           ent->entry.pw_end);
1232     if (ret) {
1233         /* OPTIONAL */
1234         free(ent->entry.pw_end);
1235         ent->entry.pw_end = NULL;
1236     }
1237
1238     ret = LDAP_get_integer_value(db, msg, "sambaPwdLastSet", &tmp_time);
1239     if (ret == 0) {
1240         time_t delta;
1241
1242         if (ent->entry.pw_end == NULL) {
1243             ent->entry.pw_end = malloc(sizeof(*ent->entry.pw_end));
1244             if (ent->entry.pw_end == NULL) {
1245                 ret = ENOMEM;
1246                 krb5_set_error_message(context, ret, "malloc: out of memory");
1247                 goto out;
1248             }
1249         }
1250
1251         delta = krb5_config_get_time_default(context, NULL,
1252                                              365 * 24 * 60 * 60,
1253                                              "kadmin",
1254                                              "password_lifetime",
1255                                              NULL);
1256         *ent->entry.pw_end = tmp_time + delta;
1257     }
1258
1259     ret = LDAP_get_integer_value(db, msg, "sambaPwdMustChange", &tmp_time);
1260     if (ret == 0) {
1261         if (ent->entry.pw_end == NULL) {
1262             ent->entry.pw_end = malloc(sizeof(*ent->entry.pw_end));
1263             if (ent->entry.pw_end == NULL) {
1264                 ret = ENOMEM;
1265                 krb5_set_error_message(context, ret, "malloc: out of memory");
1266                 goto out;
1267             }
1268         }
1269         *ent->entry.pw_end = tmp_time;
1270     }
1271
1272     /* OPTIONAL */
1273     ret = LDAP_get_integer_value(db, msg, "sambaPwdLastSet", &tmp_time);
1274     if (ret == 0)
1275         hdb_entry_set_pw_change_time(context, &ent->entry, tmp_time);
1276
1277     {
1278         int max_life;
1279
1280         ent->entry.max_life = malloc(sizeof(*ent->entry.max_life));
1281         if (ent->entry.max_life == NULL) {
1282             ret = ENOMEM;
1283             krb5_set_error_message(context, ret, "malloc: out of memory");
1284             goto out;
1285         }
1286         ret = LDAP_get_integer_value(db, msg, "krb5MaxLife", &max_life);
1287         if (ret) {
1288             free(ent->entry.max_life);
1289             ent->entry.max_life = NULL;
1290         } else
1291             *ent->entry.max_life = max_life;
1292     }
1293
1294     {
1295         int max_renew;
1296
1297         ent->entry.max_renew = malloc(sizeof(*ent->entry.max_renew));
1298         if (ent->entry.max_renew == NULL) {
1299             ret = ENOMEM;
1300             krb5_set_error_message(context, ret, "malloc: out of memory");
1301             goto out;
1302         }
1303         ret = LDAP_get_integer_value(db, msg, "krb5MaxRenew", &max_renew);
1304         if (ret) {
1305             free(ent->entry.max_renew);
1306             ent->entry.max_renew = NULL;
1307         } else
1308             *ent->entry.max_renew = max_renew;
1309     }
1310
1311     ret = LDAP_get_integer_value(db, msg, "krb5KDCFlags", &tmp);
1312     if (ret)
1313         tmp = 0;
1314
1315     ent->entry.flags = int2HDBFlags(tmp);
1316
1317     /* Try and find Samba flags to put into the mix */
1318     ret = LDAP_get_string_value(db, msg, "sambaAcctFlags", &samba_acct_flags);
1319     if (ret == 0) {
1320         /* parse the [UXW...] string:
1321
1322            'N'    No password
1323            'D'    Disabled
1324            'H'    Homedir required
1325            'T'    Temp account.
1326            'U'    User account (normal)
1327            'M'    MNS logon user account - what is this ?
1328            'W'    Workstation account
1329            'S'    Server account
1330            'L'    Locked account
1331            'X'    No Xpiry on password
1332            'I'    Interdomain trust account
1333
1334         */
1335
1336         int i;
1337         int flags_len = strlen(samba_acct_flags);
1338
1339         if (flags_len < 2)
1340             goto out2;
1341
1342         if (samba_acct_flags[0] != '['
1343             || samba_acct_flags[flags_len - 1] != ']')
1344             goto out2;
1345
1346         /* Allow forwarding */
1347         if (samba_forwardable)
1348             ent->entry.flags.forwardable = TRUE;
1349
1350         for (i=0; i < flags_len; i++) {
1351             switch (samba_acct_flags[i]) {
1352             case ' ':
1353             case '[':
1354             case ']':
1355                 break;
1356             case 'N':
1357                 /* how to handle no password in kerberos? */
1358                 break;
1359             case 'D':
1360                 ent->entry.flags.invalid = TRUE;
1361                 break;
1362             case 'H':
1363                 break;
1364             case 'T':
1365                 /* temp duplicate */
1366                 ent->entry.flags.invalid = TRUE;
1367                 break;
1368             case 'U':
1369                 ent->entry.flags.client = TRUE;
1370                 break;
1371             case 'M':
1372                 break;
1373             case 'W':
1374             case 'S':
1375                 ent->entry.flags.server = TRUE;
1376                 ent->entry.flags.client = TRUE;
1377                 break;
1378             case 'L':
1379                 ent->entry.flags.invalid = TRUE;
1380                 break;
1381             case 'X':
1382                 if (ent->entry.pw_end) {
1383                     free(ent->entry.pw_end);
1384                     ent->entry.pw_end = NULL;
1385                 }
1386                 break;
1387             case 'I':
1388                 ent->entry.flags.server = TRUE;
1389                 ent->entry.flags.client = TRUE;
1390                 break;
1391             }
1392         }
1393     out2:
1394         free(samba_acct_flags);
1395     }
1396
1397     ret = 0;
1398
1399 out:
1400     if (unparsed_name)
1401         free(unparsed_name);
1402
1403     if (ret)
1404         hdb_free_entry(context, ent);
1405
1406     return ret;
1407 }
1408
1409 static krb5_error_code
1410 LDAP_close(krb5_context context, HDB * db)
1411 {
1412     if (HDB2LDAP(db)) {
1413         ldap_unbind_ext(HDB2LDAP(db), NULL, NULL);
1414         ((struct hdbldapdb *)db->hdb_db)->h_lp = NULL;
1415     }
1416
1417     return 0;
1418 }
1419
1420 static krb5_error_code
1421 LDAP_lock(krb5_context context, HDB * db, int operation)
1422 {
1423     return 0;
1424 }
1425
1426 static krb5_error_code
1427 LDAP_unlock(krb5_context context, HDB * db)
1428 {
1429     return 0;
1430 }
1431
1432 static krb5_error_code
1433 LDAP_seq(krb5_context context, HDB * db, unsigned flags, hdb_entry_ex * entry)
1434 {
1435     int msgid, rc, parserc;
1436     krb5_error_code ret;
1437     LDAPMessage *e;
1438
1439     msgid = HDB2MSGID(db);
1440     if (msgid < 0)
1441         return HDB_ERR_NOENTRY;
1442
1443     do {
1444         rc = ldap_result(HDB2LDAP(db), msgid, LDAP_MSG_ONE, NULL, &e);
1445         switch (rc) {
1446         case LDAP_RES_SEARCH_REFERENCE:
1447             ldap_msgfree(e);
1448             ret = 0;
1449             break;
1450         case LDAP_RES_SEARCH_ENTRY:
1451             /* We have an entry. Parse it. */
1452             ret = LDAP_message2entry(context, db, e, flags, entry);
1453             ldap_msgfree(e);
1454             break;
1455         case LDAP_RES_SEARCH_RESULT:
1456             /* We're probably at the end of the results. If not, abandon. */
1457             parserc =
1458                 ldap_parse_result(HDB2LDAP(db), e, NULL, NULL, NULL,
1459                                   NULL, NULL, 1);
1460             ret = HDB_ERR_NOENTRY;
1461             if (parserc != LDAP_SUCCESS
1462                 && parserc != LDAP_MORE_RESULTS_TO_RETURN) {
1463                 krb5_set_error_message(context, ret, "ldap_parse_result: %s",
1464                                        ldap_err2string(parserc));
1465                 ldap_abandon_ext(HDB2LDAP(db), msgid, NULL, NULL);
1466             }
1467             HDBSETMSGID(db, -1);
1468             break;
1469         case LDAP_SERVER_DOWN:
1470             ldap_msgfree(e);
1471             LDAP_close(context, db);
1472             HDBSETMSGID(db, -1);
1473             ret = ENETDOWN;
1474             break;
1475         default:
1476             /* Some unspecified error (timeout?). Abandon. */
1477             ldap_msgfree(e);
1478             ldap_abandon_ext(HDB2LDAP(db), msgid, NULL, NULL);
1479             ret = HDB_ERR_NOENTRY;
1480             HDBSETMSGID(db, -1);
1481             break;
1482         }
1483     } while (rc == LDAP_RES_SEARCH_REFERENCE);
1484
1485     if (ret == 0) {
1486         if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
1487             ret = hdb_unseal_keys(context, db, &entry->entry);
1488             if (ret)
1489                 hdb_free_entry(context, entry);
1490         }
1491     }
1492
1493     return ret;
1494 }
1495
1496 static krb5_error_code
1497 LDAP_firstkey(krb5_context context, HDB *db, unsigned flags,
1498               hdb_entry_ex *entry)
1499 {
1500     krb5_error_code ret;
1501     int msgid;
1502
1503     ret = LDAP__connect(context, db);
1504     if (ret)
1505         return ret;
1506
1507     ret = LDAP_no_size_limit(context, HDB2LDAP(db));
1508     if (ret)
1509         return ret;
1510
1511     ret = ldap_search_ext(HDB2LDAP(db), HDB2BASE(db),
1512                         LDAP_SCOPE_SUBTREE,
1513                         "(|(objectClass=krb5Principal)(objectClass=sambaSamAccount))",
1514                         krb5kdcentry_attrs, 0,
1515                         NULL, NULL, NULL, 0, &msgid);
1516     if (msgid < 0)
1517         return HDB_ERR_NOENTRY;
1518
1519     HDBSETMSGID(db, msgid);
1520
1521     return LDAP_seq(context, db, flags, entry);
1522 }
1523
1524 static krb5_error_code
1525 LDAP_nextkey(krb5_context context, HDB * db, unsigned flags,
1526              hdb_entry_ex * entry)
1527 {
1528     return LDAP_seq(context, db, flags, entry);
1529 }
1530
1531 static krb5_error_code
1532 LDAP__connect(krb5_context context, HDB * db)
1533 {
1534     int rc, version = LDAP_VERSION3;
1535     /*
1536      * Empty credentials to do a SASL bind with LDAP. Note that empty
1537      * different from NULL credentials. If you provide NULL
1538      * credentials instead of empty credentials you will get a SASL
1539      * bind in progress message.
1540      */
1541     struct berval bv = { 0, "" };
1542
1543     if (HDB2LDAP(db)) {
1544         /* connection has been opened. ping server. */
1545         struct sockaddr_un addr;
1546         socklen_t len = sizeof(addr);
1547         int sd;
1548
1549         if (ldap_get_option(HDB2LDAP(db), LDAP_OPT_DESC, &sd) == 0 &&
1550             getpeername(sd, (struct sockaddr *) &addr, &len) < 0) {
1551             /* the other end has died. reopen. */
1552             LDAP_close(context, db);
1553         }
1554     }
1555
1556     if (HDB2LDAP(db) != NULL) /* server is UP */
1557         return 0;
1558
1559     rc = ldap_initialize(&((struct hdbldapdb *)db->hdb_db)->h_lp, HDB2URL(db));
1560     if (rc != LDAP_SUCCESS) {
1561         krb5_set_error_message(context, HDB_ERR_NOENTRY, "ldap_initialize: %s",
1562                                ldap_err2string(rc));
1563         return HDB_ERR_NOENTRY;
1564     }
1565
1566     rc = ldap_set_option(HDB2LDAP(db), LDAP_OPT_PROTOCOL_VERSION,
1567                          (const void *)&version);
1568     if (rc != LDAP_SUCCESS) {
1569         krb5_set_error_message(context, HDB_ERR_BADVERSION,
1570                                "ldap_set_option: %s", ldap_err2string(rc));
1571         LDAP_close(context, db);
1572         return HDB_ERR_BADVERSION;
1573     }
1574
1575     rc = ldap_sasl_bind_s(HDB2LDAP(db), NULL, "EXTERNAL", &bv,
1576                           NULL, NULL, NULL);
1577     if (rc != LDAP_SUCCESS) {
1578         krb5_set_error_message(context, HDB_ERR_BADVERSION,
1579                               "ldap_sasl_bind_s: %s", ldap_err2string(rc));
1580         LDAP_close(context, db);
1581         return HDB_ERR_BADVERSION;
1582     }
1583
1584     return 0;
1585 }
1586
1587 static krb5_error_code
1588 LDAP_open(krb5_context context, HDB * db, int flags, mode_t mode)
1589 {
1590     /* Not the right place for this. */
1591 #ifdef HAVE_SIGACTION
1592     struct sigaction sa;
1593
1594     sa.sa_flags = 0;
1595     sa.sa_handler = SIG_IGN;
1596     sigemptyset(&sa.sa_mask);
1597
1598     sigaction(SIGPIPE, &sa, NULL);
1599 #else
1600     signal(SIGPIPE, SIG_IGN);
1601 #endif /* HAVE_SIGACTION */
1602
1603     return LDAP__connect(context, db);
1604 }
1605
1606 static krb5_error_code
1607 LDAP_fetch_kvno(krb5_context context, HDB * db, krb5_const_principal principal,
1608                 unsigned flags, krb5_kvno kvno, hdb_entry_ex * entry)
1609 {
1610     LDAPMessage *msg, *e;
1611     krb5_error_code ret;
1612
1613     ret = LDAP_principal2message(context, db, principal, &msg);
1614     if (ret)
1615         return ret;
1616
1617     e = ldap_first_entry(HDB2LDAP(db), msg);
1618     if (e == NULL) {
1619         ret = HDB_ERR_NOENTRY;
1620         goto out;
1621     }
1622
1623     ret = LDAP_message2entry(context, db, e, flags, entry);
1624     if (ret == 0) {
1625         if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
1626             ret = hdb_unseal_keys(context, db, &entry->entry);
1627             if (ret)
1628                 hdb_free_entry(context, entry);
1629         }
1630     }
1631
1632   out:
1633     ldap_msgfree(msg);
1634
1635     return ret;
1636 }
1637
1638 static krb5_error_code
1639 LDAP_fetch(krb5_context context, HDB * db, krb5_const_principal principal,
1640            unsigned flags, hdb_entry_ex * entry)
1641 {
1642     return LDAP_fetch_kvno(context, db, principal,
1643                            flags & (~HDB_F_KVNO_SPECIFIED), 0, entry);
1644 }
1645
1646 static krb5_error_code
1647 LDAP_store(krb5_context context, HDB * db, unsigned flags,
1648            hdb_entry_ex * entry)
1649 {
1650     LDAPMod **mods = NULL;
1651     krb5_error_code ret;
1652     const char *errfn;
1653     int rc;
1654     LDAPMessage *msg = NULL, *e = NULL;
1655     char *dn = NULL, *name = NULL;
1656
1657     ret = LDAP_principal2message(context, db, entry->entry.principal, &msg);
1658     if (ret == 0)
1659         e = ldap_first_entry(HDB2LDAP(db), msg);
1660
1661     ret = krb5_unparse_name(context, entry->entry.principal, &name);
1662     if (ret) {
1663         free(name);
1664         return ret;
1665     }
1666
1667     ret = hdb_seal_keys(context, db, &entry->entry);
1668     if (ret)
1669         goto out;
1670
1671     /* turn new entry into LDAPMod array */
1672     ret = LDAP_entry2mods(context, db, entry, e, &mods);
1673     if (ret)
1674         goto out;
1675
1676     if (e == NULL) {
1677         ret = asprintf(&dn, "krb5PrincipalName=%s,%s", name, HDB2CREATE(db));
1678         if (ret < 0) {
1679             ret = ENOMEM;
1680             krb5_set_error_message(context, ret, "asprintf: out of memory");
1681             goto out;
1682         }
1683     } else if (flags & HDB_F_REPLACE) {
1684         /* Entry exists, and we're allowed to replace it. */
1685         dn = ldap_get_dn(HDB2LDAP(db), e);
1686     } else {
1687         /* Entry exists, but we're not allowed to replace it. Bail. */
1688         ret = HDB_ERR_EXISTS;
1689         goto out;
1690     }
1691
1692     /* write entry into directory */
1693     if (e == NULL) {
1694         /* didn't exist before */
1695         rc = ldap_add_ext_s(HDB2LDAP(db), dn, mods, NULL, NULL );
1696         errfn = "ldap_add_ext_s";
1697     } else {
1698         /* already existed, send deltas only */
1699         rc = ldap_modify_ext_s(HDB2LDAP(db), dn, mods, NULL, NULL );
1700         errfn = "ldap_modify_ext_s";
1701     }
1702
1703     if (check_ldap(context, db, rc)) {
1704         char *ld_error = NULL;
1705         ldap_get_option(HDB2LDAP(db), LDAP_OPT_ERROR_STRING,
1706                         &ld_error);
1707         ret = HDB_ERR_CANT_LOCK_DB;
1708         krb5_set_error_message(context, ret, "%s: %s (DN=%s) %s: %s",
1709                               errfn, name, dn, ldap_err2string(rc), ld_error);
1710     } else
1711         ret = 0;
1712
1713   out:
1714     /* free stuff */
1715     if (dn)
1716         free(dn);
1717     if (msg)
1718         ldap_msgfree(msg);
1719     if (mods)
1720         ldap_mods_free(mods, 1);
1721     if (name)
1722         free(name);
1723
1724     return ret;
1725 }
1726
1727 static krb5_error_code
1728 LDAP_remove(krb5_context context, HDB *db, krb5_const_principal principal)
1729 {
1730     krb5_error_code ret;
1731     LDAPMessage *msg, *e;
1732     char *dn = NULL;
1733     int rc, limit = LDAP_NO_LIMIT;
1734
1735     ret = LDAP_principal2message(context, db, principal, &msg);
1736     if (ret)
1737         goto out;
1738
1739     e = ldap_first_entry(HDB2LDAP(db), msg);
1740     if (e == NULL) {
1741         ret = HDB_ERR_NOENTRY;
1742         goto out;
1743     }
1744
1745     dn = ldap_get_dn(HDB2LDAP(db), e);
1746     if (dn == NULL) {
1747         ret = HDB_ERR_NOENTRY;
1748         goto out;
1749     }
1750
1751     rc = ldap_set_option(HDB2LDAP(db), LDAP_OPT_SIZELIMIT, (const void *)&limit);
1752     if (rc != LDAP_SUCCESS) {
1753         ret = HDB_ERR_BADVERSION;
1754         krb5_set_error_message(context, ret, "ldap_set_option: %s",
1755                               ldap_err2string(rc));
1756         goto out;
1757     }
1758
1759     rc = ldap_delete_ext_s(HDB2LDAP(db), dn, NULL, NULL );
1760     if (check_ldap(context, db, rc)) {
1761         ret = HDB_ERR_CANT_LOCK_DB;
1762         krb5_set_error_message(context, ret, "ldap_delete_ext_s: %s",
1763                                ldap_err2string(rc));
1764     } else
1765         ret = 0;
1766
1767   out:
1768     if (dn != NULL)
1769         free(dn);
1770     if (msg != NULL)
1771         ldap_msgfree(msg);
1772
1773     return ret;
1774 }
1775
1776 static krb5_error_code
1777 LDAP_destroy(krb5_context context, HDB * db)
1778 {
1779     krb5_error_code ret;
1780
1781     LDAP_close(context, db);
1782
1783     ret = hdb_clear_master_key(context, db);
1784     if (HDB2BASE(db))
1785         free(HDB2BASE(db));
1786     if (HDB2CREATE(db))
1787         free(HDB2CREATE(db));
1788     if (HDB2URL(db))
1789         free(HDB2URL(db));
1790     if (db->hdb_name)
1791         free(db->hdb_name);
1792     free(db->hdb_db);
1793     free(db);
1794
1795     return ret;
1796 }
1797
1798 static krb5_error_code
1799 hdb_ldap_common(krb5_context context,
1800                 HDB ** db,
1801                 const char *search_base,
1802                 const char *url)
1803 {
1804     struct hdbldapdb *h;
1805     const char *create_base = NULL;
1806
1807     if (search_base == NULL && search_base[0] == '\0') {
1808         krb5_set_error_message(context, ENOMEM, "ldap search base not configured");
1809         return ENOMEM; /* XXX */
1810     }
1811
1812     if (structural_object == NULL) {
1813         const char *p;
1814
1815         p = krb5_config_get_string(context, NULL, "kdc",
1816                                    "hdb-ldap-structural-object", NULL);
1817         if (p == NULL)
1818             p = default_structural_object;
1819         structural_object = strdup(p);
1820         if (structural_object == NULL) {
1821             krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
1822             return ENOMEM;
1823         }
1824     }
1825
1826     samba_forwardable =
1827         krb5_config_get_bool_default(context, NULL, TRUE,
1828                                      "kdc", "hdb-samba-forwardable", NULL);
1829
1830     *db = calloc(1, sizeof(**db));
1831     if (*db == NULL) {
1832         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
1833         return ENOMEM;
1834     }
1835     memset(*db, 0, sizeof(**db));
1836
1837     h = calloc(1, sizeof(*h));
1838     if (h == NULL) {
1839         free(*db);
1840         *db = NULL;
1841         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
1842         return ENOMEM;
1843     }
1844     (*db)->hdb_db = h;
1845
1846     /* XXX */
1847     if (asprintf(&(*db)->hdb_name, "ldap:%s", search_base) == -1) {
1848         LDAP_destroy(context, *db);
1849         *db = NULL;
1850         krb5_set_error_message(context, ENOMEM, "strdup: out of memory");
1851         return ENOMEM;
1852     }
1853
1854     h->h_url = strdup(url);
1855     h->h_base = strdup(search_base);
1856     if (h->h_url == NULL || h->h_base == NULL) {
1857         LDAP_destroy(context, *db);
1858         *db = NULL;
1859         krb5_set_error_message(context, ENOMEM, "strdup: out of memory");
1860         return ENOMEM;
1861     }
1862
1863     create_base = krb5_config_get_string(context, NULL, "kdc",
1864                                          "hdb-ldap-create-base", NULL);
1865     if (create_base == NULL)
1866         create_base = h->h_base;
1867
1868     h->h_createbase = strdup(create_base);
1869     if (h->h_createbase == NULL) {
1870         LDAP_destroy(context, *db);
1871         *db = NULL;
1872         krb5_set_error_message(context, ENOMEM, "strdup: out of memory");
1873         return ENOMEM;
1874     }
1875
1876     (*db)->hdb_master_key_set = 0;
1877     (*db)->hdb_openp = 0;
1878     (*db)->hdb_capability_flags = 0;
1879     (*db)->hdb_open = LDAP_open;
1880     (*db)->hdb_close = LDAP_close;
1881     (*db)->hdb_fetch_kvno = LDAP_fetch_kvno;
1882     (*db)->hdb_store = LDAP_store;
1883     (*db)->hdb_remove = LDAP_remove;
1884     (*db)->hdb_firstkey = LDAP_firstkey;
1885     (*db)->hdb_nextkey = LDAP_nextkey;
1886     (*db)->hdb_lock = LDAP_lock;
1887     (*db)->hdb_unlock = LDAP_unlock;
1888     (*db)->hdb_rename = NULL;
1889     (*db)->hdb__get = NULL;
1890     (*db)->hdb__put = NULL;
1891     (*db)->hdb__del = NULL;
1892     (*db)->hdb_destroy = LDAP_destroy;
1893
1894     return 0;
1895 }
1896
1897 krb5_error_code
1898 hdb_ldap_create(krb5_context context, HDB ** db, const char *arg)
1899 {
1900     return hdb_ldap_common(context, db, arg, "ldapi:///");
1901 }
1902
1903 krb5_error_code
1904 hdb_ldapi_create(krb5_context context, HDB ** db, const char *arg)
1905 {
1906     krb5_error_code ret;
1907     char *search_base, *p;
1908
1909     asprintf(&p, "ldapi:%s", arg);
1910     if (p == NULL) {
1911         *db = NULL;
1912         krb5_set_error_message(context, ENOMEM, "out of memory");
1913         return ENOMEM;
1914     }
1915     search_base = strchr(p + strlen("ldapi://"), ':');
1916     if (search_base == NULL) {
1917         *db = NULL;
1918         krb5_set_error_message(context, HDB_ERR_BADVERSION,
1919                                "search base missing");
1920         return HDB_ERR_BADVERSION;
1921     }
1922     *search_base = '\0';
1923     search_base++;
1924
1925     ret = hdb_ldap_common(context, db, search_base, p);
1926     free(p);
1927     return ret;
1928 }
1929
1930 #ifdef OPENLDAP_MODULE
1931
1932 struct hdb_so_method hdb_ldap_interface = {
1933     HDB_INTERFACE_VERSION,
1934     "ldap",
1935     hdb_ldap_create
1936 };
1937
1938 struct hdb_so_method hdb_ldapi_interface = {
1939     HDB_INTERFACE_VERSION,
1940     "ldapi",
1941     hdb_ldapi_create
1942 };
1943
1944 #endif
1945
1946 #endif                          /* OPENLDAP */