1/*	$NetBSD: ad.c,v 1.1.1.1 2011/04/13 18:15:29 elric Exp $	*/
2
3/*
4 * Copyright (c) 2004 Kungliga Tekniska Högskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#define HAVE_TSASL 1
37
38#include "kadm5_locl.h"
39#if 1
40#undef OPENLDAP
41#undef HAVE_TSASL
42#endif
43#ifdef OPENLDAP
44#include <ldap.h>
45#ifdef HAVE_TSASL
46#include <tsasl.h>
47#endif
48#include <krb5/resolve.h>
49#include <krb5/base64.h>
50#endif
51
52__RCSID("$NetBSD: ad.c,v 1.1.1.1 2011/04/13 18:15:29 elric Exp $");
53
54#ifdef OPENLDAP
55
56#define CTX2LP(context) ((LDAP *)((context)->ldap_conn))
57#define CTX2BASE(context) ((context)->base_dn)
58
59/*
60 * userAccountControl
61 */
62
63#define UF_SCRIPT	 			0x00000001
64#define UF_ACCOUNTDISABLE			0x00000002
65#define UF_UNUSED_0	 			0x00000004
66#define UF_HOMEDIR_REQUIRED			0x00000008
67#define UF_LOCKOUT	 			0x00000010
68#define UF_PASSWD_NOTREQD 			0x00000020
69#define UF_PASSWD_CANT_CHANGE 			0x00000040
70#define UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED	0x00000080
71#define UF_TEMP_DUPLICATE_ACCOUNT       	0x00000100
72#define UF_NORMAL_ACCOUNT               	0x00000200
73#define UF_UNUSED_1	 			0x00000400
74#define UF_INTERDOMAIN_TRUST_ACCOUNT    	0x00000800
75#define UF_WORKSTATION_TRUST_ACCOUNT    	0x00001000
76#define UF_SERVER_TRUST_ACCOUNT         	0x00002000
77#define UF_UNUSED_2	 			0x00004000
78#define UF_UNUSED_3	 			0x00008000
79#define UF_PASSWD_NOT_EXPIRE			0x00010000
80#define UF_MNS_LOGON_ACCOUNT			0x00020000
81#define UF_SMARTCARD_REQUIRED			0x00040000
82#define UF_TRUSTED_FOR_DELEGATION		0x00080000
83#define UF_NOT_DELEGATED			0x00100000
84#define UF_USE_DES_KEY_ONLY			0x00200000
85#define UF_DONT_REQUIRE_PREAUTH			0x00400000
86#define UF_UNUSED_4				0x00800000
87#define UF_UNUSED_5				0x01000000
88#define UF_UNUSED_6				0x02000000
89#define UF_UNUSED_7				0x04000000
90#define UF_UNUSED_8				0x08000000
91#define UF_UNUSED_9				0x10000000
92#define UF_UNUSED_10				0x20000000
93#define UF_UNUSED_11				0x40000000
94#define UF_UNUSED_12				0x80000000
95
96/*
97 *
98 */
99
100#ifndef HAVE_TSASL
101static int
102sasl_interact(LDAP *ld, unsigned flags, void *defaults, void *interact)
103{
104    return LDAP_SUCCESS;
105}
106#endif
107
108#if 0
109static Sockbuf_IO ldap_tsasl_io = {
110    NULL,			/* sbi_setup */
111    NULL,			/* sbi_remove */
112    NULL,			/* sbi_ctrl */
113    NULL,			/* sbi_read */
114    NULL,			/* sbi_write */
115    NULL			/* sbi_close */
116};
117#endif
118
119#ifdef HAVE_TSASL
120static int
121ldap_tsasl_bind_s(LDAP *ld,
122		  LDAP_CONST char *dn,
123		  LDAPControl **serverControls,
124		  LDAPControl **clientControls,
125		  const char *host)
126{
127    char *attrs[] = { "supportedSASLMechanisms", NULL };
128    struct tsasl_peer *peer = NULL;
129    struct tsasl_buffer in, out;
130    struct berval ccred, *scred;
131    LDAPMessage *m, *m0;
132    const char *mech;
133    char **vals;
134    int ret, rc;
135
136    ret = tsasl_peer_init(TSASL_FLAGS_INITIATOR | TSASL_FLAGS_CLEAR,
137			  "ldap", host, &peer);
138    if (ret != TSASL_DONE) {
139	rc = LDAP_LOCAL_ERROR;
140	goto out;
141    }
142
143    rc = ldap_search_s(ld, "", LDAP_SCOPE_BASE, NULL, attrs, 0, &m0);
144    if (rc != LDAP_SUCCESS)
145	goto out;
146
147    m = ldap_first_entry(ld, m0);
148    if (m == NULL) {
149	ldap_msgfree(m0);
150	goto out;
151    }
152
153    vals = ldap_get_values(ld, m, "supportedSASLMechanisms");
154    if (vals == NULL) {
155	ldap_msgfree(m0);
156	goto out;
157    }
158
159    ret = tsasl_find_best_mech(peer, vals, &mech);
160    if (ret) {
161	ldap_msgfree(m0);
162	goto out;
163    }
164
165    ldap_msgfree(m0);
166
167    ret = tsasl_select_mech(peer, mech);
168    if (ret != TSASL_DONE) {
169	rc = LDAP_LOCAL_ERROR;
170	goto out;
171    }
172
173    in.tb_data = NULL;
174    in.tb_size = 0;
175
176    do {
177	ret = tsasl_request(peer, &in, &out);
178	if (in.tb_size != 0) {
179	    free(in.tb_data);
180	    in.tb_data = NULL;
181	    in.tb_size = 0;
182	}
183	if (ret != TSASL_DONE && ret != TSASL_CONTINUE) {
184	    rc = LDAP_AUTH_UNKNOWN;
185	    goto out;
186	}
187
188	ccred.bv_val = out.tb_data;
189	ccred.bv_len = out.tb_size;
190
191	rc = ldap_sasl_bind_s(ld, dn, mech, &ccred,
192			      serverControls, clientControls, &scred);
193	tsasl_buffer_free(&out);
194
195	if (rc != LDAP_SUCCESS && rc != LDAP_SASL_BIND_IN_PROGRESS) {
196	    if(scred && scred->bv_len)
197		ber_bvfree(scred);
198	    goto out;
199	}
200
201	in.tb_data = malloc(scred->bv_len);
202	if (in.tb_data == NULL) {
203	    rc = LDAP_LOCAL_ERROR;
204	    goto out;
205	}
206	memcpy(in.tb_data, scred->bv_val, scred->bv_len);
207	in.tb_size = scred->bv_len;
208	ber_bvfree(scred);
209
210    } while (rc == LDAP_SASL_BIND_IN_PROGRESS);
211
212 out:
213    if (rc == LDAP_SUCCESS) {
214#if 0
215	ber_sockbuf_add_io(ld->ld_conns->lconn_sb, &ldap_tsasl_io,
216			   LBER_SBIOD_LEVEL_APPLICATION, peer);
217
218#endif
219    } else if (peer != NULL)
220	tsasl_peer_free(peer);
221
222    return rc;
223}
224#endif /* HAVE_TSASL */
225
226
227static int
228check_ldap(kadm5_ad_context *context, int ret)
229{
230    switch (ret) {
231    case LDAP_SUCCESS:
232	return 0;
233    case LDAP_SERVER_DOWN: {
234	LDAP *lp = CTX2LP(context);
235	ldap_unbind(lp);
236	context->ldap_conn = NULL;
237	free(context->base_dn);
238	context->base_dn = NULL;
239	return 1;
240    }
241    default:
242	return 1;
243    }
244}
245
246/*
247 *
248 */
249
250static void
251laddattr(char ***al, int *attrlen, char *attr)
252{
253    char **a;
254    a = realloc(*al, (*attrlen + 2) * sizeof(**al));
255    if (a == NULL)
256	return;
257    a[*attrlen] = attr;
258    a[*attrlen + 1] = NULL;
259    (*attrlen)++;
260    *al = a;
261}
262
263static kadm5_ret_t
264_kadm5_ad_connect(void *server_handle)
265{
266    kadm5_ad_context *context = server_handle;
267    struct {
268	char *server;
269	int port;
270    } *s, *servers = NULL;
271    int i, num_servers = 0;
272
273    if (context->ldap_conn)
274	return 0;
275
276    {
277	struct dns_reply *r;
278	struct resource_record *rr;
279	char *domain;
280
281	asprintf(&domain, "_ldap._tcp.%s", context->realm);
282	if (domain == NULL) {
283	    krb5_set_error_message(context->context, KADM5_NO_SRV, "malloc");
284	    return KADM5_NO_SRV;
285	}
286
287	r = dns_lookup(domain, "SRV");
288	free(domain);
289	if (r == NULL) {
290	    krb5_set_error_message(context->context, KADM5_NO_SRV, "Didn't find ldap dns");
291	    return KADM5_NO_SRV;
292	}
293
294	for (rr = r->head ; rr != NULL; rr = rr->next) {
295	    if (rr->type != rk_ns_t_srv)
296		continue;
297	    s = realloc(servers, sizeof(*servers) * (num_servers + 1));
298	    if (s == NULL) {
299		krb5_set_error_message(context->context, KADM5_RPC_ERROR, "malloc");
300		dns_free_data(r);
301		goto fail;
302	    }
303	    servers = s;
304	    num_servers++;
305	    servers[num_servers - 1].port =  rr->u.srv->port;
306	    servers[num_servers - 1].server =  strdup(rr->u.srv->target);
307	}
308	dns_free_data(r);
309    }
310
311    if (num_servers == 0) {
312	krb5_set_error_message(context->context, KADM5_NO_SRV, "No AD server found in DNS");
313	return KADM5_NO_SRV;
314    }
315
316    for (i = 0; i < num_servers; i++) {
317	int lret, version = LDAP_VERSION3;
318	LDAP *lp;
319
320	lp = ldap_init(servers[i].server, servers[i].port);
321	if (lp == NULL)
322	    continue;
323
324	if (ldap_set_option(lp, LDAP_OPT_PROTOCOL_VERSION, &version)) {
325	    ldap_unbind(lp);
326	    continue;
327	}
328
329	if (ldap_set_option(lp, LDAP_OPT_REFERRALS, LDAP_OPT_OFF)) {
330	    ldap_unbind(lp);
331	    continue;
332	}
333
334#ifdef HAVE_TSASL
335	lret = ldap_tsasl_bind_s(lp, NULL, NULL, NULL, servers[i].server);
336
337#else
338	lret = ldap_sasl_interactive_bind_s(lp, NULL, NULL, NULL, NULL,
339					    LDAP_SASL_QUIET,
340					    sasl_interact, NULL);
341#endif
342	if (lret != LDAP_SUCCESS) {
343	    krb5_set_error_message(context->context, 0,
344				   "Couldn't contact any AD servers: %s",
345				   ldap_err2string(lret));
346	    ldap_unbind(lp);
347	    continue;
348	}
349
350	context->ldap_conn = lp;
351	break;
352    }
353    if (i >= num_servers) {
354	goto fail;
355    }
356
357    {
358	LDAPMessage *m, *m0;
359	char **attr = NULL;
360	int attrlen = 0;
361	char **vals;
362	int ret;
363
364	laddattr(&attr, &attrlen, "defaultNamingContext");
365
366	ret = ldap_search_s(CTX2LP(context), "", LDAP_SCOPE_BASE,
367			    "objectclass=*", attr, 0, &m);
368	free(attr);
369	if (check_ldap(context, ret))
370	    goto fail;
371
372	if (ldap_count_entries(CTX2LP(context), m) > 0) {
373	    m0 = ldap_first_entry(CTX2LP(context), m);
374	    if (m0 == NULL) {
375		krb5_set_error_message(context->context, KADM5_RPC_ERROR,
376				       "Error in AD ldap responce");
377		ldap_msgfree(m);
378		goto fail;
379	    }
380	    vals = ldap_get_values(CTX2LP(context),
381				   m0, "defaultNamingContext");
382	    if (vals == NULL) {
383		krb5_set_error_message(context->context, KADM5_RPC_ERROR,
384				       "No naming context found");
385		goto fail;
386	    }
387	    context->base_dn = strdup(vals[0]);
388	} else
389	    goto fail;
390	ldap_msgfree(m);
391    }
392
393    for (i = 0; i < num_servers; i++)
394	free(servers[i].server);
395    free(servers);
396
397    return 0;
398
399 fail:
400    for (i = 0; i < num_servers; i++)
401	free(servers[i].server);
402    free(servers);
403
404    if (context->ldap_conn) {
405	ldap_unbind(CTX2LP(context));
406	context->ldap_conn = NULL;
407    }
408    return KADM5_RPC_ERROR;
409}
410
411#define NTTIME_EPOCH 0x019DB1DED53E8000LL
412
413static time_t
414nt2unixtime(const char *str)
415{
416    unsigned long long t;
417    t = strtoll(str, NULL, 10);
418    t = ((t - NTTIME_EPOCH) / (long long)10000000);
419    if (t > (((time_t)(~(long long)0)) >> 1))
420	return 0;
421    return (time_t)t;
422}
423
424static long long
425unix2nttime(time_t unix_time)
426{
427    long long wt;
428    wt = unix_time * (long long)10000000 + (long long)NTTIME_EPOCH;
429    return wt;
430}
431
432/* XXX create filter in a better way */
433
434static int
435ad_find_entry(kadm5_ad_context *context,
436	      const char *fqdn,
437	      const char *pn,
438	      char **name)
439{
440    LDAPMessage *m, *m0;
441    char *attr[] = { "distinguishedName", NULL };
442    char *filter;
443    int ret;
444
445    if (name)
446	*name = NULL;
447
448    if (fqdn)
449	asprintf(&filter,
450		 "(&(objectClass=computer)(|(dNSHostName=%s)(servicePrincipalName=%s)))",
451		 fqdn, pn);
452    else if(pn)
453	asprintf(&filter, "(&(objectClass=account)(userPrincipalName=%s))", pn);
454    else
455	return KADM5_RPC_ERROR;
456
457    ret = ldap_search_s(CTX2LP(context), CTX2BASE(context),
458			LDAP_SCOPE_SUBTREE,
459			filter, attr, 0, &m);
460    free(filter);
461    if (check_ldap(context, ret))
462	return KADM5_RPC_ERROR;
463
464    if (ldap_count_entries(CTX2LP(context), m) > 0) {
465	char **vals;
466	m0 = ldap_first_entry(CTX2LP(context), m);
467	vals = ldap_get_values(CTX2LP(context), m0, "distinguishedName");
468	if (vals == NULL || vals[0] == NULL) {
469	    ldap_msgfree(m);
470	    return KADM5_RPC_ERROR;
471	}
472	if (name)
473	    *name = strdup(vals[0]);
474	ldap_msgfree(m);
475    } else
476	return KADM5_UNK_PRINC;
477
478    return 0;
479}
480
481#endif /* OPENLDAP */
482
483static kadm5_ret_t
484ad_get_cred(kadm5_ad_context *context, const char *password)
485{
486    kadm5_ret_t ret;
487    krb5_ccache cc;
488    char *service;
489
490    if (context->ccache)
491	return 0;
492
493    asprintf(&service, "%s/%s@%s", KRB5_TGS_NAME,
494	     context->realm, context->realm);
495    if (service == NULL)
496	return ENOMEM;
497
498    ret = _kadm5_c_get_cred_cache(context->context,
499				  context->client_name,
500				  service,
501				  password, krb5_prompter_posix,
502				  NULL, NULL, &cc);
503    free(service);
504    if(ret)
505	return ret; /* XXX */
506    context->ccache = cc;
507    return 0;
508}
509
510static kadm5_ret_t
511kadm5_ad_chpass_principal(void *server_handle,
512			  krb5_principal principal,
513			  const char *password)
514{
515    kadm5_ad_context *context = server_handle;
516    krb5_data result_code_string, result_string;
517    int result_code;
518    kadm5_ret_t ret;
519
520    ret = ad_get_cred(context, NULL);
521    if (ret)
522	return ret;
523
524    krb5_data_zero (&result_code_string);
525    krb5_data_zero (&result_string);
526
527    ret = krb5_set_password_using_ccache (context->context,
528					  context->ccache,
529					  password,
530					  principal,
531					  &result_code,
532					  &result_code_string,
533					  &result_string);
534
535    krb5_data_free (&result_code_string);
536    krb5_data_free (&result_string);
537
538    /* XXX do mapping here on error codes */
539
540    return ret;
541}
542
543#ifdef OPENLDAP
544static const char *
545get_fqdn(krb5_context context, const krb5_principal p)
546{
547    const char *s, *hosttypes[] = { "host", "ldap", "gc", "cifs", "dns" };
548    int i;
549
550    s = krb5_principal_get_comp_string(context, p, 0);
551    if (p == NULL)
552	return NULL;
553
554    for (i = 0; i < sizeof(hosttypes)/sizeof(hosttypes[0]); i++) {
555	if (strcasecmp(s, hosttypes[i]) == 0)
556	    return krb5_principal_get_comp_string(context, p, 1);
557    }
558    return 0;
559}
560#endif
561
562
563static kadm5_ret_t
564kadm5_ad_create_principal(void *server_handle,
565			  kadm5_principal_ent_t entry,
566			  uint32_t mask,
567			  const char *password)
568{
569    kadm5_ad_context *context = server_handle;
570
571    /*
572     * KADM5_PRINC_EXPIRE_TIME
573     *
574     * return 0 || KADM5_DUP;
575     */
576
577#ifdef OPENLDAP
578    LDAPMod *attrs[8], rattrs[7], *a;
579    char *useraccvals[2] = { NULL, NULL },
580	*samvals[2], *dnsvals[2], *spnvals[5], *upnvals[2], *tv[2];
581    char *ocvals_spn[] = { "top", "person", "organizationalPerson",
582			   "user", "computer", NULL};
583    char *p, *realmless_p, *p_msrealm = NULL, *dn = NULL;
584    const char *fqdn;
585    char *s, *samname = NULL, *short_spn = NULL;
586    int ret, i;
587    int32_t uf_flags = 0;
588
589    if ((mask & KADM5_PRINCIPAL) == 0)
590	return KADM5_BAD_MASK;
591
592    for (i = 0; i < sizeof(rattrs)/sizeof(rattrs[0]); i++)
593	attrs[i] = &rattrs[i];
594    attrs[i] = NULL;
595
596    ret = ad_get_cred(context, NULL);
597    if (ret)
598	return ret;
599
600    ret = _kadm5_ad_connect(server_handle);
601    if (ret)
602	return ret;
603
604    fqdn = get_fqdn(context->context, entry->principal);
605
606    ret = krb5_unparse_name(context->context, entry->principal, &p);
607    if (ret)
608	return ret;
609
610    if (ad_find_entry(context, fqdn, p, NULL) == 0) {
611	free(p);
612	return KADM5_DUP;
613    }
614
615    if (mask & KADM5_ATTRIBUTES) {
616	if (entry->attributes & KRB5_KDB_DISALLOW_ALL_TIX)
617	    uf_flags |= UF_ACCOUNTDISABLE|UF_LOCKOUT;
618	if ((entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH) == 0)
619	    uf_flags |= UF_DONT_REQUIRE_PREAUTH;
620	if (entry->attributes & KRB5_KDB_REQUIRES_HW_AUTH)
621	    uf_flags |= UF_SMARTCARD_REQUIRED;
622    }
623
624    realmless_p = strdup(p);
625    if (realmless_p == NULL) {
626	ret = ENOMEM;
627	goto out;
628    }
629    s = strrchr(realmless_p, '@');
630    if (s)
631	*s = '\0';
632
633    if (fqdn) {
634	/* create computer account */
635	asprintf(&samname, "%s$", fqdn);
636	if (samname == NULL) {
637	    ret = ENOMEM;
638	    goto out;
639	}
640	s = strchr(samname, '.');
641	if (s) {
642	    s[0] = '$';
643	    s[1] = '\0';
644	}
645
646	short_spn = strdup(p);
647	if (short_spn == NULL) {
648	    errno = ENOMEM;
649	    goto out;
650	}
651	s = strchr(short_spn, '.');
652	if (s) {
653	    *s = '\0';
654	} else {
655	    free(short_spn);
656	    short_spn = NULL;
657	}
658
659	p_msrealm = strdup(p);
660	if (p_msrealm == NULL) {
661	    errno = ENOMEM;
662	    goto out;
663	}
664	s = strrchr(p_msrealm, '@');
665	if (s) {
666	    *s = '/';
667	} else {
668	    free(p_msrealm);
669	    p_msrealm = NULL;
670	}
671
672	asprintf(&dn, "cn=%s, cn=Computers, %s", fqdn, CTX2BASE(context));
673	if (dn == NULL) {
674	    ret = ENOMEM;
675	    goto out;
676	}
677
678	a = &rattrs[0];
679	a->mod_op = LDAP_MOD_ADD;
680	a->mod_type = "objectClass";
681	a->mod_values = ocvals_spn;
682	a++;
683
684	a->mod_op = LDAP_MOD_ADD;
685	a->mod_type = "userAccountControl";
686	a->mod_values = useraccvals;
687	asprintf(&useraccvals[0], "%d",
688		 uf_flags |
689		 UF_PASSWD_NOT_EXPIRE |
690		 UF_WORKSTATION_TRUST_ACCOUNT);
691	useraccvals[1] = NULL;
692	a++;
693
694	a->mod_op = LDAP_MOD_ADD;
695	a->mod_type = "sAMAccountName";
696	a->mod_values = samvals;
697	samvals[0] = samname;
698	samvals[1] = NULL;
699	a++;
700
701	a->mod_op = LDAP_MOD_ADD;
702	a->mod_type = "dNSHostName";
703	a->mod_values = dnsvals;
704	dnsvals[0] = (char *)fqdn;
705	dnsvals[1] = NULL;
706	a++;
707
708	/* XXX  add even more spn's */
709	a->mod_op = LDAP_MOD_ADD;
710	a->mod_type = "servicePrincipalName";
711	a->mod_values = spnvals;
712	i = 0;
713	spnvals[i++] = p;
714	spnvals[i++] = realmless_p;
715	if (short_spn)
716	    spnvals[i++] = short_spn;
717	if (p_msrealm)
718	    spnvals[i++] = p_msrealm;
719	spnvals[i++] = NULL;
720	a++;
721
722	a->mod_op = LDAP_MOD_ADD;
723	a->mod_type = "userPrincipalName";
724	a->mod_values = upnvals;
725	upnvals[0] = p;
726	upnvals[1] = NULL;
727	a++;
728
729	a->mod_op = LDAP_MOD_ADD;
730	a->mod_type = "accountExpires";
731	a->mod_values = tv;
732	tv[0] = "9223372036854775807"; /* "never" */
733	tv[1] = NULL;
734	a++;
735
736    } else {
737	/* create user account */
738
739	a = &rattrs[0];
740	a->mod_op = LDAP_MOD_ADD;
741	a->mod_type = "userAccountControl";
742	a->mod_values = useraccvals;
743	asprintf(&useraccvals[0], "%d",
744		 uf_flags |
745		 UF_PASSWD_NOT_EXPIRE);
746	useraccvals[1] = NULL;
747	a++;
748
749	a->mod_op = LDAP_MOD_ADD;
750	a->mod_type = "sAMAccountName";
751	a->mod_values = samvals;
752	samvals[0] = realmless_p;
753	samvals[1] = NULL;
754	a++;
755
756	a->mod_op = LDAP_MOD_ADD;
757	a->mod_type = "userPrincipalName";
758	a->mod_values = upnvals;
759	upnvals[0] = p;
760	upnvals[1] = NULL;
761	a++;
762
763	a->mod_op = LDAP_MOD_ADD;
764	a->mod_type = "accountExpires";
765	a->mod_values = tv;
766	tv[0] = "9223372036854775807"; /* "never" */
767	tv[1] = NULL;
768	a++;
769    }
770
771    attrs[a - &rattrs[0]] = NULL;
772
773    ret = ldap_add_s(CTX2LP(context), dn, attrs);
774
775 out:
776    if (useraccvals[0])
777	free(useraccvals[0]);
778    if (realmless_p)
779	free(realmless_p);
780    if (samname)
781	free(samname);
782    if (short_spn)
783	free(short_spn);
784    if (p_msrealm)
785	free(p_msrealm);
786    free(p);
787
788    if (check_ldap(context, ret))
789	return KADM5_RPC_ERROR;
790
791    return 0;
792#else
793    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
794    return KADM5_RPC_ERROR;
795#endif
796}
797
798static kadm5_ret_t
799kadm5_ad_delete_principal(void *server_handle, krb5_principal principal)
800{
801    kadm5_ad_context *context = server_handle;
802#ifdef OPENLDAP
803    char *p, *dn = NULL;
804    const char *fqdn;
805    int ret;
806
807    ret = ad_get_cred(context, NULL);
808    if (ret)
809	return ret;
810
811    ret = _kadm5_ad_connect(server_handle);
812    if (ret)
813	return ret;
814
815    fqdn = get_fqdn(context->context, principal);
816
817    ret = krb5_unparse_name(context->context, principal, &p);
818    if (ret)
819	return ret;
820
821    if (ad_find_entry(context, fqdn, p, &dn) != 0) {
822	free(p);
823	return KADM5_UNK_PRINC;
824    }
825
826    ret = ldap_delete_s(CTX2LP(context), dn);
827
828    free(dn);
829    free(p);
830
831    if (check_ldap(context, ret))
832	return KADM5_RPC_ERROR;
833    return 0;
834#else
835    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
836    return KADM5_RPC_ERROR;
837#endif
838}
839
840static kadm5_ret_t
841kadm5_ad_destroy(void *server_handle)
842{
843    kadm5_ad_context *context = server_handle;
844
845    if (context->ccache)
846	krb5_cc_destroy(context->context, context->ccache);
847
848#ifdef OPENLDAP
849    {
850	LDAP *lp = CTX2LP(context);
851	if (lp)
852	    ldap_unbind(lp);
853	if (context->base_dn)
854	    free(context->base_dn);
855    }
856#endif
857    free(context->realm);
858    free(context->client_name);
859    krb5_free_principal(context->context, context->caller);
860    if(context->my_context)
861	krb5_free_context(context->context);
862    return 0;
863}
864
865static kadm5_ret_t
866kadm5_ad_flush(void *server_handle)
867{
868    kadm5_ad_context *context = server_handle;
869    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
870    return KADM5_RPC_ERROR;
871}
872
873static kadm5_ret_t
874kadm5_ad_get_principal(void *server_handle,
875		       krb5_principal principal,
876		       kadm5_principal_ent_t entry,
877		       uint32_t mask)
878{
879    kadm5_ad_context *context = server_handle;
880#ifdef OPENLDAP
881    LDAPMessage *m, *m0;
882    char **attr = NULL;
883    int attrlen = 0;
884    char *filter, *p, *q, *u;
885    int ret;
886
887    /*
888     * principal
889     * KADM5_PRINCIPAL | KADM5_KVNO | KADM5_ATTRIBUTES
890     */
891
892    /*
893     * return 0 || KADM5_DUP;
894     */
895
896    memset(entry, 0, sizeof(*entry));
897
898    if (mask & KADM5_KVNO)
899	laddattr(&attr, &attrlen, "msDS-KeyVersionNumber");
900
901    if (mask & KADM5_PRINCIPAL) {
902	laddattr(&attr, &attrlen, "userPrincipalName");
903	laddattr(&attr, &attrlen, "servicePrincipalName");
904    }
905    laddattr(&attr, &attrlen, "objectClass");
906    laddattr(&attr, &attrlen, "lastLogon");
907    laddattr(&attr, &attrlen, "badPwdCount");
908    laddattr(&attr, &attrlen, "badPasswordTime");
909    laddattr(&attr, &attrlen, "pwdLastSet");
910    laddattr(&attr, &attrlen, "accountExpires");
911    laddattr(&attr, &attrlen, "userAccountControl");
912
913    krb5_unparse_name_short(context->context, principal, &p);
914    krb5_unparse_name(context->context, principal, &u);
915
916    /* replace @ in domain part with a / */
917    q = strrchr(p, '@');
918    if (q && (p != q && *(q - 1) != '\\'))
919	*q = '/';
920
921    asprintf(&filter,
922	     "(|(userPrincipalName=%s)(servicePrincipalName=%s)(servicePrincipalName=%s))",
923	     u, p, u);
924    free(p);
925    free(u);
926
927    ret = ldap_search_s(CTX2LP(context), CTX2BASE(context),
928			LDAP_SCOPE_SUBTREE,
929			filter, attr, 0, &m);
930    free(attr);
931    if (check_ldap(context, ret))
932	return KADM5_RPC_ERROR;
933
934    if (ldap_count_entries(CTX2LP(context), m) > 0) {
935	char **vals;
936	m0 = ldap_first_entry(CTX2LP(context), m);
937	if (m0 == NULL) {
938	    ldap_msgfree(m);
939	    goto fail;
940	}
941#if 0
942	vals = ldap_get_values(CTX2LP(context), m0, "servicePrincipalName");
943	if (vals)
944	    printf("servicePrincipalName %s\n", vals[0]);
945	vals = ldap_get_values(CTX2LP(context), m0, "userPrincipalName");
946	if (vals)
947	    printf("userPrincipalName %s\n", vals[0]);
948	vals = ldap_get_values(CTX2LP(context), m0, "userAccountControl");
949	if (vals)
950	    printf("userAccountControl %s\n", vals[0]);
951#endif
952	entry->princ_expire_time = 0;
953	if (mask & KADM5_PRINC_EXPIRE_TIME) {
954	    vals = ldap_get_values(CTX2LP(context), m0, "accountExpires");
955	    if (vals)
956		entry->princ_expire_time = nt2unixtime(vals[0]);
957	}
958	entry->last_success = 0;
959	if (mask & KADM5_LAST_SUCCESS) {
960	    vals = ldap_get_values(CTX2LP(context), m0, "lastLogon");
961	    if (vals)
962		entry->last_success = nt2unixtime(vals[0]);
963	}
964	if (mask & KADM5_LAST_FAILED) {
965	    vals = ldap_get_values(CTX2LP(context), m0, "badPasswordTime");
966	    if (vals)
967		entry->last_failed = nt2unixtime(vals[0]);
968	}
969	if (mask & KADM5_LAST_PWD_CHANGE) {
970	    vals = ldap_get_values(CTX2LP(context), m0, "pwdLastSet");
971	    if (vals)
972		entry->last_pwd_change = nt2unixtime(vals[0]);
973	}
974	if (mask & KADM5_FAIL_AUTH_COUNT) {
975	    vals = ldap_get_values(CTX2LP(context), m0, "badPwdCount");
976	    if (vals)
977		entry->fail_auth_count = atoi(vals[0]);
978	}
979 	if (mask & KADM5_ATTRIBUTES) {
980	    vals = ldap_get_values(CTX2LP(context), m0, "userAccountControl");
981	    if (vals) {
982		uint32_t i;
983		i = atoi(vals[0]);
984		if (i & (UF_ACCOUNTDISABLE|UF_LOCKOUT))
985		    entry->attributes |= KRB5_KDB_DISALLOW_ALL_TIX;
986		if ((i & UF_DONT_REQUIRE_PREAUTH) == 0)
987		    entry->attributes |= KRB5_KDB_REQUIRES_PRE_AUTH;
988		if (i & UF_SMARTCARD_REQUIRED)
989		    entry->attributes |= KRB5_KDB_REQUIRES_HW_AUTH;
990		if ((i & UF_WORKSTATION_TRUST_ACCOUNT) == 0)
991		    entry->attributes |= KRB5_KDB_DISALLOW_SVR;
992	    }
993	}
994	if (mask & KADM5_KVNO) {
995	    vals = ldap_get_values(CTX2LP(context), m0,
996				   "msDS-KeyVersionNumber");
997	    if (vals)
998		entry->kvno = atoi(vals[0]);
999	    else
1000		entry->kvno = 0;
1001	}
1002	ldap_msgfree(m);
1003    } else {
1004	return KADM5_UNK_PRINC;
1005    }
1006
1007    if (mask & KADM5_PRINCIPAL)
1008	krb5_copy_principal(context->context, principal, &entry->principal);
1009
1010    return 0;
1011 fail:
1012    return KADM5_RPC_ERROR;
1013#else
1014    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
1015    return KADM5_RPC_ERROR;
1016#endif
1017}
1018
1019static kadm5_ret_t
1020kadm5_ad_get_principals(void *server_handle,
1021			const char *expression,
1022			char ***principals,
1023			int *count)
1024{
1025    kadm5_ad_context *context = server_handle;
1026
1027    /*
1028     * KADM5_PRINCIPAL | KADM5_KVNO | KADM5_ATTRIBUTES
1029     */
1030
1031#ifdef OPENLDAP
1032    kadm5_ret_t ret;
1033
1034    ret = ad_get_cred(context, NULL);
1035    if (ret)
1036	return ret;
1037
1038    ret = _kadm5_ad_connect(server_handle);
1039    if (ret)
1040	return ret;
1041
1042    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
1043    return KADM5_RPC_ERROR;
1044#else
1045    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
1046    return KADM5_RPC_ERROR;
1047#endif
1048}
1049
1050static kadm5_ret_t
1051kadm5_ad_get_privs(void *server_handle, uint32_t*privs)
1052{
1053    kadm5_ad_context *context = server_handle;
1054    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
1055    return KADM5_RPC_ERROR;
1056}
1057
1058static kadm5_ret_t
1059kadm5_ad_modify_principal(void *server_handle,
1060			  kadm5_principal_ent_t entry,
1061			  uint32_t mask)
1062{
1063    kadm5_ad_context *context = server_handle;
1064
1065    /*
1066     * KADM5_ATTRIBUTES
1067     * KRB5_KDB_DISALLOW_ALL_TIX (| KADM5_KVNO)
1068     */
1069
1070#ifdef OPENLDAP
1071    LDAPMessage *m = NULL, *m0;
1072    kadm5_ret_t ret;
1073    char **attr = NULL;
1074    int attrlen = 0;
1075    char *p = NULL, *s = NULL, *q;
1076    char **vals;
1077    LDAPMod *attrs[4], rattrs[3], *a;
1078    char *uaf[2] = { NULL, NULL };
1079    char *kvno[2] = { NULL, NULL };
1080    char *tv[2] = { NULL, NULL };
1081    char *filter, *dn;
1082    int i;
1083
1084    for (i = 0; i < sizeof(rattrs)/sizeof(rattrs[0]); i++)
1085	attrs[i] = &rattrs[i];
1086    attrs[i] = NULL;
1087    a = &rattrs[0];
1088
1089    ret = _kadm5_ad_connect(server_handle);
1090    if (ret)
1091	return ret;
1092
1093    if (mask & KADM5_KVNO)
1094	laddattr(&attr, &attrlen, "msDS-KeyVersionNumber");
1095    if (mask & KADM5_PRINC_EXPIRE_TIME)
1096	laddattr(&attr, &attrlen, "accountExpires");
1097    if (mask & KADM5_ATTRIBUTES)
1098	laddattr(&attr, &attrlen, "userAccountControl");
1099    laddattr(&attr, &attrlen, "distinguishedName");
1100
1101    krb5_unparse_name(context->context, entry->principal, &p);
1102
1103    s = strdup(p);
1104
1105    q = strrchr(s, '@');
1106    if (q && (p != q && *(q - 1) != '\\'))
1107	*q = '\0';
1108
1109    asprintf(&filter,
1110	     "(|(userPrincipalName=%s)(servicePrincipalName=%s))",
1111	     s, s);
1112    free(p);
1113    free(s);
1114
1115    ret = ldap_search_s(CTX2LP(context), CTX2BASE(context),
1116			LDAP_SCOPE_SUBTREE,
1117			filter, attr, 0, &m);
1118    free(attr);
1119    free(filter);
1120    if (check_ldap(context, ret))
1121	return KADM5_RPC_ERROR;
1122
1123    if (ldap_count_entries(CTX2LP(context), m) <= 0) {
1124	ret = KADM5_RPC_ERROR;
1125	goto out;
1126    }
1127
1128    m0 = ldap_first_entry(CTX2LP(context), m);
1129
1130    if (mask & KADM5_ATTRIBUTES) {
1131	int32_t i;
1132
1133	vals = ldap_get_values(CTX2LP(context), m0, "userAccountControl");
1134	if (vals == NULL) {
1135	    ret = KADM5_RPC_ERROR;
1136	    goto out;
1137	}
1138
1139	i = atoi(vals[0]);
1140	if (i == 0)
1141	    return KADM5_RPC_ERROR;
1142
1143	if (entry->attributes & KRB5_KDB_DISALLOW_ALL_TIX)
1144	    i |= (UF_ACCOUNTDISABLE|UF_LOCKOUT);
1145	else
1146	    i &= ~(UF_ACCOUNTDISABLE|UF_LOCKOUT);
1147	if (entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH)
1148	    i &= ~UF_DONT_REQUIRE_PREAUTH;
1149	else
1150	    i |= UF_DONT_REQUIRE_PREAUTH;
1151	if (entry->attributes & KRB5_KDB_REQUIRES_HW_AUTH)
1152	    i |= UF_SMARTCARD_REQUIRED;
1153	else
1154	    i &= UF_SMARTCARD_REQUIRED;
1155	if (entry->attributes & KRB5_KDB_DISALLOW_SVR)
1156	    i &= ~UF_WORKSTATION_TRUST_ACCOUNT;
1157	else
1158	    i |= UF_WORKSTATION_TRUST_ACCOUNT;
1159
1160	asprintf(&uaf[0], "%d", i);
1161
1162	a->mod_op = LDAP_MOD_REPLACE;
1163	a->mod_type = "userAccountControl";
1164	a->mod_values = uaf;
1165	a++;
1166    }
1167
1168    if (mask & KADM5_KVNO) {
1169	vals = ldap_get_values(CTX2LP(context), m0, "msDS-KeyVersionNumber");
1170	if (vals == NULL) {
1171	    entry->kvno = 0;
1172	} else {
1173	    asprintf(&kvno[0], "%d", entry->kvno);
1174
1175	    a->mod_op = LDAP_MOD_REPLACE;
1176	    a->mod_type = "msDS-KeyVersionNumber";
1177	    a->mod_values = kvno;
1178	    a++;
1179	}
1180    }
1181
1182    if (mask & KADM5_PRINC_EXPIRE_TIME) {
1183	long long wt;
1184	vals = ldap_get_values(CTX2LP(context), m0, "accountExpires");
1185	if (vals == NULL) {
1186	    ret = KADM5_RPC_ERROR;
1187	    goto out;
1188	}
1189
1190	wt = unix2nttime(entry->princ_expire_time);
1191
1192	asprintf(&tv[0], "%llu", wt);
1193
1194	a->mod_op = LDAP_MOD_REPLACE;
1195	a->mod_type = "accountExpires";
1196	a->mod_values = tv;
1197	a++;
1198    }
1199
1200    vals = ldap_get_values(CTX2LP(context), m0, "distinguishedName");
1201    if (vals == NULL) {
1202	ret = KADM5_RPC_ERROR;
1203	goto out;
1204    }
1205    dn = vals[0];
1206
1207    attrs[a - &rattrs[0]] = NULL;
1208
1209    ret = ldap_modify_s(CTX2LP(context), dn, attrs);
1210    if (check_ldap(context, ret))
1211	return KADM5_RPC_ERROR;
1212
1213 out:
1214    if (m)
1215	ldap_msgfree(m);
1216    if (uaf[0])
1217	free(uaf[0]);
1218    if (kvno[0])
1219	free(kvno[0]);
1220    if (tv[0])
1221	free(tv[0]);
1222    return ret;
1223#else
1224    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
1225    return KADM5_RPC_ERROR;
1226#endif
1227}
1228
1229static kadm5_ret_t
1230kadm5_ad_randkey_principal(void *server_handle,
1231			   krb5_principal principal,
1232			   krb5_keyblock **keys,
1233			   int *n_keys)
1234{
1235    kadm5_ad_context *context = server_handle;
1236
1237    /*
1238     * random key
1239     */
1240
1241#ifdef OPENLDAP
1242    krb5_data result_code_string, result_string;
1243    int result_code, plen;
1244    kadm5_ret_t ret;
1245    char *password;
1246
1247    *keys = NULL;
1248    *n_keys = 0;
1249
1250    {
1251	char p[64];
1252	krb5_generate_random_block(p, sizeof(p));
1253	plen = base64_encode(p, sizeof(p), &password);
1254	if (plen < 0)
1255	    return ENOMEM;
1256    }
1257
1258    ret = ad_get_cred(context, NULL);
1259    if (ret) {
1260	free(password);
1261	return ret;
1262    }
1263
1264    krb5_data_zero (&result_code_string);
1265    krb5_data_zero (&result_string);
1266
1267    ret = krb5_set_password_using_ccache (context->context,
1268					  context->ccache,
1269					  password,
1270					  principal,
1271					  &result_code,
1272					  &result_code_string,
1273					  &result_string);
1274
1275    krb5_data_free (&result_code_string);
1276    krb5_data_free (&result_string);
1277
1278    if (ret == 0) {
1279
1280	*keys = malloc(sizeof(**keys) * 1);
1281	if (*keys == NULL) {
1282	    ret = ENOMEM;
1283	    goto out;
1284	}
1285	*n_keys = 1;
1286
1287	ret = krb5_string_to_key(context->context,
1288				 ENCTYPE_ARCFOUR_HMAC_MD5,
1289				 password,
1290				 principal,
1291				 &(*keys)[0]);
1292	memset(password, 0, sizeof(password));
1293	if (ret) {
1294	    free(*keys);
1295	    *keys = NULL;
1296	    *n_keys = 0;
1297	    goto out;
1298	}
1299    }
1300    memset(password, 0, plen);
1301    free(password);
1302 out:
1303    return ret;
1304#else
1305    *keys = NULL;
1306    *n_keys = 0;
1307
1308    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
1309    return KADM5_RPC_ERROR;
1310#endif
1311}
1312
1313static kadm5_ret_t
1314kadm5_ad_rename_principal(void *server_handle,
1315			  krb5_principal from,
1316			  krb5_principal to)
1317{
1318    kadm5_ad_context *context = server_handle;
1319    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
1320    return KADM5_RPC_ERROR;
1321}
1322
1323static kadm5_ret_t
1324kadm5_ad_chpass_principal_with_key(void *server_handle,
1325				   krb5_principal princ,
1326				   int n_key_data,
1327				   krb5_key_data *key_data)
1328{
1329    kadm5_ad_context *context = server_handle;
1330    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
1331    return KADM5_RPC_ERROR;
1332}
1333
1334static void
1335set_funcs(kadm5_ad_context *c)
1336{
1337#define SET(C, F) (C)->funcs.F = kadm5_ad_ ## F
1338    SET(c, chpass_principal);
1339    SET(c, chpass_principal_with_key);
1340    SET(c, create_principal);
1341    SET(c, delete_principal);
1342    SET(c, destroy);
1343    SET(c, flush);
1344    SET(c, get_principal);
1345    SET(c, get_principals);
1346    SET(c, get_privs);
1347    SET(c, modify_principal);
1348    SET(c, randkey_principal);
1349    SET(c, rename_principal);
1350}
1351
1352kadm5_ret_t
1353kadm5_ad_init_with_password_ctx(krb5_context context,
1354				const char *client_name,
1355				const char *password,
1356				const char *service_name,
1357				kadm5_config_params *realm_params,
1358				unsigned long struct_version,
1359				unsigned long api_version,
1360				void **server_handle)
1361{
1362    kadm5_ret_t ret;
1363    kadm5_ad_context *ctx;
1364
1365    ctx = malloc(sizeof(*ctx));
1366    if(ctx == NULL)
1367	return ENOMEM;
1368    memset(ctx, 0, sizeof(*ctx));
1369    set_funcs(ctx);
1370
1371    ctx->context = context;
1372    krb5_add_et_list (context, initialize_kadm5_error_table_r);
1373
1374    ret = krb5_parse_name(ctx->context, client_name, &ctx->caller);
1375    if(ret) {
1376	free(ctx);
1377	return ret;
1378    }
1379
1380    if(realm_params->mask & KADM5_CONFIG_REALM) {
1381	ret = 0;
1382	ctx->realm = strdup(realm_params->realm);
1383	if (ctx->realm == NULL)
1384	    ret = ENOMEM;
1385    } else
1386	ret = krb5_get_default_realm(ctx->context, &ctx->realm);
1387    if (ret) {
1388	free(ctx);
1389	return ret;
1390    }
1391
1392    ctx->client_name = strdup(client_name);
1393
1394    if(password != NULL && *password != '\0')
1395	ret = ad_get_cred(ctx, password);
1396    else
1397	ret = ad_get_cred(ctx, NULL);
1398    if(ret) {
1399	kadm5_ad_destroy(ctx);
1400	return ret;
1401    }
1402
1403#ifdef OPENLDAP
1404    ret = _kadm5_ad_connect(ctx);
1405    if (ret) {
1406	kadm5_ad_destroy(ctx);
1407	return ret;
1408    }
1409#endif
1410
1411    *server_handle = ctx;
1412    return 0;
1413}
1414
1415kadm5_ret_t
1416kadm5_ad_init_with_password(const char *client_name,
1417			    const char *password,
1418			    const char *service_name,
1419			    kadm5_config_params *realm_params,
1420			    unsigned long struct_version,
1421			    unsigned long api_version,
1422			    void **server_handle)
1423{
1424    krb5_context context;
1425    kadm5_ret_t ret;
1426    kadm5_ad_context *ctx;
1427
1428    ret = krb5_init_context(&context);
1429    if (ret)
1430	return ret;
1431    ret = kadm5_ad_init_with_password_ctx(context,
1432					  client_name,
1433					  password,
1434					  service_name,
1435					  realm_params,
1436					  struct_version,
1437					  api_version,
1438					  server_handle);
1439    if(ret) {
1440	krb5_free_context(context);
1441	return ret;
1442    }
1443    ctx = *server_handle;
1444    ctx->my_context = 1;
1445    return 0;
1446}
1447