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