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