xref: /titanic_52/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c (revision 0bb073995ac5a95bd35f2dd790df1ea3d8c2d507)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"@(#)smbns_ads.c	1.12	08/08/06 SMI"
27 
28 #include <sys/param.h>
29 #include <ldap.h>
30 #include <stdlib.h>
31 #include <gssapi/gssapi.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <netinet/in.h>
35 #include <arpa/inet.h>
36 #include <sys/time.h>
37 #include <netdb.h>
38 #include <pthread.h>
39 #include <unistd.h>
40 #include <arpa/nameser.h>
41 #include <resolv.h>
42 #include <sys/synch.h>
43 #include <string.h>
44 #include <strings.h>
45 #include <syslog.h>
46 #include <fcntl.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 
50 #include <smbsrv/libsmbns.h>
51 #include <smbns_ads.h>
52 #include <smbns_dyndns.h>
53 #include <smbns_krb.h>
54 
55 #define	SMB_ADS_DN_MAX	300
56 #define	SMB_ADS_MAXMSGLEN 512
57 #define	SMB_ADS_COMPUTERS_CN "Computers"
58 #define	SMB_ADS_COMPUTER_NUM_ATTR 8
59 #define	SMB_ADS_SHARE_NUM_ATTR 3
60 #define	SMB_ADS_SITE_MAX MAXHOSTNAMELEN
61 
62 #define	SMB_ADS_MSDCS_SRV_DC_RR		"_ldap._tcp.dc._msdcs"
63 #define	SMB_ADS_MSDCS_SRV_SITE_RR	"_ldap._tcp.%s._sites.dc._msdcs"
64 
65 /*
66  * domainControllerFunctionality
67  *
68  * This rootDSE attribute indicates the functional level of the DC.
69  */
70 #define	SMB_ADS_ATTR_DCLEVEL	"domainControllerFunctionality"
71 #define	SMB_ADS_DCLEVEL_W2K	0
72 #define	SMB_ADS_DCLEVEL_W2K3	2
73 #define	SMB_ADS_DCLEVEL_W2K8	3
74 
75 /*
76  * msDs-supportedEncryptionTypes (Windows Server 2008 only)
77  *
78  * This attribute defines the encryption types supported by the system.
79  * Encryption Types:
80  *  - DES cbc mode with CRC-32
81  *  - DES cbc mode with RSA-MD5
82  *  - ArcFour with HMAC/md5
83  *  - AES-128
84  *  - AES-256
85  */
86 #define	SMB_ADS_ATTR_ENCTYPES	"msDs-supportedEncryptionTypes"
87 #define	SMB_ADS_ENC_DES_CRC	1
88 #define	SMB_ADS_ENC_DES_MD5	2
89 #define	SMB_ADS_ENC_RC4		4
90 #define	SMB_ADS_ENC_AES128	8
91 #define	SMB_ADS_ENC_AES256	16
92 
93 #define	SMB_ADS_ATTR_SAMACCT	"sAMAccountName"
94 #define	SMB_ADS_ATTR_UPN	"userPrincipalName"
95 #define	SMB_ADS_ATTR_SPN	"servicePrincipalName"
96 #define	SMB_ADS_ATTR_CTL	"userAccountControl"
97 #define	SMB_ADS_ATTR_DNSHOST	"dNSHostName"
98 #define	SMB_ADS_ATTR_KVNO	"msDS-KeyVersionNumber"
99 #define	SMB_ADS_ATTR_DN		"distinguishedName"
100 
101 /* current ADS server to communicate with */
102 static smb_ads_host_info_t *ads_host_info = NULL;
103 static mutex_t ads_host_mtx;
104 static char ads_site[SMB_ADS_SITE_MAX];
105 static mutex_t ads_site_mtx;
106 
107 /*
108  * adjoin_errmsg
109  *
110  * Use the adjoin return status defined in adjoin_status_t as the index
111  * to this table.
112  */
113 static char *adjoin_errmsg[] = {
114 	"ADJOIN succeeded.",
115 	"ADJOIN failed to get handle.",
116 	"ADJOIN failed to generate machine password.",
117 	"ADJOIN failed to add workstation trust account.",
118 	"ADJOIN failed to modify workstation trust account.",
119 	"ADJOIN failed to get list of encryption types.",
120 	"ADJOIN failed to initialize kerberos context.",
121 	"ADJOIN failed to get Kerberos principal.",
122 	"ADJOIN failed to set machine account password on AD.",
123 	"ADJOIN failed to modify CONTROL attribute of the account.",
124 	"ADJOIN failed to write Kerberos keytab file.",
125 	"ADJOIN failed to configure domain_name property for idmapd.",
126 	"ADJOIN failed to refresh idmap service."
127 };
128 
129 static smb_ads_handle_t *smb_ads_open_main(char *, char *, char *);
130 static int smb_ads_bind(smb_ads_handle_t *);
131 static int smb_ads_add_computer(smb_ads_handle_t *, int, char *);
132 static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *);
133 static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *);
134 static int smb_ads_lookup_computer_n_attr(smb_ads_handle_t *, char *, char **,
135     int, char *);
136 static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *);
137 static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *);
138 static int smb_ads_gen_machine_passwd(char *, int);
139 static smb_ads_host_info_t *smb_ads_get_host_info(void);
140 static void smb_ads_set_host_info(smb_ads_host_info_t *);
141 static void smb_ads_free_host_info(void);
142 static int smb_ads_get_spnset(char *, char **);
143 static void smb_ads_free_spnset(char **);
144 static int smb_ads_alloc_attr(LDAPMod **, int);
145 static void smb_ads_free_attr(LDAPMod **);
146 static int smb_ads_get_dc_level(smb_ads_handle_t *);
147 static smb_ads_host_info_t *smb_ads_select_dc(smb_ads_host_list_t *);
148 
149 /*
150  * smb_ads_init
151  *
152  * Initializes the ads_site global variable.
153  */
154 void
155 smb_ads_init(void)
156 {
157 	(void) mutex_lock(&ads_site_mtx);
158 	(void) smb_config_getstr(SMB_CI_ADS_SITE, ads_site, sizeof (ads_site));
159 	(void) mutex_unlock(&ads_site_mtx);
160 }
161 
162 /*
163  * smb_ads_refresh
164  *
165  * If the ads_site has changed, clear the ads_host_info cache.
166  */
167 void
168 smb_ads_refresh(void)
169 {
170 	char new_site[SMB_ADS_SITE_MAX];
171 
172 	(void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, sizeof (new_site));
173 	(void) mutex_lock(&ads_site_mtx);
174 	if (strcasecmp(ads_site, new_site)) {
175 		(void) strlcpy(ads_site, new_site, sizeof (ads_site));
176 		smb_ads_free_host_info();
177 	}
178 	(void) mutex_unlock(&ads_site_mtx);
179 }
180 
181 /*
182  * smb_ads_build_unc_name
183  *
184  * Construct the UNC name of the share object in the format of
185  * \\hostname.domain\shareUNC
186  *
187  * Returns 0 on success, -1 on error.
188  */
189 int
190 smb_ads_build_unc_name(char *unc_name, int maxlen,
191     const char *hostname, const char *shareUNC)
192 {
193 	char my_domain[MAXHOSTNAMELEN];
194 
195 	if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0)
196 		return (-1);
197 
198 	(void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s",
199 	    hostname, my_domain, shareUNC);
200 	return (0);
201 }
202 
203 /*
204  * smb_ads_ldap_ping
205  *
206  * This is used to bind to an ADS server to see
207  * if it is still alive.
208  *
209  * Returns:
210  *   -1: error
211  *    0: successful
212  */
213 /*ARGSUSED*/
214 static int
215 smb_ads_ldap_ping(smb_ads_host_info_t *ads_host)
216 {
217 	struct in_addr addr;
218 	int ldversion = LDAP_VERSION3, status, timeoutms = 5 * 1000;
219 	LDAP *ld = NULL;
220 
221 	addr.s_addr = ads_host->ip_addr;
222 
223 	ld = ldap_init((char *)inet_ntoa(addr), ads_host->port);
224 	if (ld == NULL)
225 		return (-1);
226 
227 	ldversion = LDAP_VERSION3;
228 	(void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion);
229 	/* setup TCP/IP connect timeout */
230 	(void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms);
231 
232 	status = ldap_bind_s(ld, "", NULL, LDAP_AUTH_SIMPLE);
233 
234 	if (status != LDAP_SUCCESS) {
235 		(void) ldap_unbind(ld);
236 		return (-1);
237 	}
238 
239 	(void) ldap_unbind(ld);
240 
241 	return (0);
242 }
243 
244 /*
245  * smb_ads_set_host_info
246  * Cache the result of the ADS discovery if the cache is empty.
247  */
248 static void
249 smb_ads_set_host_info(smb_ads_host_info_t *host)
250 {
251 	(void) mutex_lock(&ads_host_mtx);
252 	if (!ads_host_info)
253 		ads_host_info = host;
254 	(void) mutex_unlock(&ads_host_mtx);
255 }
256 
257 /*
258  * smb_ads_get_host_info
259  * Get the cached ADS host info.
260  */
261 static smb_ads_host_info_t *
262 smb_ads_get_host_info(void)
263 {
264 	smb_ads_host_info_t *host;
265 
266 	(void) mutex_lock(&ads_host_mtx);
267 	host = ads_host_info;
268 	(void) mutex_unlock(&ads_host_mtx);
269 	return (host);
270 }
271 
272 /*
273  * smb_ads_skip_ques_sec
274  * Skips the question section.
275  */
276 static int
277 smb_ads_skip_ques_sec(int qcnt, uchar_t **ptr, uchar_t *eom)
278 {
279 	int i, len;
280 
281 	for (i = 0; i < qcnt; i++) {
282 		if ((len = dn_skipname(*ptr, eom)) < 0) {
283 			syslog(LOG_ERR, "DNS query invalid message format");
284 			return (-1);
285 		}
286 		*ptr += len + QFIXEDSZ;
287 	}
288 
289 	return (0);
290 }
291 
292 /*
293  * smb_ads_decode_host_ans_sec
294  * Decodes ADS hosts, priority, weight and port number from the answer
295  * section based on the current buffer pointer.
296  */
297 static int
298 smb_ads_decode_host_ans_sec(int ans_cnt, uchar_t **ptr, uchar_t *eom,
299     uchar_t *buf, smb_ads_host_info_t *ads_host_list)
300 {
301 	int i, len;
302 	smb_ads_host_info_t *ads_host;
303 
304 	for (i = 0; i < ans_cnt; i++) {
305 		ads_host = &ads_host_list[i];
306 
307 		if ((len = dn_skipname(*ptr, eom)) < 0) {
308 			syslog(LOG_ERR, "DNS query invalid message format");
309 			return (-1);
310 		}
311 
312 		*ptr += len;
313 
314 		/* skip type, class, ttl */
315 		*ptr += 8;
316 		/* data size */
317 		*ptr += 2;
318 
319 		/* Get priority, weight */
320 		/* LINTED: E_CONSTANT_CONDITION */
321 		NS_GET16(ads_host->priority, *ptr);
322 		/* LINTED: E_CONSTANT_CONDITION */
323 		NS_GET16(ads_host->weight, *ptr);
324 
325 		/* port */
326 		/* LINTED: E_CONSTANT_CONDITION */
327 		NS_GET16(ads_host->port, *ptr);
328 		/* domain name */
329 		len = dn_expand(buf, eom, *ptr, ads_host->name, MAXHOSTNAMELEN);
330 		if (len < 0) {
331 			syslog(LOG_ERR, "DNS query invalid SRV record");
332 			return (-1);
333 		}
334 		*ptr += len;
335 	}
336 
337 	return (0);
338 }
339 
340 /*
341  * smb_ads_skip_auth_sec
342  * Skips the authority section.
343  */
344 static int
345 smb_ads_skip_auth_sec(int ns_cnt, uchar_t **ptr, uchar_t *eom)
346 {
347 	int i, len;
348 	uint16_t size;
349 
350 	for (i = 0; i < ns_cnt; i++) {
351 		if ((len = dn_skipname(*ptr, eom)) < 0) {
352 			syslog(LOG_ERR, "DNS query invalid message format");
353 			return (-1);
354 		}
355 		*ptr += len;
356 		/* skip type, class, ttl */
357 		*ptr += 8;
358 		/* get len of data */
359 		/* LINTED: E_CONSTANT_CONDITION */
360 		NS_GET16(size, *ptr);
361 		if ((*ptr + size) > eom) {
362 			syslog(LOG_ERR, "DNS query invalid message format");
363 			return (-1);
364 		}
365 
366 		*ptr += size;
367 	}
368 
369 	return (0);
370 }
371 
372 /*
373  * smb_ads_decode_host_addi_sec
374  * Decodes ADS hosts and IP Addresses from the additional section based
375  * on the current buffer pointer.
376  */
377 static int
378 smb_ads_decode_host_addi_sec(int addit_cnt, uchar_t **ptr, uchar_t *eom,
379     uchar_t *buf, smb_ads_host_info_t *ads_host_list)
380 {
381 	int i, len;
382 	in_addr_t ipaddr;
383 	smb_ads_host_info_t *ads_host;
384 
385 	for (i = 0; i < addit_cnt; i++) {
386 		ads_host = &ads_host_list[i];
387 
388 		/* domain name */
389 		len = dn_expand(buf, eom, *ptr, ads_host->name, MAXHOSTNAMELEN);
390 		if (len < 0) {
391 			syslog(LOG_ERR, "DNS query invalid SRV record");
392 			return (-1);
393 		}
394 		*ptr += len;
395 
396 		/* skip type, class, TTL, data len */
397 		*ptr += 10;
398 
399 		/* LINTED: E_CONSTANT_CONDITION */
400 		NS_GET32(ipaddr, *ptr);
401 
402 		ads_host->ip_addr = htonl(ipaddr);
403 	}
404 
405 	return (0);
406 }
407 
408 static void
409 smb_ads_init_host_info(smb_ads_host_info_t *ads_host,
410     char *name, in_addr_t ipaddr, int priority, int weight, int port)
411 {
412 	(void) strlcpy(ads_host->name, name, MAXHOSTNAMELEN);
413 	ads_host->ip_addr = ipaddr;
414 	ads_host->priority = priority;
415 	ads_host->weight = weight;
416 	ads_host->port = port;
417 }
418 
419 /*
420  * smb_ads_hlist_alloc
421  */
422 smb_ads_host_list_t *
423 smb_ads_hlist_alloc(int count)
424 {
425 	int size;
426 	smb_ads_host_list_t *hlist;
427 
428 	size = sizeof (smb_ads_host_info_t) * count;
429 	hlist = (smb_ads_host_list_t *)malloc(sizeof (smb_ads_host_list_t));
430 	if (hlist == NULL)
431 		return (NULL);
432 
433 	hlist->ah_cnt = count;
434 	hlist->ah_list = (smb_ads_host_info_t *)malloc(size);
435 	if (hlist->ah_list == NULL) {
436 		free(hlist);
437 		return (NULL);
438 	}
439 
440 	bzero(hlist->ah_list, size);
441 	return (hlist);
442 }
443 
444 /*
445  * smb_ads_hlist_free
446  */
447 static void
448 smb_ads_hlist_free(smb_ads_host_list_t *host_list)
449 {
450 	if (host_list == NULL)
451 		return;
452 
453 	free(host_list->ah_list);
454 	free(host_list);
455 }
456 
457 /*
458  * smb_ads_find_host
459  *
460  * This routine sends a DNS service location (SRV) query message to the
461  * DNS server via TCP to query it for a list of ADS server(s).  Once a reply
462  * is received, the reply message is parsed to get the hostname.  A host
463  * lookup by name is done to get the IP address if the additional section of
464  * the reply packet does not contain any IP addresses.  If there are IP
465  * addresses populated in the additional section then the additional section
466  * is parsed to obtain the IP addresses.
467  *
468  * The service location of _ldap._tcp.dc.msdcs.<ADS domain> is used to
469  * guarantee that Microsoft domain controllers are returned.  Microsoft domain
470  * controllers are also ADS servers.
471  *
472  * The ADS hostnames are stored in the answer section of the DNS reply message.
473  * The IP addresses are stored in the additional section.
474  *
475  * The DNS reply message may be in compress formed.  The compression is done
476  * on repeating domain name label in the message.  i.e hostname.
477  *
478  * An ADS host is returned from the list of ADS servers.
479  *
480  * Parameters:
481  *   domain: domain of ADS host.
482  *   sought: the ADS host to be sought. It can be set to empty string to find
483  *           any ADS hosts in that domain.
484  *
485  * Returns:
486  *   ADS host: fully qualified hostname, ip address, ldap port.
487  *   port    : LDAP port of ADS host.
488  *
489  */
490 /*ARGSUSED*/
491 smb_ads_host_info_t *
492 smb_ads_find_host(char *domain, char *sought, int *port)
493 {
494 	int i, error;
495 	struct hostent *h;
496 	struct in_addr addr;
497 	smb_ads_host_list_t *ans_hlist, *adt_hlist, *res_hlist;
498 	smb_ads_host_info_t *ads_host = NULL, *res_host = NULL;
499 	smb_ads_host_info_t *ans_hep;	/* Pointer to answer host entry */
500 	smb_ads_host_info_t *adt_hep;	/* Pointer to additional host entry */
501 	char *curdom = NULL;
502 	boolean_t same_domain, istrunc = B_FALSE;
503 	int len, qcnt, ans_cnt, ns_cnt, addit_cnt;
504 	uchar_t *ptr, *eom;
505 	struct __res_state res_state;
506 	union {
507 		HEADER hdr;
508 		uchar_t buf[NS_MAXMSG];
509 	} msg;
510 	char site_service[MAXHOSTNAMELEN];
511 	char *svc_name[2] = {site_service, SMB_ADS_MSDCS_SRV_DC_RR};
512 	int hlist_cnt = 0;
513 
514 	/*
515 	 * If we have already found an ADS server in the given domain, return
516 	 * the cached ADS host if either the sought host is not specified or
517 	 * the cached ADS host matches the sought host.
518 	 */
519 	res_host = smb_ads_get_host_info();
520 	if (res_host) {
521 		curdom = strchr(res_host->name, '.');
522 		same_domain = (curdom && !strcasecmp(++curdom, domain));
523 		if (same_domain && (!sought ||
524 		    (sought && !strncasecmp(res_host->name, sought,
525 		    strlen(sought))))) {
526 			if (smb_ads_ldap_ping(res_host) == 0)
527 				return (res_host);
528 		}
529 
530 		smb_ads_free_host_info();
531 	}
532 
533 	bzero(&res_state, sizeof (struct __res_state));
534 	if (res_ninit(&res_state) < 0)
535 		return (NULL);
536 
537 	/* use TCP */
538 	res_state.options |= RES_USEVC;
539 
540 	(void) mutex_lock(&ads_site_mtx);
541 	if (*ads_site == '\0')
542 		*site_service = '\0';
543 	else
544 		(void) snprintf(site_service, sizeof (site_service),
545 		    SMB_ADS_MSDCS_SRV_SITE_RR, ads_site);
546 	(void) mutex_unlock(&ads_site_mtx);
547 
548 	/*
549 	 * First look for ADS hosts in ADS site if configured.  Then try
550 	 * without ADS site info.
551 	 */
552 	for (i = 0; i < 2; i++) {
553 		if (*svc_name[i] == '\0')
554 			continue;
555 
556 		len = res_nquerydomain(&res_state, svc_name[i], domain,
557 		    C_IN, T_SRV, msg.buf, sizeof (msg.buf));
558 
559 		smb_tracef("Querying DNS for SRV RRs named '%s'", svc_name[i]);
560 
561 		if (len < 0) {
562 			smb_tracef("DNS query for '%s' failed (%s)",
563 			    svc_name[i], hstrerror(res_state.res_h_errno));
564 			continue;
565 		}
566 		if (len > sizeof (msg.buf)) {
567 			syslog(LOG_ERR, "DNS query %ib message doesn't fit"
568 			    " into %ib buffer",
569 			    len, sizeof (msg.buf));
570 			continue;
571 		}
572 
573 		/* 2. parse the reply, skip header and question sections */
574 
575 		ptr = msg.buf + sizeof (msg.hdr);
576 		eom = msg.buf + len;
577 
578 		/* check truncated message bit */
579 		if (msg.hdr.tc)
580 			istrunc = B_TRUE;
581 
582 		qcnt = ntohs(msg.hdr.qdcount);
583 		ans_cnt = ntohs(msg.hdr.ancount);
584 		ns_cnt = ntohs(msg.hdr.nscount);
585 		addit_cnt = ntohs(msg.hdr.arcount);
586 
587 		if (smb_ads_skip_ques_sec(qcnt, &ptr, eom) != 0) {
588 			res_ndestroy(&res_state);
589 			return (NULL);
590 		}
591 
592 		ans_hlist = smb_ads_hlist_alloc(ans_cnt);
593 		if (ans_hlist == NULL) {
594 			res_ndestroy(&res_state);
595 			return (NULL);
596 		}
597 
598 		ans_hep = ans_hlist->ah_list;
599 
600 		/* 3. walk through the answer section */
601 
602 		if (smb_ads_decode_host_ans_sec(ans_cnt, &ptr, eom, msg.buf,
603 		    ans_hep) != 0) {
604 			smb_ads_hlist_free(ans_hlist);
605 			res_ndestroy(&res_state);
606 			return (NULL);
607 		}
608 
609 		/* check authority section */
610 		if (ns_cnt > 0) {
611 			if (smb_ads_skip_auth_sec(ns_cnt, &ptr, eom) != 0) {
612 				smb_ads_hlist_free(ans_hlist);
613 				res_ndestroy(&res_state);
614 				return (NULL);
615 			}
616 		}
617 
618 		/*
619 		 * Check additional section to get IP address of ADS host.
620 		 * If additional section contains no IP address(es) then
621 		 * do a host lookup by hostname to get the IP address.
622 		 */
623 		if (addit_cnt > 0) {
624 			int j;
625 
626 			adt_hlist = smb_ads_hlist_alloc(addit_cnt);
627 			if (adt_hlist == NULL) {
628 				smb_ads_hlist_free(ans_hlist);
629 				res_ndestroy(&res_state);
630 				return (NULL);
631 			}
632 			adt_hep = adt_hlist->ah_list;
633 
634 			if (smb_ads_decode_host_addi_sec(addit_cnt, &ptr, eom,
635 			    msg.buf, adt_hep) != 0) {
636 				smb_ads_hlist_free(ans_hlist);
637 				smb_ads_hlist_free(adt_hlist);
638 				res_ndestroy(&res_state);
639 				return (NULL);
640 			}
641 
642 			res_hlist = smb_ads_hlist_alloc(addit_cnt);
643 			if (res_hlist == NULL) {
644 				smb_ads_hlist_free(ans_hlist);
645 				smb_ads_hlist_free(adt_hlist);
646 				res_ndestroy(&res_state);
647 				return (NULL);
648 			}
649 			ads_host = res_hlist->ah_list;
650 
651 			/* pick a host that is up */
652 			for (i = 0; i < addit_cnt; i++) {
653 				if ((sought) &&
654 				    (strncasecmp(sought,
655 				    adt_hep[i].name,
656 				    strlen(sought)) != 0))
657 					continue;
658 				/*
659 				 * find the host in the list of hosts from
660 				 * the answer section to get the port number.
661 				 */
662 				for (j = 0; j < ans_cnt; j++)
663 					if (strcmp(adt_hep[i].name,
664 					    ans_hep[j].name) == 0)
665 						break;
666 
667 				if (j == ans_cnt) {
668 					smb_ads_hlist_free(ans_hlist);
669 					smb_ads_hlist_free(adt_hlist);
670 					smb_ads_hlist_free(res_hlist);
671 					res_ndestroy(&res_state);
672 					return (NULL);
673 				}
674 
675 				smb_ads_init_host_info(ads_host,
676 				    adt_hep[i].name, adt_hep[i].ip_addr,
677 				    ans_hep[j].priority, ans_hep[j].weight,
678 				    ans_hep[j].port);
679 
680 				*port = ads_host->port;
681 				addr.s_addr = ads_host->ip_addr;
682 				smb_tracef("smb_ads: Found ADS server: "
683 				    "%s (%s) [%d][%d]", ads_host->name,
684 				    inet_ntoa(addr), ads_host->priority,
685 				    ads_host->weight);
686 				hlist_cnt++;
687 				ads_host++;
688 			}
689 			smb_ads_hlist_free(ans_hlist);
690 			smb_ads_hlist_free(adt_hlist);
691 			res_ndestroy(&res_state);
692 
693 			if (hlist_cnt != 0) {
694 				res_hlist->ah_cnt = hlist_cnt;
695 				res_host = smb_ads_select_dc(res_hlist);
696 				smb_ads_set_host_info(res_host);
697 			}
698 			smb_ads_hlist_free(res_hlist);
699 			return (res_host);
700 		} else {
701 			res_hlist = smb_ads_hlist_alloc(ans_cnt);
702 			if (res_hlist == NULL) {
703 				smb_ads_hlist_free(ans_hlist);
704 				res_ndestroy(&res_state);
705 				return (NULL);
706 			}
707 			ads_host = res_hlist->ah_list;
708 
709 			/*
710 			 * Shouldn't get here unless entries exist in DNS but
711 			 * DNS server did not put them in additional section of
712 			 * DNS reply packet.
713 			 */
714 			for (i = 0; i < ans_cnt; i++) {
715 				if ((sought) &&
716 				    (strncasecmp(sought, ans_hep[i].name,
717 				    strlen(sought)) != 0))
718 					continue;
719 
720 				h = getipnodebyname(ans_hep[i].name,
721 				    AF_INET, 0, &error);
722 
723 				if (h == NULL || h->h_addr == NULL)
724 					continue;
725 
726 				(void) memcpy(&ans_hep[i].ip_addr,
727 				    h->h_addr, sizeof (addr.s_addr));
728 
729 				freehostent(h);
730 
731 				smb_ads_init_host_info(ads_host,
732 				    ans_hep[i].name, ans_hep[i].ip_addr,
733 				    ans_hep[i].priority, ans_hep[i].weight,
734 				    ans_hep[i].port);
735 
736 				*port = ads_host->port;
737 				addr.s_addr = ads_host->ip_addr;
738 				smb_tracef("smb_ads: Found ADS server: "
739 				    "%s (%s) [%d][%d]", ads_host->name,
740 				    inet_ntoa(addr), ads_host->priority,
741 				    ads_host->weight);
742 				hlist_cnt++;
743 				ads_host++;
744 			}
745 			smb_ads_hlist_free(ans_hlist);
746 			res_ndestroy(&res_state);
747 
748 			if (hlist_cnt != 0) {
749 				res_hlist->ah_cnt = hlist_cnt;
750 				res_host = smb_ads_select_dc(res_hlist);
751 				smb_ads_set_host_info(res_host);
752 			}
753 			smb_ads_hlist_free(res_hlist);
754 			return (res_host);
755 		}
756 	}
757 	res_ndestroy(&res_state);
758 	syslog(LOG_ERR, "smb_ads: ADS server is either not found or offline.");
759 
760 	if (istrunc && sought && *sought != 0)
761 		syslog(LOG_WARNING, "smb_ads: Truncated TCP reply message is "
762 		    "detected for DNS query (SRV) of ADS hosts.\n");
763 
764 	return (NULL);
765 }
766 
767 /*
768  * smb_ads_convert_domain
769  * Converts a domain string into its distinguished name i.e. a unique
770  * name for an entry in the Directory Service.
771  * Memory is allocated
772  * for the new string.
773  * i.e. procom.com -> dc=procom,dc=com
774  * Parameters:
775  *   s: fully qualified DNS domain string
776  * Returns:
777  *   NULL if error
778  *   DNS domain in LDAP DN string format
779  */
780 static char *
781 smb_ads_convert_domain(char *s)
782 {
783 	char *t, *s2, *t2;
784 	int len, cnt;
785 
786 	if (s == NULL || *s == 0)
787 		return (NULL);
788 
789 	cnt = 0;
790 	t = s;
791 	while (*t) {
792 		if (*t++ == '.') {
793 			cnt++;
794 		}
795 	}
796 
797 	len = 3 + strlen(s) + cnt*3 + 1;
798 
799 	s2 = (char *)malloc(len);
800 	if (s2 == NULL)
801 		return (NULL);
802 
803 	bzero(s2, len);
804 
805 	t = s2;
806 	(void) strncpy(t, "dc=", 3);
807 	t += 3;
808 	t2 = s;
809 	while (*s) {
810 		if (*s == '.') {
811 			if (t + 3 >= s2 + len - 1) {
812 				syslog(LOG_ERR, "[smb_ads_convert_domain] "
813 				    "buffer overrun for string "
814 				    "conversion of %s: tot buf "
815 				    "sz alloc: %d, last "
816 				    "written buf offset: %d\n",
817 				    t2, len, t+3-s2);
818 				free(s2);
819 				return (NULL);
820 			}
821 			(void) strncpy(t, ",dc=", 4);
822 			t += 4;
823 			s++;
824 		} else {
825 			if (t >= s2 + len - 1) {
826 				syslog(LOG_ERR, "[smb_ads_convert_domain] "
827 				    "buffer overrun for string "
828 				    "conversion of %s: tot buf "
829 				    "sz alloc: %d, last "
830 				    "written buf offset: %d\n",
831 				    t2, len, t-s2);
832 				free(s2);
833 				return (NULL);
834 			}
835 			*t++ = *s++;
836 		}
837 	}
838 	*t = '\0';
839 	return (s2);
840 }
841 
842 /*
843  * smb_ads_free_host_info
844  * Free the memory use by the global ads_host_info and set it to NULL.
845  */
846 static void
847 smb_ads_free_host_info(void)
848 {
849 	(void) mutex_lock(&ads_host_mtx);
850 	if (ads_host_info) {
851 		free(ads_host_info);
852 		ads_host_info = NULL;
853 	}
854 	(void) mutex_unlock(&ads_host_mtx);
855 }
856 
857 /*
858  * smb_ads_open
859  * Open a LDAP connection to an ADS server if the system is in domain mode.
860  * Acquire both Kerberos TGT and LDAP service tickets for the host principal.
861  *
862  * This function should only be called after the system is successfully joined
863  * to a domain.
864  */
865 smb_ads_handle_t *
866 smb_ads_open(void)
867 {
868 	char domain[MAXHOSTNAMELEN];
869 
870 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
871 		return (NULL);
872 
873 	if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
874 		return (NULL);
875 
876 	return (smb_ads_open_main(domain, NULL, NULL));
877 }
878 
879 /*
880  * smb_ads_open_main
881  * Open a LDAP connection to an ADS server.
882  * If ADS is enabled and the administrative username, password, and
883  * ADS domain are defined then query DNS to find an ADS server if this is the
884  * very first call to this routine.  After an ADS server is found then this
885  * server will be used everytime this routine is called until the system is
886  * rebooted or the ADS server becomes unavailable then an ADS server will
887  * be queried again.  After the connection is made then an ADS handle
888  * is created to be returned.
889  *
890  * After the LDAP connection, the LDAP version will be set to 3 using
891  * ldap_set_option().
892  *
893  * The smb_ads_bind() routine is also called before the ADS handle is returned.
894  * Parameters:
895  *   domain - fully-qualified domain name
896  *   user   - the user account for whom the Kerberos TGT ticket and ADS
897  *            service tickets are acquired.
898  *   password - password of the specified user
899  *
900  * Returns:
901  *   NULL              : can't connect to ADS server or other errors
902  *   smb_ads_handle_t* : handle to ADS server
903  */
904 static smb_ads_handle_t *
905 smb_ads_open_main(char *domain, char *user, char *password)
906 {
907 	smb_ads_handle_t *ah;
908 	LDAP *ld;
909 	int version = 3, ads_port;
910 	smb_ads_host_info_t *ads_host = NULL;
911 	struct in_addr addr;
912 
913 	ads_host = smb_ads_get_host_info();
914 	if (!ads_host) {
915 		ads_host = smb_ads_find_host(domain, NULL, &ads_port);
916 		if (ads_host == NULL) {
917 			syslog(LOG_ERR, "smb_ads: No ADS host found from "
918 			    "configured nameservers");
919 			return (NULL);
920 		}
921 	}
922 
923 	ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
924 	if (ah == NULL)
925 		return (NULL);
926 	(void) memset(ah, 0, sizeof (smb_ads_handle_t));
927 
928 	addr.s_addr = ads_host->ip_addr;
929 	if ((ld = ldap_init((char *)inet_ntoa(addr), ads_host->port)) == NULL) {
930 		syslog(LOG_ERR, "smb_ads: Could not open connection "
931 		    "to host: %s\n", ads_host->name);
932 		smb_ads_free_host_info();
933 		free(ah);
934 		return (NULL);
935 	}
936 
937 	if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version)
938 	    != LDAP_SUCCESS) {
939 		syslog(LOG_ERR, "smb_ads: Could not set "
940 		    "LDAP_OPT_PROTOCOL_VERSION %d\n", version);
941 		smb_ads_free_host_info();
942 		free(ah);
943 		(void) ldap_unbind(ld);
944 		return (NULL);
945 	}
946 
947 	(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
948 	ah->ld = ld;
949 	ah->user = (user) ? strdup(user) : NULL;
950 	ah->pwd = (password) ? strdup(password) : NULL;
951 	ah->domain = strdup(domain);
952 
953 	if (ah->domain == NULL) {
954 		smb_ads_close(ah);
955 		return (NULL);
956 	}
957 
958 	ah->domain_dn = smb_ads_convert_domain(domain);
959 	if (ah->domain_dn == NULL) {
960 		smb_ads_close(ah);
961 		return (NULL);
962 	}
963 
964 	ah->hostname = strdup(ads_host->name);
965 	if (ah->hostname == NULL) {
966 		smb_ads_close(ah);
967 		return (NULL);
968 	}
969 	(void) mutex_lock(&ads_site_mtx);
970 	if (*ads_site != '\0') {
971 		if ((ah->site = strdup(ads_site)) == NULL) {
972 			smb_ads_close(ah);
973 			(void) mutex_unlock(&ads_site_mtx);
974 			return (NULL);
975 		}
976 	} else {
977 		ah->site = NULL;
978 	}
979 	(void) mutex_unlock(&ads_site_mtx);
980 
981 	if (smb_ads_bind(ah) == -1) {
982 		smb_ads_close(ah);
983 		return (NULL);
984 	}
985 
986 	return (ah);
987 }
988 
989 /*
990  * smb_ads_close
991  * Close connection to ADS server and free memory allocated for ADS handle.
992  * LDAP unbind is called here.
993  * Parameters:
994  *   ah: handle to ADS server
995  * Returns:
996  *   void
997  */
998 void
999 smb_ads_close(smb_ads_handle_t *ah)
1000 {
1001 	int len;
1002 
1003 	if (ah == NULL)
1004 		return;
1005 	/* close and free connection resources */
1006 	if (ah->ld)
1007 		(void) ldap_unbind(ah->ld);
1008 
1009 	free(ah->user);
1010 	if (ah->pwd) {
1011 		len = strlen(ah->pwd);
1012 		/* zero out the memory that contains user's password */
1013 		if (len > 0)
1014 			bzero(ah->pwd, len);
1015 		free(ah->pwd);
1016 	}
1017 	free(ah->domain);
1018 	free(ah->domain_dn);
1019 	free(ah->hostname);
1020 	free(ah->site);
1021 	free(ah);
1022 }
1023 
1024 /*
1025  * smb_ads_display_stat
1026  * Display error message for GSS-API routines.
1027  * Parameters:
1028  *   maj:  GSS major status
1029  *   min:  GSS minor status
1030  * Returns:
1031  *   None
1032  */
1033 static void
1034 smb_ads_display_stat(OM_uint32 maj, OM_uint32 min)
1035 {
1036 	gss_buffer_desc msg;
1037 	OM_uint32 msg_ctx = 0;
1038 	OM_uint32 min2;
1039 	(void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID,
1040 	    &msg_ctx, &msg);
1041 	syslog(LOG_ERR, "smb_ads: major status error: %s\n", (char *)msg.value);
1042 	(void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID,
1043 	    &msg_ctx, &msg);
1044 	syslog(LOG_ERR, "smb_ads: minor status error: %s\n", (char *)msg.value);
1045 }
1046 
1047 /*
1048  * smb_ads_alloc_attr
1049  *
1050  * Since the attrs is a null-terminated array, all elements
1051  * in the array (except the last one) will point to allocated
1052  * memory.
1053  */
1054 static int
1055 smb_ads_alloc_attr(LDAPMod *attrs[], int num)
1056 {
1057 	int i;
1058 
1059 	bzero(attrs, num * sizeof (LDAPMod *));
1060 	for (i = 0; i < (num - 1); i++) {
1061 		attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod));
1062 		if (attrs[i] == NULL) {
1063 			smb_ads_free_attr(attrs);
1064 			return (-1);
1065 		}
1066 	}
1067 
1068 	return (0);
1069 }
1070 
1071 /*
1072  * smb_ads_free_attr
1073  * Free memory allocated when publishing a share.
1074  * Parameters:
1075  *   attrs: an array of LDAPMod pointers
1076  * Returns:
1077  *   None
1078  */
1079 static void
1080 smb_ads_free_attr(LDAPMod *attrs[])
1081 {
1082 	int i;
1083 	for (i = 0; attrs[i]; i++) {
1084 		free(attrs[i]);
1085 	}
1086 }
1087 
1088 /*
1089  * smb_ads_get_spnset
1090  *
1091  * Derives the core set of SPNs based on the FQHN.
1092  * The spn_set is a null-terminated array of char pointers.
1093  *
1094  * Returns 0 upon success. Otherwise, returns -1.
1095  */
1096 static int
1097 smb_ads_get_spnset(char *fqhost, char **spn_set)
1098 {
1099 	int i;
1100 
1101 	bzero(spn_set, (SMBKRB5_SPN_IDX_MAX + 1) * sizeof (char *));
1102 	for (i = 0; i < SMBKRB5_SPN_IDX_MAX; i++) {
1103 		if ((spn_set[i] = smb_krb5_get_spn(i, fqhost)) == NULL) {
1104 			smb_ads_free_spnset(spn_set);
1105 			return (-1);
1106 		}
1107 	}
1108 
1109 	return (0);
1110 }
1111 
1112 /*
1113  * smb_ads_free_spnset
1114  *
1115  * Free the memory allocated for the set of SPNs.
1116  */
1117 static void
1118 smb_ads_free_spnset(char **spn_set)
1119 {
1120 	int i;
1121 	for (i = 0; spn_set[i]; i++)
1122 		free(spn_set[i]);
1123 }
1124 
1125 /*
1126  * smb_ads_acquire_cred
1127  * Called by smb_ads_bind() to get a handle to administrative user's credential
1128  * stored locally on the system.  The credential is the TGT.  If the attempt at
1129  * getting handle fails then a second attempt will be made after getting a
1130  * new TGT.
1131  * Please look at smb_ads_bind() for more information.
1132  *
1133  * Paramters:
1134  *   ah         : handle to ADS server
1135  *   kinit_retry: if 0 then a second attempt will be made to get handle to the
1136  *                credential if the first attempt fails
1137  * Returns:
1138  *   cred_handle: handle to the administrative user's credential (TGT)
1139  *   oid        : contains Kerberos 5 object identifier
1140  *   kinit_retry: A 1 indicates that a second attempt has been made to get
1141  *                handle to the credential and no further attempts can be made
1142  *   -1         : error
1143  *    0         : success
1144  */
1145 static int
1146 smb_ads_acquire_cred(smb_ads_handle_t *ah, gss_cred_id_t *cred_handle,
1147     gss_OID *oid, int *kinit_retry)
1148 {
1149 	return (krb5_acquire_cred_kinit(ah->user, ah->pwd, cred_handle, oid,
1150 	    kinit_retry, "ads"));
1151 }
1152 
1153 /*
1154  * smb_ads_establish_sec_context
1155  * Called by smb_ads_bind() to establish a security context to an LDAP service
1156  * on an ADS server. If the attempt at establishing the security context fails
1157  * then a second attempt will be made by smb_ads_bind() if a new TGT has not
1158  * been already obtained in ads_acquire_cred.  The second attempt, if allowed,
1159  * will obtained a new TGT here and a new handle to the credential will also be
1160  * obtained in ads_acquire_cred.  LDAP SASL bind is used to send and receive
1161  * the GSS tokens to and from the ADS server.
1162  * Please look at ads_bind for more information.
1163  * Paramters:
1164  *   ah             : handle to ADS server
1165  *   cred_handle    : handle to administrative user's credential (TGT)
1166  *   oid            : Kerberos 5 object identifier
1167  *   kinit_retry    : if 0 then a second attempt can be made to establish a
1168  *                    security context with ADS server if first attempt fails
1169  * Returns:
1170  *   gss_context    : security context to ADS server
1171  *   sercred        : encrypted ADS server's supported security layers
1172  *   do_acquire_cred: if 1 then a second attempt will be made to establish a
1173  *                    security context with ADS server after getting a new
1174  *                    handle to the user's credential
1175  *   kinit_retry    : if 1 then a second attempt will be made to establish a
1176  *                    a security context and no further attempts can be made
1177  *   -1             : error
1178  *    0             : success
1179  */
1180 static int
1181 smb_ads_establish_sec_context(smb_ads_handle_t *ah, gss_ctx_id_t *gss_context,
1182     gss_cred_id_t cred_handle, gss_OID oid, struct berval **sercred,
1183     int *kinit_retry, int *do_acquire_cred)
1184 {
1185 	OM_uint32 maj, min, time_rec;
1186 	char service_name[SMB_ADS_MAXBUFLEN];
1187 	gss_buffer_desc send_tok, service_buf;
1188 	gss_name_t target_name;
1189 	gss_buffer_desc input;
1190 	gss_buffer_desc *inputptr;
1191 	struct berval cred;
1192 	OM_uint32 ret_flags;
1193 	int stat;
1194 	int gss_flags;
1195 
1196 	(void) snprintf(service_name, SMB_ADS_MAXBUFLEN, "ldap@%s",
1197 	    ah->hostname);
1198 	service_buf.value = service_name;
1199 	service_buf.length = strlen(service_name)+1;
1200 	if ((maj = gss_import_name(&min, &service_buf,
1201 	    (gss_OID) gss_nt_service_name,
1202 	    &target_name)) != GSS_S_COMPLETE) {
1203 		smb_ads_display_stat(maj, min);
1204 		if (oid != GSS_C_NO_OID)
1205 			(void) gss_release_oid(&min, &oid);
1206 		return (-1);
1207 	}
1208 
1209 	*gss_context = GSS_C_NO_CONTEXT;
1210 	*sercred = NULL;
1211 	inputptr = GSS_C_NO_BUFFER;
1212 	gss_flags = GSS_C_MUTUAL_FLAG;
1213 	do {
1214 		if (krb5_establish_sec_ctx_kinit(ah->user, ah->pwd,
1215 		    cred_handle, gss_context, target_name, oid,
1216 		    gss_flags, inputptr, &send_tok,
1217 		    &ret_flags, &time_rec, kinit_retry,
1218 		    do_acquire_cred, &maj, "ads") == -1) {
1219 			if (oid != GSS_C_NO_OID)
1220 				(void) gss_release_oid(&min, &oid);
1221 			(void) gss_release_name(&min, &target_name);
1222 			return (-1);
1223 		}
1224 
1225 		cred.bv_val = send_tok.value;
1226 		cred.bv_len = send_tok.length;
1227 		if (*sercred) {
1228 			ber_bvfree(*sercred);
1229 			*sercred = NULL;
1230 		}
1231 		stat = ldap_sasl_bind_s(ah->ld, NULL, "GSSAPI",
1232 		    &cred, NULL, NULL, sercred);
1233 		if (stat != LDAP_SUCCESS &&
1234 		    stat != LDAP_SASL_BIND_IN_PROGRESS) {
1235 			/* LINTED - E_SEC_PRINTF_VAR_FMT */
1236 			syslog(LOG_ERR, ldap_err2string(stat));
1237 			if (oid != GSS_C_NO_OID)
1238 				(void) gss_release_oid(&min, &oid);
1239 			(void) gss_release_name(&min, &target_name);
1240 			(void) gss_release_buffer(&min, &send_tok);
1241 			return (-1);
1242 		}
1243 		input.value = (*sercred)->bv_val;
1244 		input.length = (*sercred)->bv_len;
1245 		inputptr = &input;
1246 		if (send_tok.length > 0)
1247 			(void) gss_release_buffer(&min, &send_tok);
1248 	} while (maj != GSS_S_COMPLETE);
1249 
1250 	if (oid != GSS_C_NO_OID)
1251 		(void) gss_release_oid(&min, &oid);
1252 	(void) gss_release_name(&min, &target_name);
1253 
1254 	return (0);
1255 }
1256 
1257 /*
1258  * smb_ads_negotiate_sec_layer
1259  * Call by smb_ads_bind() to negotiate additional security layer for further
1260  * communication after security context establishment.  No additional security
1261  * is needed so a "no security layer" is negotiated.  The security layer is
1262  * described in the SASL RFC 2478 and this step is needed for secure LDAP
1263  * binding.  LDAP SASL bind is used to send and receive the GSS tokens to and
1264  * from the ADS server.
1265  * Please look at smb_ads_bind for more information.
1266  *
1267  * Paramters:
1268  *   ah         : handle to ADS server
1269  *   gss_context: security context to ADS server
1270  *   sercred    : encrypted ADS server's supported security layers
1271  * Returns:
1272  *   -1         : error
1273  *    0         : success
1274  */
1275 static int
1276 smb_ads_negotiate_sec_layer(smb_ads_handle_t *ah, gss_ctx_id_t gss_context,
1277     struct berval *sercred)
1278 {
1279 	OM_uint32 maj, min;
1280 	gss_buffer_desc unwrap_inbuf, unwrap_outbuf;
1281 	gss_buffer_desc wrap_inbuf, wrap_outbuf;
1282 	int conf_state, sec_layer;
1283 	char auth_id[5];
1284 	struct berval cred;
1285 	int stat;
1286 	gss_qop_t qt;
1287 
1288 	/* check for server supported security layer */
1289 	unwrap_inbuf.value = sercred->bv_val;
1290 	unwrap_inbuf.length = sercred->bv_len;
1291 	if ((maj = gss_unwrap(&min, gss_context,
1292 	    &unwrap_inbuf, &unwrap_outbuf,
1293 	    &conf_state, &qt)) != GSS_S_COMPLETE) {
1294 		smb_ads_display_stat(maj, min);
1295 		if (sercred)
1296 			ber_bvfree(sercred);
1297 		return (-1);
1298 	}
1299 	sec_layer = *((char *)unwrap_outbuf.value);
1300 	(void) gss_release_buffer(&min, &unwrap_outbuf);
1301 	if (!(sec_layer & 1)) {
1302 		syslog(LOG_ERR, "smb_ads: ADS server does not support "
1303 		    "no security layer!\n");
1304 		if (sercred) ber_bvfree(sercred);
1305 		return (-1);
1306 	}
1307 	if (sercred) ber_bvfree(sercred);
1308 
1309 	/* no security layer needed after successful binding */
1310 	auth_id[0] = 0x01;
1311 
1312 	/* byte 2-4: max client recv size in network byte order */
1313 	auth_id[1] = 0x00;
1314 	auth_id[2] = 0x40;
1315 	auth_id[3] = 0x00;
1316 	wrap_inbuf.value = auth_id;
1317 	wrap_inbuf.length = 4;
1318 	conf_state = 0;
1319 	if ((maj = gss_wrap(&min, gss_context, conf_state, 0, &wrap_inbuf,
1320 	    &conf_state, &wrap_outbuf)) != GSS_S_COMPLETE) {
1321 		smb_ads_display_stat(maj, min);
1322 		return (-1);
1323 	}
1324 
1325 	cred.bv_val = wrap_outbuf.value;
1326 	cred.bv_len = wrap_outbuf.length;
1327 	sercred = NULL;
1328 	stat = ldap_sasl_bind_s(ah->ld, NULL, "GSSAPI", &cred, NULL, NULL,
1329 	    &sercred);
1330 	if (stat != LDAP_SUCCESS && stat != LDAP_SASL_BIND_IN_PROGRESS) {
1331 		/* LINTED - E_SEC_PRINTF_VAR_FMT */
1332 		syslog(LOG_ERR, ldap_err2string(stat));
1333 		(void) gss_release_buffer(&min, &wrap_outbuf);
1334 		return (-1);
1335 	}
1336 
1337 	(void) gss_release_buffer(&min, &wrap_outbuf);
1338 	if (sercred)
1339 		ber_bvfree(sercred);
1340 
1341 	return (0);
1342 }
1343 
1344 /*
1345  * smb_ads_bind
1346  * Use secure binding to bind to ADS server.
1347  * Use GSS-API with Kerberos 5 as the security mechanism and LDAP SASL with
1348  * Kerberos 5 as the security mechanisn to authenticate, obtain a security
1349  * context, and securely bind an administrative user so that other LDAP
1350  * commands can be used, i.e. add and delete.
1351  *
1352  * To obtain the security context, a Kerberos ticket-granting ticket (TGT)
1353  * for the user is needed to obtain a ticket for the LDAP service.  To get
1354  * a TGT for the user, the username and password is needed.  Once a TGT is
1355  * obtained then it will be stored locally and used until it is expired.
1356  * This routine will automatically obtained a TGT for the first time or when
1357  * it expired.  LDAP SASL bind is then finally used to send GSS tokens to
1358  * obtain a security context for the LDAP service on the ADS server.  If
1359  * there is any problem getting the security context then a new TGT will be
1360  * obtain to try getting the security context once more.
1361  *
1362  * After the security context is obtain and established, the LDAP SASL bind
1363  * is used to negotiate an additional security layer.  No further security is
1364  * needed so a "no security layer" is negotiated.  After this the security
1365  * context can be deleted and further LDAP commands can be sent to the ADS
1366  * server until a LDAP unbind command is issued to the ADS server.
1367  * Paramaters:
1368  *   ah: handle to ADS server
1369  * Returns:
1370  *  -1: error
1371  *   0: success
1372  */
1373 static int
1374 smb_ads_bind(smb_ads_handle_t *ah)
1375 {
1376 	OM_uint32 min;
1377 	gss_cred_id_t cred_handle;
1378 	gss_ctx_id_t gss_context;
1379 	gss_OID oid;
1380 	struct berval *sercred;
1381 	int kinit_retry, do_acquire_cred;
1382 	int rc = 0;
1383 
1384 	kinit_retry = 0;
1385 	do_acquire_cred = 0;
1386 
1387 acquire_cred:
1388 
1389 	if (smb_ads_acquire_cred(ah, &cred_handle, &oid, &kinit_retry))
1390 		return (-1);
1391 
1392 	if (smb_ads_establish_sec_context(ah, &gss_context, cred_handle,
1393 	    oid, &sercred, &kinit_retry, &do_acquire_cred)) {
1394 		(void) gss_release_cred(&min, &cred_handle);
1395 		if (do_acquire_cred) {
1396 			do_acquire_cred = 0;
1397 			goto acquire_cred;
1398 		}
1399 		return (-1);
1400 	}
1401 	rc = smb_ads_negotiate_sec_layer(ah, gss_context, sercred);
1402 
1403 	if (cred_handle != GSS_C_NO_CREDENTIAL)
1404 		(void) gss_release_cred(&min, &cred_handle);
1405 	(void) gss_delete_sec_context(&min, &gss_context, NULL);
1406 
1407 	return ((rc) ? -1 : 0);
1408 }
1409 
1410 /*
1411  * smb_ads_add_share
1412  * Call by smb_ads_publish_share to create share object in ADS.
1413  * This routine specifies the attributes of an ADS LDAP share object. The first
1414  * attribute and values define the type of ADS object, the share object.  The
1415  * second attribute and value define the UNC of the share data for the share
1416  * object. The LDAP synchronous add command is used to add the object into ADS.
1417  * The container location to add the object needs to specified.
1418  * Parameters:
1419  *   ah          : handle to ADS server
1420  *   adsShareName: name of share object to be created in ADS
1421  *   shareUNC    : share name on NetForce
1422  *   adsContainer: location in ADS to create share object
1423  *
1424  * Returns:
1425  *   -1          : error
1426  *    0          : success
1427  */
1428 int
1429 smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName,
1430     const char *unc_name, const char *adsContainer)
1431 {
1432 	LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR];
1433 	char *tmp1[5], *tmp2[5];
1434 	int j = 0;
1435 	char *share_dn;
1436 	char buf[SMB_ADS_MAXMSGLEN];
1437 	int len, ret;
1438 
1439 	len = 5 + strlen(adsShareName) + strlen(adsContainer) +
1440 	    strlen(ah->domain_dn) + 1;
1441 
1442 	share_dn = (char *)malloc(len);
1443 	if (share_dn == NULL)
1444 		return (-1);
1445 
1446 	(void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName,
1447 	    adsContainer, ah->domain_dn);
1448 
1449 	if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) {
1450 		free(share_dn);
1451 		return (-1);
1452 	}
1453 
1454 	attrs[j]->mod_op = LDAP_MOD_ADD;
1455 	attrs[j]->mod_type = "objectClass";
1456 	tmp1[0] = "top";
1457 	tmp1[1] = "leaf";
1458 	tmp1[2] = "connectionPoint";
1459 	tmp1[3] = "volume";
1460 	tmp1[4] = 0;
1461 	attrs[j]->mod_values = tmp1;
1462 
1463 	attrs[++j]->mod_op = LDAP_MOD_ADD;
1464 	attrs[j]->mod_type = "uNCName";
1465 	tmp2[0] = (char *)unc_name;
1466 	tmp2[1] = 0;
1467 	attrs[j]->mod_values = tmp2;
1468 
1469 	if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) {
1470 		(void) snprintf(buf, SMB_ADS_MAXMSGLEN,
1471 		    "smb_ads_add_share: %s:", share_dn);
1472 		/* LINTED - E_SEC_PRINTF_VAR_FMT */
1473 		syslog(LOG_ERR, ldap_err2string(ret));
1474 		smb_ads_free_attr(attrs);
1475 		free(share_dn);
1476 		return (ret);
1477 	}
1478 	free(share_dn);
1479 	smb_ads_free_attr(attrs);
1480 
1481 	(void) snprintf(buf, SMB_ADS_MAXMSGLEN,
1482 	    "Share %s has been added to ADS container: %s.\n", adsShareName,
1483 	    adsContainer);
1484 	smb_tracef("smb_ads: %s", buf);
1485 
1486 	return (0);
1487 }
1488 
1489 /*
1490  * smb_ads_del_share
1491  * Call by smb_ads_remove_share to remove share object from ADS.  The container
1492  * location to remove the object needs to specified.  The LDAP synchronous
1493  * delete command is used.
1494  * Parameters:
1495  *   ah          : handle to ADS server
1496  *   adsShareName: name of share object in ADS to be removed
1497  *   adsContainer: location of share object in ADS
1498  * Returns:
1499  *   -1          : error
1500  *    0          : success
1501  */
1502 static int
1503 smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName,
1504     const char *adsContainer)
1505 {
1506 	char *share_dn, buf[SMB_ADS_MAXMSGLEN];
1507 	int len, ret;
1508 
1509 	len = 5 + strlen(adsShareName) + strlen(adsContainer) +
1510 	    strlen(ah->domain_dn) + 1;
1511 
1512 	share_dn = (char *)malloc(len);
1513 	if (share_dn == NULL)
1514 		return (-1);
1515 
1516 	(void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName,
1517 	    adsContainer, ah->domain_dn);
1518 	if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) {
1519 		/* LINTED - E_SEC_PRINTF_VAR_FMT */
1520 		syslog(LOG_ERR, ldap_err2string(ret));
1521 		free(share_dn);
1522 		return (-1);
1523 	}
1524 	free(share_dn);
1525 
1526 	(void) snprintf(buf, SMB_ADS_MAXMSGLEN,
1527 	    "Share %s has been removed from ADS container: %s.\n",
1528 	    adsShareName, adsContainer);
1529 	smb_tracef("smb_ads: %s", buf);
1530 
1531 	return (0);
1532 }
1533 
1534 
1535 /*
1536  * smb_ads_escape_search_filter_chars
1537  *
1538  * This routine will escape the special characters found in a string
1539  * that will later be passed to the ldap search filter.
1540  *
1541  * RFC 1960 - A String Representation of LDAP Search Filters
1542  * 3.  String Search Filter Definition
1543  * If a value must contain one of the characters '*' OR '(' OR ')',
1544  * these characters
1545  * should be escaped by preceding them with the backslash '\' character.
1546  *
1547  * RFC 2252 - LDAP Attribute Syntax Definitions
1548  * a backslash quoting mechanism is used to escape
1549  * the following separator symbol character (such as "'", "$" or "#") if
1550  * it should occur in that string.
1551  */
1552 static int
1553 smb_ads_escape_search_filter_chars(const char *src, char *dst)
1554 {
1555 	int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */
1556 
1557 	if (src == NULL || dst == NULL)
1558 		return (-1);
1559 
1560 	while (*src) {
1561 		if (!avail) {
1562 			*dst = 0;
1563 			return (-1);
1564 		}
1565 
1566 		switch (*src) {
1567 		case '\\':
1568 		case '\'':
1569 		case '$':
1570 		case '#':
1571 		case '*':
1572 		case '(':
1573 		case ')':
1574 			*dst++ = '\\';
1575 			avail--;
1576 			/* fall through */
1577 
1578 		default:
1579 			*dst++ = *src++;
1580 			avail--;
1581 		}
1582 	}
1583 
1584 	*dst = 0;
1585 
1586 	return (0);
1587 }
1588 
1589 /*
1590  * smb_ads_lookup_share
1591  * The search filter is set to search for a specific share name in the
1592  * specified ADS container.  The LDSAP synchronous search command is used.
1593  * Parameters:
1594  *   ah          : handle to ADS server
1595  *   adsShareName: name of share object in ADS to be searched
1596  *   adsContainer: location of share object in ADS
1597  * Returns:
1598  *   -1          : error
1599  *    0          : not found
1600  *    1          : found
1601  */
1602 int
1603 smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName,
1604     const char *adsContainer, char *unc_name)
1605 {
1606 	char *attrs[4], filter[SMB_ADS_MAXBUFLEN];
1607 	char *share_dn;
1608 	int len, ret;
1609 	LDAPMessage *res;
1610 	char tmpbuf[SMB_ADS_MAXBUFLEN];
1611 
1612 	if (adsShareName == NULL || adsContainer == NULL)
1613 		return (-1);
1614 
1615 	len = 5 + strlen(adsShareName) + strlen(adsContainer) +
1616 	    strlen(ah->domain_dn) + 1;
1617 
1618 	share_dn = (char *)malloc(len);
1619 	if (share_dn == NULL)
1620 		return (-1);
1621 
1622 	(void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName,
1623 	    adsContainer, ah->domain_dn);
1624 
1625 	res = NULL;
1626 	attrs[0] = "cn";
1627 	attrs[1] = "objectClass";
1628 	attrs[2] = "uNCName";
1629 	attrs[3] = NULL;
1630 
1631 	if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) {
1632 		free(share_dn);
1633 		return (-1);
1634 	}
1635 
1636 	(void) snprintf(filter, sizeof (filter),
1637 	    "(&(objectClass=volume)(uNCName=%s))", tmpbuf);
1638 
1639 	if ((ret = ldap_search_s(ah->ld, share_dn,
1640 	    LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) {
1641 		if (ret != LDAP_NO_SUCH_OBJECT) {
1642 			syslog(LOG_ERR, "ldap[%s]: %s", share_dn,
1643 			    ldap_err2string(ret));
1644 		}
1645 		(void) ldap_msgfree(res);
1646 		free(share_dn);
1647 		return (0);
1648 	}
1649 
1650 	(void) free(share_dn);
1651 
1652 	/* no match is found */
1653 	if (ldap_count_entries(ah->ld, res) == 0) {
1654 		(void) ldap_msgfree(res);
1655 		return (0);
1656 	}
1657 
1658 	/* free the search results */
1659 	(void) ldap_msgfree(res);
1660 
1661 	return (1);
1662 }
1663 
1664 /*
1665  * smb_ads_convert_directory
1666  * Convert relative share directory to UNC to be appended to hostname.
1667  * i.e. cvol/a/b -> cvol\a\b
1668  */
1669 char *
1670 smb_ads_convert_directory(char *rel_dir)
1671 {
1672 	char *t, *s2;
1673 	int len;
1674 
1675 	if (rel_dir == NULL)
1676 		return (NULL);
1677 
1678 	len = strlen(rel_dir) + 1;
1679 	s2 = (char *)malloc(len);
1680 	if (s2 == NULL)
1681 		return (NULL);
1682 
1683 	t = s2;
1684 	while (*rel_dir) {
1685 		if (*rel_dir == '/') {
1686 			*t++ = '\\';
1687 			rel_dir++;
1688 		} else {
1689 			*t++ = *rel_dir++;
1690 		}
1691 	}
1692 	*t = '\0';
1693 	return (s2);
1694 }
1695 
1696 /*
1697  * smb_ads_publish_share
1698  * Publish share into ADS.  If a share name already exist in ADS in the same
1699  * container then the existing share object is removed before adding the new
1700  * share object.
1701  * Parameters:
1702  *   ah          : handle return from smb_ads_open
1703  *   adsShareName: name of share to be added to ADS directory
1704  *   shareUNC    : name of share on client, can be NULL to use the same name
1705  *                 as adsShareName
1706  *   adsContainer: location for share to be added in ADS directory, ie
1707  *                   ou=share_folder
1708  *   uncType     : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR
1709  *                   to use host ip addr for UNC.
1710  * Returns:
1711  *   -1          : error
1712  *    0          : success
1713  */
1714 int
1715 smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName,
1716     const char *shareUNC, const char *adsContainer, const char *hostname)
1717 {
1718 	int ret;
1719 	char unc_name[SMB_ADS_MAXBUFLEN];
1720 
1721 	if (adsShareName == NULL || adsContainer == NULL)
1722 		return (-1);
1723 
1724 	if (shareUNC == 0 || *shareUNC == 0)
1725 		shareUNC = adsShareName;
1726 
1727 	if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1728 	    hostname, shareUNC) < 0) {
1729 		smb_tracef("smb_ads: Cannot publish share '%s' "
1730 		    "[missing UNC name]", shareUNC);
1731 		return (-1);
1732 	}
1733 
1734 	ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1735 
1736 	switch (ret) {
1737 	case 1:
1738 		(void) smb_ads_del_share(ah, adsShareName, adsContainer);
1739 		ret = smb_ads_add_share(ah, adsShareName, unc_name,
1740 		    adsContainer);
1741 		break;
1742 
1743 	case 0:
1744 		ret = smb_ads_add_share(ah, adsShareName, unc_name,
1745 		    adsContainer);
1746 		if (ret == LDAP_ALREADY_EXISTS) {
1747 			smb_tracef("smb_ads: Cannot publish share '%s' "
1748 			    "[name is already in use]", adsShareName);
1749 			ret = -1;
1750 		}
1751 		break;
1752 
1753 	case -1:
1754 	default:
1755 		/* return with error code */
1756 		ret = -1;
1757 	}
1758 
1759 	return (ret);
1760 }
1761 
1762 /*
1763  * smb_ads_remove_share
1764  * Remove share from ADS.  A search is done first before explicitly removing
1765  * the share.
1766  * Parameters:
1767  *   ah          : handle return from smb_ads_open
1768  *   adsShareName: name of share to be removed from ADS directory
1769  *   adsContainer: location for share to be removed from ADS directory, ie
1770  *                   ou=share_folder
1771  * Returns:
1772  *   -1          : error
1773  *    0          : success
1774  */
1775 int
1776 smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName,
1777     const char *shareUNC, const char *adsContainer, const char *hostname)
1778 {
1779 	int ret;
1780 	char unc_name[SMB_ADS_MAXBUFLEN];
1781 
1782 	if (adsShareName == NULL || adsContainer == NULL)
1783 		return (-1);
1784 	if (shareUNC == 0 || *shareUNC == 0)
1785 		shareUNC = adsShareName;
1786 
1787 	if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1788 	    hostname, shareUNC) < 0) {
1789 		smb_tracef("smb_ads: Unable to remove share '%s' from "
1790 		    "ADS [missing UNC name]", shareUNC);
1791 		return (-1);
1792 	}
1793 
1794 	ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1795 	if (ret == 0)
1796 		return (0);
1797 	if (ret == -1)
1798 		return (-1);
1799 
1800 	return (smb_ads_del_share(ah, adsShareName, adsContainer));
1801 }
1802 
1803 /*
1804  * smb_ads_get_computer_dn
1805  *
1806  * Build the distinguish name for this system.
1807  */
1808 static void
1809 smb_ads_get_computer_dn(smb_ads_handle_t *ah, char *buf, size_t buflen)
1810 {
1811 	char hostname[MAXHOSTNAMELEN];
1812 
1813 	(void) smb_gethostname(hostname, MAXHOSTNAMELEN, 0);
1814 	(void) snprintf(buf, buflen, "cn=%s,cn=%s,%s",
1815 	    hostname, SMB_ADS_COMPUTERS_CN, ah->domain_dn);
1816 }
1817 
1818 /*
1819  * smb_ads_add_computer
1820  *
1821  * Returns 0 upon success. Otherwise, returns -1.
1822  */
1823 static int
1824 smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1825 {
1826 	return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn));
1827 }
1828 
1829 /*
1830  * smb_ads_modify_computer
1831  *
1832  * Returns 0 upon success. Otherwise, returns -1.
1833  */
1834 static int
1835 smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1836 {
1837 	return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn));
1838 }
1839 
1840 /*
1841  * smb_ads_get_dc_level
1842  *
1843  * Returns the functional level of the DC upon success.
1844  * Otherwise, -1 is returned.
1845  */
1846 static int
1847 smb_ads_get_dc_level(smb_ads_handle_t *ah)
1848 {
1849 	LDAPMessage *res, *entry;
1850 	char *attr[2];
1851 	char **vals;
1852 	int rc = -1;
1853 
1854 	res = NULL;
1855 	attr[0] = SMB_ADS_ATTR_DCLEVEL;
1856 	attr[1] = NULL;
1857 	if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr,
1858 	    0, &res) != LDAP_SUCCESS) {
1859 		(void) ldap_msgfree(res);
1860 		return (-1);
1861 	}
1862 
1863 	/* no match for the specified attribute is found */
1864 	if (ldap_count_entries(ah->ld, res) == 0) {
1865 		(void) ldap_msgfree(res);
1866 		return (-1);
1867 	}
1868 
1869 	entry = ldap_first_entry(ah->ld, res);
1870 	if (entry) {
1871 		if ((vals = ldap_get_values(ah->ld, entry,
1872 		    SMB_ADS_ATTR_DCLEVEL)) == NULL) {
1873 			/*
1874 			 * Observed the values aren't populated
1875 			 * by the Windows 2000 server.
1876 			 */
1877 			(void) ldap_msgfree(res);
1878 			return (SMB_ADS_DCLEVEL_W2K);
1879 		}
1880 
1881 		if (vals[0] != NULL)
1882 			rc = atoi(vals[0]);
1883 
1884 		ldap_value_free(vals);
1885 	}
1886 
1887 	(void) ldap_msgfree(res);
1888 	return (rc);
1889 }
1890 
1891 static int
1892 smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
1893 {
1894 	LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR];
1895 	char *oc_vals[6], *sam_val[2], *usr_val[2];
1896 	char *spn_set[SMBKRB5_SPN_IDX_MAX + 1], *ctl_val[2], *fqh_val[2];
1897 	char *encrypt_val[2];
1898 	int j = -1;
1899 	int ret, usrctl_flags = 0;
1900 	char sam_acct[MAXHOSTNAMELEN + 1];
1901 	char fqhost[MAXHOSTNAMELEN];
1902 	char *user_principal;
1903 	char usrctl_buf[16];
1904 	char encrypt_buf[16];
1905 	int max;
1906 
1907 	if (smb_gethostname(fqhost, MAXHOSTNAMELEN, 0) != 0)
1908 		return (-1);
1909 
1910 	(void) strlcpy(sam_acct, fqhost, MAXHOSTNAMELEN + 1);
1911 	(void) strlcat(sam_acct, "$", MAXHOSTNAMELEN + 1);
1912 	(void) snprintf(fqhost, MAXHOSTNAMELEN, "%s.%s", fqhost,
1913 	    ah->domain);
1914 
1915 	if (smb_ads_get_spnset(fqhost, spn_set) != 0)
1916 		return (-1);
1917 
1918 	/*
1919 	 * Windows 2008 DC expects the UPN attribute to be host/fqhn while
1920 	 * both Windows 2000 & 2003 expect it to be host/fqhn@realm.
1921 	 */
1922 	if (dclevel == SMB_ADS_DCLEVEL_W2K8)
1923 		user_principal = smb_krb5_get_spn(SMBKRB5_SPN_IDX_HOST, fqhost);
1924 	else
1925 		user_principal = smb_krb5_get_upn(spn_set[SMBKRB5_SPN_IDX_HOST],
1926 		    ah->domain);
1927 
1928 	if (user_principal == NULL) {
1929 		smb_ads_free_spnset(spn_set);
1930 		return (-1);
1931 	}
1932 
1933 	max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0))
1934 	    - (dclevel == SMB_ADS_DCLEVEL_W2K8 ?  0 : 1);
1935 
1936 	if (smb_ads_alloc_attr(attrs, max) != 0) {
1937 		free(user_principal);
1938 		smb_ads_free_spnset(spn_set);
1939 		return (-1);
1940 	}
1941 
1942 	/* objectClass attribute is not modifiable. */
1943 	if (op == LDAP_MOD_ADD) {
1944 		attrs[++j]->mod_op = op;
1945 		attrs[j]->mod_type = "objectClass";
1946 		oc_vals[0] = "top";
1947 		oc_vals[1] = "person";
1948 		oc_vals[2] = "organizationalPerson";
1949 		oc_vals[3] = "user";
1950 		oc_vals[4] = "computer";
1951 		oc_vals[5] = 0;
1952 		attrs[j]->mod_values = oc_vals;
1953 	}
1954 
1955 	attrs[++j]->mod_op = op;
1956 	attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT;
1957 	sam_val[0] = sam_acct;
1958 	sam_val[1] = 0;
1959 	attrs[j]->mod_values = sam_val;
1960 
1961 	attrs[++j]->mod_op = op;
1962 	attrs[j]->mod_type = SMB_ADS_ATTR_UPN;
1963 	usr_val[0] = user_principal;
1964 	usr_val[1] = 0;
1965 	attrs[j]->mod_values = usr_val;
1966 
1967 	attrs[++j]->mod_op = op;
1968 	attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
1969 	attrs[j]->mod_values = spn_set;
1970 
1971 	attrs[++j]->mod_op = op;
1972 	attrs[j]->mod_type = SMB_ADS_ATTR_CTL;
1973 	usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1974 	    SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD |
1975 	    SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE);
1976 	(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
1977 	ctl_val[0] = usrctl_buf;
1978 	ctl_val[1] = 0;
1979 	attrs[j]->mod_values = ctl_val;
1980 
1981 	attrs[++j]->mod_op = op;
1982 	attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST;
1983 	fqh_val[0] = fqhost;
1984 	fqh_val[1] = 0;
1985 	attrs[j]->mod_values = fqh_val;
1986 
1987 	/* enctypes support starting in Windows Server 2008 */
1988 	if (dclevel > SMB_ADS_DCLEVEL_W2K3) {
1989 		attrs[++j]->mod_op = op;
1990 		attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES;
1991 		(void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d",
1992 		    SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 +
1993 		    SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC);
1994 		encrypt_val[0] = encrypt_buf;
1995 		encrypt_val[1] = 0;
1996 		attrs[j]->mod_values = encrypt_val;
1997 	}
1998 
1999 	switch (op) {
2000 	case LDAP_MOD_ADD:
2001 		if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
2002 			syslog(LOG_ERR, "smb_ads_add_computer: %s",
2003 			    ldap_err2string(ret));
2004 			ret = -1;
2005 		}
2006 		break;
2007 
2008 	case LDAP_MOD_REPLACE:
2009 		if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
2010 			syslog(LOG_ERR, "smb_ads_modify_computer: %s",
2011 			    ldap_err2string(ret));
2012 			ret = -1;
2013 		}
2014 		break;
2015 
2016 	default:
2017 		ret = -1;
2018 
2019 	}
2020 
2021 	smb_ads_free_attr(attrs);
2022 	free(user_principal);
2023 	smb_ads_free_spnset(spn_set);
2024 
2025 	return (ret);
2026 }
2027 
2028 /*
2029  * Delete an ADS computer account.
2030  */
2031 static void
2032 smb_ads_del_computer(smb_ads_handle_t *ah, char *dn)
2033 {
2034 	int rc;
2035 
2036 	if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS)
2037 		smb_tracef("smb_ads_del_computer: %s", ldap_err2string(rc));
2038 }
2039 
2040 /*
2041  * smb_ads_lookup_computer_n_attr
2042  *
2043  * Lookup the value of the specified attribute on the computer
2044  * object. If the specified attribute can be found, its value is returned
2045  * via 'val' parameter.
2046  *
2047  * 'attr' parameter can be set to NULL if you only attempt to
2048  * see whether the computer object exists on AD or not.
2049  *
2050  * Return:
2051  *  1 if both the computer and the specified attribute is found.
2052  *  0 if either the computer or the specified attribute is not found.
2053  * -1 on error.
2054  */
2055 static int
2056 smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, char *attr, char **val,
2057     int scope, char *dn)
2058 {
2059 	char *attrs[2], filter[SMB_ADS_MAXBUFLEN];
2060 	LDAPMessage *res, *entry;
2061 	char **vals;
2062 	char tmpbuf[SMB_ADS_MAXBUFLEN];
2063 	char my_hostname[MAXHOSTNAMELEN];
2064 
2065 	if (smb_gethostname(my_hostname, MAXHOSTNAMELEN, 0) != 0)
2066 		return (-1);
2067 
2068 	res = NULL;
2069 	attrs[0] = attr;
2070 	attrs[1] = NULL;
2071 
2072 	if (smb_ads_escape_search_filter_chars(my_hostname, tmpbuf) != 0)
2073 		return (-1);
2074 
2075 	(void) snprintf(filter, sizeof (filter),
2076 	    "(&(objectClass=computer)(cn=%s))", tmpbuf);
2077 
2078 	if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0,
2079 	    &res) != LDAP_SUCCESS) {
2080 		(void) ldap_msgfree(res);
2081 		return (0);
2082 	}
2083 
2084 	if (attr) {
2085 		/* no match for the specified attribute is found */
2086 		if (ldap_count_entries(ah->ld, res) == 0) {
2087 			if (val)
2088 				*val = NULL;
2089 
2090 			(void) ldap_msgfree(res);
2091 			return (0);
2092 		}
2093 
2094 		entry = ldap_first_entry(ah->ld, res);
2095 		if (entry) {
2096 			vals = ldap_get_values(ah->ld, entry, attr);
2097 			if (!vals && val) {
2098 				*val = NULL;
2099 				(void) ldap_msgfree(res);
2100 				return (0);
2101 			}
2102 
2103 			if (vals[0] != NULL && val)
2104 				*val = strdup(vals[0]);
2105 
2106 			ldap_value_free(vals);
2107 		}
2108 	}
2109 
2110 	/* free the search results */
2111 	(void) ldap_msgfree(res);
2112 	return (1);
2113 }
2114 
2115 /*
2116  * smb_ads_find_computer
2117  *
2118  * Starts by searching for the system's AD computer object in the default
2119  * container (i.e. cn=Computers).  If not found, searches the entire directory.
2120  *
2121  * If found, B_TRUE is returned and 'dn' will be set to the
2122  * distinguishedName of the system's AD computer object.  Otherwise, returns
2123  * B_FALSE.
2124  */
2125 static boolean_t
2126 smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
2127 {
2128 	boolean_t found;
2129 	char *val = NULL;
2130 
2131 	smb_ads_get_computer_dn(ah, dn, SMB_ADS_DN_MAX);
2132 	found = (smb_ads_lookup_computer_n_attr(ah, NULL, NULL, LDAP_SCOPE_BASE,
2133 	    dn) == 1);
2134 
2135 	if (!found) {
2136 		(void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX);
2137 		found = (smb_ads_lookup_computer_n_attr(ah, SMB_ADS_ATTR_DN,
2138 		    &val, LDAP_SCOPE_SUBTREE, dn) == 1);
2139 
2140 		if (found && val)
2141 			(void) strlcpy(dn, val, SMB_ADS_DN_MAX);
2142 		else
2143 			found = B_FALSE;
2144 	}
2145 
2146 	return (found);
2147 }
2148 
2149 /*
2150  * smb_ads_update_computer_cntrl_attr
2151  *
2152  * Modify the user account control attribute of an existing computer
2153  * object on AD.
2154  *
2155  * Returns 0 on success. Otherwise, returns -1.
2156  */
2157 static int
2158 smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int des_only, char *dn)
2159 {
2160 	LDAPMod *attrs[2];
2161 	char *ctl_val[2];
2162 	int ret, usrctl_flags = 0;
2163 	char usrctl_buf[16];
2164 
2165 	if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
2166 		return (-1);
2167 
2168 	attrs[0]->mod_op = LDAP_MOD_REPLACE;
2169 	attrs[0]->mod_type = SMB_ADS_ATTR_CTL;
2170 
2171 	usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
2172 	    SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION |
2173 	    SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
2174 
2175 	if (des_only)
2176 		usrctl_flags |= SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY;
2177 
2178 	(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
2179 	ctl_val[0] = usrctl_buf;
2180 	ctl_val[1] = 0;
2181 	attrs[0]->mod_values = ctl_val;
2182 
2183 	if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
2184 		syslog(LOG_ERR, "smb_ads_modify_computer: %s",
2185 		    ldap_err2string(ret));
2186 		ret = -1;
2187 	}
2188 
2189 	smb_ads_free_attr(attrs);
2190 	return (ret);
2191 }
2192 
2193 /*
2194  * smb_ads_update_attrs
2195  *
2196  * Updates the servicePrincipalName and dNSHostName attributes of the
2197  * system's AD computer object.
2198  */
2199 int
2200 smb_ads_update_attrs(void)
2201 {
2202 	smb_ads_handle_t *ah;
2203 	LDAPMod *attrs[3];
2204 	char *fqh_val[2];
2205 	int i = 0;
2206 	int ret;
2207 	char fqhost[MAXHOSTNAMELEN];
2208 	char dn[SMB_ADS_DN_MAX];
2209 	char *spn_set[SMBKRB5_SPN_IDX_MAX + 1];
2210 
2211 	if ((ah = smb_ads_open()) == NULL)
2212 		return (-1);
2213 
2214 	if (!smb_ads_find_computer(ah, dn)) {
2215 		smb_ads_close(ah);
2216 		return (-1);
2217 	}
2218 
2219 	if (smb_getfqhostname(fqhost, MAXHOSTNAMELEN) != 0) {
2220 		smb_ads_close(ah);
2221 		return (-1);
2222 	}
2223 
2224 	if (smb_ads_get_spnset(fqhost, spn_set) != 0) {
2225 		smb_ads_close(ah);
2226 		return (-1);
2227 	}
2228 
2229 	if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *))
2230 	    != 0) {
2231 		smb_ads_free_spnset(spn_set);
2232 		smb_ads_close(ah);
2233 		return (-1);
2234 	}
2235 
2236 	attrs[i]->mod_op = LDAP_MOD_REPLACE;
2237 	attrs[i]->mod_type = SMB_ADS_ATTR_SPN;
2238 	attrs[i]->mod_values = spn_set;
2239 
2240 	attrs[++i]->mod_op = LDAP_MOD_REPLACE;
2241 	attrs[i]->mod_type = SMB_ADS_ATTR_DNSHOST;
2242 	fqh_val[0] = fqhost;
2243 	fqh_val[1] = 0;
2244 	attrs[i]->mod_values = fqh_val;
2245 
2246 	if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
2247 		syslog(LOG_ERR, "smb_ads_update_attrs: %s",
2248 		    ldap_err2string(ret));
2249 		ret = -1;
2250 	}
2251 
2252 	smb_ads_free_attr(attrs);
2253 	smb_ads_free_spnset(spn_set);
2254 	smb_ads_close(ah);
2255 
2256 	return (ret);
2257 
2258 }
2259 
2260 /*
2261  * smb_ads_lookup_computer_attr_kvno
2262  *
2263  * Lookup the value of the Kerberos version number attribute of the computer
2264  * account.
2265  */
2266 static krb5_kvno
2267 smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
2268 {
2269 	char *val = NULL;
2270 	int kvno = 1;
2271 
2272 	if (smb_ads_lookup_computer_n_attr(ah, SMB_ADS_ATTR_KVNO, &val,
2273 	    LDAP_SCOPE_BASE, dn) == 1) {
2274 		if (val) {
2275 			kvno = atoi(val);
2276 			free(val);
2277 		}
2278 	}
2279 
2280 	return (kvno);
2281 }
2282 
2283 /*
2284  * smb_ads_gen_machine_passwd
2285  *
2286  * Returned a null-terminated machine password generated randomly
2287  * from [0-9a-zA-Z] character set. In order to pass the password
2288  * quality check (three character classes), an uppercase letter is
2289  * used as the first character of the machine password.
2290  */
2291 static int
2292 smb_ads_gen_machine_passwd(char *machine_passwd, int bufsz)
2293 {
2294 	char *data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJK"
2295 	    "LMNOPQRSTUVWXYZ";
2296 	int datalen = strlen(data);
2297 	int i, data_idx;
2298 
2299 	if (!machine_passwd || bufsz == 0)
2300 		return (-1);
2301 
2302 	/*
2303 	 * The decimal value of upper case 'A' is 65. Randomly pick
2304 	 * an upper-case letter from the ascii table.
2305 	 */
2306 	machine_passwd[0] = (random() % 26) + 65;
2307 	for (i = 1; i < bufsz - 1; i++) {
2308 		data_idx = random() % datalen;
2309 		machine_passwd[i] = data[data_idx];
2310 	}
2311 
2312 	machine_passwd[bufsz - 1] = 0;
2313 	return (0);
2314 }
2315 
2316 /*
2317  * smb_ads_domain_change_cleanup
2318  *
2319  * If we're attempting to join the system to a new domain, the keys for
2320  * the host principal regarding the old domain should be removed from
2321  * Kerberos keytab. Also, the ads_host_info cache should be cleared.
2322  *
2323  * newdom is fully-qualified domain name.  It can be set to empty string
2324  * if user attempts to switch to workgroup mode.
2325  */
2326 int
2327 smb_ads_domain_change_cleanup(char *newdom)
2328 {
2329 	char origdom[MAXHOSTNAMELEN];
2330 	krb5_context ctx = NULL;
2331 	krb5_principal krb5princs[SMBKRB5_SPN_IDX_MAX];
2332 	int rc;
2333 
2334 	if (smb_getfqdomainname(origdom, MAXHOSTNAMELEN)) {
2335 		if (smb_getdomainname(origdom, MAXHOSTNAMELEN) == 0)
2336 			if (strncasecmp(origdom, newdom, strlen(origdom)))
2337 				smb_ads_free_host_info();
2338 
2339 		return (0);
2340 	}
2341 
2342 	if (strcasecmp(origdom, newdom) == 0)
2343 		return (0);
2344 
2345 	smb_ads_free_host_info();
2346 
2347 	if (smb_krb5_ctx_init(&ctx) != 0)
2348 		return (-1);
2349 
2350 	if (smb_krb5_get_principals(origdom, ctx, krb5princs) != 0) {
2351 		smb_krb5_ctx_fini(ctx);
2352 		return (-1);
2353 
2354 	}
2355 
2356 	rc = smb_krb5_remove_keytab_entries(ctx, krb5princs,
2357 	    SMBNS_KRB5_KEYTAB);
2358 
2359 	smb_krb5_free_principals(ctx, krb5princs, SMBKRB5_SPN_IDX_MAX);
2360 	smb_krb5_ctx_fini(ctx);
2361 
2362 	return (rc);
2363 }
2364 
2365 
2366 
2367 /*
2368  * smb_ads_join
2369  *
2370  * Besides the NT-4 style domain join (using MS-RPC), CIFS server also
2371  * provides the domain join using Kerberos Authentication, Keberos
2372  * Change & Set password, and LDAP protocols. Basically, AD join
2373  * operation would require the following tickets to be acquired for the
2374  * the user account that is provided for the domain join.
2375  *
2376  * 1) a Keberos TGT ticket,
2377  * 2) a ldap service ticket, and
2378  * 3) kadmin/changpw service ticket
2379  *
2380  * The ADS client first sends a ldap search request to find out whether
2381  * or not the workstation trust account already exists in the Active Directory.
2382  * The existing computer object for this workstation will be removed and
2383  * a new one will be added. The machine account password is randomly
2384  * generated and set for the newly created computer object using KPASSWD
2385  * protocol (See RFC 3244). Once the password is set, our ADS client
2386  * finalizes the machine account by modifying the user acount control
2387  * attribute of the computer object. Kerberos keys derived from the machine
2388  * account password will be stored locally in /etc/krb5/krb5.keytab file.
2389  * That would be needed while acquiring Kerberos TGT ticket for the host
2390  * principal after the domain join operation.
2391  */
2392 smb_adjoin_status_t
2393 smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd,
2394     int len)
2395 {
2396 	smb_ads_handle_t *ah = NULL;
2397 	krb5_context ctx = NULL;
2398 	krb5_principal krb5princs[SMBKRB5_SPN_IDX_MAX];
2399 	krb5_kvno kvno;
2400 	boolean_t des_only, delete = B_TRUE;
2401 	smb_adjoin_status_t rc = SMB_ADJOIN_SUCCESS;
2402 	boolean_t new_acct;
2403 	int dclevel, num;
2404 	char dn[SMB_ADS_DN_MAX];
2405 
2406 	/*
2407 	 * Call library functions that can be used to get
2408 	 * the list of encryption algorithms available on the system.
2409 	 * (similar to what 'encrypt -l' CLI does). For now,
2410 	 * unless someone has modified the configuration of the
2411 	 * cryptographic framework (very unlikely), the following is the
2412 	 * list of algorithms available on any system running Nevada
2413 	 * by default.
2414 	 */
2415 	krb5_enctype w2k8enctypes[] = {ENCTYPE_AES256_CTS_HMAC_SHA1_96,
2416 	    ENCTYPE_AES128_CTS_HMAC_SHA1_96, ENCTYPE_ARCFOUR_HMAC,
2417 	    ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5,
2418 	};
2419 
2420 	krb5_enctype pre_w2k8enctypes[] = {ENCTYPE_ARCFOUR_HMAC,
2421 	    ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5,
2422 	};
2423 
2424 	krb5_enctype *encptr;
2425 
2426 	if ((ah = smb_ads_open_main(domain, user, usr_passwd)) == NULL) {
2427 		smb_ccache_remove(SMB_CCACHE_PATH);
2428 		return (SMB_ADJOIN_ERR_GET_HANDLE);
2429 	}
2430 
2431 	if (smb_ads_gen_machine_passwd(machine_passwd, len) != 0) {
2432 		smb_ads_close(ah);
2433 		smb_ccache_remove(SMB_CCACHE_PATH);
2434 		return (SMB_ADJOIN_ERR_GEN_PASSWD);
2435 	}
2436 
2437 	if ((dclevel = smb_ads_get_dc_level(ah)) == -1) {
2438 		smb_ads_close(ah);
2439 		smb_ccache_remove(SMB_CCACHE_PATH);
2440 		return (SMB_ADJOIN_ERR_GET_DCLEVEL);
2441 	}
2442 
2443 	if (smb_ads_find_computer(ah, dn)) {
2444 		new_acct = B_FALSE;
2445 		if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
2446 			smb_ads_close(ah);
2447 			smb_ccache_remove(SMB_CCACHE_PATH);
2448 			return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT);
2449 		}
2450 	} else {
2451 		new_acct = B_TRUE;
2452 		smb_ads_get_computer_dn(ah, dn, SMB_ADS_DN_MAX);
2453 		if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
2454 			smb_ads_close(ah);
2455 			smb_ccache_remove(SMB_CCACHE_PATH);
2456 			return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT);
2457 		}
2458 	}
2459 
2460 	des_only = B_FALSE;
2461 
2462 	if (smb_krb5_ctx_init(&ctx) != 0) {
2463 		rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
2464 		goto adjoin_cleanup;
2465 	}
2466 
2467 	if (smb_krb5_get_principals(ah->domain, ctx, krb5princs) != 0) {
2468 		rc = SMB_ADJOIN_ERR_GET_SPNS;
2469 		goto adjoin_cleanup;
2470 	}
2471 
2472 	if (smb_krb5_setpwd(ctx, krb5princs[SMBKRB5_SPN_IDX_HOST],
2473 	    machine_passwd) != 0) {
2474 		rc = SMB_ADJOIN_ERR_KSETPWD;
2475 		goto adjoin_cleanup;
2476 	}
2477 
2478 	kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
2479 
2480 	if (smb_ads_update_computer_cntrl_attr(ah, des_only, dn)
2481 	    != 0) {
2482 		rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
2483 		goto adjoin_cleanup;
2484 	}
2485 
2486 	if (dclevel == SMB_ADS_DCLEVEL_W2K8) {
2487 		num = sizeof (w2k8enctypes) / sizeof (krb5_enctype);
2488 		encptr = w2k8enctypes;
2489 	} else {
2490 		num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype);
2491 		encptr = pre_w2k8enctypes;
2492 	}
2493 
2494 	if (smb_krb5_update_keytab_entries(ctx, krb5princs, SMBNS_KRB5_KEYTAB,
2495 	    kvno, machine_passwd, encptr, num) != 0) {
2496 		rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
2497 		goto adjoin_cleanup;
2498 	}
2499 
2500 	/* Set IDMAP config */
2501 	if (smb_config_set_idmap_domain(ah->domain) != 0) {
2502 		rc = SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN;
2503 		goto adjoin_cleanup;
2504 	}
2505 
2506 	/* Refresh IDMAP service */
2507 	if (smb_config_refresh_idmap() != 0) {
2508 		rc = SMB_ADJOIN_ERR_IDMAP_REFRESH;
2509 		goto adjoin_cleanup;
2510 	}
2511 
2512 	delete = B_FALSE;
2513 adjoin_cleanup:
2514 	if (new_acct && delete)
2515 		smb_ads_del_computer(ah, dn);
2516 
2517 	if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) {
2518 		if (rc != SMB_ADJOIN_ERR_GET_SPNS)
2519 			smb_krb5_free_principals(ctx, krb5princs,
2520 			    SMBKRB5_SPN_IDX_MAX);
2521 		smb_krb5_ctx_fini(ctx);
2522 	}
2523 
2524 	smb_ads_close(ah);
2525 	smb_ccache_remove(SMB_CCACHE_PATH);
2526 	return (rc);
2527 }
2528 
2529 /*
2530  * smb_adjoin_report_err
2531  *
2532  * Display error message for the specific adjoin error code.
2533  */
2534 char *
2535 smb_adjoin_report_err(smb_adjoin_status_t status)
2536 {
2537 	if (status < 0 || status >= SMB_ADJOIN_NUM_STATUS)
2538 		return ("ADJOIN: unknown status");
2539 
2540 	return (adjoin_errmsg[status]);
2541 }
2542 
2543 /*
2544  * smb_ads_get_pdc_ip
2545  *
2546  * Check to see if there is any configured PDC.
2547  * If there is then converts the string IP to
2548  * integer format and returns it.
2549  */
2550 static uint32_t
2551 smb_ads_get_pdc_ip(void)
2552 {
2553 	char p[INET_ADDRSTRLEN];
2554 	uint32_t ipaddr = 0;
2555 	int rc;
2556 
2557 	rc = smb_config_getstr(SMB_CI_DOMAIN_SRV, p, sizeof (p));
2558 	if (rc == SMBD_SMF_OK) {
2559 		rc = inet_pton(AF_INET, p, &ipaddr);
2560 		if (rc == 0)
2561 			ipaddr = 0;
2562 	}
2563 
2564 	return (ipaddr);
2565 }
2566 
2567 /*
2568  * smb_ads_dc_compare
2569  *
2570  * Comparision function for sorting host entries (SRV records of DC) via qsort.
2571  */
2572 static int
2573 smb_ads_dc_compare(const void *p, const void *q)
2574 {
2575 	smb_ads_host_info_t *h1 = (smb_ads_host_info_t *)p;
2576 	smb_ads_host_info_t *h2 = (smb_ads_host_info_t *)q;
2577 
2578 	if (h1->priority < h2->priority)
2579 		return (-1);
2580 	if (h1->priority > h2->priority)
2581 		return (1);
2582 
2583 	/* Priorities are equal */
2584 	if (h1->weight < h2->weight)
2585 		return (1);
2586 	if (h1->weight > h2->weight)
2587 		return (-1);
2588 
2589 	return (0);
2590 }
2591 
2592 /*
2593  * smb_ads_dc_recommend
2594  *
2595  * RFC 2052/2782 are taken as reference, while implementing this algorithm.
2596  *
2597  * Domain Controllers(DCs) with lowest priority in their SRV DNS records
2598  * are selected first. If they have equal priorities, then DC with highest
2599  * weight in its SRV DNS record is selected. If the priority and weight are
2600  * both equal, then the DC at the top of the list is selected.
2601  *
2602  * Returns NULL if no DC found or if the found DC is not responding to
2603  * smb_ads_ldap_ping.
2604  */
2605 static smb_ads_host_info_t *
2606 smb_ads_dc_recommend(smb_ads_host_list_t *hlist)
2607 {
2608 	smb_ads_host_info_t *hinfo = NULL;
2609 	int i;
2610 
2611 	if (hlist->ah_cnt == 1) {
2612 		hinfo = hlist->ah_list;
2613 		return (hinfo);
2614 	}
2615 
2616 	qsort(hlist->ah_list, hlist->ah_cnt,
2617 	    sizeof (smb_ads_host_info_t), smb_ads_dc_compare);
2618 
2619 	/* We have the sorted list. Return the first DC that is up. */
2620 	for (i = 0; i < hlist->ah_cnt; i++) {
2621 		hinfo = &hlist->ah_list[i];
2622 		if (smb_ads_ldap_ping(hinfo) == 0) {
2623 			smb_tracef("smb_ads_dc_recommend: "
2624 			    "Selected DC %s(pri=%d wt=%d)",
2625 			    hinfo->name, hinfo->priority, hinfo->weight);
2626 			return (hinfo);
2627 		}
2628 	}
2629 	return (NULL);
2630 }
2631 
2632 /*
2633  * smb_ads_select_pdc
2634  *
2635  * This method selects a Preferred DC (PDC) from the passed list of DCs (hlist),
2636  * if the configured PDC property is set and the selected PDC responds to
2637  * ldap_ping.
2638  *
2639  * The passed hinfo structure contains a pointer to the selected DC.
2640  * Returns true if the PDC is found, false otherwise.
2641  */
2642 static boolean_t
2643 smb_ads_select_pdc(smb_ads_host_info_t *hinfo, smb_ads_host_list_t *hlist)
2644 {
2645 	uint32_t ip;
2646 	smb_ads_host_info_t *thinfo = NULL;
2647 	int r;
2648 
2649 	if (ip = smb_ads_get_pdc_ip()) {
2650 		for (r = 0; r < hlist->ah_cnt; r++) {
2651 			if (hlist->ah_list[r].ip_addr == ip) {
2652 				thinfo = &hlist->ah_list[r];
2653 				if (smb_ads_ldap_ping(thinfo) == 0) {
2654 					smb_ads_init_host_info(hinfo,
2655 					    thinfo->name, thinfo->ip_addr,
2656 					    thinfo->priority, thinfo->weight,
2657 					    thinfo->port);
2658 					return (B_TRUE);
2659 				} else
2660 					smb_tracef("PDC not responding.");
2661 			}
2662 		}
2663 	}
2664 	return (B_FALSE);
2665 }
2666 
2667 /*
2668  * smb_ads_select_dcfromsubnet
2669  *
2670  * This method builds a list of DC's in the same subnet and sorts the list
2671  * according to the priority and weight of the DC entry.
2672  * Returns the DC from the top of the sorted list, that responds to ldap ping.
2673  *
2674  * The passed hinfo structure contains a pointer to the selected DC.
2675  * mem_err argument is set, if there are memory allocation issues.
2676  * Returns true if DC is found in the same subnet, false otherwise.
2677  */
2678 static boolean_t
2679 smb_ads_select_dcfromsubnet(smb_ads_host_info_t *hinfo,
2680     smb_ads_host_list_t *hlist, int *mem_err)
2681 {
2682 	smb_ads_host_list_t *snet_hlist = NULL;
2683 	smb_ads_host_info_t *snet_hinfo = NULL, *thinfo = NULL;
2684 	smb_nic_t *lnic;
2685 	int r, snet_count = 0;
2686 	smb_niciter_t ni;
2687 
2688 	snet_hlist = smb_ads_hlist_alloc(hlist->ah_cnt);
2689 	if (snet_hlist == NULL) {
2690 		*mem_err = 1;
2691 		return (B_FALSE);
2692 	}
2693 	snet_hinfo = snet_hlist->ah_list;
2694 	for (r = 0; r < hlist->ah_cnt; r++) {
2695 		thinfo = &hlist->ah_list[r];
2696 		if (smb_nic_getfirst(&ni) != 0)
2697 			break;
2698 		do {
2699 			lnic = &ni.ni_nic;
2700 
2701 			if ((thinfo->ip_addr & lnic->nic_mask) ==
2702 			    (lnic->nic_ip & lnic->nic_mask)) {
2703 				snet_count++;
2704 				smb_ads_init_host_info(snet_hinfo, thinfo->name,
2705 				    thinfo->ip_addr, thinfo->priority,
2706 				    thinfo->weight, thinfo->port);
2707 				snet_hinfo++;
2708 			}
2709 		} while (smb_nic_getnext(&ni) == 0);
2710 	}
2711 	snet_hlist->ah_cnt = snet_count;
2712 
2713 	if (snet_hlist->ah_cnt > 0) {
2714 		thinfo = smb_ads_dc_recommend(snet_hlist);
2715 		if (thinfo) {
2716 			smb_ads_init_host_info(hinfo, thinfo->name,
2717 			    thinfo->ip_addr, thinfo->priority, thinfo->weight,
2718 			    thinfo->port);
2719 			smb_ads_hlist_free(snet_hlist);
2720 			return (B_TRUE);
2721 		}
2722 	}
2723 	smb_ads_hlist_free(snet_hlist);
2724 	return (B_FALSE);
2725 }
2726 
2727 /*
2728  * smb_ads_select_dcfromlist
2729  *
2730  * This method sorts the list of DCs by priority and weight and
2731  * returns the first DC that responds to ldap ping.
2732  *
2733  * The passed hinfo structure contains a pointer to the selected DC.
2734  * Returns true if DC is found, false otherwise.
2735  */
2736 static boolean_t
2737 smb_ads_select_dcfromlist(smb_ads_host_info_t *hinfo,
2738     smb_ads_host_list_t *hlist)
2739 {
2740 	smb_ads_host_info_t *thinfo = NULL;
2741 
2742 	thinfo = smb_ads_dc_recommend(hlist);
2743 	if (thinfo) {
2744 		smb_ads_init_host_info(hinfo,
2745 		    thinfo->name, thinfo->ip_addr,
2746 		    thinfo->priority, thinfo->weight,
2747 		    thinfo->port);
2748 		return (B_TRUE);
2749 	}
2750 	return (B_FALSE);
2751 }
2752 
2753 /*
2754  * smb_ads_select_dc
2755  *
2756  * Given the list of DCs returned by ADS lookup, this routine returns
2757  * a DC according to the following.
2758  *
2759  *  - If there is a configured PDC and it's in the ADS list,
2760  *    then return the DC, if it responds to ldap ping.
2761  *  - Build a list of DC's in the same subnet. Sort the list
2762  *    according to priority and weight of the DC entry.
2763  *    Return the DC from the top of the list, that responds to ldap ping.
2764  *  - Else, sort DC list by priority & weight and return first DC that responds
2765  *    to ldap ping.
2766  *
2767  * Returns NULL on error.
2768  */
2769 static smb_ads_host_info_t *
2770 smb_ads_select_dc(smb_ads_host_list_t *hlist)
2771 {
2772 	smb_ads_host_info_t *hinfo = NULL;
2773 	int mem_err = 0;
2774 
2775 	hinfo = (smb_ads_host_info_t *)malloc(sizeof (smb_ads_host_info_t));
2776 	if (hinfo == NULL)
2777 		return (NULL);
2778 
2779 	/* 1. Check PDC. */
2780 	if (smb_ads_select_pdc(hinfo, hlist))
2781 		return (hinfo);
2782 
2783 	/* 2. Check subnet. */
2784 	if (smb_ads_select_dcfromsubnet(hinfo, hlist, &mem_err))
2785 		return (hinfo);
2786 
2787 	if (mem_err) {
2788 		free(hinfo);
2789 		return (NULL);
2790 	}
2791 
2792 	/*
2793 	 * 3. Sort DC list by priority & weight and return first DC that
2794 	 *    responds to LDAP ping.
2795 	 */
2796 	if (smb_ads_select_dcfromlist(hinfo, hlist))
2797 		return (hinfo);
2798 
2799 	free(hinfo);
2800 	return (NULL);
2801 }
2802