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