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