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