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