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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25 #include <sys/param.h>
26 #include <ldap.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <netinet/in.h>
31 #include <arpa/inet.h>
32 #include <sys/time.h>
33 #include <netdb.h>
34 #include <pthread.h>
35 #include <unistd.h>
36 #include <arpa/nameser.h>
37 #include <resolv.h>
38 #include <sys/synch.h>
39 #include <string.h>
40 #include <strings.h>
41 #include <fcntl.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <assert.h>
45 #include <sasl/sasl.h>
46 #include <note.h>
47 #include <errno.h>
48 #include <cryptoutil.h>
49
50 #include <smbsrv/libsmbns.h>
51 #include <smbns_dyndns.h>
52 #include <smbns_krb.h>
53
54 #define SMB_ADS_AF_UNKNOWN(x) (((x)->ipaddr.a_family != AF_INET) && \
55 ((x)->ipaddr.a_family != AF_INET6))
56
57 #define SMB_ADS_MAXBUFLEN 100
58 #define SMB_ADS_DN_MAX 300
59 #define SMB_ADS_MAXMSGLEN 512
60 #define SMB_ADS_COMPUTERS_CN "Computers"
61 #define SMB_ADS_COMPUTER_NUM_ATTR 8
62 #define SMB_ADS_SHARE_NUM_ATTR 3
63 #define SMB_ADS_SITE_MAX MAXHOSTNAMELEN
64
65 /*
66 * [MS-DISO] A machine password is an ASCII string of randomly chosen
67 * characters. Each character's ASCII code is between 32 and 122 inclusive.
68 */
69 #define SMB_ADS_PWD_CHAR_NUM 91
70 #define SMB_ADS_PWD_CHAR_START 32
71
72 #define SMB_ADS_MSDCS_SRV_DC_RR "_ldap._tcp.dc._msdcs"
73 #define SMB_ADS_MSDCS_SRV_SITE_RR "_ldap._tcp.%s._sites.dc._msdcs"
74
75 /*
76 * domainControllerFunctionality
77 *
78 * This rootDSE attribute indicates the functional level of the DC.
79 */
80 #define SMB_ADS_ATTR_DCLEVEL "domainControllerFunctionality"
81 #define SMB_ADS_DCLEVEL_W2K 0
82 #define SMB_ADS_DCLEVEL_W2K3 2
83 #define SMB_ADS_DCLEVEL_W2K8 3
84 #define SMB_ADS_DCLEVEL_W2K8_R2 4
85
86 /*
87 * msDs-supportedEncryptionTypes (Windows Server 2008 only)
88 *
89 * This attribute defines the encryption types supported by the system.
90 * Encryption Types:
91 * - DES cbc mode with CRC-32
92 * - DES cbc mode with RSA-MD5
93 * - ArcFour with HMAC/md5
94 * - AES-128
95 * - AES-256
96 */
97 #define SMB_ADS_ATTR_ENCTYPES "msDs-supportedEncryptionTypes"
98 #define SMB_ADS_ENC_DES_CRC 1
99 #define SMB_ADS_ENC_DES_MD5 2
100 #define SMB_ADS_ENC_RC4 4
101 #define SMB_ADS_ENC_AES128 8
102 #define SMB_ADS_ENC_AES256 16
103
104 static krb5_enctype w2k8enctypes[] = {
105 ENCTYPE_AES256_CTS_HMAC_SHA1_96,
106 ENCTYPE_AES128_CTS_HMAC_SHA1_96,
107 ENCTYPE_ARCFOUR_HMAC,
108 ENCTYPE_DES_CBC_CRC,
109 ENCTYPE_DES_CBC_MD5,
110 };
111
112 static krb5_enctype pre_w2k8enctypes[] = {
113 ENCTYPE_ARCFOUR_HMAC,
114 ENCTYPE_DES_CBC_CRC,
115 ENCTYPE_DES_CBC_MD5,
116 };
117
118 #define SMB_ADS_ATTR_SAMACCT "sAMAccountName"
119 #define SMB_ADS_ATTR_UPN "userPrincipalName"
120 #define SMB_ADS_ATTR_SPN "servicePrincipalName"
121 #define SMB_ADS_ATTR_CTL "userAccountControl"
122 #define SMB_ADS_ATTR_DNSHOST "dNSHostName"
123 #define SMB_ADS_ATTR_KVNO "msDS-KeyVersionNumber"
124 #define SMB_ADS_ATTR_DN "distinguishedName"
125
126 /*
127 * UserAccountControl flags: manipulate user account properties.
128 *
129 * The hexadecimal value of the following property flags are based on MSDN
130 * article # 305144.
131 */
132 #define SMB_ADS_USER_ACCT_CTL_SCRIPT 0x00000001
133 #define SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE 0x00000002
134 #define SMB_ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED 0x00000008
135 #define SMB_ADS_USER_ACCT_CTL_LOCKOUT 0x00000010
136 #define SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD 0x00000020
137 #define SMB_ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE 0x00000040
138 #define SMB_ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED 0x00000080
139 #define SMB_ADS_USER_ACCT_CTL_TMP_DUP_ACCT 0x00000100
140 #define SMB_ADS_USER_ACCT_CTL_NORMAL_ACCT 0x00000200
141 #define SMB_ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT 0x00000800
142 #define SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT 0x00001000
143 #define SMB_ADS_USER_ACCT_CTL_SRV_TRUST_ACCT 0x00002000
144 #define SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD 0x00010000
145 #define SMB_ADS_USER_ACCT_CTL_MNS_LOGON_ACCT 0x00020000
146 #define SMB_ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED 0x00040000
147 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION 0x00080000
148 #define SMB_ADS_USER_ACCT_CTL_NOT_DELEGATED 0x00100000
149 #define SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY 0x00200000
150 #define SMB_ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH 0x00400000
151 #define SMB_ADS_USER_ACCT_CTL_PASSWD_EXPIRED 0x00800000
152 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION 0x01000000
153
154 /*
155 * Length of "dc=" prefix.
156 */
157 #define SMB_ADS_DN_PREFIX_LEN 3
158
159 static char *smb_ads_computer_objcls[] = {
160 "top", "person", "organizationalPerson",
161 "user", "computer", NULL
162 };
163
164 static char *smb_ads_share_objcls[] = {
165 "top", "leaf", "connectionPoint", "volume", NULL
166 };
167
168 /* Cached ADS server to communicate with */
169 static smb_ads_host_info_t *smb_ads_cached_host_info = NULL;
170 static mutex_t smb_ads_cached_host_mtx;
171
172 /*
173 * SMB ADS config cache is maintained to facilitate the detection of
174 * changes in configuration that is relevant to AD selection.
175 */
176 typedef struct smb_ads_config {
177 char c_site[SMB_ADS_SITE_MAX];
178 smb_inaddr_t c_pdc;
179 mutex_t c_mtx;
180 } smb_ads_config_t;
181
182 static smb_ads_config_t smb_ads_cfg;
183
184
185 /* attribute/value pair */
186 typedef struct smb_ads_avpair {
187 char *avp_attr;
188 char *avp_val;
189 } smb_ads_avpair_t;
190
191 /* query status */
192 typedef enum smb_ads_qstat {
193 SMB_ADS_STAT_ERR = -2,
194 SMB_ADS_STAT_DUP,
195 SMB_ADS_STAT_NOT_FOUND,
196 SMB_ADS_STAT_FOUND
197 } smb_ads_qstat_t;
198
199 typedef struct smb_ads_host_list {
200 int ah_cnt;
201 smb_ads_host_info_t *ah_list;
202 } smb_ads_host_list_t;
203
204 static smb_ads_handle_t *smb_ads_open_main(char *, char *, char *);
205 static int smb_ads_add_computer(smb_ads_handle_t *, int, char *);
206 static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *);
207 static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *);
208 static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *,
209 smb_ads_avpair_t *, int, char *);
210 static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *);
211 static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *);
212 static int smb_ads_gen_machine_passwd(char *, size_t);
213 static void smb_ads_free_cached_host(void);
214 static int smb_ads_alloc_attr(LDAPMod **, int);
215 static void smb_ads_free_attr(LDAPMod **);
216 static int smb_ads_get_dc_level(smb_ads_handle_t *);
217 static smb_ads_host_info_t *smb_ads_select_dc(smb_ads_host_list_t *);
218 static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *, char *);
219 static smb_ads_qstat_t smb_ads_getattr(LDAP *, LDAPMessage *,
220 smb_ads_avpair_t *);
221 static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *, LDAPMessage *,
222 smb_ads_avpair_t *);
223 static boolean_t smb_ads_match_pdc(smb_ads_host_info_t *);
224 static boolean_t smb_ads_is_sought_host(smb_ads_host_info_t *, char *);
225 static boolean_t smb_ads_is_same_domain(char *, char *);
226 static boolean_t smb_ads_is_pdc_configured(void);
227 static smb_ads_host_info_t *smb_ads_dup_host_info(smb_ads_host_info_t *);
228 static char *smb_ads_get_sharedn(const char *, const char *, const char *);
229 static krb5_enctype *smb_ads_get_enctypes(int, int *);
230
231 /*
232 * smb_ads_init
233 *
234 * Initializes the ADS config cache.
235 */
236 void
smb_ads_init(void)237 smb_ads_init(void)
238 {
239 (void) mutex_lock(&smb_ads_cfg.c_mtx);
240 (void) smb_config_getstr(SMB_CI_ADS_SITE,
241 smb_ads_cfg.c_site, SMB_ADS_SITE_MAX);
242 (void) smb_config_getip(SMB_CI_DOMAIN_SRV, &smb_ads_cfg.c_pdc);
243 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
244 }
245
246 void
smb_ads_fini(void)247 smb_ads_fini(void)
248 {
249 smb_ads_free_cached_host();
250 }
251
252 /*
253 * smb_ads_refresh
254 *
255 * This function will be called when smb/server SMF service is refreshed.
256 * Clearing the smb_ads_cached_host_info would allow the next DC
257 * discovery process to pick up an AD based on the new AD configuration.
258 */
259 void
smb_ads_refresh(void)260 smb_ads_refresh(void)
261 {
262 char new_site[SMB_ADS_SITE_MAX];
263 smb_inaddr_t new_pdc;
264 boolean_t purge = B_FALSE;
265
266 (void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX);
267 (void) smb_config_getip(SMB_CI_DOMAIN_SRV, &new_pdc);
268 (void) mutex_lock(&smb_ads_cfg.c_mtx);
269 if (smb_strcasecmp(smb_ads_cfg.c_site, new_site, 0)) {
270 (void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX);
271 purge = B_TRUE;
272 }
273
274 smb_ads_cfg.c_pdc = new_pdc;
275 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
276
277 (void) mutex_lock(&smb_ads_cached_host_mtx);
278 if (smb_ads_cached_host_info &&
279 smb_ads_is_pdc_configured() &&
280 !smb_ads_match_pdc(smb_ads_cached_host_info))
281 purge = B_TRUE;
282 (void) mutex_unlock(&smb_ads_cached_host_mtx);
283
284 if (purge)
285 smb_ads_free_cached_host();
286 }
287
288
289
290 static boolean_t
smb_ads_is_pdc_configured(void)291 smb_ads_is_pdc_configured(void)
292 {
293 boolean_t configured;
294
295 (void) mutex_lock(&smb_ads_cfg.c_mtx);
296 configured = !smb_inet_iszero(&smb_ads_cfg.c_pdc);
297 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
298
299 return (configured);
300 }
301
302 /*
303 * smb_ads_build_unc_name
304 *
305 * Construct the UNC name of the share object in the format of
306 * \\hostname.domain\shareUNC
307 *
308 * Returns 0 on success, -1 on error.
309 */
310 int
smb_ads_build_unc_name(char * unc_name,int maxlen,const char * hostname,const char * shareUNC)311 smb_ads_build_unc_name(char *unc_name, int maxlen,
312 const char *hostname, const char *shareUNC)
313 {
314 char my_domain[MAXHOSTNAMELEN];
315
316 if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0)
317 return (-1);
318
319 (void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s",
320 hostname, my_domain, shareUNC);
321 return (0);
322 }
323
324 /*
325 * smb_ads_ldap_ping
326 *
327 * This is used to bind to an ADS server to see
328 * if it is still alive.
329 *
330 * Returns:
331 * -1: error
332 * 0: successful
333 */
334 /*ARGSUSED*/
335 static int
smb_ads_ldap_ping(smb_ads_host_info_t * ads_host)336 smb_ads_ldap_ping(smb_ads_host_info_t *ads_host)
337 {
338 int ldversion = LDAP_VERSION3, status, timeoutms = 5 * 1000;
339 LDAP *ld = NULL;
340
341 ld = ldap_init(ads_host->name, ads_host->port);
342 if (ld == NULL)
343 return (-1);
344
345 ldversion = LDAP_VERSION3;
346 (void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion);
347 /* setup TCP/IP connect timeout */
348 (void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms);
349
350 status = ldap_bind_s(ld, "", NULL, LDAP_AUTH_SIMPLE);
351
352 if (status != LDAP_SUCCESS) {
353 (void) ldap_unbind(ld);
354 return (-1);
355 }
356
357 (void) ldap_unbind(ld);
358
359 return (0);
360 }
361
362 /*
363 * The cached ADS host is no longer valid if one of the following criteria
364 * is satisfied:
365 *
366 * 1) not in the specified domain
367 * 2) not the sought host (if specified)
368 * 3) not reachable
369 *
370 * The caller is responsible for acquiring the smb_ads_cached_host_mtx lock
371 * prior to calling this function.
372 *
373 * Return B_TRUE if the cache host is still valid. Otherwise, return B_FALSE.
374 */
375 static boolean_t
smb_ads_validate_cache_host(char * domain,char * srv)376 smb_ads_validate_cache_host(char *domain, char *srv)
377 {
378 if (!smb_ads_cached_host_info)
379 return (B_FALSE);
380
381 if (!smb_ads_is_same_domain(smb_ads_cached_host_info->name, domain))
382 return (B_FALSE);
383
384 if (smb_ads_ldap_ping(smb_ads_cached_host_info) == 0) {
385 if (!srv)
386 return (B_TRUE);
387
388 if (smb_ads_is_sought_host(smb_ads_cached_host_info, srv))
389 return (B_TRUE);
390 }
391
392 return (B_FALSE);
393 }
394
395 /*
396 * smb_ads_is_sought_host
397 *
398 * Returns true, if the sought host name matches the input host (host) name.
399 * The sought host is expected to be in Fully Qualified Domain Name (FQDN)
400 * format.
401 */
402 static boolean_t
smb_ads_is_sought_host(smb_ads_host_info_t * host,char * sought_host_name)403 smb_ads_is_sought_host(smb_ads_host_info_t *host, char *sought_host_name)
404 {
405 if ((host == NULL) || (sought_host_name == NULL))
406 return (B_FALSE);
407
408 if (smb_strcasecmp(host->name, sought_host_name, 0))
409 return (B_FALSE);
410
411 return (B_TRUE);
412 }
413
414 /*
415 * smb_ads_match_hosts_same_domain
416 *
417 * Returns true, if the cached ADS host is in the same domain as the
418 * current (given) domain.
419 */
420 static boolean_t
smb_ads_is_same_domain(char * cached_host_name,char * current_domain)421 smb_ads_is_same_domain(char *cached_host_name, char *current_domain)
422 {
423 char *cached_host_domain;
424
425 if ((cached_host_name == NULL) || (current_domain == NULL))
426 return (B_FALSE);
427
428 cached_host_domain = strchr(cached_host_name, '.');
429 if (cached_host_domain == NULL)
430 return (B_FALSE);
431
432 ++cached_host_domain;
433 if (smb_strcasecmp(cached_host_domain, current_domain, 0))
434 return (B_FALSE);
435
436 return (B_TRUE);
437 }
438
439 /*
440 * smb_ads_skip_ques_sec
441 * Skips the question section.
442 */
443 static int
smb_ads_skip_ques_sec(int qcnt,uchar_t ** ptr,uchar_t * eom)444 smb_ads_skip_ques_sec(int qcnt, uchar_t **ptr, uchar_t *eom)
445 {
446 int i, len;
447
448 for (i = 0; i < qcnt; i++) {
449 if ((len = dn_skipname(*ptr, eom)) < 0)
450 return (-1);
451
452 *ptr += len + QFIXEDSZ;
453 }
454
455 return (0);
456 }
457
458 /*
459 * smb_ads_decode_host_ans_sec
460 * Decodes ADS hosts, priority, weight and port number from the answer
461 * section based on the current buffer pointer.
462 */
463 static int
smb_ads_decode_host_ans_sec(int ans_cnt,uchar_t ** ptr,uchar_t * eom,uchar_t * buf,smb_ads_host_info_t * ads_host_list)464 smb_ads_decode_host_ans_sec(int ans_cnt, uchar_t **ptr, uchar_t *eom,
465 uchar_t *buf, smb_ads_host_info_t *ads_host_list)
466 {
467 int i, len;
468 smb_ads_host_info_t *ads_host;
469
470 for (i = 0; i < ans_cnt; i++) {
471 ads_host = &ads_host_list[i];
472
473 if ((len = dn_skipname(*ptr, eom)) < 0)
474 return (-1);
475
476
477 *ptr += len;
478
479 /* skip type, class, ttl */
480 *ptr += 8;
481 /* data size */
482 *ptr += 2;
483
484 /* Get priority, weight */
485 /* LINTED: E_CONSTANT_CONDITION */
486 NS_GET16(ads_host->priority, *ptr);
487 /* LINTED: E_CONSTANT_CONDITION */
488 NS_GET16(ads_host->weight, *ptr);
489
490 /* port */
491 /* LINTED: E_CONSTANT_CONDITION */
492 NS_GET16(ads_host->port, *ptr);
493 /* domain name */
494 len = dn_expand(buf, eom, *ptr, ads_host->name, MAXHOSTNAMELEN);
495 if (len < 0)
496 return (-1);
497
498 *ptr += len;
499 }
500
501 return (0);
502 }
503
504 /*
505 * smb_ads_skip_auth_sec
506 * Skips the authority section.
507 */
508 static int
smb_ads_skip_auth_sec(int ns_cnt,uchar_t ** ptr,uchar_t * eom)509 smb_ads_skip_auth_sec(int ns_cnt, uchar_t **ptr, uchar_t *eom)
510 {
511 int i, len;
512 uint16_t size;
513
514 for (i = 0; i < ns_cnt; i++) {
515 if ((len = dn_skipname(*ptr, eom)) < 0)
516 return (-1);
517
518 *ptr += len;
519 /* skip type, class, ttl */
520 *ptr += 8;
521 /* get len of data */
522 /* LINTED: E_CONSTANT_CONDITION */
523 NS_GET16(size, *ptr);
524 if ((*ptr + size) > eom)
525 return (-1);
526
527 *ptr += size;
528 }
529
530 return (0);
531 }
532
533 /*
534 * smb_ads_decode_host_ip
535 *
536 * Decodes ADS hosts and IP Addresses from the additional section based
537 * on the current buffer pointer.
538 */
539 static int
smb_ads_decode_host_ip(int addit_cnt,int ans_cnt,uchar_t ** ptr,uchar_t * eom,uchar_t * buf,smb_ads_host_info_t * ads_host_list)540 smb_ads_decode_host_ip(int addit_cnt, int ans_cnt, uchar_t **ptr,
541 uchar_t *eom, uchar_t *buf, smb_ads_host_info_t *ads_host_list)
542 {
543 int i, j, len;
544 smb_inaddr_t ipaddr;
545 char hostname[MAXHOSTNAMELEN];
546 char *name;
547 uint16_t size = 0;
548
549 for (i = 0; i < addit_cnt; i++) {
550
551 /* domain name */
552 len = dn_expand(buf, eom, *ptr, hostname, MAXHOSTNAMELEN);
553 if (len < 0)
554 return (-1);
555
556 *ptr += len;
557
558 /* skip type, class, TTL, data len */
559 *ptr += 8;
560 /* LINTED: E_CONSTANT_CONDITION */
561 NS_GET16(size, *ptr);
562
563 if (size == INADDRSZ) {
564 /* LINTED: E_CONSTANT_CONDITION */
565 NS_GET32(ipaddr.a_ipv4, *ptr);
566 ipaddr.a_ipv4 = htonl(ipaddr.a_ipv4);
567 ipaddr.a_family = AF_INET;
568 } else if (size == IN6ADDRSZ) {
569 #ifdef BIG_ENDIAN
570 bcopy(*ptr, &ipaddr.a_ipv6, IN6ADDRSZ);
571 #else
572 for (i = 0; i < IN6ADDRSZ; i++)
573 (uint8_t *)(ipaddr.a_ipv6)
574 [IN6ADDRSZ-1-i] = *(*ptr+i);
575 #endif
576 ipaddr.a_family = AF_INET6;
577 *ptr += size;
578 }
579
580 /*
581 * find the host in the list of DC records from
582 * the answer section, that matches the host in the
583 * additional section, and set its IP address.
584 */
585 for (j = 0; j < ans_cnt; j++) {
586 if ((name = ads_host_list[j].name) == NULL)
587 continue;
588 if (smb_strcasecmp(name, hostname, 0) == 0) {
589 ads_host_list[j].ipaddr = ipaddr;
590 }
591 }
592 }
593 return (0);
594 }
595
596 /*
597 * smb_ads_dup_host_info
598 *
599 * Duplicates the passed smb_ads_host_info_t structure.
600 * Caller must free memory allocated by this method.
601 *
602 * Returns a reference to the duplicated smb_ads_host_info_t structure.
603 * Returns NULL on error.
604 */
605 static smb_ads_host_info_t *
smb_ads_dup_host_info(smb_ads_host_info_t * ads_host)606 smb_ads_dup_host_info(smb_ads_host_info_t *ads_host)
607 {
608 smb_ads_host_info_t *dup_host;
609
610 if (ads_host == NULL)
611 return (NULL);
612
613 dup_host = malloc(sizeof (smb_ads_host_info_t));
614
615 if (dup_host != NULL)
616 bcopy(ads_host, dup_host, sizeof (smb_ads_host_info_t));
617
618 return (dup_host);
619 }
620
621 /*
622 * smb_ads_hlist_alloc
623 */
624 static smb_ads_host_list_t *
smb_ads_hlist_alloc(int count)625 smb_ads_hlist_alloc(int count)
626 {
627 int size;
628 smb_ads_host_list_t *hlist;
629
630 if (count == 0)
631 return (NULL);
632
633 size = sizeof (smb_ads_host_info_t) * count;
634 hlist = (smb_ads_host_list_t *)malloc(sizeof (smb_ads_host_list_t));
635 if (hlist == NULL)
636 return (NULL);
637
638 hlist->ah_cnt = count;
639 hlist->ah_list = (smb_ads_host_info_t *)malloc(size);
640 if (hlist->ah_list == NULL) {
641 free(hlist);
642 return (NULL);
643 }
644
645 bzero(hlist->ah_list, size);
646 return (hlist);
647 }
648
649 /*
650 * smb_ads_hlist_free
651 */
652 static void
smb_ads_hlist_free(smb_ads_host_list_t * host_list)653 smb_ads_hlist_free(smb_ads_host_list_t *host_list)
654 {
655 if (host_list == NULL)
656 return;
657
658 free(host_list->ah_list);
659 free(host_list);
660 }
661
662 /*
663 * smb_ads_query_dns_server
664 *
665 * This routine sends a DNS service location (SRV) query message to the
666 * DNS server via TCP to query it for a list of ADS server(s). Once a reply
667 * is received, the reply message is parsed to get the hostname. If there are IP
668 * addresses populated in the additional section then the additional section
669 * is parsed to obtain the IP addresses.
670 *
671 * The service location of _ldap._tcp.dc.msdcs.<ADS domain> is used to
672 * guarantee that Microsoft domain controllers are returned. Microsoft domain
673 * controllers are also ADS servers.
674 *
675 * The ADS hostnames are stored in the answer section of the DNS reply message.
676 * The IP addresses are stored in the additional section.
677 *
678 * The DNS reply message may be in compress formed. The compression is done
679 * on repeating domain name label in the message. i.e hostname.
680 *
681 * Upon successful completion, host list of ADS server(s) is returned.
682 */
683 static smb_ads_host_list_t *
smb_ads_query_dns_server(char * domain,char * msdcs_svc_name)684 smb_ads_query_dns_server(char *domain, char *msdcs_svc_name)
685 {
686 smb_ads_host_list_t *hlist = NULL;
687 int len, qcnt, ans_cnt, ns_cnt, addit_cnt;
688 uchar_t *ptr, *eom;
689 struct __res_state res_state;
690 union {
691 HEADER hdr;
692 uchar_t buf[NS_MAXMSG];
693 } msg;
694
695 bzero(&res_state, sizeof (struct __res_state));
696 if (res_ninit(&res_state) < 0)
697 return (NULL);
698
699 /* use TCP */
700 res_state.options |= RES_USEVC;
701
702 len = res_nquerydomain(&res_state, msdcs_svc_name, domain,
703 C_IN, T_SRV, msg.buf, sizeof (msg.buf));
704
705 if (len < 0) {
706 syslog(LOG_NOTICE, "DNS query for %s failed: %s",
707 msdcs_svc_name, hstrerror(res_state.res_h_errno));
708 res_ndestroy(&res_state);
709 return (NULL);
710 }
711
712 if (len > sizeof (msg.buf)) {
713 syslog(LOG_NOTICE,
714 "DNS query for %s failed: too big", msdcs_svc_name);
715 res_ndestroy(&res_state);
716 return (NULL);
717 }
718
719 /* parse the reply, skip header and question sections */
720 ptr = msg.buf + sizeof (msg.hdr);
721 eom = msg.buf + len;
722
723 /* check truncated message bit */
724 if (msg.hdr.tc)
725 syslog(LOG_NOTICE,
726 "DNS query for %s failed: truncated", msdcs_svc_name);
727
728 qcnt = ntohs(msg.hdr.qdcount);
729 ans_cnt = ntohs(msg.hdr.ancount);
730 ns_cnt = ntohs(msg.hdr.nscount);
731 addit_cnt = ntohs(msg.hdr.arcount);
732
733 if (smb_ads_skip_ques_sec(qcnt, &ptr, eom) != 0) {
734 res_ndestroy(&res_state);
735 return (NULL);
736 }
737
738 hlist = smb_ads_hlist_alloc(ans_cnt);
739 if (hlist == NULL) {
740 res_ndestroy(&res_state);
741 return (NULL);
742 }
743
744 /* walk through the answer section */
745 if (smb_ads_decode_host_ans_sec(ans_cnt, &ptr, eom, msg.buf,
746 hlist->ah_list) != 0) {
747 smb_ads_hlist_free(hlist);
748 res_ndestroy(&res_state);
749 return (NULL);
750 }
751
752 /* check authority section */
753 if (ns_cnt > 0) {
754 if (smb_ads_skip_auth_sec(ns_cnt, &ptr, eom) != 0) {
755 smb_ads_hlist_free(hlist);
756 res_ndestroy(&res_state);
757 return (NULL);
758 }
759 }
760
761 /*
762 * Check additional section to get IP address of ADS host.
763 */
764 if (addit_cnt > 0) {
765 if (smb_ads_decode_host_ip(addit_cnt, ans_cnt,
766 &ptr, eom, msg.buf, hlist->ah_list) != 0) {
767 smb_ads_hlist_free(hlist);
768 res_ndestroy(&res_state);
769 return (NULL);
770 }
771 }
772
773 res_ndestroy(&res_state);
774 return (hlist);
775 }
776
777 /*
778 * smb_ads_get_site_service
779 *
780 * Gets the msdcs SRV RR for the specified site.
781 */
782 static void
smb_ads_get_site_service(char * site_service,size_t len)783 smb_ads_get_site_service(char *site_service, size_t len)
784 {
785 (void) mutex_lock(&smb_ads_cfg.c_mtx);
786 if (*smb_ads_cfg.c_site == '\0')
787 *site_service = '\0';
788 else
789 (void) snprintf(site_service, len,
790 SMB_ADS_MSDCS_SRV_SITE_RR, smb_ads_cfg.c_site);
791
792 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
793 }
794
795 /*
796 * smb_ads_getipnodebyname
797 *
798 * This method gets the IP address by doing a host name lookup.
799 */
800 static int
smb_ads_getipnodebyname(smb_ads_host_info_t * hentry)801 smb_ads_getipnodebyname(smb_ads_host_info_t *hentry)
802 {
803 struct hostent *h;
804 int error;
805
806 switch (hentry->ipaddr.a_family) {
807 case AF_INET6:
808 h = getipnodebyname(hentry->name, hentry->ipaddr.a_family,
809 AI_DEFAULT, &error);
810 if (h == NULL || h->h_length != IPV6_ADDR_LEN)
811 return (-1);
812 break;
813
814 case AF_INET:
815 h = getipnodebyname(hentry->name, hentry->ipaddr.a_family,
816 0, &error);
817 if (h == NULL || h->h_length != INADDRSZ)
818 return (-1);
819 break;
820
821 default:
822 return (-1);
823 }
824 bcopy(*(h->h_addr_list), &hentry->ipaddr.a_ip, h->h_length);
825 freehostent(h);
826 return (0);
827 }
828
829 /*
830 * Checks the IP address to see if it is zero. If so, then do a host
831 * lookup by hostname to get the IP address based on the IP family.
832 *
833 * If the family is unknown then do a lookup by hostame based on the
834 * setting of the SMB_CI_IPV6_ENABLE property.
835 */
836 static int
smb_ads_set_ipaddr(smb_ads_host_info_t * hentry)837 smb_ads_set_ipaddr(smb_ads_host_info_t *hentry)
838 {
839 if (smb_inet_iszero(&hentry->ipaddr)) {
840 if (smb_ads_getipnodebyname(hentry) < 0)
841 return (-1);
842 } else if (SMB_ADS_AF_UNKNOWN(hentry)) {
843 hentry->ipaddr.a_family =
844 smb_config_getbool(SMB_CI_IPV6_ENABLE) ? AF_INET6 : AF_INET;
845
846 if (smb_ads_getipnodebyname(hentry) < 0) {
847 hentry->ipaddr.a_family = 0;
848 return (-1);
849 }
850 }
851
852 return (0);
853 }
854
855 /*
856 * smb_ads_find_host
857 *
858 * Finds an ADS host in a given domain.
859 *
860 * If the cached host is valid, it will be used. Otherwise, a DC will
861 * be selected based on the following criteria:
862 *
863 * 1) pdc (aka preferred DC) configuration
864 * 2) AD site configuration - the scope of the DNS lookup will be
865 * restricted to the specified site.
866 * 3) DC on the same subnet
867 * 4) DC with the lowest priority/highest weight
868 *
869 * The above items are listed in decreasing preference order. The selected
870 * DC must be online.
871 *
872 * If this function is called during domain join, the specified kpasswd server
873 * takes precedence over preferred DC, AD site, and so on.
874 *
875 * Parameters:
876 * domain: fully-qualified domain name.
877 * kpasswd_srv: fully-quailifed hostname of the kpasswd server.
878 *
879 * Returns:
880 * A copy of the cached host info is returned. The caller is responsible
881 * for deallocating the memory returned by this function.
882 */
883 /*ARGSUSED*/
884 smb_ads_host_info_t *
smb_ads_find_host(char * domain,char * kpasswd_srv)885 smb_ads_find_host(char *domain, char *kpasswd_srv)
886 {
887 int i;
888 char site_service[MAXHOSTNAMELEN];
889 smb_ads_host_list_t *hlist, *hlist2;
890 smb_ads_host_info_t *hlistp = NULL, *host = NULL;
891 smb_ads_host_info_t *found_kpasswd_srv = NULL;
892 smb_ads_host_info_t *found_pdc = NULL;
893
894 if ((kpasswd_srv) && (*kpasswd_srv == '\0'))
895 kpasswd_srv = NULL;
896
897 (void) mutex_lock(&smb_ads_cached_host_mtx);
898 if (smb_ads_validate_cache_host(domain, kpasswd_srv)) {
899 host = smb_ads_dup_host_info(smb_ads_cached_host_info);
900 (void) mutex_unlock(&smb_ads_cached_host_mtx);
901 return (host);
902 }
903
904 (void) mutex_unlock(&smb_ads_cached_host_mtx);
905 smb_ads_free_cached_host();
906
907 /*
908 * First look for ADS hosts in ADS site if configured. Then try
909 * without ADS site info.
910 */
911 hlist = NULL;
912 smb_ads_get_site_service(site_service, MAXHOSTNAMELEN);
913
914 /*
915 * If we're given an AD, the DNS SRV RR lookup should not be restricted
916 * to the specified site since there is no guarantee that the specified
917 * AD is in the specified site.
918 */
919 if (*site_service != '\0' && !kpasswd_srv &&
920 !smb_ads_is_pdc_configured())
921 hlist = smb_ads_query_dns_server(domain, site_service);
922
923 if (!hlist)
924 hlist = smb_ads_query_dns_server(domain,
925 SMB_ADS_MSDCS_SRV_DC_RR);
926
927 if ((hlist == NULL) || (hlist->ah_list == NULL) || (hlist->ah_cnt == 0))
928 return (NULL);
929
930 for (i = 0, hlistp = hlist->ah_list; i < hlist->ah_cnt; i++) {
931 if (smb_ads_set_ipaddr(&hlistp[i]) < 0)
932 continue;
933
934 if (smb_ads_is_sought_host(&hlistp[i], kpasswd_srv))
935 found_kpasswd_srv = &hlistp[i];
936
937 if (smb_ads_match_pdc(&hlistp[i]))
938 found_pdc = &hlistp[i];
939 }
940
941 if (found_kpasswd_srv && smb_ads_ldap_ping(found_kpasswd_srv) == 0) {
942 host = found_kpasswd_srv;
943 goto update_cache;
944 }
945
946 if (found_pdc && smb_ads_ldap_ping(found_pdc) == 0) {
947 host = found_pdc;
948 goto update_cache;
949 }
950
951 /*
952 * If the specified DC (kpasswd_srv or pdc) is not found, fallback
953 * to find a DC in the specified AD site.
954 */
955 if (*site_service != '\0' &&
956 (kpasswd_srv || smb_ads_is_pdc_configured())) {
957 hlist2 = smb_ads_query_dns_server(domain, site_service);
958 if (hlist2 && hlist2->ah_list && hlist2->ah_cnt != 0) {
959 smb_ads_hlist_free(hlist);
960 hlist = hlist2;
961 hlistp = hlist->ah_list;
962
963 for (i = 0; i < hlist->ah_cnt; i++)
964 (void) smb_ads_set_ipaddr(&hlistp[i]);
965 }
966 }
967
968 /* Select DC from DC list */
969 host = smb_ads_select_dc(hlist);
970
971 update_cache:
972 if (host) {
973 (void) mutex_lock(&smb_ads_cached_host_mtx);
974 if (!smb_ads_cached_host_info)
975 smb_ads_cached_host_info = smb_ads_dup_host_info(host);
976 host = smb_ads_dup_host_info(smb_ads_cached_host_info);
977 (void) mutex_unlock(&smb_ads_cached_host_mtx);
978 }
979
980 smb_ads_hlist_free(hlist);
981 return (host);
982 }
983
984 /*
985 * Return the number of dots in a string.
986 */
987 static int
smb_ads_count_dots(const char * s)988 smb_ads_count_dots(const char *s)
989 {
990 int ndots = 0;
991
992 while (*s) {
993 if (*s++ == '.')
994 ndots++;
995 }
996
997 return (ndots);
998 }
999
1000 /*
1001 * Convert a domain name in dot notation to distinguished name format,
1002 * for example: sun.com -> dc=sun,dc=com.
1003 *
1004 * Returns a pointer to an allocated buffer containing the distinguished
1005 * name.
1006 */
1007 static char *
smb_ads_convert_domain(const char * domain_name)1008 smb_ads_convert_domain(const char *domain_name)
1009 {
1010 const char *s;
1011 char *dn_name;
1012 char buf[2];
1013 int ndots;
1014 int len;
1015
1016 if (domain_name == NULL || *domain_name == 0)
1017 return (NULL);
1018
1019 ndots = smb_ads_count_dots(domain_name);
1020 ++ndots;
1021 len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1;
1022
1023 if ((dn_name = malloc(len)) == NULL)
1024 return (NULL);
1025
1026 bzero(dn_name, len);
1027 (void) strlcpy(dn_name, "dc=", len);
1028
1029 buf[1] = '\0';
1030 s = domain_name;
1031
1032 while (*s) {
1033 if (*s == '.') {
1034 (void) strlcat(dn_name, ",dc=", len);
1035 } else {
1036 buf[0] = *s;
1037 (void) strlcat(dn_name, buf, len);
1038 }
1039 ++s;
1040 }
1041
1042 return (dn_name);
1043 }
1044
1045 /*
1046 * smb_ads_free_cached_host
1047 *
1048 * Free the memory use by the global smb_ads_cached_host_info & set it to NULL.
1049 */
1050 static void
smb_ads_free_cached_host(void)1051 smb_ads_free_cached_host(void)
1052 {
1053 (void) mutex_lock(&smb_ads_cached_host_mtx);
1054 if (smb_ads_cached_host_info) {
1055 free(smb_ads_cached_host_info);
1056 smb_ads_cached_host_info = NULL;
1057 }
1058 (void) mutex_unlock(&smb_ads_cached_host_mtx);
1059 }
1060
1061 /*
1062 * smb_ads_open
1063 * Open a LDAP connection to an ADS server if the system is in domain mode.
1064 * Acquire both Kerberos TGT and LDAP service tickets for the host principal.
1065 *
1066 * This function should only be called after the system is successfully joined
1067 * to a domain.
1068 */
1069 smb_ads_handle_t *
smb_ads_open(void)1070 smb_ads_open(void)
1071 {
1072 char domain[MAXHOSTNAMELEN];
1073
1074 if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
1075 return (NULL);
1076
1077 if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
1078 return (NULL);
1079
1080 return (smb_ads_open_main(domain, NULL, NULL));
1081 }
1082
1083 static int
smb_ads_saslcallback(LDAP * ld,unsigned flags,void * defaults,void * prompts)1084 smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts)
1085 {
1086 NOTE(ARGUNUSED(ld, defaults));
1087 sasl_interact_t *interact;
1088
1089 if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
1090 return (LDAP_PARAM_ERROR);
1091
1092 /* There should be no extra arguemnts for SASL/GSSAPI authentication */
1093 for (interact = prompts; interact->id != SASL_CB_LIST_END;
1094 interact++) {
1095 interact->result = NULL;
1096 interact->len = 0;
1097 }
1098 return (LDAP_SUCCESS);
1099 }
1100
1101 /*
1102 * smb_ads_open_main
1103 * Open a LDAP connection to an ADS server.
1104 * If ADS is enabled and the administrative username, password, and
1105 * ADS domain are defined then query DNS to find an ADS server if this is the
1106 * very first call to this routine. After an ADS server is found then this
1107 * server will be used everytime this routine is called until the system is
1108 * rebooted or the ADS server becomes unavailable then an ADS server will
1109 * be queried again. After the connection is made then an ADS handle
1110 * is created to be returned.
1111 *
1112 * After the LDAP connection, the LDAP version will be set to 3 using
1113 * ldap_set_option().
1114 *
1115 * The LDAP connection is bound before the ADS handle is returned.
1116 * Parameters:
1117 * domain - fully-qualified domain name
1118 * user - the user account for whom the Kerberos TGT ticket and ADS
1119 * service tickets are acquired.
1120 * password - password of the specified user
1121 *
1122 * Returns:
1123 * NULL : can't connect to ADS server or other errors
1124 * smb_ads_handle_t* : handle to ADS server
1125 */
1126 static smb_ads_handle_t *
smb_ads_open_main(char * domain,char * user,char * password)1127 smb_ads_open_main(char *domain, char *user, char *password)
1128 {
1129 smb_ads_handle_t *ah;
1130 LDAP *ld;
1131 int version = 3;
1132 smb_ads_host_info_t *ads_host = NULL;
1133 int rc;
1134
1135 if (user != NULL) {
1136 if (smb_kinit(user, password) == 0)
1137 return (NULL);
1138 user = NULL;
1139 password = NULL;
1140 }
1141
1142 ads_host = smb_ads_find_host(domain, NULL);
1143 if (ads_host == NULL)
1144 return (NULL);
1145
1146 ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
1147 if (ah == NULL) {
1148 free(ads_host);
1149 return (NULL);
1150 }
1151
1152 (void) memset(ah, 0, sizeof (smb_ads_handle_t));
1153
1154 if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) {
1155 smb_ads_free_cached_host();
1156 free(ah);
1157 free(ads_host);
1158 return (NULL);
1159 }
1160
1161 if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version)
1162 != LDAP_SUCCESS) {
1163 smb_ads_free_cached_host();
1164 free(ah);
1165 free(ads_host);
1166 (void) ldap_unbind(ld);
1167 return (NULL);
1168 }
1169
1170 (void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
1171 ah->ld = ld;
1172 ah->domain = strdup(domain);
1173
1174 if (ah->domain == NULL) {
1175 smb_ads_close(ah);
1176 free(ads_host);
1177 return (NULL);
1178 }
1179
1180 /*
1181 * ah->domain is often used for generating service principal name.
1182 * Convert it to lower case for RFC 4120 section 6.2.1 conformance.
1183 */
1184 (void) smb_strlwr(ah->domain);
1185 ah->domain_dn = smb_ads_convert_domain(domain);
1186 if (ah->domain_dn == NULL) {
1187 smb_ads_close(ah);
1188 free(ads_host);
1189 return (NULL);
1190 }
1191
1192 ah->hostname = strdup(ads_host->name);
1193 if (ah->hostname == NULL) {
1194 smb_ads_close(ah);
1195 free(ads_host);
1196 return (NULL);
1197 }
1198 (void) mutex_lock(&smb_ads_cfg.c_mtx);
1199 if (*smb_ads_cfg.c_site != '\0') {
1200 if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) {
1201 smb_ads_close(ah);
1202 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
1203 free(ads_host);
1204 return (NULL);
1205 }
1206 } else {
1207 ah->site = NULL;
1208 }
1209 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
1210
1211 rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL,
1212 LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL);
1213 if (rc != LDAP_SUCCESS) {
1214 syslog(LOG_ERR, "ldal_sasl_interactive_bind_s failed (%s)",
1215 ldap_err2string(rc));
1216 smb_ads_close(ah);
1217 free(ads_host);
1218 return (NULL);
1219 }
1220
1221 free(ads_host);
1222 return (ah);
1223 }
1224
1225 /*
1226 * smb_ads_close
1227 * Close connection to ADS server and free memory allocated for ADS handle.
1228 * LDAP unbind is called here.
1229 * Parameters:
1230 * ah: handle to ADS server
1231 * Returns:
1232 * void
1233 */
1234 void
smb_ads_close(smb_ads_handle_t * ah)1235 smb_ads_close(smb_ads_handle_t *ah)
1236 {
1237 if (ah == NULL)
1238 return;
1239 /* close and free connection resources */
1240 if (ah->ld)
1241 (void) ldap_unbind(ah->ld);
1242
1243 free(ah->domain);
1244 free(ah->domain_dn);
1245 free(ah->hostname);
1246 free(ah->site);
1247 free(ah);
1248 }
1249
1250 /*
1251 * smb_ads_alloc_attr
1252 *
1253 * Since the attrs is a null-terminated array, all elements
1254 * in the array (except the last one) will point to allocated
1255 * memory.
1256 */
1257 static int
smb_ads_alloc_attr(LDAPMod * attrs[],int num)1258 smb_ads_alloc_attr(LDAPMod *attrs[], int num)
1259 {
1260 int i;
1261
1262 bzero(attrs, num * sizeof (LDAPMod *));
1263 for (i = 0; i < (num - 1); i++) {
1264 attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod));
1265 if (attrs[i] == NULL) {
1266 smb_ads_free_attr(attrs);
1267 return (-1);
1268 }
1269 }
1270
1271 return (0);
1272 }
1273
1274 /*
1275 * smb_ads_free_attr
1276 * Free memory allocated when publishing a share.
1277 * Parameters:
1278 * attrs: an array of LDAPMod pointers
1279 * Returns:
1280 * None
1281 */
1282 static void
smb_ads_free_attr(LDAPMod * attrs[])1283 smb_ads_free_attr(LDAPMod *attrs[])
1284 {
1285 int i;
1286 for (i = 0; attrs[i]; i++) {
1287 free(attrs[i]);
1288 }
1289 }
1290
1291 /*
1292 * Returns share DN in an allocated buffer. The format of the DN is
1293 * cn=<sharename>,<container RDNs>,<domain DN>
1294 *
1295 * If the domain DN is not included in the container parameter,
1296 * then it will be appended to create the share DN.
1297 *
1298 * The caller must free the allocated buffer.
1299 */
1300 static char *
smb_ads_get_sharedn(const char * sharename,const char * container,const char * domain_dn)1301 smb_ads_get_sharedn(const char *sharename, const char *container,
1302 const char *domain_dn)
1303 {
1304 char *share_dn;
1305 int rc, offset, container_len, domain_len;
1306 boolean_t append_domain = B_TRUE;
1307
1308 container_len = strlen(container);
1309 domain_len = strlen(domain_dn);
1310
1311 if (container_len >= domain_len) {
1312
1313 /* offset to last domain_len characters */
1314 offset = container_len - domain_len;
1315
1316 if (smb_strcasecmp(container + offset,
1317 domain_dn, domain_len) == 0)
1318 append_domain = B_FALSE;
1319 }
1320
1321 if (append_domain)
1322 rc = asprintf(&share_dn, "cn=%s,%s,%s", sharename,
1323 container, domain_dn);
1324 else
1325 rc = asprintf(&share_dn, "cn=%s,%s", sharename,
1326 container);
1327
1328 return ((rc == -1) ? NULL : share_dn);
1329 }
1330
1331 /*
1332 * smb_ads_add_share
1333 * Call by smb_ads_publish_share to create share object in ADS.
1334 * This routine specifies the attributes of an ADS LDAP share object. The first
1335 * attribute and values define the type of ADS object, the share object. The
1336 * second attribute and value define the UNC of the share data for the share
1337 * object. The LDAP synchronous add command is used to add the object into ADS.
1338 * The container location to add the object needs to specified.
1339 * Parameters:
1340 * ah : handle to ADS server
1341 * adsShareName: name of share object to be created in ADS
1342 * shareUNC : share name on NetForce
1343 * adsContainer: location in ADS to create share object
1344 *
1345 * Returns:
1346 * -1 : error
1347 * 0 : success
1348 */
1349 int
smb_ads_add_share(smb_ads_handle_t * ah,const char * adsShareName,const char * unc_name,const char * adsContainer)1350 smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName,
1351 const char *unc_name, const char *adsContainer)
1352 {
1353 LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR];
1354 int j = 0;
1355 char *share_dn;
1356 int ret;
1357 char *unc_names[] = {(char *)unc_name, NULL};
1358
1359 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1360 ah->domain_dn)) == NULL)
1361 return (-1);
1362
1363 if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) {
1364 free(share_dn);
1365 return (-1);
1366 }
1367
1368 attrs[j]->mod_op = LDAP_MOD_ADD;
1369 attrs[j]->mod_type = "objectClass";
1370 attrs[j]->mod_values = smb_ads_share_objcls;
1371
1372 attrs[++j]->mod_op = LDAP_MOD_ADD;
1373 attrs[j]->mod_type = "uNCName";
1374 attrs[j]->mod_values = unc_names;
1375
1376 if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) {
1377 if (ret == LDAP_NO_SUCH_OBJECT) {
1378 syslog(LOG_ERR, "Failed to publish share %s in" \
1379 " AD. Container does not exist: %s.\n",
1380 adsShareName, share_dn);
1381
1382 } else {
1383 syslog(LOG_ERR, "Failed to publish share %s in" \
1384 " AD: %s (%s).\n", adsShareName, share_dn,
1385 ldap_err2string(ret));
1386 }
1387 smb_ads_free_attr(attrs);
1388 free(share_dn);
1389 return (ret);
1390 }
1391 free(share_dn);
1392 smb_ads_free_attr(attrs);
1393
1394 return (0);
1395 }
1396
1397 /*
1398 * smb_ads_del_share
1399 * Call by smb_ads_remove_share to remove share object from ADS. The container
1400 * location to remove the object needs to specified. The LDAP synchronous
1401 * delete command is used.
1402 * Parameters:
1403 * ah : handle to ADS server
1404 * adsShareName: name of share object in ADS to be removed
1405 * adsContainer: location of share object in ADS
1406 * Returns:
1407 * -1 : error
1408 * 0 : success
1409 */
1410 static int
smb_ads_del_share(smb_ads_handle_t * ah,const char * adsShareName,const char * adsContainer)1411 smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName,
1412 const char *adsContainer)
1413 {
1414 char *share_dn;
1415 int ret;
1416
1417 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1418 ah->domain_dn)) == NULL)
1419 return (-1);
1420
1421 if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) {
1422 smb_tracef("ldap_delete: %s", ldap_err2string(ret));
1423 free(share_dn);
1424 return (-1);
1425 }
1426 free(share_dn);
1427
1428 return (0);
1429 }
1430
1431
1432 /*
1433 * smb_ads_escape_search_filter_chars
1434 *
1435 * This routine will escape the special characters found in a string
1436 * that will later be passed to the ldap search filter.
1437 *
1438 * RFC 1960 - A String Representation of LDAP Search Filters
1439 * 3. String Search Filter Definition
1440 * If a value must contain one of the characters '*' OR '(' OR ')',
1441 * these characters
1442 * should be escaped by preceding them with the backslash '\' character.
1443 *
1444 * RFC 2252 - LDAP Attribute Syntax Definitions
1445 * a backslash quoting mechanism is used to escape
1446 * the following separator symbol character (such as "'", "$" or "#") if
1447 * it should occur in that string.
1448 */
1449 static int
smb_ads_escape_search_filter_chars(const char * src,char * dst)1450 smb_ads_escape_search_filter_chars(const char *src, char *dst)
1451 {
1452 int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */
1453
1454 if (src == NULL || dst == NULL)
1455 return (-1);
1456
1457 while (*src) {
1458 if (!avail) {
1459 *dst = 0;
1460 return (-1);
1461 }
1462
1463 switch (*src) {
1464 case '\\':
1465 case '\'':
1466 case '$':
1467 case '#':
1468 case '*':
1469 case '(':
1470 case ')':
1471 *dst++ = '\\';
1472 avail--;
1473 /* fall through */
1474
1475 default:
1476 *dst++ = *src++;
1477 avail--;
1478 }
1479 }
1480
1481 *dst = 0;
1482
1483 return (0);
1484 }
1485
1486 /*
1487 * smb_ads_lookup_share
1488 * The search filter is set to search for a specific share name in the
1489 * specified ADS container. The LDSAP synchronous search command is used.
1490 * Parameters:
1491 * ah : handle to ADS server
1492 * adsShareName: name of share object in ADS to be searched
1493 * adsContainer: location of share object in ADS
1494 * Returns:
1495 * -1 : error
1496 * 0 : not found
1497 * 1 : found
1498 */
1499 int
smb_ads_lookup_share(smb_ads_handle_t * ah,const char * adsShareName,const char * adsContainer,char * unc_name)1500 smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName,
1501 const char *adsContainer, char *unc_name)
1502 {
1503 char *attrs[4], filter[SMB_ADS_MAXBUFLEN];
1504 char *share_dn;
1505 int ret;
1506 LDAPMessage *res;
1507 char tmpbuf[SMB_ADS_MAXBUFLEN];
1508
1509 if (adsShareName == NULL || adsContainer == NULL)
1510 return (-1);
1511
1512 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1513 ah->domain_dn)) == NULL)
1514 return (-1);
1515
1516 res = NULL;
1517 attrs[0] = "cn";
1518 attrs[1] = "objectClass";
1519 attrs[2] = "uNCName";
1520 attrs[3] = NULL;
1521
1522 if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) {
1523 free(share_dn);
1524 return (-1);
1525 }
1526
1527 (void) snprintf(filter, sizeof (filter),
1528 "(&(objectClass=volume)(uNCName=%s))", tmpbuf);
1529
1530 if ((ret = ldap_search_s(ah->ld, share_dn,
1531 LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) {
1532 if (ret != LDAP_NO_SUCH_OBJECT)
1533 smb_tracef("%s: ldap_search: %s", share_dn,
1534 ldap_err2string(ret));
1535
1536 (void) ldap_msgfree(res);
1537 free(share_dn);
1538 return (0);
1539 }
1540
1541 (void) free(share_dn);
1542
1543 /* no match is found */
1544 if (ldap_count_entries(ah->ld, res) == 0) {
1545 (void) ldap_msgfree(res);
1546 return (0);
1547 }
1548
1549 /* free the search results */
1550 (void) ldap_msgfree(res);
1551
1552 return (1);
1553 }
1554
1555 /*
1556 * smb_ads_publish_share
1557 * Publish share into ADS. If a share name already exist in ADS in the same
1558 * container then the existing share object is removed before adding the new
1559 * share object.
1560 * Parameters:
1561 * ah : handle return from smb_ads_open
1562 * adsShareName: name of share to be added to ADS directory
1563 * shareUNC : name of share on client, can be NULL to use the same name
1564 * as adsShareName
1565 * adsContainer: location for share to be added in ADS directory, ie
1566 * ou=share_folder
1567 * uncType : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR
1568 * to use host ip addr for UNC.
1569 * Returns:
1570 * -1 : error
1571 * 0 : success
1572 */
1573 int
smb_ads_publish_share(smb_ads_handle_t * ah,const char * adsShareName,const char * shareUNC,const char * adsContainer,const char * hostname)1574 smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName,
1575 const char *shareUNC, const char *adsContainer, const char *hostname)
1576 {
1577 int ret;
1578 char unc_name[SMB_ADS_MAXBUFLEN];
1579
1580 if (adsShareName == NULL || adsContainer == NULL)
1581 return (-1);
1582
1583 if (shareUNC == 0 || *shareUNC == 0)
1584 shareUNC = adsShareName;
1585
1586 if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1587 hostname, shareUNC) < 0)
1588 return (-1);
1589
1590 ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1591
1592 switch (ret) {
1593 case 1:
1594 (void) smb_ads_del_share(ah, adsShareName, adsContainer);
1595 ret = smb_ads_add_share(ah, adsShareName, unc_name,
1596 adsContainer);
1597 break;
1598
1599 case 0:
1600 ret = smb_ads_add_share(ah, adsShareName, unc_name,
1601 adsContainer);
1602 if (ret == LDAP_ALREADY_EXISTS)
1603 ret = -1;
1604
1605 break;
1606
1607 case -1:
1608 default:
1609 /* return with error code */
1610 ret = -1;
1611 }
1612
1613 return (ret);
1614 }
1615
1616 /*
1617 * smb_ads_remove_share
1618 * Remove share from ADS. A search is done first before explicitly removing
1619 * the share.
1620 * Parameters:
1621 * ah : handle return from smb_ads_open
1622 * adsShareName: name of share to be removed from ADS directory
1623 * adsContainer: location for share to be removed from ADS directory, ie
1624 * ou=share_folder
1625 * Returns:
1626 * -1 : error
1627 * 0 : success
1628 */
1629 int
smb_ads_remove_share(smb_ads_handle_t * ah,const char * adsShareName,const char * shareUNC,const char * adsContainer,const char * hostname)1630 smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName,
1631 const char *shareUNC, const char *adsContainer, const char *hostname)
1632 {
1633 int ret;
1634 char unc_name[SMB_ADS_MAXBUFLEN];
1635
1636 if (adsShareName == NULL || adsContainer == NULL)
1637 return (-1);
1638 if (shareUNC == 0 || *shareUNC == 0)
1639 shareUNC = adsShareName;
1640
1641 if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1642 hostname, shareUNC) < 0)
1643 return (-1);
1644
1645 ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1646 if (ret == 0)
1647 return (0);
1648 if (ret == -1)
1649 return (-1);
1650
1651 return (smb_ads_del_share(ah, adsShareName, adsContainer));
1652 }
1653
1654 /*
1655 * smb_ads_get_default_comp_container_dn
1656 *
1657 * Build the distinguished name for the default computer conatiner (i.e. the
1658 * pre-defined Computers container).
1659 */
1660 static void
smb_ads_get_default_comp_container_dn(smb_ads_handle_t * ah,char * buf,size_t buflen)1661 smb_ads_get_default_comp_container_dn(smb_ads_handle_t *ah, char *buf,
1662 size_t buflen)
1663 {
1664 (void) snprintf(buf, buflen, "cn=%s,%s", SMB_ADS_COMPUTERS_CN,
1665 ah->domain_dn);
1666 }
1667
1668 /*
1669 * smb_ads_get_default_comp_dn
1670 *
1671 * Build the distinguished name for this system.
1672 */
1673 static void
smb_ads_get_default_comp_dn(smb_ads_handle_t * ah,char * buf,size_t buflen)1674 smb_ads_get_default_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen)
1675 {
1676 char nbname[NETBIOS_NAME_SZ];
1677 char container_dn[SMB_ADS_DN_MAX];
1678
1679 (void) smb_getnetbiosname(nbname, sizeof (nbname));
1680 smb_ads_get_default_comp_container_dn(ah, container_dn, SMB_ADS_DN_MAX);
1681 (void) snprintf(buf, buflen, "cn=%s,%s", nbname, container_dn);
1682 }
1683
1684 /*
1685 * smb_ads_add_computer
1686 *
1687 * Returns 0 upon success. Otherwise, returns -1.
1688 */
1689 static int
smb_ads_add_computer(smb_ads_handle_t * ah,int dclevel,char * dn)1690 smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1691 {
1692 return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn));
1693 }
1694
1695 /*
1696 * smb_ads_modify_computer
1697 *
1698 * Returns 0 upon success. Otherwise, returns -1.
1699 */
1700 static int
smb_ads_modify_computer(smb_ads_handle_t * ah,int dclevel,char * dn)1701 smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1702 {
1703 return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn));
1704 }
1705
1706 /*
1707 * smb_ads_get_dc_level
1708 *
1709 * Returns the functional level of the DC upon success.
1710 * Otherwise, -1 is returned.
1711 */
1712 static int
smb_ads_get_dc_level(smb_ads_handle_t * ah)1713 smb_ads_get_dc_level(smb_ads_handle_t *ah)
1714 {
1715 LDAPMessage *res, *entry;
1716 char *attr[2];
1717 char **vals;
1718 int rc = -1;
1719
1720 res = NULL;
1721 attr[0] = SMB_ADS_ATTR_DCLEVEL;
1722 attr[1] = NULL;
1723 if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr,
1724 0, &res) != LDAP_SUCCESS) {
1725 (void) ldap_msgfree(res);
1726 return (-1);
1727 }
1728
1729 /* no match for the specified attribute is found */
1730 if (ldap_count_entries(ah->ld, res) == 0) {
1731 (void) ldap_msgfree(res);
1732 return (-1);
1733 }
1734
1735 entry = ldap_first_entry(ah->ld, res);
1736 if (entry) {
1737 if ((vals = ldap_get_values(ah->ld, entry,
1738 SMB_ADS_ATTR_DCLEVEL)) == NULL) {
1739 /*
1740 * Observed the values aren't populated
1741 * by the Windows 2000 server.
1742 */
1743 (void) ldap_msgfree(res);
1744 return (SMB_ADS_DCLEVEL_W2K);
1745 }
1746
1747 if (vals[0] != NULL)
1748 rc = atoi(vals[0]);
1749
1750 ldap_value_free(vals);
1751 }
1752
1753 (void) ldap_msgfree(res);
1754 return (rc);
1755 }
1756
1757 /*
1758 * The fully-qualified hostname returned by this function is often used for
1759 * constructing service principal name. Return the fully-qualified hostname
1760 * in lower case for RFC 4120 section 6.2.1 conformance.
1761 */
1762 static int
smb_ads_getfqhostname(smb_ads_handle_t * ah,char * fqhost,int len)1763 smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len)
1764 {
1765 if (smb_gethostname(fqhost, len, SMB_CASE_LOWER) != 0)
1766 return (-1);
1767
1768 (void) snprintf(fqhost, len, "%s.%s", fqhost,
1769 ah->domain);
1770
1771 return (0);
1772 }
1773
1774 static int
smb_ads_computer_op(smb_ads_handle_t * ah,int op,int dclevel,char * dn)1775 smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
1776 {
1777 LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR];
1778 char *sam_val[2];
1779 char *ctl_val[2], *fqh_val[2];
1780 char *encrypt_val[2];
1781 int j = -1;
1782 int ret, usrctl_flags = 0;
1783 char sam_acct[SMB_SAMACCT_MAXLEN];
1784 char fqhost[MAXHOSTNAMELEN];
1785 char usrctl_buf[16];
1786 char encrypt_buf[16];
1787 int max;
1788 smb_krb5_pn_set_t spn, upn;
1789
1790 if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1791 return (-1);
1792
1793 if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1794 return (-1);
1795
1796 /* The SPN attribute is multi-valued and must be 1 or greater */
1797 if (smb_krb5_get_pn_set(&spn, SMB_PN_SPN_ATTR, ah->domain) == 0)
1798 return (-1);
1799
1800 /* The UPN attribute is single-valued and cannot be zero */
1801 if (smb_krb5_get_pn_set(&upn, SMB_PN_UPN_ATTR, ah->domain) != 1) {
1802 smb_krb5_free_pn_set(&spn);
1803 smb_krb5_free_pn_set(&upn);
1804 return (-1);
1805 }
1806
1807 max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0))
1808 - (dclevel >= SMB_ADS_DCLEVEL_W2K8 ? 0 : 1);
1809
1810 if (smb_ads_alloc_attr(attrs, max) != 0) {
1811 smb_krb5_free_pn_set(&spn);
1812 smb_krb5_free_pn_set(&upn);
1813 return (-1);
1814 }
1815
1816 /* objectClass attribute is not modifiable. */
1817 if (op == LDAP_MOD_ADD) {
1818 attrs[++j]->mod_op = op;
1819 attrs[j]->mod_type = "objectClass";
1820 attrs[j]->mod_values = smb_ads_computer_objcls;
1821 }
1822
1823 attrs[++j]->mod_op = op;
1824 attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT;
1825 sam_val[0] = sam_acct;
1826 sam_val[1] = 0;
1827 attrs[j]->mod_values = sam_val;
1828
1829 attrs[++j]->mod_op = op;
1830 attrs[j]->mod_type = SMB_ADS_ATTR_UPN;
1831 attrs[j]->mod_values = upn.s_pns;
1832
1833 attrs[++j]->mod_op = op;
1834 attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
1835 attrs[j]->mod_values = spn.s_pns;
1836
1837 attrs[++j]->mod_op = op;
1838 attrs[j]->mod_type = SMB_ADS_ATTR_CTL;
1839 usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1840 SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD |
1841 SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE);
1842 (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
1843 ctl_val[0] = usrctl_buf;
1844 ctl_val[1] = 0;
1845 attrs[j]->mod_values = ctl_val;
1846
1847 attrs[++j]->mod_op = op;
1848 attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST;
1849 fqh_val[0] = fqhost;
1850 fqh_val[1] = 0;
1851 attrs[j]->mod_values = fqh_val;
1852
1853 /* enctypes support starting in Windows Server 2008 */
1854 if (dclevel > SMB_ADS_DCLEVEL_W2K3) {
1855 attrs[++j]->mod_op = op;
1856 attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES;
1857 (void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d",
1858 SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 +
1859 SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC);
1860 encrypt_val[0] = encrypt_buf;
1861 encrypt_val[1] = 0;
1862 attrs[j]->mod_values = encrypt_val;
1863 }
1864
1865 switch (op) {
1866 case LDAP_MOD_ADD:
1867 if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1868 syslog(LOG_NOTICE, "ldap_add: %s",
1869 ldap_err2string(ret));
1870 ret = -1;
1871 }
1872 break;
1873
1874 case LDAP_MOD_REPLACE:
1875 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1876 syslog(LOG_NOTICE, "ldap_modify: %s",
1877 ldap_err2string(ret));
1878 ret = -1;
1879 }
1880 break;
1881
1882 default:
1883 ret = -1;
1884
1885 }
1886
1887 smb_ads_free_attr(attrs);
1888 smb_krb5_free_pn_set(&spn);
1889 smb_krb5_free_pn_set(&upn);
1890
1891 return (ret);
1892 }
1893
1894 /*
1895 * Delete an ADS computer account.
1896 */
1897 static void
smb_ads_del_computer(smb_ads_handle_t * ah,char * dn)1898 smb_ads_del_computer(smb_ads_handle_t *ah, char *dn)
1899 {
1900 int rc;
1901
1902 if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS)
1903 smb_tracef("ldap_delete: %s", ldap_err2string(rc));
1904 }
1905
1906 /*
1907 * Gets the value of the given attribute.
1908 */
1909 static smb_ads_qstat_t
smb_ads_getattr(LDAP * ld,LDAPMessage * entry,smb_ads_avpair_t * avpair)1910 smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair)
1911 {
1912 char **vals;
1913 smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1914
1915 assert(avpair);
1916 avpair->avp_val = NULL;
1917 vals = ldap_get_values(ld, entry, avpair->avp_attr);
1918 if (!vals)
1919 return (SMB_ADS_STAT_NOT_FOUND);
1920
1921 if (!vals[0]) {
1922 ldap_value_free(vals);
1923 return (SMB_ADS_STAT_NOT_FOUND);
1924 }
1925
1926 avpair->avp_val = strdup(vals[0]);
1927 if (!avpair->avp_val)
1928 rc = SMB_ADS_STAT_ERR;
1929
1930 ldap_value_free(vals);
1931 return (rc);
1932 }
1933
1934 /*
1935 * Process query's result.
1936 */
1937 static smb_ads_qstat_t
smb_ads_get_qstat(smb_ads_handle_t * ah,LDAPMessage * res,smb_ads_avpair_t * avpair)1938 smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
1939 smb_ads_avpair_t *avpair)
1940 {
1941 char fqhost[MAXHOSTNAMELEN];
1942 smb_ads_avpair_t dnshost_avp;
1943 smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1944 LDAPMessage *entry;
1945
1946 if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1947 return (SMB_ADS_STAT_ERR);
1948
1949 if (ldap_count_entries(ah->ld, res) == 0)
1950 return (SMB_ADS_STAT_NOT_FOUND);
1951
1952 if ((entry = ldap_first_entry(ah->ld, res)) == NULL)
1953 return (SMB_ADS_STAT_ERR);
1954
1955 dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
1956 rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
1957
1958 switch (rc) {
1959 case SMB_ADS_STAT_FOUND:
1960 /*
1961 * Returns SMB_ADS_STAT_DUP to avoid overwriting
1962 * the computer account of another system whose
1963 * NetBIOS name collides with that of the current
1964 * system.
1965 */
1966 if (strcasecmp(dnshost_avp.avp_val, fqhost))
1967 rc = SMB_ADS_STAT_DUP;
1968
1969 free(dnshost_avp.avp_val);
1970 break;
1971
1972 case SMB_ADS_STAT_NOT_FOUND:
1973 /*
1974 * Pre-created computer account doesn't have
1975 * the dNSHostname attribute. It's been observed
1976 * that the dNSHostname attribute is only set after
1977 * a successful domain join.
1978 * Returns SMB_ADS_STAT_FOUND as the account is
1979 * pre-created for the current system.
1980 */
1981 rc = SMB_ADS_STAT_FOUND;
1982 break;
1983
1984 default:
1985 break;
1986 }
1987
1988 if (rc != SMB_ADS_STAT_FOUND)
1989 return (rc);
1990
1991 if (avpair)
1992 rc = smb_ads_getattr(ah->ld, entry, avpair);
1993
1994 return (rc);
1995
1996 }
1997
1998 /*
1999 * smb_ads_lookup_computer_n_attr
2000 *
2001 * If avpair is NULL, checks the status of the specified computer account.
2002 * Otherwise, looks up the value of the specified computer account's attribute.
2003 * If found, the value field of the avpair will be allocated and set. The
2004 * caller should free the allocated buffer.
2005 *
2006 * Return:
2007 * SMB_ADS_STAT_FOUND - if both the computer and the specified attribute is
2008 * found.
2009 * SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute
2010 * is not found.
2011 * SMB_ADS_STAT_DUP - if the computer account is already used by other systems
2012 * in the AD. This could happen if the hostname of multiple
2013 * systems resolved to the same NetBIOS name.
2014 * SMB_ADS_STAT_ERR - any failure.
2015 */
2016 static smb_ads_qstat_t
smb_ads_lookup_computer_n_attr(smb_ads_handle_t * ah,smb_ads_avpair_t * avpair,int scope,char * dn)2017 smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair,
2018 int scope, char *dn)
2019 {
2020 char *attrs[3], filter[SMB_ADS_MAXBUFLEN];
2021 LDAPMessage *res;
2022 char sam_acct[SMB_SAMACCT_MAXLEN], sam_acct2[SMB_SAMACCT_MAXLEN];
2023 smb_ads_qstat_t rc;
2024
2025 if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
2026 return (SMB_ADS_STAT_ERR);
2027
2028 res = NULL;
2029 attrs[0] = SMB_ADS_ATTR_DNSHOST;
2030 attrs[1] = NULL;
2031 attrs[2] = NULL;
2032
2033 if (avpair) {
2034 if (!avpair->avp_attr)
2035 return (SMB_ADS_STAT_ERR);
2036
2037 attrs[1] = avpair->avp_attr;
2038 }
2039
2040 if (smb_ads_escape_search_filter_chars(sam_acct, sam_acct2) != 0)
2041 return (SMB_ADS_STAT_ERR);
2042
2043 (void) snprintf(filter, sizeof (filter),
2044 "(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT,
2045 sam_acct2);
2046
2047 if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0,
2048 &res) != LDAP_SUCCESS) {
2049 (void) ldap_msgfree(res);
2050 return (SMB_ADS_STAT_NOT_FOUND);
2051 }
2052
2053 rc = smb_ads_get_qstat(ah, res, avpair);
2054 /* free the search results */
2055 (void) ldap_msgfree(res);
2056 return (rc);
2057 }
2058
2059 /*
2060 * smb_ads_find_computer
2061 *
2062 * Starts by searching for the system's AD computer object in the default
2063 * container (i.e. cn=Computers). If not found, searches the entire directory.
2064 * If found, 'dn' will be set to the distinguished name of the system's AD
2065 * computer object.
2066 */
2067 static smb_ads_qstat_t
smb_ads_find_computer(smb_ads_handle_t * ah,char * dn)2068 smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
2069 {
2070 smb_ads_qstat_t stat;
2071 smb_ads_avpair_t avpair;
2072
2073 avpair.avp_attr = SMB_ADS_ATTR_DN;
2074 smb_ads_get_default_comp_container_dn(ah, dn, SMB_ADS_DN_MAX);
2075 stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_ONELEVEL,
2076 dn);
2077
2078 if (stat == SMB_ADS_STAT_NOT_FOUND) {
2079 (void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX);
2080 stat = smb_ads_lookup_computer_n_attr(ah, &avpair,
2081 LDAP_SCOPE_SUBTREE, dn);
2082 }
2083
2084 if (stat == SMB_ADS_STAT_FOUND) {
2085 (void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX);
2086 free(avpair.avp_val);
2087 }
2088
2089 return (stat);
2090 }
2091
2092 /*
2093 * smb_ads_update_computer_cntrl_attr
2094 *
2095 * Modify the user account control attribute of an existing computer
2096 * object on AD.
2097 *
2098 * Returns LDAP error code.
2099 */
2100 static int
smb_ads_update_computer_cntrl_attr(smb_ads_handle_t * ah,int flags,char * dn)2101 smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn)
2102 {
2103 LDAPMod *attrs[2];
2104 char *ctl_val[2];
2105 int ret = 0;
2106 char usrctl_buf[16];
2107
2108 if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
2109 return (LDAP_NO_MEMORY);
2110
2111 attrs[0]->mod_op = LDAP_MOD_REPLACE;
2112 attrs[0]->mod_type = SMB_ADS_ATTR_CTL;
2113
2114 (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags);
2115 ctl_val[0] = usrctl_buf;
2116 ctl_val[1] = 0;
2117 attrs[0]->mod_values = ctl_val;
2118 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
2119 syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
2120 }
2121
2122 smb_ads_free_attr(attrs);
2123 return (ret);
2124 }
2125
2126 /*
2127 * smb_ads_lookup_computer_attr_kvno
2128 *
2129 * Lookup the value of the Kerberos version number attribute of the computer
2130 * account.
2131 */
2132 static krb5_kvno
smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t * ah,char * dn)2133 smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
2134 {
2135 smb_ads_avpair_t avpair;
2136 int kvno = 1;
2137
2138 avpair.avp_attr = SMB_ADS_ATTR_KVNO;
2139 if (smb_ads_lookup_computer_n_attr(ah, &avpair,
2140 LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) {
2141 kvno = atoi(avpair.avp_val);
2142 free(avpair.avp_val);
2143 }
2144
2145 return (kvno);
2146 }
2147
2148 static int
smb_ads_gen_machine_passwd(char * machine_passwd,size_t bufsz)2149 smb_ads_gen_machine_passwd(char *machine_passwd, size_t bufsz)
2150 {
2151 int i;
2152 size_t pwdlen;
2153 uint8_t *random_bytes;
2154
2155 errno = 0;
2156 if (machine_passwd == NULL || bufsz == 0) {
2157 errno = EINVAL;
2158 return (-1);
2159 }
2160
2161 pwdlen = bufsz - 1;
2162 random_bytes = calloc(1, pwdlen);
2163 if (random_bytes == NULL)
2164 return (-1);
2165
2166 if (pkcs11_get_random(random_bytes, pwdlen) != 0) {
2167 free(random_bytes);
2168 return (-1);
2169 }
2170
2171 for (i = 0; i < pwdlen; i++)
2172 machine_passwd[i] = (random_bytes[i] % SMB_ADS_PWD_CHAR_NUM) +
2173 SMB_ADS_PWD_CHAR_START;
2174
2175 machine_passwd[pwdlen] = 0;
2176 bzero(random_bytes, pwdlen);
2177 free(random_bytes);
2178 return (0);
2179 }
2180
2181 /*
2182 * smb_ads_join
2183 *
2184 * Besides the NT-4 style domain join (using MS-RPC), CIFS server also
2185 * provides the domain join using Kerberos Authentication, Keberos
2186 * Change & Set password, and LDAP protocols. Basically, AD join
2187 * operation would require the following tickets to be acquired for the
2188 * the user account that is provided for the domain join.
2189 *
2190 * 1) a Keberos TGT ticket,
2191 * 2) a ldap service ticket, and
2192 * 3) kadmin/changpw service ticket
2193 *
2194 * The ADS client first sends a ldap search request to find out whether
2195 * or not the workstation trust account already exists in the Active Directory.
2196 * The existing computer object for this workstation will be removed and
2197 * a new one will be added. The machine account password is randomly
2198 * generated and set for the newly created computer object using KPASSWD
2199 * protocol (See RFC 3244). Once the password is set, our ADS client
2200 * finalizes the machine account by modifying the user acount control
2201 * attribute of the computer object. Kerberos keys derived from the machine
2202 * account password will be stored locally in /etc/krb5/krb5.keytab file.
2203 * That would be needed while acquiring Kerberos TGT ticket for the host
2204 * principal after the domain join operation.
2205 */
2206 smb_adjoin_status_t
smb_ads_join(char * domain,char * user,char * usr_passwd,char * machine_passwd,size_t len)2207 smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd,
2208 size_t len)
2209 {
2210 smb_ads_handle_t *ah = NULL;
2211 krb5_context ctx = NULL;
2212 krb5_principal *krb5princs = NULL;
2213 krb5_kvno kvno;
2214 boolean_t des_only, delete = B_TRUE;
2215 smb_adjoin_status_t rc = SMB_ADJOIN_SUCCESS;
2216 boolean_t new_acct;
2217 int dclevel, num, usrctl_flags = 0;
2218 smb_ads_qstat_t qstat;
2219 char dn[SMB_ADS_DN_MAX];
2220 char *tmpfile;
2221 int cnt;
2222 smb_krb5_pn_set_t spns;
2223
2224 krb5_enctype *encptr;
2225
2226 if ((ah = smb_ads_open_main(domain, user, usr_passwd)) == NULL) {
2227 smb_ccache_remove(SMB_CCACHE_PATH);
2228 return (SMB_ADJOIN_ERR_GET_HANDLE);
2229 }
2230
2231 if (smb_ads_gen_machine_passwd(machine_passwd, len) != 0) {
2232 syslog(LOG_NOTICE, "machine password generation: %m");
2233 smb_ads_close(ah);
2234 smb_ccache_remove(SMB_CCACHE_PATH);
2235 return (SMB_ADJOIN_ERR_GEN_PWD);
2236 }
2237
2238 if ((dclevel = smb_ads_get_dc_level(ah)) == -1) {
2239 smb_ads_close(ah);
2240 smb_ccache_remove(SMB_CCACHE_PATH);
2241 return (SMB_ADJOIN_ERR_GET_DCLEVEL);
2242 }
2243
2244 qstat = smb_ads_find_computer(ah, dn);
2245 switch (qstat) {
2246 case SMB_ADS_STAT_FOUND:
2247 new_acct = B_FALSE;
2248 if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
2249 smb_ads_close(ah);
2250 smb_ccache_remove(SMB_CCACHE_PATH);
2251 return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT);
2252 }
2253 break;
2254
2255 case SMB_ADS_STAT_NOT_FOUND:
2256 new_acct = B_TRUE;
2257 smb_ads_get_default_comp_dn(ah, dn, SMB_ADS_DN_MAX);
2258 if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
2259 smb_ads_close(ah);
2260 smb_ccache_remove(SMB_CCACHE_PATH);
2261 return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT);
2262 }
2263 break;
2264
2265 default:
2266 if (qstat == SMB_ADS_STAT_DUP)
2267 rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT;
2268 else
2269 rc = SMB_ADJOIN_ERR_TRUST_ACCT;
2270 smb_ads_close(ah);
2271 smb_ccache_remove(SMB_CCACHE_PATH);
2272 return (rc);
2273 }
2274
2275 des_only = B_FALSE;
2276
2277 if (smb_krb5_ctx_init(&ctx) != 0) {
2278 rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
2279 goto adjoin_cleanup;
2280 }
2281
2282 if (smb_krb5_get_pn_set(&spns, SMB_PN_KEYTAB_ENTRY, ah->domain) == 0) {
2283 rc = SMB_ADJOIN_ERR_GET_SPNS;
2284 goto adjoin_cleanup;
2285 }
2286
2287 if (smb_krb5_get_kprincs(ctx, spns.s_pns, spns.s_cnt, &krb5princs)
2288 != 0) {
2289 smb_krb5_free_pn_set(&spns);
2290 rc = SMB_ADJOIN_ERR_GET_SPNS;
2291 goto adjoin_cleanup;
2292 }
2293
2294 cnt = spns.s_cnt;
2295 smb_krb5_free_pn_set(&spns);
2296
2297 if (smb_krb5_setpwd(ctx, ah->domain, machine_passwd) != 0) {
2298 rc = SMB_ADJOIN_ERR_KSETPWD;
2299 goto adjoin_cleanup;
2300 }
2301
2302 kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
2303
2304 /*
2305 * Only members of Domain Admins and Enterprise Admins can set
2306 * the TRUSTED_FOR_DELEGATION userAccountControl flag.
2307 */
2308 if (smb_ads_update_computer_cntrl_attr(ah,
2309 SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
2310 SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION, dn)
2311 == LDAP_INSUFFICIENT_ACCESS) {
2312 usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
2313 SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
2314
2315 syslog(LOG_NOTICE, "Unable to set the "
2316 "TRUSTED_FOR_DELEGATION userAccountControl flag on "
2317 "the machine account in Active Directory. Please refer "
2318 "to the Troubleshooting guide for more information.");
2319
2320 } else {
2321 usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
2322 SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION |
2323 SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
2324 }
2325
2326 if (des_only)
2327 usrctl_flags |= SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY;
2328
2329 if (smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn)
2330 != 0) {
2331 rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
2332 goto adjoin_cleanup;
2333 }
2334
2335 tmpfile = mktemp(SMBNS_KRB5_KEYTAB_TMP);
2336 if (tmpfile == NULL)
2337 tmpfile = SMBNS_KRB5_KEYTAB_TMP;
2338
2339 encptr = smb_ads_get_enctypes(dclevel, &num);
2340 if (smb_krb5_kt_populate(ctx, ah->domain, krb5princs, cnt,
2341 tmpfile, kvno, machine_passwd, encptr, num) != 0) {
2342 rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
2343 goto adjoin_cleanup;
2344 }
2345
2346 delete = B_FALSE;
2347 adjoin_cleanup:
2348 if (new_acct && delete)
2349 smb_ads_del_computer(ah, dn);
2350
2351 if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) {
2352 if (rc != SMB_ADJOIN_ERR_GET_SPNS)
2353 smb_krb5_free_kprincs(ctx, krb5princs, cnt);
2354 smb_krb5_ctx_fini(ctx);
2355 }
2356
2357 /* commit keytab file */
2358 if (rc == SMB_ADJOIN_SUCCESS) {
2359 if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) {
2360 (void) unlink(tmpfile);
2361 rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB;
2362 } else {
2363 /* Set IDMAP config */
2364 if (smb_config_set_idmap_domain(ah->domain) != 0) {
2365 rc = SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN;
2366 } else {
2367
2368 /* Refresh IDMAP service */
2369 if (smb_config_refresh_idmap() != 0)
2370 rc = SMB_ADJOIN_ERR_IDMAP_REFRESH;
2371 }
2372 }
2373 } else {
2374 (void) unlink(tmpfile);
2375 }
2376
2377 smb_ads_close(ah);
2378 smb_ccache_remove(SMB_CCACHE_PATH);
2379 return (rc);
2380 }
2381
2382 /*
2383 * smb_ads_join_errmsg
2384 *
2385 * Display error message for the specific adjoin error code.
2386 */
2387 void
smb_ads_join_errmsg(smb_adjoin_status_t status)2388 smb_ads_join_errmsg(smb_adjoin_status_t status)
2389 {
2390 int i;
2391 struct xlate_table {
2392 smb_adjoin_status_t status;
2393 char *msg;
2394 } adjoin_table[] = {
2395 { SMB_ADJOIN_ERR_GET_HANDLE, "Failed to connect to an "
2396 "Active Directory server." },
2397 { SMB_ADJOIN_ERR_GEN_PWD, "Failed to generate machine "
2398 "password." },
2399 { SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of "
2400 "the domain controller. The rootDSE attribute named "
2401 "\"domainControllerFunctionality\" is missing from the "
2402 "Active Directory." },
2403 { SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the "
2404 "workstation trust account." },
2405 { SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the "
2406 "workstation trust account." },
2407 { SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the "
2408 "workstation trust account because its name is already "
2409 "in use." },
2410 { SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the "
2411 "workstation trust account" },
2412 { SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos "
2413 "context." },
2414 { SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos "
2415 "principals." },
2416 { SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." },
2417 { SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR, "Failed to modify "
2418 "userAccountControl attribute of the workstation trust "
2419 "account." },
2420 { SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local "
2421 "keytab file (i.e /etc/krb5/krb5.keytab)." },
2422 { SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap "
2423 "configuration." },
2424 { SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap "
2425 "service." },
2426 { SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to "
2427 "local keytab file (i.e. /etc/krb5/krb5.keytab)." }
2428 };
2429
2430 for (i = 0; i < sizeof (adjoin_table) / sizeof (adjoin_table[0]); i++) {
2431 if (adjoin_table[i].status == status)
2432 syslog(LOG_NOTICE, "%s", adjoin_table[i].msg);
2433 }
2434 }
2435
2436 /*
2437 * smb_ads_match_pdc
2438 *
2439 * Returns B_TRUE if the given host's IP address matches the preferred DC's
2440 * IP address. Otherwise, returns B_FALSE.
2441 */
2442 static boolean_t
smb_ads_match_pdc(smb_ads_host_info_t * host)2443 smb_ads_match_pdc(smb_ads_host_info_t *host)
2444 {
2445 boolean_t match = B_FALSE;
2446
2447 if (!host)
2448 return (match);
2449
2450 (void) mutex_lock(&smb_ads_cfg.c_mtx);
2451 if (smb_inet_equal(&host->ipaddr, &smb_ads_cfg.c_pdc))
2452 match = B_TRUE;
2453 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
2454
2455 return (match);
2456 }
2457
2458 /*
2459 * smb_ads_select_dcfromsubnet
2460 *
2461 * This method walks the list of DCs and returns the first DC record that
2462 * responds to ldap ping and is in the same subnet as the host.
2463 *
2464 * Returns a pointer to the found DC record.
2465 * Returns NULL, on error or if no DC record is found.
2466 */
2467 static smb_ads_host_info_t *
smb_ads_select_dcfromsubnet(smb_ads_host_list_t * hlist)2468 smb_ads_select_dcfromsubnet(smb_ads_host_list_t *hlist)
2469 {
2470 smb_ads_host_info_t *hentry;
2471 smb_nic_t *lnic;
2472 smb_niciter_t ni;
2473 size_t cnt;
2474 int i;
2475
2476 if (smb_nic_getfirst(&ni) != SMB_NIC_SUCCESS)
2477 return (NULL);
2478 do {
2479 lnic = &ni.ni_nic;
2480 cnt = hlist->ah_cnt;
2481
2482 for (i = 0; i < cnt; i++) {
2483 hentry = &hlist->ah_list[i];
2484 if ((hentry->ipaddr.a_family == AF_INET) &&
2485 (lnic->nic_ip.a_family == AF_INET)) {
2486 if ((hentry->ipaddr.a_ipv4 &
2487 lnic->nic_mask) ==
2488 (lnic->nic_ip.a_ipv4 &
2489 lnic->nic_mask))
2490 if (smb_ads_ldap_ping(hentry) == 0)
2491 return (hentry);
2492 }
2493 }
2494 } while (smb_nic_getnext(&ni) == SMB_NIC_SUCCESS);
2495
2496 return (NULL);
2497 }
2498
2499 /*
2500 * smb_ads_select_dcfromlist
2501 *
2502 * This method walks the list of DCs and returns the first DC that
2503 * responds to ldap ping.
2504 *
2505 * Returns a pointer to the found DC record.
2506 * Returns NULL if no DC record is found.
2507 */
2508 static smb_ads_host_info_t *
smb_ads_select_dcfromlist(smb_ads_host_list_t * hlist)2509 smb_ads_select_dcfromlist(smb_ads_host_list_t *hlist)
2510 {
2511 smb_ads_host_info_t *hentry;
2512 size_t cnt;
2513 int i;
2514
2515 cnt = hlist->ah_cnt;
2516 for (i = 0; i < cnt; i++) {
2517 hentry = &hlist->ah_list[i];
2518 if (smb_ads_ldap_ping(hentry) == 0)
2519 return (hentry);
2520 }
2521
2522 return (NULL);
2523 }
2524
2525 /*
2526 * smb_ads_dc_compare
2527 *
2528 * Comparision function for sorting host entries (SRV records of DC) via qsort.
2529 * RFC 2052/2782 are taken as reference, while implementing this algorithm.
2530 *
2531 * Domain Controllers(DCs) with lowest priority in their SRV DNS records
2532 * are selected first. If they have equal priorities, then DC with highest
2533 * weight in its SRV DNS record is selected. If the priority and weight are
2534 * both equal, then the DC at the top of the list is selected.
2535 */
2536 static int
smb_ads_dc_compare(const void * p,const void * q)2537 smb_ads_dc_compare(const void *p, const void *q)
2538 {
2539 smb_ads_host_info_t *h1 = (smb_ads_host_info_t *)p;
2540 smb_ads_host_info_t *h2 = (smb_ads_host_info_t *)q;
2541
2542 if (h1->priority < h2->priority)
2543 return (-1);
2544 if (h1->priority > h2->priority)
2545 return (1);
2546
2547 /* Priorities are equal */
2548 if (h1->weight < h2->weight)
2549 return (1);
2550 if (h1->weight > h2->weight)
2551 return (-1);
2552
2553 return (0);
2554 }
2555
2556 /*
2557 * smb_ads_select_dc
2558 *
2559 * The list of ADS hosts returned by ADS lookup, is sorted by lowest priority
2560 * and highest weight. On this sorted list, following additional rules are
2561 * applied, to select a DC.
2562 *
2563 * - If there is a DC in the same subnet, then return the DC,
2564 * if it responds to ldap ping.
2565 * - Else, return first DC that responds to ldap ping.
2566 *
2567 * A reference to the host entry from input host list is returned.
2568 *
2569 * Returns NULL on error.
2570 */
2571 static smb_ads_host_info_t *
smb_ads_select_dc(smb_ads_host_list_t * hlist)2572 smb_ads_select_dc(smb_ads_host_list_t *hlist)
2573 {
2574 smb_ads_host_info_t *hentry = NULL;
2575
2576 if (hlist->ah_cnt == 0)
2577 return (NULL);
2578
2579 if (hlist->ah_cnt == 1) {
2580 hentry = hlist->ah_list;
2581 if (smb_ads_ldap_ping(hentry) == 0)
2582 return (hentry);
2583 }
2584
2585 /* Sort the list by priority and weight */
2586 qsort(hlist->ah_list, hlist->ah_cnt,
2587 sizeof (smb_ads_host_info_t), smb_ads_dc_compare);
2588
2589 if ((hentry = smb_ads_select_dcfromsubnet(hlist)) != NULL)
2590 return (hentry);
2591
2592 if ((hentry = smb_ads_select_dcfromlist(hlist)) != NULL)
2593 return (hentry);
2594
2595 return (NULL);
2596 }
2597
2598 /*
2599 * smb_ads_lookup_msdcs
2600 *
2601 * If server argument is set, try to locate the specified DC.
2602 * If it is set to empty string, locate any DCs in the specified domain.
2603 * Returns the discovered DC via buf.
2604 *
2605 * fqdn - fully-qualified domain name
2606 * server - fully-qualifed hostname of a DC
2607 * buf - the hostname of the discovered DC
2608 */
2609 boolean_t
smb_ads_lookup_msdcs(char * fqdn,char * server,char * buf,uint32_t buflen)2610 smb_ads_lookup_msdcs(char *fqdn, char *server, char *buf, uint32_t buflen)
2611 {
2612 smb_ads_host_info_t *hinfo = NULL;
2613 char *p;
2614 char *sought_host;
2615 char ipstr[INET6_ADDRSTRLEN];
2616
2617 if (!fqdn || !buf)
2618 return (B_FALSE);
2619
2620 ipstr[0] = '\0';
2621 *buf = '\0';
2622 sought_host = (*server == 0 ? NULL : server);
2623 if ((hinfo = smb_ads_find_host(fqdn, sought_host)) == NULL)
2624 return (B_FALSE);
2625
2626 (void) smb_inet_ntop(&hinfo->ipaddr, ipstr,
2627 SMB_IPSTRLEN(hinfo->ipaddr.a_family));
2628 smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr);
2629
2630 (void) strlcpy(buf, hinfo->name, buflen);
2631 /*
2632 * Remove the domain extension
2633 */
2634 if ((p = strchr(buf, '.')) != 0)
2635 *p = '\0';
2636
2637 free(hinfo);
2638 return (B_TRUE);
2639 }
2640
2641 static krb5_enctype *
smb_ads_get_enctypes(int dclevel,int * num)2642 smb_ads_get_enctypes(int dclevel, int *num)
2643 {
2644 krb5_enctype *encptr;
2645
2646 if (dclevel >= SMB_ADS_DCLEVEL_W2K8) {
2647 *num = sizeof (w2k8enctypes) / sizeof (krb5_enctype);
2648 encptr = w2k8enctypes;
2649 } else {
2650 *num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype);
2651 encptr = pre_w2k8enctypes;
2652 }
2653
2654 return (encptr);
2655 }
2656