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