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