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