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 * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
24 * Copyright 2021-2024 RackTop Systems, Inc.
25 */
26
27 #include <sys/param.h>
28 #include <ldap.h>
29 #include <stdlib.h>
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 #include <sys/time.h>
35 #include <netdb.h>
36 #include <pthread.h>
37 #include <unistd.h>
38 #include <arpa/nameser.h>
39 #include <resolv.h>
40 #include <sys/synch.h>
41 #include <string.h>
42 #include <strings.h>
43 #include <fcntl.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <assert.h>
47 #include <sasl/sasl.h>
48 #include <note.h>
49 #include <errno.h>
50 #include <cryptoutil.h>
51 #include <ads/dsgetdc.h>
52
53 #include <smbsrv/libsmbns.h>
54 #include <smbns_dyndns.h>
55 #include <smbns_krb.h>
56
57 #define SMB_ADS_AF_UNKNOWN(x) (((x)->ipaddr.a_family != AF_INET) && \
58 ((x)->ipaddr.a_family != AF_INET6))
59
60 #define SMB_ADS_MAXBUFLEN 100
61 #define SMB_ADS_DN_MAX 300
62 #define SMB_ADS_MAXMSGLEN 512
63 #define SMB_ADS_COMPUTERS_CN "Computers"
64 #define SMB_ADS_COMPUTER_NUM_ATTR 8
65 #define SMB_ADS_SHARE_NUM_ATTR 3
66 #define SMB_ADS_SITE_MAX MAXHOSTNAMELEN
67
68 #define SMB_ADS_MSDCS_SRV_DC_RR "_ldap._tcp.dc._msdcs"
69 #define SMB_ADS_MSDCS_SRV_SITE_RR "_ldap._tcp.%s._sites.dc._msdcs"
70
71 /*
72 * domainControllerFunctionality
73 *
74 * This rootDSE attribute indicates the functional level of the DC.
75 */
76 #define SMB_ADS_ATTR_DCLEVEL "domainControllerFunctionality"
77 #define SMB_ADS_DCLEVEL_W2K 0
78 #define SMB_ADS_DCLEVEL_W2K3 2
79 #define SMB_ADS_DCLEVEL_W2K8 3
80 #define SMB_ADS_DCLEVEL_W2K8_R2 4
81
82 /*
83 * msDs-supportedEncryptionTypes (Windows Server 2008 only)
84 *
85 * This attribute defines the encryption types supported by the system.
86 * Encryption Types:
87 * - DES cbc mode with CRC-32
88 * - DES cbc mode with RSA-MD5
89 * - ArcFour with HMAC/md5
90 * - AES-128
91 * - AES-256
92 */
93 #define SMB_ADS_ATTR_ENCTYPES "msDs-supportedEncryptionTypes"
94 #define SMB_ADS_ENC_DES_CRC 1
95 #define SMB_ADS_ENC_DES_MD5 2
96 #define SMB_ADS_ENC_RC4 4
97 #define SMB_ADS_ENC_AES128 8
98 #define SMB_ADS_ENC_AES256 16
99
100 static krb5_enctype w2k8enctypes[] = {
101 ENCTYPE_AES256_CTS_HMAC_SHA1_96,
102 ENCTYPE_AES128_CTS_HMAC_SHA1_96,
103 ENCTYPE_ARCFOUR_HMAC,
104 ENCTYPE_DES_CBC_CRC,
105 ENCTYPE_DES_CBC_MD5,
106 };
107
108 static krb5_enctype pre_w2k8enctypes[] = {
109 ENCTYPE_ARCFOUR_HMAC,
110 ENCTYPE_DES_CBC_CRC,
111 ENCTYPE_DES_CBC_MD5,
112 };
113
114 #define SMB_ADS_ATTR_SAMACCT "sAMAccountName"
115 #define SMB_ADS_ATTR_UPN "userPrincipalName"
116 #define SMB_ADS_ATTR_SPN "servicePrincipalName"
117 #define SMB_ADS_ATTR_CTL "userAccountControl"
118 #define SMB_ADS_ATTR_UCPWD "unicodePwd"
119 #define SMB_ADS_ATTR_DNSHOST "dNSHostName"
120 #define SMB_ADS_ATTR_KVNO "msDS-KeyVersionNumber"
121 #define SMB_ADS_ATTR_DN "distinguishedName"
122
123 /*
124 * UserAccountControl flags: manipulate user account properties.
125 *
126 * The hexadecimal value of the following property flags are based on MSDN
127 * article # 305144.
128 */
129 #define SMB_ADS_USER_ACCT_CTL_SCRIPT 0x00000001
130 #define SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE 0x00000002
131 #define SMB_ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED 0x00000008
132 #define SMB_ADS_USER_ACCT_CTL_LOCKOUT 0x00000010
133 #define SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD 0x00000020
134 #define SMB_ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE 0x00000040
135 #define SMB_ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED 0x00000080
136 #define SMB_ADS_USER_ACCT_CTL_TMP_DUP_ACCT 0x00000100
137 #define SMB_ADS_USER_ACCT_CTL_NORMAL_ACCT 0x00000200
138 #define SMB_ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT 0x00000800
139 #define SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT 0x00001000
140 #define SMB_ADS_USER_ACCT_CTL_SRV_TRUST_ACCT 0x00002000
141 #define SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD 0x00010000
142 #define SMB_ADS_USER_ACCT_CTL_MNS_LOGON_ACCT 0x00020000
143 #define SMB_ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED 0x00040000
144 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION 0x00080000
145 #define SMB_ADS_USER_ACCT_CTL_NOT_DELEGATED 0x00100000
146 #define SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY 0x00200000
147 #define SMB_ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH 0x00400000
148 #define SMB_ADS_USER_ACCT_CTL_PASSWD_EXPIRED 0x00800000
149 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION 0x01000000
150
151 /*
152 * Length of "dc=" prefix.
153 */
154 #define SMB_ADS_DN_PREFIX_LEN 3
155
156 static char *smb_ads_computer_objcls[] = {
157 "top", "person", "organizationalPerson",
158 "user", "computer", NULL
159 };
160
161 static char *smb_ads_share_objcls[] = {
162 "top", "leaf", "connectionPoint", "volume", NULL
163 };
164
165 /* Cached ADS server to communicate with */
166 static smb_ads_host_info_t *smb_ads_cached_host_info = NULL;
167 static mutex_t smb_ads_cached_host_mtx;
168
169 /*
170 * SMB ADS config cache is maintained to facilitate the detection of
171 * changes in configuration that is relevant to AD selection.
172 */
173 typedef struct smb_ads_config {
174 char c_site[SMB_ADS_SITE_MAX];
175 mutex_t c_mtx;
176 } smb_ads_config_t;
177
178 static smb_ads_config_t smb_ads_cfg;
179
180
181 /* attribute/value pair */
182 typedef struct smb_ads_avpair {
183 char *avp_attr;
184 char *avp_val;
185 } smb_ads_avpair_t;
186
187 /* query status */
188 typedef enum smb_ads_qstat {
189 SMB_ADS_STAT_ERR = -2,
190 SMB_ADS_STAT_DUP,
191 SMB_ADS_STAT_NOT_FOUND,
192 SMB_ADS_STAT_FOUND
193 } smb_ads_qstat_t;
194
195 typedef struct smb_ads_host_list {
196 int ah_cnt;
197 smb_ads_host_info_t *ah_list;
198 } smb_ads_host_list_t;
199
200 static int smb_ads_open_main(smb_ads_handle_t **, char *, char *, char *);
201 static int smb_ads_add_computer(smb_ads_handle_t *, int, char *);
202 static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *);
203 static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *);
204 static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *,
205 smb_ads_avpair_t *, int, char *);
206 static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *);
207 static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *);
208 static void smb_ads_free_cached_host(void);
209 static int smb_ads_alloc_attr(LDAPMod **, int);
210 static void smb_ads_free_attr(LDAPMod **);
211 static int smb_ads_get_dc_level(smb_ads_handle_t *);
212 static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *, char *);
213 static smb_ads_qstat_t smb_ads_getattr(LDAP *, LDAPMessage *,
214 smb_ads_avpair_t *);
215 static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *, LDAPMessage *,
216 smb_ads_avpair_t *);
217 static boolean_t smb_ads_is_same_domain(char *, char *);
218 static smb_ads_host_info_t *smb_ads_dup_host_info(smb_ads_host_info_t *);
219 static char *smb_ads_get_sharedn(const char *, const char *, const char *);
220 static krb5_enctype *smb_ads_get_enctypes(int, int *);
221
222 /*
223 * smb_ads_init
224 *
225 * Initializes the ADS config cache.
226 */
227 void
smb_ads_init(void)228 smb_ads_init(void)
229 {
230 (void) mutex_lock(&smb_ads_cfg.c_mtx);
231 (void) smb_config_getstr(SMB_CI_ADS_SITE,
232 smb_ads_cfg.c_site, SMB_ADS_SITE_MAX);
233 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
234
235 /* Force -lads to load, for dtrace. */
236 DsFreeDcInfo(NULL);
237 }
238
239 void
smb_ads_fini(void)240 smb_ads_fini(void)
241 {
242 smb_ads_free_cached_host();
243 }
244
245 /*
246 * smb_ads_refresh
247 *
248 * This function will be called when smb/server SMF service is refreshed.
249 * (See smbd_join.c)
250 *
251 * Clearing the smb_ads_cached_host_info would allow the next DC
252 * discovery process to pick up an AD based on the new AD configuration.
253 */
254 void
smb_ads_refresh(boolean_t force_rediscovery)255 smb_ads_refresh(boolean_t force_rediscovery)
256 {
257 char new_site[SMB_ADS_SITE_MAX];
258
259 (void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX);
260 (void) mutex_lock(&smb_ads_cfg.c_mtx);
261 (void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX);
262 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
263
264 smb_ads_free_cached_host();
265
266 if (force_rediscovery) {
267 (void) _DsForceRediscovery(NULL, 0);
268 }
269 }
270
271
272 /*
273 * smb_ads_build_unc_name
274 *
275 * Construct the UNC name of the share object in the format of
276 * \\hostname.domain\shareUNC
277 *
278 * Returns 0 on success, -1 on error.
279 */
280 int
smb_ads_build_unc_name(char * unc_name,int maxlen,const char * hostname,const char * shareUNC)281 smb_ads_build_unc_name(char *unc_name, int maxlen,
282 const char *hostname, const char *shareUNC)
283 {
284 char my_domain[MAXHOSTNAMELEN];
285
286 if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0)
287 return (-1);
288
289 (void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s",
290 hostname, my_domain, shareUNC);
291 return (0);
292 }
293
294 /*
295 * The cached ADS host is no longer valid if one of the following criteria
296 * is satisfied:
297 *
298 * 1) not in the specified domain
299 * 2) not the sought host (if specified)
300 * 3) not reachable
301 *
302 * The caller is responsible for acquiring the smb_ads_cached_host_mtx lock
303 * prior to calling this function.
304 *
305 * Return B_TRUE if the cache host is still valid. Otherwise, return B_FALSE.
306 */
307 static boolean_t
smb_ads_validate_cache_host(char * domain)308 smb_ads_validate_cache_host(char *domain)
309 {
310 if (!smb_ads_cached_host_info)
311 return (B_FALSE);
312
313 if (!smb_ads_is_same_domain(smb_ads_cached_host_info->name, domain))
314 return (B_FALSE);
315
316 return (B_TRUE);
317 }
318
319 /*
320 * smb_ads_match_hosts_same_domain
321 *
322 * Returns true, if the cached ADS host is in the same domain as the
323 * current (given) domain.
324 */
325 static boolean_t
smb_ads_is_same_domain(char * cached_host_name,char * current_domain)326 smb_ads_is_same_domain(char *cached_host_name, char *current_domain)
327 {
328 char *cached_host_domain;
329
330 if ((cached_host_name == NULL) || (current_domain == NULL))
331 return (B_FALSE);
332
333 cached_host_domain = strchr(cached_host_name, '.');
334 if (cached_host_domain == NULL)
335 return (B_FALSE);
336
337 ++cached_host_domain;
338 if (smb_strcasecmp(cached_host_domain, current_domain, 0))
339 return (B_FALSE);
340
341 return (B_TRUE);
342 }
343
344 /*
345 * smb_ads_dup_host_info
346 *
347 * Duplicates the passed smb_ads_host_info_t structure.
348 * Caller must free memory allocated by this method.
349 *
350 * Returns a reference to the duplicated smb_ads_host_info_t structure.
351 * Returns NULL on error.
352 */
353 static smb_ads_host_info_t *
smb_ads_dup_host_info(smb_ads_host_info_t * ads_host)354 smb_ads_dup_host_info(smb_ads_host_info_t *ads_host)
355 {
356 smb_ads_host_info_t *dup_host;
357
358 if (ads_host == NULL)
359 return (NULL);
360
361 dup_host = malloc(sizeof (smb_ads_host_info_t));
362
363 if (dup_host != NULL)
364 bcopy(ads_host, dup_host, sizeof (smb_ads_host_info_t));
365
366 return (dup_host);
367 }
368
369 /*
370 * smb_ads_find_host
371 *
372 * Finds an ADS host in a given domain.
373 *
374 * If the cached host is valid, it will be used. Otherwise, a DC will
375 * be selected based on the following criteria:
376 *
377 * 1) pdc (aka preferred DC) configuration
378 * 2) AD site configuration - the scope of the DNS lookup will be
379 * restricted to the specified site.
380 * 3) DC on the same subnet
381 * 4) DC with the lowest priority/highest weight
382 *
383 * The above items are listed in decreasing preference order. The selected
384 * DC must be online.
385 *
386 * If this function is called during domain join, the specified kpasswd server
387 * takes precedence over preferred DC, AD site, and so on.
388 *
389 * Parameters:
390 * domain: fully-qualified domain name.
391 *
392 * Returns:
393 * A copy of the cached host info is returned. The caller is responsible
394 * for deallocating the memory returned by this function.
395 */
396 /*ARGSUSED*/
397 smb_ads_host_info_t *
smb_ads_find_host(char * domain)398 smb_ads_find_host(char *domain)
399 {
400 smb_ads_host_info_t *host = NULL;
401 DOMAIN_CONTROLLER_INFO *dci = NULL;
402 struct sockaddr_storage *ss;
403 uint32_t flags = DS_DS_FLAG;
404 uint32_t status;
405 int tries;
406
407 (void) mutex_lock(&smb_ads_cached_host_mtx);
408 if (smb_ads_validate_cache_host(domain)) {
409 host = smb_ads_dup_host_info(smb_ads_cached_host_info);
410 (void) mutex_unlock(&smb_ads_cached_host_mtx);
411 return (host);
412 }
413
414 (void) mutex_unlock(&smb_ads_cached_host_mtx);
415 smb_ads_free_cached_host();
416
417 /*
418 * The _real_ DC Locator is over in idmapd.
419 * Door call over there to get it.
420 */
421 tries = 15;
422 again:
423 status = _DsGetDcName(
424 NULL, /* ComputerName */
425 domain,
426 NULL, /* DomainGuid */
427 NULL, /* SiteName */
428 flags,
429 &dci);
430 switch (status) {
431 case 0:
432 break;
433 /*
434 * We can see these errors when joining a domain, if we race
435 * asking idmap for the DC before it knows the new domain.
436 */
437 case NT_STATUS_NO_SUCH_DOMAIN: /* Specified domain unknown */
438 case NT_STATUS_INVALID_SERVER_STATE: /* not in domain mode. */
439 if (--tries > 0) {
440 (void) sleep(1);
441 goto again;
442 }
443 /* FALLTHROUGH */
444 case NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND:
445 case NT_STATUS_CANT_WAIT: /* timeout over in idmap */
446 default:
447 return (NULL);
448 }
449
450 host = calloc(1, sizeof (*host));
451 if (host == NULL)
452 goto out;
453
454 (void) strlcpy(host->name, dci->DomainControllerName, MAXHOSTNAMELEN);
455 ss = (void *)dci->_sockaddr;
456 switch (ss->ss_family) {
457 case AF_INET: {
458 struct sockaddr_in *sin = (void *)ss;
459 host->port = ntohs(sin->sin_port);
460 host->ipaddr.a_family = AF_INET;
461 (void) memcpy(&host->ipaddr.a_ipv4, &sin->sin_addr,
462 sizeof (in_addr_t));
463 break;
464 }
465 case AF_INET6: {
466 struct sockaddr_in6 *sin6 = (void *)ss;
467 host->port = ntohs(sin6->sin6_port);
468 host->ipaddr.a_family = AF_INET6;
469 (void) memcpy(&host->ipaddr.a_ipv6, &sin6->sin6_addr,
470 sizeof (in6_addr_t));
471 break;
472 }
473 default:
474 syslog(LOG_ERR, "no addr for DC %s",
475 dci->DomainControllerName);
476 free(host);
477 host = NULL;
478 goto out;
479 }
480
481 host->flags = dci->Flags;
482
483 (void) mutex_lock(&smb_ads_cached_host_mtx);
484 if (!smb_ads_cached_host_info)
485 smb_ads_cached_host_info = smb_ads_dup_host_info(host);
486 (void) mutex_unlock(&smb_ads_cached_host_mtx);
487
488 out:
489 DsFreeDcInfo(dci);
490 return (host);
491 }
492
493 /*
494 * Return the number of dots in a string.
495 */
496 static int
smb_ads_count_dots(const char * s)497 smb_ads_count_dots(const char *s)
498 {
499 int ndots = 0;
500
501 while (*s) {
502 if (*s++ == '.')
503 ndots++;
504 }
505
506 return (ndots);
507 }
508
509 /*
510 * Convert a domain name in dot notation to distinguished name format,
511 * for example: sun.com -> dc=sun,dc=com.
512 *
513 * Returns a pointer to an allocated buffer containing the distinguished
514 * name.
515 */
516 static char *
smb_ads_convert_domain(const char * domain_name)517 smb_ads_convert_domain(const char *domain_name)
518 {
519 const char *s;
520 char *dn_name;
521 char buf[2];
522 int ndots;
523 int len;
524
525 if (domain_name == NULL || *domain_name == 0)
526 return (NULL);
527
528 ndots = smb_ads_count_dots(domain_name);
529 ++ndots;
530 len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1;
531
532 if ((dn_name = malloc(len)) == NULL)
533 return (NULL);
534
535 bzero(dn_name, len);
536 (void) strlcpy(dn_name, "dc=", len);
537
538 buf[1] = '\0';
539 s = domain_name;
540
541 while (*s) {
542 if (*s == '.') {
543 (void) strlcat(dn_name, ",dc=", len);
544 } else {
545 buf[0] = *s;
546 (void) strlcat(dn_name, buf, len);
547 }
548 ++s;
549 }
550
551 return (dn_name);
552 }
553
554 /*
555 * smb_ads_free_cached_host
556 *
557 * Free the memory use by the global smb_ads_cached_host_info & set it to NULL.
558 */
559 static void
smb_ads_free_cached_host(void)560 smb_ads_free_cached_host(void)
561 {
562 (void) mutex_lock(&smb_ads_cached_host_mtx);
563 if (smb_ads_cached_host_info) {
564 free(smb_ads_cached_host_info);
565 smb_ads_cached_host_info = NULL;
566 }
567 (void) mutex_unlock(&smb_ads_cached_host_mtx);
568 }
569
570 /*
571 * smb_ads_open
572 * Open a LDAP connection to an ADS server if the system is in domain mode.
573 * Acquire both Kerberos TGT and LDAP service tickets for the host principal.
574 *
575 * This function should only be called after the system is successfully joined
576 * to a domain.
577 */
578 smb_ads_handle_t *
smb_ads_open(void)579 smb_ads_open(void)
580 {
581 char domain[MAXHOSTNAMELEN];
582 smb_ads_handle_t *h;
583 smb_ads_status_t err;
584
585 if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
586 return (NULL);
587
588 if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
589 return (NULL);
590
591 err = smb_ads_open_main(&h, domain, NULL, NULL);
592 if (err != 0) {
593 smb_ads_log_errmsg(err);
594 return (NULL);
595 }
596
597 return (h);
598 }
599
600 static int
smb_ads_saslcallback(LDAP * ld,unsigned flags,void * defaults,void * prompts)601 smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts)
602 {
603 NOTE(ARGUNUSED(ld, defaults));
604 sasl_interact_t *interact;
605
606 if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
607 return (LDAP_PARAM_ERROR);
608
609 /* There should be no extra arguemnts for SASL/GSSAPI authentication */
610 for (interact = prompts; interact->id != SASL_CB_LIST_END;
611 interact++) {
612 interact->result = NULL;
613 interact->len = 0;
614 }
615 return (LDAP_SUCCESS);
616 }
617
618 /*
619 * smb_ads_open_main
620 * Open a LDAP connection to an ADS server.
621 * If ADS is enabled and the administrative username, password, and
622 * ADS domain are defined then query DNS to find an ADS server if this is the
623 * very first call to this routine. After an ADS server is found then this
624 * server will be used everytime this routine is called until the system is
625 * rebooted or the ADS server becomes unavailable then an ADS server will
626 * be queried again. After the connection is made then an ADS handle
627 * is created to be returned.
628 *
629 * After the LDAP connection, the LDAP version will be set to 3 using
630 * ldap_set_option().
631 *
632 * The LDAP connection is bound before the ADS handle is returned.
633 * Parameters:
634 * domain - fully-qualified domain name
635 * user - the user account for whom the Kerberos TGT ticket and ADS
636 * service tickets are acquired.
637 * password - password of the specified user
638 *
639 * Returns:
640 * NULL : can't connect to ADS server or other errors
641 * smb_ads_handle_t* : handle to ADS server
642 */
643 static int
smb_ads_open_main(smb_ads_handle_t ** hp,char * domain,char * user,char * password)644 smb_ads_open_main(smb_ads_handle_t **hp, char *domain, char *user,
645 char *password)
646 {
647 smb_ads_handle_t *ah;
648 LDAP *ld;
649 int version = 3;
650 smb_ads_host_info_t *ads_host = NULL;
651 int err, rc;
652
653 *hp = NULL;
654
655 if (user != NULL) {
656 err = smb_kinit(domain, user, password);
657 if (err != 0) {
658 syslog(LOG_ERR, "smbns: kinit failed");
659 return (err);
660 }
661 user = NULL;
662 password = NULL;
663 }
664
665 ads_host = smb_ads_find_host(domain);
666 if (ads_host == NULL)
667 return (SMB_ADS_CANT_LOCATE_DC);
668
669 ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
670 if (ah == NULL) {
671 free(ads_host);
672 return (ENOMEM);
673 }
674
675 (void) memset(ah, 0, sizeof (smb_ads_handle_t));
676
677 if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) {
678 syslog(LOG_ERR, "smbns: ldap_init failed");
679 smb_ads_free_cached_host();
680 free(ah);
681 free(ads_host);
682 return (SMB_ADS_LDAP_INIT);
683 }
684
685 if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version)
686 != LDAP_SUCCESS) {
687 smb_ads_free_cached_host();
688 free(ah);
689 free(ads_host);
690 (void) ldap_unbind(ld);
691 return (SMB_ADS_LDAP_SETOPT);
692 }
693
694 (void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
695 ah->ld = ld;
696 ah->domain = strdup(domain);
697
698 if (ah->domain == NULL) {
699 smb_ads_close(ah);
700 free(ads_host);
701 return (SMB_ADS_LDAP_SETOPT);
702 }
703
704 /*
705 * ah->domain is often used for generating service principal name.
706 * Convert it to lower case for RFC 4120 section 6.2.1 conformance.
707 */
708 (void) smb_strlwr(ah->domain);
709 ah->domain_dn = smb_ads_convert_domain(domain);
710 if (ah->domain_dn == NULL) {
711 smb_ads_close(ah);
712 free(ads_host);
713 return (SMB_ADS_LDAP_SET_DOM);
714 }
715
716 ah->hostname = strdup(ads_host->name);
717 if (ah->hostname == NULL) {
718 smb_ads_close(ah);
719 free(ads_host);
720 return (ENOMEM);
721 }
722 (void) mutex_lock(&smb_ads_cfg.c_mtx);
723 if (*smb_ads_cfg.c_site != '\0') {
724 if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) {
725 smb_ads_close(ah);
726 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
727 free(ads_host);
728 return (ENOMEM);
729 }
730 } else {
731 ah->site = NULL;
732 }
733 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
734
735 syslog(LOG_DEBUG, "smbns: smb_ads_open_main");
736 syslog(LOG_DEBUG, "smbns: domain: %s", ah->domain);
737 syslog(LOG_DEBUG, "smbns: domain_dn: %s", ah->domain_dn);
738 syslog(LOG_DEBUG, "smbns: ip_addr: %s", ah->ip_addr);
739 syslog(LOG_DEBUG, "smbns: hostname: %s", ah->hostname);
740 syslog(LOG_DEBUG, "smbns: site: %s",
741 (ah->site != NULL) ? ah->site : "");
742
743 rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL,
744 LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL);
745 if (rc != LDAP_SUCCESS) {
746 syslog(LOG_ERR, "smbns: ldap_sasl_..._bind_s failed (%s)",
747 ldap_err2string(rc));
748 smb_ads_close(ah);
749 free(ads_host);
750 return (SMB_ADS_LDAP_SASL_BIND);
751 }
752 syslog(LOG_DEBUG, "smbns: ldap_sasl_..._bind_s success");
753
754 free(ads_host);
755 *hp = ah;
756
757 return (SMB_ADS_SUCCESS);
758 }
759
760 /*
761 * smb_ads_close
762 * Close connection to ADS server and free memory allocated for ADS handle.
763 * LDAP unbind is called here.
764 * Parameters:
765 * ah: handle to ADS server
766 * Returns:
767 * void
768 */
769 void
smb_ads_close(smb_ads_handle_t * ah)770 smb_ads_close(smb_ads_handle_t *ah)
771 {
772 if (ah == NULL)
773 return;
774 /* close and free connection resources */
775 if (ah->ld)
776 (void) ldap_unbind(ah->ld);
777
778 free(ah->domain);
779 free(ah->domain_dn);
780 free(ah->hostname);
781 free(ah->site);
782 free(ah);
783 }
784
785 /*
786 * smb_ads_alloc_attr
787 *
788 * Since the attrs is a null-terminated array, all elements
789 * in the array (except the last one) will point to allocated
790 * memory.
791 */
792 static int
smb_ads_alloc_attr(LDAPMod * attrs[],int num)793 smb_ads_alloc_attr(LDAPMod *attrs[], int num)
794 {
795 int i;
796
797 bzero(attrs, num * sizeof (LDAPMod *));
798 for (i = 0; i < (num - 1); i++) {
799 attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod));
800 if (attrs[i] == NULL) {
801 smb_ads_free_attr(attrs);
802 return (-1);
803 }
804 }
805
806 return (0);
807 }
808
809 /*
810 * smb_ads_free_attr
811 * Free memory allocated when publishing a share.
812 * Parameters:
813 * attrs: an array of LDAPMod pointers
814 * Returns:
815 * None
816 */
817 static void
smb_ads_free_attr(LDAPMod * attrs[])818 smb_ads_free_attr(LDAPMod *attrs[])
819 {
820 int i;
821 for (i = 0; attrs[i]; i++) {
822 free(attrs[i]);
823 }
824 }
825
826 /*
827 * Returns share DN in an allocated buffer. The format of the DN is
828 * cn=<sharename>,<container RDNs>,<domain DN>
829 *
830 * If the domain DN is not included in the container parameter,
831 * then it will be appended to create the share DN.
832 *
833 * The caller must free the allocated buffer.
834 */
835 static char *
smb_ads_get_sharedn(const char * sharename,const char * container,const char * domain_dn)836 smb_ads_get_sharedn(const char *sharename, const char *container,
837 const char *domain_dn)
838 {
839 char *share_dn;
840 int rc, offset, container_len, domain_len;
841 boolean_t append_domain = B_TRUE;
842
843 container_len = strlen(container);
844 domain_len = strlen(domain_dn);
845
846 if (container_len >= domain_len) {
847
848 /* offset to last domain_len characters */
849 offset = container_len - domain_len;
850
851 if (smb_strcasecmp(container + offset,
852 domain_dn, domain_len) == 0)
853 append_domain = B_FALSE;
854 }
855
856 if (append_domain)
857 rc = asprintf(&share_dn, "cn=%s,%s,%s", sharename,
858 container, domain_dn);
859 else
860 rc = asprintf(&share_dn, "cn=%s,%s", sharename,
861 container);
862
863 return ((rc == -1) ? NULL : share_dn);
864 }
865
866 /*
867 * smb_ads_add_share
868 * Call by smb_ads_publish_share to create share object in ADS.
869 * This routine specifies the attributes of an ADS LDAP share object. The first
870 * attribute and values define the type of ADS object, the share object. The
871 * second attribute and value define the UNC of the share data for the share
872 * object. The LDAP synchronous add command is used to add the object into ADS.
873 * The container location to add the object needs to specified.
874 * Parameters:
875 * ah : handle to ADS server
876 * adsShareName: name of share object to be created in ADS
877 * shareUNC : share name on NetForce
878 * adsContainer: location in ADS to create share object
879 *
880 * Returns:
881 * -1 : error
882 * 0 : success
883 */
884 int
smb_ads_add_share(smb_ads_handle_t * ah,const char * adsShareName,const char * unc_name,const char * adsContainer)885 smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName,
886 const char *unc_name, const char *adsContainer)
887 {
888 LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR];
889 int j = 0;
890 char *share_dn;
891 int ret;
892 char *unc_names[] = {(char *)unc_name, NULL};
893
894 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
895 ah->domain_dn)) == NULL)
896 return (-1);
897
898 if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) {
899 free(share_dn);
900 return (-1);
901 }
902
903 attrs[j]->mod_op = LDAP_MOD_ADD;
904 attrs[j]->mod_type = "objectClass";
905 attrs[j]->mod_values = smb_ads_share_objcls;
906
907 attrs[++j]->mod_op = LDAP_MOD_ADD;
908 attrs[j]->mod_type = "uNCName";
909 attrs[j]->mod_values = unc_names;
910
911 if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) {
912 if (ret == LDAP_NO_SUCH_OBJECT) {
913 syslog(LOG_ERR, "Failed to publish share %s in" \
914 " AD. Container does not exist: %s.\n",
915 adsShareName, share_dn);
916
917 } else {
918 syslog(LOG_ERR, "Failed to publish share %s in" \
919 " AD: %s (%s).\n", adsShareName, share_dn,
920 ldap_err2string(ret));
921 }
922 smb_ads_free_attr(attrs);
923 free(share_dn);
924 return (ret);
925 }
926 free(share_dn);
927 smb_ads_free_attr(attrs);
928
929 return (0);
930 }
931
932 /*
933 * smb_ads_del_share
934 * Call by smb_ads_remove_share to remove share object from ADS. The container
935 * location to remove the object needs to specified. The LDAP synchronous
936 * delete command is used.
937 * Parameters:
938 * ah : handle to ADS server
939 * adsShareName: name of share object in ADS to be removed
940 * adsContainer: location of share object in ADS
941 * Returns:
942 * -1 : error
943 * 0 : success
944 */
945 static int
smb_ads_del_share(smb_ads_handle_t * ah,const char * adsShareName,const char * adsContainer)946 smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName,
947 const char *adsContainer)
948 {
949 char *share_dn;
950 int ret;
951
952 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
953 ah->domain_dn)) == NULL)
954 return (-1);
955
956 if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) {
957 smb_tracef("ldap_delete: %s", ldap_err2string(ret));
958 free(share_dn);
959 return (-1);
960 }
961 free(share_dn);
962
963 return (0);
964 }
965
966
967 /*
968 * smb_ads_escape_search_filter_chars
969 *
970 * This routine will escape the special characters found in a string
971 * that will later be passed to the ldap search filter.
972 *
973 * RFC 1960 - A String Representation of LDAP Search Filters
974 * 3. String Search Filter Definition
975 * If a value must contain one of the characters '*' OR '(' OR ')',
976 * these characters
977 * should be escaped by preceding them with the backslash '\' character.
978 *
979 * RFC 2252 - LDAP Attribute Syntax Definitions
980 * a backslash quoting mechanism is used to escape
981 * the following separator symbol character (such as "'", "$" or "#") if
982 * it should occur in that string.
983 */
984 static int
smb_ads_escape_search_filter_chars(const char * src,char * dst)985 smb_ads_escape_search_filter_chars(const char *src, char *dst)
986 {
987 int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */
988
989 if (src == NULL || dst == NULL)
990 return (-1);
991
992 while (*src) {
993 if (!avail) {
994 *dst = 0;
995 return (-1);
996 }
997
998 switch (*src) {
999 case '\\':
1000 case '\'':
1001 case '$':
1002 case '#':
1003 case '*':
1004 case '(':
1005 case ')':
1006 *dst++ = '\\';
1007 avail--;
1008 /* fall through */
1009
1010 default:
1011 *dst++ = *src++;
1012 avail--;
1013 }
1014 }
1015
1016 *dst = 0;
1017
1018 return (0);
1019 }
1020
1021 /*
1022 * smb_ads_lookup_share
1023 * The search filter is set to search for a specific share name in the
1024 * specified ADS container. The LDSAP synchronous search command is used.
1025 * Parameters:
1026 * ah : handle to ADS server
1027 * adsShareName: name of share object in ADS to be searched
1028 * adsContainer: location of share object in ADS
1029 * Returns:
1030 * -1 : error
1031 * 0 : not found
1032 * 1 : found
1033 */
1034 int
smb_ads_lookup_share(smb_ads_handle_t * ah,const char * adsShareName,const char * adsContainer,char * unc_name)1035 smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName,
1036 const char *adsContainer, char *unc_name)
1037 {
1038 char *attrs[4], filter[SMB_ADS_MAXBUFLEN];
1039 char *share_dn;
1040 int ret;
1041 LDAPMessage *res;
1042 char tmpbuf[SMB_ADS_MAXBUFLEN];
1043
1044 if (adsShareName == NULL || adsContainer == NULL)
1045 return (-1);
1046
1047 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1048 ah->domain_dn)) == NULL)
1049 return (-1);
1050
1051 res = NULL;
1052 attrs[0] = "cn";
1053 attrs[1] = "objectClass";
1054 attrs[2] = "uNCName";
1055 attrs[3] = NULL;
1056
1057 if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) {
1058 free(share_dn);
1059 return (-1);
1060 }
1061
1062 (void) snprintf(filter, sizeof (filter),
1063 "(&(objectClass=volume)(uNCName=%s))", tmpbuf);
1064
1065 if ((ret = ldap_search_s(ah->ld, share_dn,
1066 LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) {
1067 if (ret != LDAP_NO_SUCH_OBJECT)
1068 smb_tracef("%s: ldap_search: %s", share_dn,
1069 ldap_err2string(ret));
1070
1071 (void) ldap_msgfree(res);
1072 free(share_dn);
1073 return (0);
1074 }
1075
1076 (void) free(share_dn);
1077
1078 /* no match is found */
1079 if (ldap_count_entries(ah->ld, res) == 0) {
1080 (void) ldap_msgfree(res);
1081 return (0);
1082 }
1083
1084 /* free the search results */
1085 (void) ldap_msgfree(res);
1086
1087 return (1);
1088 }
1089
1090 /*
1091 * smb_ads_publish_share
1092 * Publish share into ADS. If a share name already exist in ADS in the same
1093 * container then the existing share object is removed before adding the new
1094 * share object.
1095 * Parameters:
1096 * ah : handle return from smb_ads_open
1097 * adsShareName: name of share to be added to ADS directory
1098 * shareUNC : name of share on client, can be NULL to use the same name
1099 * as adsShareName
1100 * adsContainer: location for share to be added in ADS directory, ie
1101 * ou=share_folder
1102 * uncType : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR
1103 * to use host ip addr for UNC.
1104 * Returns:
1105 * -1 : error
1106 * 0 : success
1107 */
1108 int
smb_ads_publish_share(smb_ads_handle_t * ah,const char * adsShareName,const char * shareUNC,const char * adsContainer,const char * hostname)1109 smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName,
1110 const char *shareUNC, const char *adsContainer, const char *hostname)
1111 {
1112 int ret;
1113 char unc_name[SMB_ADS_MAXBUFLEN];
1114
1115 if (adsShareName == NULL || adsContainer == NULL)
1116 return (-1);
1117
1118 if (shareUNC == 0 || *shareUNC == 0)
1119 shareUNC = adsShareName;
1120
1121 if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1122 hostname, shareUNC) < 0)
1123 return (-1);
1124
1125 ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1126
1127 switch (ret) {
1128 case 1:
1129 (void) smb_ads_del_share(ah, adsShareName, adsContainer);
1130 ret = smb_ads_add_share(ah, adsShareName, unc_name,
1131 adsContainer);
1132 break;
1133
1134 case 0:
1135 ret = smb_ads_add_share(ah, adsShareName, unc_name,
1136 adsContainer);
1137 if (ret == LDAP_ALREADY_EXISTS)
1138 ret = -1;
1139
1140 break;
1141
1142 case -1:
1143 default:
1144 /* return with error code */
1145 ret = -1;
1146 }
1147
1148 return (ret);
1149 }
1150
1151 /*
1152 * smb_ads_remove_share
1153 * Remove share from ADS. A search is done first before explicitly removing
1154 * the share.
1155 * Parameters:
1156 * ah : handle return from smb_ads_open
1157 * adsShareName: name of share to be removed from ADS directory
1158 * adsContainer: location for share to be removed from ADS directory, ie
1159 * ou=share_folder
1160 * Returns:
1161 * -1 : error
1162 * 0 : success
1163 */
1164 int
smb_ads_remove_share(smb_ads_handle_t * ah,const char * adsShareName,const char * shareUNC,const char * adsContainer,const char * hostname)1165 smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName,
1166 const char *shareUNC, const char *adsContainer, const char *hostname)
1167 {
1168 int ret;
1169 char unc_name[SMB_ADS_MAXBUFLEN];
1170
1171 if (adsShareName == NULL || adsContainer == NULL)
1172 return (-1);
1173 if (shareUNC == 0 || *shareUNC == 0)
1174 shareUNC = adsShareName;
1175
1176 if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1177 hostname, shareUNC) < 0)
1178 return (-1);
1179
1180 ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1181 if (ret == 0)
1182 return (0);
1183 if (ret == -1)
1184 return (-1);
1185
1186 return (smb_ads_del_share(ah, adsShareName, adsContainer));
1187 }
1188
1189 /*
1190 * smb_ads_get_new_comp_dn
1191 *
1192 * Build the distinguished name for a new machine account
1193 * prepend: cn=SamAccountName, cn=Computers, ...domain_dn...
1194 */
1195 static void
smb_ads_get_new_comp_dn(smb_ads_handle_t * ah,char * buf,size_t buflen,char * container)1196 smb_ads_get_new_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen,
1197 char *container)
1198 {
1199 char nbname[NETBIOS_NAME_SZ];
1200 if (container == NULL)
1201 container = "cn=" SMB_ADS_COMPUTERS_CN;
1202
1203 (void) smb_getnetbiosname(nbname, sizeof (nbname));
1204 (void) snprintf(buf, buflen, "cn=%s,%s,%s",
1205 nbname, container, ah->domain_dn);
1206 }
1207
1208 /*
1209 * smb_ads_add_computer
1210 *
1211 * Returns 0 upon success. Otherwise, returns -1.
1212 */
1213 static int
smb_ads_add_computer(smb_ads_handle_t * ah,int dclevel,char * dn)1214 smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1215 {
1216 return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn));
1217 }
1218
1219 /*
1220 * smb_ads_modify_computer
1221 *
1222 * Returns 0 upon success. Otherwise, returns -1.
1223 */
1224 static int
smb_ads_modify_computer(smb_ads_handle_t * ah,int dclevel,char * dn)1225 smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1226 {
1227 return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn));
1228 }
1229
1230 /*
1231 * smb_ads_get_dc_level
1232 *
1233 * Returns the functional level of the DC upon success.
1234 * Otherwise, -1 is returned.
1235 */
1236 static int
smb_ads_get_dc_level(smb_ads_handle_t * ah)1237 smb_ads_get_dc_level(smb_ads_handle_t *ah)
1238 {
1239 LDAPMessage *res, *entry;
1240 char *attr[2];
1241 char **vals;
1242 int rc;
1243
1244 res = NULL;
1245 attr[0] = SMB_ADS_ATTR_DCLEVEL;
1246 attr[1] = NULL;
1247 rc = ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr, 0, &res);
1248 if (rc != LDAP_SUCCESS) {
1249 syslog(LOG_ERR, "smb_ads_get_dc_level: "
1250 "LDAP search, error %s", ldap_err2string(rc));
1251 (void) ldap_msgfree(res);
1252 return (-1);
1253 }
1254
1255 /* no match for the specified attribute is found */
1256 if (ldap_count_entries(ah->ld, res) == 0) {
1257 (void) ldap_msgfree(res);
1258 return (-1);
1259 }
1260
1261 rc = -1;
1262 entry = ldap_first_entry(ah->ld, res);
1263 if (entry) {
1264 if ((vals = ldap_get_values(ah->ld, entry,
1265 SMB_ADS_ATTR_DCLEVEL)) == NULL) {
1266 /*
1267 * Observed the values aren't populated
1268 * by the Windows 2000 server.
1269 */
1270 syslog(LOG_DEBUG, "smb_ads_get_dc_level: "
1271 "LDAP values missing, assume W2K");
1272 (void) ldap_msgfree(res);
1273 return (SMB_ADS_DCLEVEL_W2K);
1274 }
1275
1276 if (vals[0] != NULL) {
1277 rc = atoi(vals[0]);
1278 syslog(LOG_DEBUG, "smb_ads_get_dc_level: "
1279 "LDAP value %d", rc);
1280 }
1281 ldap_value_free(vals);
1282 }
1283
1284 (void) ldap_msgfree(res);
1285 return (rc);
1286 }
1287
1288 /*
1289 * The fully-qualified hostname returned by this function is often used for
1290 * constructing service principal name. Return the fully-qualified hostname
1291 * in lower case for RFC 4120 section 6.2.1 conformance.
1292 */
1293 static int
smb_ads_getfqhostname(smb_ads_handle_t * ah,char * fqhost,int len)1294 smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len)
1295 {
1296 if (smb_gethostname(fqhost, len, SMB_CASE_LOWER) != 0)
1297 return (-1);
1298
1299 (void) strlcat(fqhost, ".", len);
1300 (void) strlcat(fqhost, ah->domain, len);
1301
1302 return (0);
1303 }
1304
1305 static int
smb_ads_computer_op(smb_ads_handle_t * ah,int op,int dclevel,char * dn)1306 smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
1307 {
1308 LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR];
1309 char *sam_val[2];
1310 char *ctl_val[2], *fqh_val[2];
1311 char *encrypt_val[2];
1312 int j = -1;
1313 int ret, usrctl_flags = 0;
1314 char sam_acct[SMB_SAMACCT_MAXLEN];
1315 char fqhost[MAXHOSTNAMELEN];
1316 char usrctl_buf[16];
1317 char encrypt_buf[16];
1318 int max;
1319 smb_krb5_pn_set_t spn, upn;
1320
1321 syslog(LOG_DEBUG, "smb_ads_computer_op, op=%s dn=%s",
1322 (op == LDAP_MOD_ADD) ? "add" : "replace", dn);
1323
1324 if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1325 return (-1);
1326
1327 if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1328 return (-1);
1329
1330 /* The SPN attribute is multi-valued and must be 1 or greater */
1331 if (smb_krb5_get_pn_set(&spn, SMB_PN_SPN_ATTR, ah->domain) == 0)
1332 return (-1);
1333
1334 /* The UPN attribute is single-valued and cannot be zero */
1335 if (smb_krb5_get_pn_set(&upn, SMB_PN_UPN_ATTR, ah->domain) != 1) {
1336 smb_krb5_free_pn_set(&spn);
1337 smb_krb5_free_pn_set(&upn);
1338 return (-1);
1339 }
1340
1341 max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0))
1342 - (dclevel >= SMB_ADS_DCLEVEL_W2K8 ? 0 : 1);
1343
1344 if (smb_ads_alloc_attr(attrs, max) != 0) {
1345 smb_krb5_free_pn_set(&spn);
1346 smb_krb5_free_pn_set(&upn);
1347 return (-1);
1348 }
1349
1350 /* objectClass attribute is not modifiable. */
1351 if (op == LDAP_MOD_ADD) {
1352 attrs[++j]->mod_op = op;
1353 attrs[j]->mod_type = "objectClass";
1354 attrs[j]->mod_values = smb_ads_computer_objcls;
1355 }
1356
1357 attrs[++j]->mod_op = op;
1358 attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT;
1359 sam_val[0] = sam_acct;
1360 sam_val[1] = 0;
1361 attrs[j]->mod_values = sam_val;
1362
1363 attrs[++j]->mod_op = op;
1364 attrs[j]->mod_type = SMB_ADS_ATTR_UPN;
1365 attrs[j]->mod_values = upn.s_pns;
1366
1367 attrs[++j]->mod_op = op;
1368 attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
1369 attrs[j]->mod_values = spn.s_pns;
1370
1371 attrs[++j]->mod_op = op;
1372 attrs[j]->mod_type = SMB_ADS_ATTR_CTL;
1373 usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1374 SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD |
1375 SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE);
1376 (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
1377 ctl_val[0] = usrctl_buf;
1378 ctl_val[1] = 0;
1379 attrs[j]->mod_values = ctl_val;
1380
1381 attrs[++j]->mod_op = op;
1382 attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST;
1383 fqh_val[0] = fqhost;
1384 fqh_val[1] = 0;
1385 attrs[j]->mod_values = fqh_val;
1386
1387 /* enctypes support starting in Windows Server 2008 */
1388 if (dclevel > SMB_ADS_DCLEVEL_W2K3) {
1389 attrs[++j]->mod_op = op;
1390 attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES;
1391 (void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d",
1392 SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 +
1393 SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC);
1394 encrypt_val[0] = encrypt_buf;
1395 encrypt_val[1] = 0;
1396 attrs[j]->mod_values = encrypt_val;
1397 }
1398
1399 switch (op) {
1400 case LDAP_MOD_ADD:
1401 if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1402 syslog(LOG_NOTICE, "ldap_add: %s",
1403 ldap_err2string(ret));
1404 ret = -1;
1405 }
1406 break;
1407
1408 case LDAP_MOD_REPLACE:
1409 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1410 syslog(LOG_NOTICE, "ldap_modify: %s",
1411 ldap_err2string(ret));
1412 ret = -1;
1413 }
1414 break;
1415
1416 default:
1417 ret = -1;
1418
1419 }
1420
1421 smb_ads_free_attr(attrs);
1422 smb_krb5_free_pn_set(&spn);
1423 smb_krb5_free_pn_set(&upn);
1424
1425 return (ret);
1426 }
1427
1428 /*
1429 * Delete an ADS computer account.
1430 */
1431 static void
smb_ads_del_computer(smb_ads_handle_t * ah,char * dn)1432 smb_ads_del_computer(smb_ads_handle_t *ah, char *dn)
1433 {
1434 int rc;
1435
1436 if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS)
1437 smb_tracef("ldap_delete: %s", ldap_err2string(rc));
1438 }
1439
1440 /*
1441 * Gets the value of the given attribute.
1442 */
1443 static smb_ads_qstat_t
smb_ads_getattr(LDAP * ld,LDAPMessage * entry,smb_ads_avpair_t * avpair)1444 smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair)
1445 {
1446 char **vals;
1447 smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1448
1449 assert(avpair);
1450 avpair->avp_val = NULL;
1451
1452 syslog(LOG_DEBUG, "smbns: ads_getattr (%s)", avpair->avp_attr);
1453 vals = ldap_get_values(ld, entry, avpair->avp_attr);
1454 if (!vals) {
1455 syslog(LOG_DEBUG, "smbns: ads_getattr err: no vals");
1456 return (SMB_ADS_STAT_NOT_FOUND);
1457 }
1458 if (!vals[0]) {
1459 syslog(LOG_DEBUG, "smbns: ads_getattr err: no vals[0]");
1460 ldap_value_free(vals);
1461 return (SMB_ADS_STAT_NOT_FOUND);
1462 }
1463
1464 avpair->avp_val = strdup(vals[0]);
1465 if (!avpair->avp_val) {
1466 syslog(LOG_DEBUG, "smbns: ads_getattr err: no mem");
1467 rc = SMB_ADS_STAT_ERR;
1468 } else {
1469 syslog(LOG_DEBUG, "smbns: ads_getattr (%s) OK, val=%s",
1470 avpair->avp_attr, avpair->avp_val);
1471 }
1472
1473 ldap_value_free(vals);
1474 return (rc);
1475 }
1476
1477 /*
1478 * Process query's result, making sure we have what we need.
1479 *
1480 * There's some non-obvious logic here for checking the returned
1481 * DNS name for the machine account, trying to avoid modifying
1482 * someone else's machine account. When we search for a machine
1483 * account we always ask for the DNS name. For a pre-created
1484 * machine account, the DNS name will be not set, and that's OK.
1485 * If we see a DNS name and it doesn't match our DNS name, we'll
1486 * assume the account belongs to someone else and return "DUP".
1487 *
1488 * Only do the DNS name check for our initial search for the
1489 * machine account, which has avpair->avp_attr = SMB_ADS_ATTR_DN
1490 */
1491 static smb_ads_qstat_t
smb_ads_get_qstat(smb_ads_handle_t * ah,LDAPMessage * res,smb_ads_avpair_t * avpair)1492 smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
1493 smb_ads_avpair_t *avpair)
1494 {
1495 smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1496 LDAPMessage *entry;
1497
1498 if (ldap_count_entries(ah->ld, res) == 0) {
1499 syslog(LOG_DEBUG, "smbns: find_computer, "
1500 "ldap_count_entries zero");
1501 return (SMB_ADS_STAT_NOT_FOUND);
1502 }
1503
1504 if ((entry = ldap_first_entry(ah->ld, res)) == NULL) {
1505 syslog(LOG_DEBUG, "smbns: find_computer, "
1506 "ldap_first_entry error");
1507 return (SMB_ADS_STAT_ERR);
1508 }
1509
1510 /* Have an LDAP entry (found something) */
1511 syslog(LOG_DEBUG, "smbns: find_computer, have LDAP resp.");
1512
1513 if (avpair != NULL &&
1514 strcmp(avpair->avp_attr, SMB_ADS_ATTR_DN) == 0) {
1515 char fqhost[MAXHOSTNAMELEN];
1516 smb_ads_avpair_t dnshost_avp;
1517
1518 syslog(LOG_DEBUG, "smbns: find_computer, check DNS name");
1519
1520 if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1521 return (SMB_ADS_STAT_ERR);
1522
1523 dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
1524 dnshost_avp.avp_val = NULL;
1525 rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
1526
1527 /*
1528 * Status from finding the DNS name value
1529 */
1530 switch (rc) {
1531 case SMB_ADS_STAT_FOUND:
1532 /*
1533 * Found a DNS name. If it doesn't match ours,
1534 * returns SMB_ADS_STAT_DUP to avoid overwriting
1535 * the computer account of another system whose
1536 * NetBIOS name collides with that of the current
1537 * system.
1538 */
1539 if (strcasecmp(dnshost_avp.avp_val, fqhost)) {
1540 syslog(LOG_DEBUG, "smbns: find_computer, "
1541 "duplicate name (%s)",
1542 dnshost_avp.avp_val);
1543 rc = SMB_ADS_STAT_DUP;
1544 }
1545 free(dnshost_avp.avp_val);
1546 break;
1547
1548 case SMB_ADS_STAT_NOT_FOUND:
1549 /*
1550 * No dNSHostname attribute, so probably a
1551 * pre-created computer account. Use it.
1552 *
1553 * Returns SMB_ADS_STAT_FOUND for the status
1554 * of finding the machine account.
1555 */
1556 rc = SMB_ADS_STAT_FOUND;
1557 break;
1558
1559 default:
1560 break;
1561 }
1562
1563 if (rc != SMB_ADS_STAT_FOUND)
1564 return (rc);
1565 }
1566
1567 if (avpair) {
1568 syslog(LOG_DEBUG, "smbns: find_computer, check %s",
1569 avpair->avp_attr);
1570 rc = smb_ads_getattr(ah->ld, entry, avpair);
1571 }
1572
1573 return (rc);
1574 }
1575
1576 /*
1577 * smb_ads_lookup_computer_n_attr
1578 *
1579 * If avpair is NULL, checks the status of the specified computer account.
1580 * Otherwise, looks up the value of the specified computer account's attribute.
1581 * If found, the value field of the avpair will be allocated and set. The
1582 * caller should free the allocated buffer. Caller avpair requests are:
1583 * smb_ads_find_computer() asks for SMB_ADS_ATTR_DN
1584 * smb_ads_lookup_computer_attr_kvno() SMB_ADS_ATTR_KVNO
1585 *
1586 * Return:
1587 * SMB_ADS_STAT_FOUND - if both the computer and the specified attribute is
1588 * found.
1589 * SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute
1590 * is not found.
1591 * SMB_ADS_STAT_DUP - if the computer account is already used by other systems
1592 * in the AD. This could happen if the hostname of multiple
1593 * systems resolved to the same NetBIOS name.
1594 * SMB_ADS_STAT_ERR - any failure.
1595 */
1596 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)1597 smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair,
1598 int scope, char *dn)
1599 {
1600 char *attrs[3], filter[SMB_ADS_MAXBUFLEN];
1601 LDAPMessage *res;
1602 char sam_acct[SMB_SAMACCT_MAXLEN];
1603 char tmpbuf[SMB_ADS_MAXBUFLEN];
1604 smb_ads_qstat_t rc;
1605 int err;
1606
1607 if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1608 return (SMB_ADS_STAT_ERR);
1609
1610 res = NULL;
1611 attrs[0] = SMB_ADS_ATTR_DNSHOST;
1612 attrs[1] = NULL;
1613 attrs[2] = NULL;
1614
1615 if (avpair) {
1616 if (!avpair->avp_attr)
1617 return (SMB_ADS_STAT_ERR);
1618
1619 attrs[1] = avpair->avp_attr;
1620 }
1621
1622 if (smb_ads_escape_search_filter_chars(sam_acct, tmpbuf) != 0)
1623 return (SMB_ADS_STAT_ERR);
1624
1625 (void) snprintf(filter, sizeof (filter),
1626 "(&(objectClass=computer)(%s=%s))",
1627 SMB_ADS_ATTR_SAMACCT, tmpbuf);
1628
1629 syslog(LOG_DEBUG, "smbns: lookup_computer, "
1630 "dn=%s, scope=%d", dn, scope);
1631 syslog(LOG_DEBUG, "smbns: lookup_computer, "
1632 "filter=%s", filter);
1633 syslog(LOG_DEBUG, "smbns: lookup_computer, "
1634 "attrs[0]=%s", attrs[0]);
1635 syslog(LOG_DEBUG, "smbns: lookup_computer, "
1636 "attrs[1]=%s", attrs[1] ? attrs[1] : "");
1637
1638 err = ldap_search_s(ah->ld, dn, scope, filter, attrs, 0, &res);
1639 if (err != LDAP_SUCCESS) {
1640 syslog(LOG_DEBUG, "smbns: lookup_computer, "
1641 "LDAP search failed, dn=(%s), scope=%d, err=%s",
1642 dn, scope, ldap_err2string(err));
1643 (void) ldap_msgfree(res);
1644 return (SMB_ADS_STAT_NOT_FOUND);
1645 }
1646 syslog(LOG_DEBUG, "smbns: find_computer, ldap_search OK");
1647
1648 rc = smb_ads_get_qstat(ah, res, avpair);
1649 if (rc == SMB_ADS_STAT_FOUND) {
1650 syslog(LOG_DEBUG, "smbns: find_computer, attr %s = %s",
1651 avpair->avp_attr, avpair->avp_val);
1652 } else {
1653 syslog(LOG_DEBUG, "smbns: find_computer, "
1654 "get query status, error %d", rc);
1655 }
1656
1657 /* free the search results */
1658 (void) ldap_msgfree(res);
1659
1660 return (rc);
1661 }
1662
1663 /*
1664 * smb_ads_find_computer
1665 *
1666 * Searches the directory for the machine account (SamAccountName)
1667 * If found, 'dn' will be set to the distinguished name of the system's
1668 * AD computer object.
1669 */
1670 static smb_ads_qstat_t
smb_ads_find_computer(smb_ads_handle_t * ah,char * dn)1671 smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
1672 {
1673 smb_ads_qstat_t stat;
1674 smb_ads_avpair_t avpair;
1675
1676 avpair.avp_attr = SMB_ADS_ATTR_DN;
1677 avpair.avp_val = NULL;
1678
1679 (void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX);
1680 stat = smb_ads_lookup_computer_n_attr(ah, &avpair,
1681 LDAP_SCOPE_SUBTREE, dn);
1682
1683 if (stat == SMB_ADS_STAT_FOUND) {
1684 (void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX);
1685 free(avpair.avp_val);
1686 }
1687
1688 return (stat);
1689 }
1690
1691 /*
1692 * Set a machine account password using LDAP
1693 *
1694 * Compose the funky unicode password string per
1695 * [MS-ADTS] 3.1.1.3.1.5.1 unicodePwd
1696 * which includes literal double quotes.
1697 */
1698 static int
smb_ads_update_acct_passwd(smb_ads_handle_t * ah,char * passwd,char * dn)1699 smb_ads_update_acct_passwd(smb_ads_handle_t *ah, char *passwd, char *dn)
1700 {
1701 LDAPMod *attrs[2];
1702 struct berval *bvp[2];
1703 struct berval bval;
1704 char *qpass_buf = NULL; /* quoted password */
1705 int qpass_len = 0;
1706 smb_wchar_t *ucpwd_buf = NULL; /* unicode pw */
1707 int ucpwd_len = 0; /* length in bytes */
1708 int ret = 0;
1709
1710 if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
1711 return (LDAP_NO_MEMORY);
1712
1713 qpass_len = asprintf(&qpass_buf, "\"%s\"", passwd);
1714 if (qpass_len < 0) {
1715 qpass_len = 0;
1716 ret = LDAP_NO_MEMORY;
1717 goto out;
1718 }
1719
1720 /*
1721 * Allocate and fill Unicode passwd buffer.
1722 * Size to include a smb_wchar_t null termination.
1723 */
1724 ucpwd_len = smb_wcequiv_strlen(qpass_buf);
1725 ucpwd_buf = calloc(1, ucpwd_len + sizeof (smb_wchar_t));
1726 if (ucpwd_buf == NULL) {
1727 ret = LDAP_NO_MEMORY;
1728 goto out;
1729 }
1730 ret = smb_mbstowcs(ucpwd_buf, qpass_buf, ucpwd_len / 2);
1731 if (ret < 0) {
1732 ret = LDAP_ENCODING_ERROR;
1733 goto out;
1734 }
1735
1736 bval.bv_val = (char *)ucpwd_buf;
1737 bval.bv_len = ucpwd_len;
1738 bvp[0] = &bval;
1739 bvp[1] = NULL;
1740
1741 attrs[0]->mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
1742 attrs[0]->mod_type = SMB_ADS_ATTR_UCPWD;
1743 attrs[0]->mod_bvalues = bvp;
1744
1745 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1746 syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
1747 }
1748
1749 out:
1750 freezero(ucpwd_buf, ucpwd_len);
1751 freezero(qpass_buf, qpass_len);
1752 smb_ads_free_attr(attrs);
1753 return (ret);
1754 }
1755
1756 /*
1757 * smb_ads_update_computer_cntrl_attr
1758 *
1759 * Modify the user account control attribute of an existing computer
1760 * object on AD.
1761 *
1762 * Returns LDAP error code.
1763 */
1764 static int
smb_ads_update_computer_cntrl_attr(smb_ads_handle_t * ah,int flags,char * dn)1765 smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn)
1766 {
1767 LDAPMod *attrs[2];
1768 char *ctl_val[2];
1769 int ret = 0;
1770 char usrctl_buf[16];
1771
1772 if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
1773 return (LDAP_NO_MEMORY);
1774
1775 attrs[0]->mod_op = LDAP_MOD_REPLACE;
1776 attrs[0]->mod_type = SMB_ADS_ATTR_CTL;
1777
1778 (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags);
1779 ctl_val[0] = usrctl_buf;
1780 ctl_val[1] = 0;
1781 attrs[0]->mod_values = ctl_val;
1782 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1783 syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
1784 }
1785
1786 smb_ads_free_attr(attrs);
1787 return (ret);
1788 }
1789
1790 /*
1791 * smb_ads_lookup_computer_attr_kvno
1792 *
1793 * Lookup the value of the Kerberos version number attribute of the computer
1794 * account.
1795 */
1796 static krb5_kvno
smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t * ah,char * dn)1797 smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
1798 {
1799 smb_ads_avpair_t avpair;
1800 int kvno = 1;
1801
1802 avpair.avp_attr = SMB_ADS_ATTR_KVNO;
1803 avpair.avp_val = NULL;
1804 if (smb_ads_lookup_computer_n_attr(ah, &avpair,
1805 LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) {
1806 kvno = atoi(avpair.avp_val);
1807 free(avpair.avp_val);
1808 }
1809
1810 return (kvno);
1811 }
1812
1813 /* Available as a work-around if smb_ads_update_acct_passwd fails */
1814 boolean_t smbns_ads_try_kpasswd = 0;
1815
1816 /*
1817 * smb_ads_join
1818 *
1819 * Besides the NT-4 style domain join (using MS-RPC), CIFS server also
1820 * provides the domain join using Kerberos Authentication, Keberos
1821 * Change & Set password, and LDAP protocols. Basically, AD join
1822 * operation would require the following tickets to be acquired for the
1823 * the user account that is provided for the domain join.
1824 *
1825 * 1) a Keberos TGT ticket,
1826 * 2) a ldap service ticket, and
1827 * 3) kadmin/changpw service ticket
1828 *
1829 * The ADS client first sends a ldap search request to find out whether
1830 * or not the workstation trust account already exists in the Active Directory.
1831 * The existing computer object for this workstation will be removed and
1832 * a new one will be added. The machine account password is randomly
1833 * generated and set for the newly created computer object using KPASSWD
1834 * protocol (See RFC 3244). Once the password is set, our ADS client
1835 * finalizes the machine account by modifying the user acount control
1836 * attribute of the computer object. Kerberos keys derived from the machine
1837 * account password will be stored locally in /etc/krb5/krb5.keytab file.
1838 * That would be needed while acquiring Kerberos TGT ticket for the host
1839 * principal after the domain join operation.
1840 */
1841 smb_ads_status_t
smb_ads_join(char * domain,char * container,char * user,char * usr_passwd,char * machine_passwd)1842 smb_ads_join(char *domain, char *container,
1843 char *user, char *usr_passwd, char *machine_passwd)
1844 {
1845 smb_ads_handle_t *ah = NULL;
1846 krb5_context ctx = NULL;
1847 krb5_principal *krb5princs = NULL;
1848 krb5_kvno kvno;
1849 boolean_t delete = B_TRUE;
1850 smb_ads_status_t rc;
1851 boolean_t new_acct;
1852 int dclevel, num, usrctl_flags = 0;
1853 smb_ads_qstat_t qstat;
1854 char dn[SMB_ADS_DN_MAX];
1855 char tmpfile[] = SMBNS_KRB5_KEYTAB_TMP;
1856 int cnt, x;
1857 smb_krb5_pn_set_t spns;
1858 krb5_enctype *encptr;
1859
1860 rc = smb_ads_open_main(&ah, domain, user, usr_passwd);
1861 if (rc != 0) {
1862 const char *s = smb_ads_strerror(rc);
1863 syslog(LOG_ERR, "smb_ads_join: open_main, error %s", s);
1864 smb_ccache_remove(SMB_CCACHE_PATH);
1865 return (rc);
1866 }
1867
1868 if ((dclevel = smb_ads_get_dc_level(ah)) == -1) {
1869 smb_ads_close(ah);
1870 smb_ccache_remove(SMB_CCACHE_PATH);
1871 return (SMB_ADJOIN_ERR_GET_DCLEVEL);
1872 }
1873
1874 qstat = smb_ads_find_computer(ah, dn);
1875 switch (qstat) {
1876 case SMB_ADS_STAT_FOUND:
1877 new_acct = B_FALSE;
1878 syslog(LOG_INFO, "smb_ads_join: machine account found."
1879 " Updating: %s", dn);
1880 if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
1881 smb_ads_close(ah);
1882 smb_ccache_remove(SMB_CCACHE_PATH);
1883 return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT);
1884 }
1885 break;
1886
1887 case SMB_ADS_STAT_NOT_FOUND:
1888 new_acct = B_TRUE;
1889 smb_ads_get_new_comp_dn(ah, dn, SMB_ADS_DN_MAX, container);
1890 syslog(LOG_INFO, "smb_ads_join: machine account not found."
1891 " Creating: %s", dn);
1892 if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
1893 smb_ads_close(ah);
1894 smb_ccache_remove(SMB_CCACHE_PATH);
1895 return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT);
1896 }
1897 break;
1898
1899 default:
1900 syslog(LOG_INFO, "smb_ads_find_computer, rc=%d", qstat);
1901 if (qstat == SMB_ADS_STAT_DUP)
1902 rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT;
1903 else
1904 rc = SMB_ADJOIN_ERR_TRUST_ACCT;
1905 smb_ads_close(ah);
1906 smb_ccache_remove(SMB_CCACHE_PATH);
1907 return (rc);
1908 }
1909
1910 if (smb_krb5_ctx_init(&ctx) != 0) {
1911 rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
1912 goto adjoin_cleanup;
1913 }
1914
1915 if (smb_krb5_get_pn_set(&spns, SMB_PN_KEYTAB_ENTRY, ah->domain) == 0) {
1916 rc = SMB_ADJOIN_ERR_GET_SPNS;
1917 goto adjoin_cleanup;
1918 }
1919
1920 if (smb_krb5_get_kprincs(ctx, spns.s_pns, spns.s_cnt, &krb5princs)
1921 != 0) {
1922 smb_krb5_free_pn_set(&spns);
1923 rc = SMB_ADJOIN_ERR_GET_SPNS;
1924 goto adjoin_cleanup;
1925 }
1926
1927 cnt = spns.s_cnt;
1928 smb_krb5_free_pn_set(&spns);
1929
1930 /* New machine_passwd was filled in by our caller. */
1931 x = smb_ads_update_acct_passwd(ah, machine_passwd, dn);
1932 if (x != 0 && smbns_ads_try_kpasswd)
1933 x = smb_krb5_setpwd(ctx, ah->domain, machine_passwd);
1934 if (x != 0) {
1935 rc = SMB_ADJOIN_ERR_KSETPWD;
1936 goto adjoin_cleanup;
1937 }
1938
1939 kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
1940
1941 /*
1942 * Only members of Domain Admins and Enterprise Admins can set
1943 * the TRUSTED_FOR_DELEGATION userAccountControl flag.
1944 * Try to set this, but don't fail the join if we can't.
1945 * Look into just removing this...
1946 */
1947 usrctl_flags = (
1948 SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1949 SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION |
1950 SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
1951 set_ctl_again:
1952 x = smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn);
1953 if (x != LDAP_SUCCESS && (usrctl_flags &
1954 SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION) != 0) {
1955 syslog(LOG_NOTICE, "Unable to set the "
1956 "TRUSTED_FOR_DELEGATION userAccountControl flag on the "
1957 "machine account in Active Directory. It may be necessary "
1958 "to set that via Active Directory administration.");
1959 usrctl_flags &=
1960 ~SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION;
1961 goto set_ctl_again;
1962 }
1963 if (x != LDAP_SUCCESS) {
1964 rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
1965 goto adjoin_cleanup;
1966 }
1967
1968 if (mktemp(tmpfile) == NULL) {
1969 rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
1970 goto adjoin_cleanup;
1971 }
1972
1973 encptr = smb_ads_get_enctypes(dclevel, &num);
1974 if (smb_krb5_kt_populate(ctx, ah->domain, krb5princs, cnt,
1975 tmpfile, kvno, machine_passwd, encptr, num) != 0) {
1976 rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
1977 goto adjoin_cleanup;
1978 }
1979
1980 delete = B_FALSE;
1981 rc = SMB_ADS_SUCCESS;
1982
1983 adjoin_cleanup:
1984 if (new_acct && delete)
1985 smb_ads_del_computer(ah, dn);
1986
1987 if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) {
1988 if (rc != SMB_ADJOIN_ERR_GET_SPNS)
1989 smb_krb5_free_kprincs(ctx, krb5princs, cnt);
1990 smb_krb5_ctx_fini(ctx);
1991 }
1992
1993 /* commit keytab file */
1994 if (rc == SMB_ADS_SUCCESS) {
1995 if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) {
1996 (void) unlink(tmpfile);
1997 rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB;
1998 }
1999 } else {
2000 (void) unlink(tmpfile);
2001 }
2002
2003 smb_ads_close(ah);
2004 smb_ccache_remove(SMB_CCACHE_PATH);
2005 return (rc);
2006 }
2007
2008 struct xlate_table {
2009 int err;
2010 const char * const msg;
2011 };
2012
2013 static const struct xlate_table
2014 adjoin_table[] = {
2015 { SMB_ADS_SUCCESS, "Success" },
2016 { SMB_ADS_KRB5_INIT_CTX,
2017 "Failed creating a Kerberos context." },
2018 { SMB_ADS_KRB5_CC_DEFAULT,
2019 "Failed to resolve default credential cache." },
2020 { SMB_ADS_KRB5_PARSE_PRINCIPAL,
2021 "Failed parsing the user principal name." },
2022 { SMB_ADS_KRB5_GET_INIT_CREDS_OTHER,
2023 "Failed getting initial credentials. (See svc. log)" },
2024 { SMB_ADS_KRB5_GET_INIT_CREDS_PW,
2025 "Failed getting initial credentials. (Wrong password?)" },
2026 { SMB_ADS_KRB5_GET_INIT_CREDS_SKEW,
2027 "Failed getting initial credentials. (Clock skew too great)" },
2028 { SMB_ADS_KRB5_CC_INITIALIZE,
2029 "Failed initializing the credential cache." },
2030 { SMB_ADS_KRB5_CC_STORE_CRED,
2031 "Failed to update the credential cache." },
2032 { SMB_ADS_CANT_LOCATE_DC,
2033 "Failed to locate a domain controller." },
2034 { SMB_ADS_LDAP_INIT,
2035 "Failed to create an LDAP handle." },
2036 { SMB_ADS_LDAP_SETOPT,
2037 "Failed to set an LDAP option." },
2038 { SMB_ADS_LDAP_SET_DOM,
2039 "Failed to set the LDAP handle DN." },
2040 { SMB_ADS_LDAP_SASL_BIND,
2041 "Failed to bind the LDAP handle. "
2042 "Usually indicates an authentication problem." },
2043
2044 { SMB_ADJOIN_ERR_GEN_PWD,
2045 "Failed to generate machine password." },
2046 { SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of "
2047 "the domain controller. The rootDSE attribute named "
2048 "\"domainControllerFunctionality\" is missing from the "
2049 "Active Directory." },
2050 { SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the "
2051 "workstation trust account." },
2052 { SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the "
2053 "workstation trust account." },
2054 { SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the "
2055 "workstation trust account because its name is already "
2056 "in use." },
2057 { SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the "
2058 "workstation trust account" },
2059 { SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos "
2060 "context." },
2061 { SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos "
2062 "principals." },
2063 { SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." },
2064 { SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR, "Failed to modify "
2065 "userAccountControl attribute of the workstation trust "
2066 "account." },
2067 { SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local "
2068 "keytab file (i.e /etc/krb5/krb5.keytab)." },
2069 { SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap "
2070 "configuration." },
2071 { SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap "
2072 "service." },
2073 { SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to "
2074 "local keytab file (i.e. /etc/krb5/krb5.keytab)." },
2075 { SMB_ADJOIN_ERR_AUTH_NETLOGON,
2076 "Failed to authenticate using the new computer account." },
2077 { SMB_ADJOIN_ERR_STORE_PROPS,
2078 "Failed to store computer account information locally." },
2079 { SMB_ADJOIN_ERR_ALREADY_JOINED,
2080 "Already joined to a domain. "
2081 "Run \"smbadm join -w workgroup\" first." },
2082 { 0, NULL }
2083 };
2084
2085 /*
2086 * smb_ads_strerror
2087 *
2088 * Lookup an error message for the specific adjoin error code.
2089 */
2090 const char *
smb_ads_strerror(int err)2091 smb_ads_strerror(int err)
2092 {
2093 const struct xlate_table *xt;
2094
2095 if (err > 0 && err < SMB_ADS_ERRNO_GAP)
2096 return (strerror(err));
2097
2098 for (xt = adjoin_table; xt->msg; xt++)
2099 if (xt->err == err)
2100 return (xt->msg);
2101
2102 return ("Unknown error code.");
2103 }
2104
2105 void
smb_ads_log_errmsg(smb_ads_status_t err)2106 smb_ads_log_errmsg(smb_ads_status_t err)
2107 {
2108 const char *s = smb_ads_strerror(err);
2109 syslog(LOG_NOTICE, "%s", s);
2110 }
2111
2112
2113 /*
2114 * smb_ads_lookup_msdcs
2115 *
2116 * If server argument is set, try to locate the specified DC.
2117 * If it is set to empty string, locate any DCs in the specified domain.
2118 * Returns the discovered DC via buf.
2119 *
2120 * fqdn - fully-qualified domain name
2121 * dci - the name and address of the found DC
2122 */
2123 uint32_t
smb_ads_lookup_msdcs(char * fqdn,smb_dcinfo_t * dci)2124 smb_ads_lookup_msdcs(char *fqdn, smb_dcinfo_t *dci)
2125 {
2126 smb_ads_host_info_t *hinfo = NULL;
2127 char ipstr[INET6_ADDRSTRLEN];
2128
2129 if (!fqdn || !dci)
2130 return (NT_STATUS_INTERNAL_ERROR);
2131
2132 ipstr[0] = '\0';
2133 if ((hinfo = smb_ads_find_host(fqdn)) == NULL)
2134 return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
2135
2136 (void) smb_inet_ntop(&hinfo->ipaddr, ipstr,
2137 SMB_IPSTRLEN(hinfo->ipaddr.a_family));
2138 smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr);
2139
2140 (void) strlcpy(dci->dc_name, hinfo->name, sizeof (dci->dc_name));
2141 dci->dc_addr = hinfo->ipaddr;
2142 dci->dc_flags = hinfo->flags;
2143
2144 free(hinfo);
2145 return (NT_STATUS_SUCCESS);
2146 }
2147
2148 static krb5_enctype *
smb_ads_get_enctypes(int dclevel,int * num)2149 smb_ads_get_enctypes(int dclevel, int *num)
2150 {
2151 krb5_enctype *encptr;
2152
2153 if (dclevel >= SMB_ADS_DCLEVEL_W2K8) {
2154 *num = sizeof (w2k8enctypes) / sizeof (krb5_enctype);
2155 encptr = w2k8enctypes;
2156 } else {
2157 *num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype);
2158 encptr = pre_w2k8enctypes;
2159 }
2160
2161 return (encptr);
2162 }
2163