dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / lib / smbsrv / libsmbns / common / smbns_ads.c
blob4c574786d5acf0d949e08f5d910c91496ef4b369
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
26 #include <sys/param.h>
27 #include <ldap.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <sys/time.h>
34 #include <netdb.h>
35 #include <pthread.h>
36 #include <unistd.h>
37 #include <arpa/nameser.h>
38 #include <resolv.h>
39 #include <sys/synch.h>
40 #include <string.h>
41 #include <strings.h>
42 #include <fcntl.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <assert.h>
46 #include <sasl/sasl.h>
47 #include <note.h>
48 #include <errno.h>
49 #include <cryptoutil.h>
50 #include <ads/dsgetdc.h>
52 #include <smbsrv/libsmbns.h>
53 #include <smbns_dyndns.h>
54 #include <smbns_krb.h>
56 #define SMB_ADS_AF_UNKNOWN(x) (((x)->ipaddr.a_family != AF_INET) && \
57 ((x)->ipaddr.a_family != AF_INET6))
59 #define SMB_ADS_MAXBUFLEN 100
60 #define SMB_ADS_DN_MAX 300
61 #define SMB_ADS_MAXMSGLEN 512
62 #define SMB_ADS_COMPUTERS_CN "Computers"
63 #define SMB_ADS_COMPUTER_NUM_ATTR 8
64 #define SMB_ADS_SHARE_NUM_ATTR 3
65 #define SMB_ADS_SITE_MAX MAXHOSTNAMELEN
67 #define SMB_ADS_MSDCS_SRV_DC_RR "_ldap._tcp.dc._msdcs"
68 #define SMB_ADS_MSDCS_SRV_SITE_RR "_ldap._tcp.%s._sites.dc._msdcs"
71 * domainControllerFunctionality
73 * This rootDSE attribute indicates the functional level of the DC.
75 #define SMB_ADS_ATTR_DCLEVEL "domainControllerFunctionality"
76 #define SMB_ADS_DCLEVEL_W2K 0
77 #define SMB_ADS_DCLEVEL_W2K3 2
78 #define SMB_ADS_DCLEVEL_W2K8 3
79 #define SMB_ADS_DCLEVEL_W2K8_R2 4
82 * msDs-supportedEncryptionTypes (Windows Server 2008 only)
84 * This attribute defines the encryption types supported by the system.
85 * Encryption Types:
86 * - DES cbc mode with CRC-32
87 * - DES cbc mode with RSA-MD5
88 * - ArcFour with HMAC/md5
89 * - AES-128
90 * - AES-256
92 #define SMB_ADS_ATTR_ENCTYPES "msDs-supportedEncryptionTypes"
93 #define SMB_ADS_ENC_DES_CRC 1
94 #define SMB_ADS_ENC_DES_MD5 2
95 #define SMB_ADS_ENC_RC4 4
96 #define SMB_ADS_ENC_AES128 8
97 #define SMB_ADS_ENC_AES256 16
99 static krb5_enctype w2k8enctypes[] = {
100 ENCTYPE_AES256_CTS_HMAC_SHA1_96,
101 ENCTYPE_AES128_CTS_HMAC_SHA1_96,
102 ENCTYPE_ARCFOUR_HMAC,
103 ENCTYPE_DES_CBC_CRC,
104 ENCTYPE_DES_CBC_MD5,
107 static krb5_enctype pre_w2k8enctypes[] = {
108 ENCTYPE_ARCFOUR_HMAC,
109 ENCTYPE_DES_CBC_CRC,
110 ENCTYPE_DES_CBC_MD5,
113 #define SMB_ADS_ATTR_SAMACCT "sAMAccountName"
114 #define SMB_ADS_ATTR_UPN "userPrincipalName"
115 #define SMB_ADS_ATTR_SPN "servicePrincipalName"
116 #define SMB_ADS_ATTR_CTL "userAccountControl"
117 #define SMB_ADS_ATTR_DNSHOST "dNSHostName"
118 #define SMB_ADS_ATTR_KVNO "msDS-KeyVersionNumber"
119 #define SMB_ADS_ATTR_DN "distinguishedName"
122 * UserAccountControl flags: manipulate user account properties.
124 * The hexadecimal value of the following property flags are based on MSDN
125 * article # 305144.
127 #define SMB_ADS_USER_ACCT_CTL_SCRIPT 0x00000001
128 #define SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE 0x00000002
129 #define SMB_ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED 0x00000008
130 #define SMB_ADS_USER_ACCT_CTL_LOCKOUT 0x00000010
131 #define SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD 0x00000020
132 #define SMB_ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE 0x00000040
133 #define SMB_ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED 0x00000080
134 #define SMB_ADS_USER_ACCT_CTL_TMP_DUP_ACCT 0x00000100
135 #define SMB_ADS_USER_ACCT_CTL_NORMAL_ACCT 0x00000200
136 #define SMB_ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT 0x00000800
137 #define SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT 0x00001000
138 #define SMB_ADS_USER_ACCT_CTL_SRV_TRUST_ACCT 0x00002000
139 #define SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD 0x00010000
140 #define SMB_ADS_USER_ACCT_CTL_MNS_LOGON_ACCT 0x00020000
141 #define SMB_ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED 0x00040000
142 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION 0x00080000
143 #define SMB_ADS_USER_ACCT_CTL_NOT_DELEGATED 0x00100000
144 #define SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY 0x00200000
145 #define SMB_ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH 0x00400000
146 #define SMB_ADS_USER_ACCT_CTL_PASSWD_EXPIRED 0x00800000
147 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION 0x01000000
150 * Length of "dc=" prefix.
152 #define SMB_ADS_DN_PREFIX_LEN 3
154 static char *smb_ads_computer_objcls[] = {
155 "top", "person", "organizationalPerson",
156 "user", "computer", NULL
159 static char *smb_ads_share_objcls[] = {
160 "top", "leaf", "connectionPoint", "volume", NULL
163 /* Cached ADS server to communicate with */
164 static smb_ads_host_info_t *smb_ads_cached_host_info = NULL;
165 static mutex_t smb_ads_cached_host_mtx;
168 * SMB ADS config cache is maintained to facilitate the detection of
169 * changes in configuration that is relevant to AD selection.
171 typedef struct smb_ads_config {
172 char c_site[SMB_ADS_SITE_MAX];
173 mutex_t c_mtx;
174 } smb_ads_config_t;
176 static smb_ads_config_t smb_ads_cfg;
179 /* attribute/value pair */
180 typedef struct smb_ads_avpair {
181 char *avp_attr;
182 char *avp_val;
183 } smb_ads_avpair_t;
185 /* query status */
186 typedef enum smb_ads_qstat {
187 SMB_ADS_STAT_ERR = -2,
188 SMB_ADS_STAT_DUP,
189 SMB_ADS_STAT_NOT_FOUND,
190 SMB_ADS_STAT_FOUND
191 } smb_ads_qstat_t;
193 typedef struct smb_ads_host_list {
194 int ah_cnt;
195 smb_ads_host_info_t *ah_list;
196 } smb_ads_host_list_t;
198 static int smb_ads_open_main(smb_ads_handle_t **, char *, char *, char *);
199 static int smb_ads_add_computer(smb_ads_handle_t *, int, char *);
200 static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *);
201 static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *);
202 static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *,
203 smb_ads_avpair_t *, int, char *);
204 static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *);
205 static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *);
206 static void smb_ads_free_cached_host(void);
207 static int smb_ads_alloc_attr(LDAPMod **, int);
208 static void smb_ads_free_attr(LDAPMod **);
209 static int smb_ads_get_dc_level(smb_ads_handle_t *);
210 static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *, char *);
211 static smb_ads_qstat_t smb_ads_getattr(LDAP *, LDAPMessage *,
212 smb_ads_avpair_t *);
213 static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *, LDAPMessage *,
214 smb_ads_avpair_t *);
215 static boolean_t smb_ads_is_same_domain(char *, char *);
216 static smb_ads_host_info_t *smb_ads_dup_host_info(smb_ads_host_info_t *);
217 static char *smb_ads_get_sharedn(const char *, const char *, const char *);
218 static krb5_enctype *smb_ads_get_enctypes(int, int *);
221 * smb_ads_init
223 * Initializes the ADS config cache.
225 void
226 smb_ads_init(void)
228 (void) mutex_lock(&smb_ads_cfg.c_mtx);
229 (void) smb_config_getstr(SMB_CI_ADS_SITE,
230 smb_ads_cfg.c_site, SMB_ADS_SITE_MAX);
231 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
233 /* Force -lads to load, for dtrace. */
234 DsFreeDcInfo(NULL);
237 void
238 smb_ads_fini(void)
240 smb_ads_free_cached_host();
244 * smb_ads_refresh
246 * This function will be called when smb/server SMF service is refreshed.
247 * (See smbd_join.c)
249 * Clearing the smb_ads_cached_host_info would allow the next DC
250 * discovery process to pick up an AD based on the new AD configuration.
252 void
253 smb_ads_refresh(boolean_t force_rediscovery)
255 char new_site[SMB_ADS_SITE_MAX];
257 (void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX);
258 (void) mutex_lock(&smb_ads_cfg.c_mtx);
259 (void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX);
260 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
262 smb_ads_free_cached_host();
264 if (force_rediscovery) {
265 (void) _DsForceRediscovery(NULL, 0);
271 * smb_ads_build_unc_name
273 * Construct the UNC name of the share object in the format of
274 * \\hostname.domain\shareUNC
276 * Returns 0 on success, -1 on error.
279 smb_ads_build_unc_name(char *unc_name, int maxlen,
280 const char *hostname, const char *shareUNC)
282 char my_domain[MAXHOSTNAMELEN];
284 if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0)
285 return (-1);
287 (void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s",
288 hostname, my_domain, shareUNC);
289 return (0);
293 * The cached ADS host is no longer valid if one of the following criteria
294 * is satisfied:
296 * 1) not in the specified domain
297 * 2) not the sought host (if specified)
298 * 3) not reachable
300 * The caller is responsible for acquiring the smb_ads_cached_host_mtx lock
301 * prior to calling this function.
303 * Return B_TRUE if the cache host is still valid. Otherwise, return B_FALSE.
305 static boolean_t
306 smb_ads_validate_cache_host(char *domain)
308 if (!smb_ads_cached_host_info)
309 return (B_FALSE);
311 if (!smb_ads_is_same_domain(smb_ads_cached_host_info->name, domain))
312 return (B_FALSE);
314 return (B_TRUE);
318 * smb_ads_match_hosts_same_domain
320 * Returns true, if the cached ADS host is in the same domain as the
321 * current (given) domain.
323 static boolean_t
324 smb_ads_is_same_domain(char *cached_host_name, char *current_domain)
326 char *cached_host_domain;
328 if ((cached_host_name == NULL) || (current_domain == NULL))
329 return (B_FALSE);
331 cached_host_domain = strchr(cached_host_name, '.');
332 if (cached_host_domain == NULL)
333 return (B_FALSE);
335 ++cached_host_domain;
336 if (smb_strcasecmp(cached_host_domain, current_domain, 0))
337 return (B_FALSE);
339 return (B_TRUE);
343 * smb_ads_dup_host_info
345 * Duplicates the passed smb_ads_host_info_t structure.
346 * Caller must free memory allocated by this method.
348 * Returns a reference to the duplicated smb_ads_host_info_t structure.
349 * Returns NULL on error.
351 static smb_ads_host_info_t *
352 smb_ads_dup_host_info(smb_ads_host_info_t *ads_host)
354 smb_ads_host_info_t *dup_host;
356 if (ads_host == NULL)
357 return (NULL);
359 dup_host = malloc(sizeof (smb_ads_host_info_t));
361 if (dup_host != NULL)
362 bcopy(ads_host, dup_host, sizeof (smb_ads_host_info_t));
364 return (dup_host);
368 * smb_ads_find_host
370 * Finds an ADS host in a given domain.
372 * If the cached host is valid, it will be used. Otherwise, a DC will
373 * be selected based on the following criteria:
375 * 1) pdc (aka preferred DC) configuration
376 * 2) AD site configuration - the scope of the DNS lookup will be
377 * restricted to the specified site.
378 * 3) DC on the same subnet
379 * 4) DC with the lowest priority/highest weight
381 * The above items are listed in decreasing preference order. The selected
382 * DC must be online.
384 * If this function is called during domain join, the specified kpasswd server
385 * takes precedence over preferred DC, AD site, and so on.
387 * Parameters:
388 * domain: fully-qualified domain name.
390 * Returns:
391 * A copy of the cached host info is returned. The caller is responsible
392 * for deallocating the memory returned by this function.
394 /*ARGSUSED*/
395 smb_ads_host_info_t *
396 smb_ads_find_host(char *domain)
398 smb_ads_host_info_t *host = NULL;
399 DOMAIN_CONTROLLER_INFO *dci = NULL;
400 struct sockaddr_storage *ss;
401 uint32_t flags = DS_DS_FLAG;
402 uint32_t status;
403 int tries;
405 (void) mutex_lock(&smb_ads_cached_host_mtx);
406 if (smb_ads_validate_cache_host(domain)) {
407 host = smb_ads_dup_host_info(smb_ads_cached_host_info);
408 (void) mutex_unlock(&smb_ads_cached_host_mtx);
409 return (host);
412 (void) mutex_unlock(&smb_ads_cached_host_mtx);
413 smb_ads_free_cached_host();
416 * The _real_ DC Locator is over in idmapd.
417 * Door call over there to get it.
419 tries = 15;
420 again:
421 status = _DsGetDcName(
422 NULL, /* ComputerName */
423 domain,
424 NULL, /* DomainGuid */
425 NULL, /* SiteName */
426 flags,
427 &dci);
428 switch (status) {
429 case 0:
430 break;
432 * We can see these errors when joining a domain, if we race
433 * asking idmap for the DC before it knows the new domain.
435 case NT_STATUS_NO_SUCH_DOMAIN: /* Specified domain unknown */
436 case NT_STATUS_INVALID_SERVER_STATE: /* not in domain mode. */
437 if (--tries > 0) {
438 (void) sleep(1);
439 goto again;
441 /* FALLTHROUGH */
442 case NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND:
443 case NT_STATUS_CANT_WAIT: /* timeout over in idmap */
444 default:
445 return (NULL);
448 host = calloc(1, sizeof (*host));
449 if (host == NULL)
450 goto out;
452 (void) strlcpy(host->name, dci->DomainControllerName, MAXHOSTNAMELEN);
453 ss = (void *)dci->_sockaddr;
454 switch (ss->ss_family) {
455 case AF_INET: {
456 struct sockaddr_in *sin = (void *)ss;
457 host->port = ntohs(sin->sin_port);
458 host->ipaddr.a_family = AF_INET;
459 (void) memcpy(&host->ipaddr.a_ipv4, &sin->sin_addr,
460 sizeof (in_addr_t));
461 break;
463 case AF_INET6: {
464 struct sockaddr_in6 *sin6 = (void *)ss;
465 host->port = ntohs(sin6->sin6_port);
466 host->ipaddr.a_family = AF_INET6;
467 (void) memcpy(&host->ipaddr.a_ipv6, &sin6->sin6_addr,
468 sizeof (in6_addr_t));
469 break;
471 default:
472 syslog(LOG_ERR, "no addr for DC %s",
473 dci->DomainControllerName);
474 free(host);
475 host = NULL;
476 goto out;
479 (void) mutex_lock(&smb_ads_cached_host_mtx);
480 if (!smb_ads_cached_host_info)
481 smb_ads_cached_host_info = smb_ads_dup_host_info(host);
482 host = smb_ads_dup_host_info(smb_ads_cached_host_info);
483 (void) mutex_unlock(&smb_ads_cached_host_mtx);
485 out:
486 DsFreeDcInfo(dci);
487 return (host);
491 * Return the number of dots in a string.
493 static int
494 smb_ads_count_dots(const char *s)
496 int ndots = 0;
498 while (*s) {
499 if (*s++ == '.')
500 ndots++;
503 return (ndots);
507 * Convert a domain name in dot notation to distinguished name format,
508 * for example: sun.com -> dc=sun,dc=com.
510 * Returns a pointer to an allocated buffer containing the distinguished
511 * name.
513 static char *
514 smb_ads_convert_domain(const char *domain_name)
516 const char *s;
517 char *dn_name;
518 char buf[2];
519 int ndots;
520 int len;
522 if (domain_name == NULL || *domain_name == 0)
523 return (NULL);
525 ndots = smb_ads_count_dots(domain_name);
526 ++ndots;
527 len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1;
529 if ((dn_name = malloc(len)) == NULL)
530 return (NULL);
532 bzero(dn_name, len);
533 (void) strlcpy(dn_name, "dc=", len);
535 buf[1] = '\0';
536 s = domain_name;
538 while (*s) {
539 if (*s == '.') {
540 (void) strlcat(dn_name, ",dc=", len);
541 } else {
542 buf[0] = *s;
543 (void) strlcat(dn_name, buf, len);
545 ++s;
548 return (dn_name);
552 * smb_ads_free_cached_host
554 * Free the memory use by the global smb_ads_cached_host_info & set it to NULL.
556 static void
557 smb_ads_free_cached_host(void)
559 (void) mutex_lock(&smb_ads_cached_host_mtx);
560 if (smb_ads_cached_host_info) {
561 free(smb_ads_cached_host_info);
562 smb_ads_cached_host_info = NULL;
564 (void) mutex_unlock(&smb_ads_cached_host_mtx);
568 * smb_ads_open
569 * Open a LDAP connection to an ADS server if the system is in domain mode.
570 * Acquire both Kerberos TGT and LDAP service tickets for the host principal.
572 * This function should only be called after the system is successfully joined
573 * to a domain.
575 smb_ads_handle_t *
576 smb_ads_open(void)
578 char domain[MAXHOSTNAMELEN];
579 smb_ads_handle_t *h;
580 smb_ads_status_t err;
582 if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
583 return (NULL);
585 if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
586 return (NULL);
588 err = smb_ads_open_main(&h, domain, NULL, NULL);
589 if (err != 0) {
590 smb_ads_log_errmsg(err);
591 return (NULL);
594 return (h);
597 static int
598 smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts)
600 NOTE(ARGUNUSED(ld, defaults));
601 sasl_interact_t *interact;
603 if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
604 return (LDAP_PARAM_ERROR);
606 /* There should be no extra arguemnts for SASL/GSSAPI authentication */
607 for (interact = prompts; interact->id != SASL_CB_LIST_END;
608 interact++) {
609 interact->result = NULL;
610 interact->len = 0;
612 return (LDAP_SUCCESS);
616 * smb_ads_open_main
617 * Open a LDAP connection to an ADS server.
618 * If ADS is enabled and the administrative username, password, and
619 * ADS domain are defined then query DNS to find an ADS server if this is the
620 * very first call to this routine. After an ADS server is found then this
621 * server will be used everytime this routine is called until the system is
622 * rebooted or the ADS server becomes unavailable then an ADS server will
623 * be queried again. After the connection is made then an ADS handle
624 * is created to be returned.
626 * After the LDAP connection, the LDAP version will be set to 3 using
627 * ldap_set_option().
629 * The LDAP connection is bound before the ADS handle is returned.
630 * Parameters:
631 * domain - fully-qualified domain name
632 * user - the user account for whom the Kerberos TGT ticket and ADS
633 * service tickets are acquired.
634 * password - password of the specified user
636 * Returns:
637 * NULL : can't connect to ADS server or other errors
638 * smb_ads_handle_t* : handle to ADS server
640 static int
641 smb_ads_open_main(smb_ads_handle_t **hp, char *domain, char *user,
642 char *password)
644 smb_ads_handle_t *ah;
645 LDAP *ld;
646 int version = 3;
647 smb_ads_host_info_t *ads_host = NULL;
648 int err, rc;
650 *hp = NULL;
652 if (user != NULL) {
653 err = smb_kinit(domain, user, password);
654 if (err != 0)
655 return (err);
656 user = NULL;
657 password = NULL;
660 ads_host = smb_ads_find_host(domain);
661 if (ads_host == NULL)
662 return (SMB_ADS_CANT_LOCATE_DC);
664 ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
665 if (ah == NULL) {
666 free(ads_host);
667 return (ENOMEM);
670 (void) memset(ah, 0, sizeof (smb_ads_handle_t));
672 if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) {
673 syslog(LOG_ERR, "smbns: ldap_init failed");
674 smb_ads_free_cached_host();
675 free(ah);
676 free(ads_host);
677 return (SMB_ADS_LDAP_INIT);
680 if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version)
681 != LDAP_SUCCESS) {
682 smb_ads_free_cached_host();
683 free(ah);
684 free(ads_host);
685 (void) ldap_unbind(ld);
686 return (SMB_ADS_LDAP_SETOPT);
689 (void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
690 ah->ld = ld;
691 ah->domain = strdup(domain);
693 if (ah->domain == NULL) {
694 smb_ads_close(ah);
695 free(ads_host);
696 return (SMB_ADS_LDAP_SETOPT);
700 * ah->domain is often used for generating service principal name.
701 * Convert it to lower case for RFC 4120 section 6.2.1 conformance.
703 (void) smb_strlwr(ah->domain);
704 ah->domain_dn = smb_ads_convert_domain(domain);
705 if (ah->domain_dn == NULL) {
706 smb_ads_close(ah);
707 free(ads_host);
708 return (SMB_ADS_LDAP_SET_DOM);
711 ah->hostname = strdup(ads_host->name);
712 if (ah->hostname == NULL) {
713 smb_ads_close(ah);
714 free(ads_host);
715 return (ENOMEM);
717 (void) mutex_lock(&smb_ads_cfg.c_mtx);
718 if (*smb_ads_cfg.c_site != '\0') {
719 if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) {
720 smb_ads_close(ah);
721 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
722 free(ads_host);
723 return (ENOMEM);
725 } else {
726 ah->site = NULL;
728 (void) mutex_unlock(&smb_ads_cfg.c_mtx);
730 rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL,
731 LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL);
732 if (rc != LDAP_SUCCESS) {
733 syslog(LOG_ERR, "smbns: ldap_sasl_..._bind_s failed (%s)",
734 ldap_err2string(rc));
735 smb_ads_close(ah);
736 free(ads_host);
737 return (SMB_ADS_LDAP_SASL_BIND);
740 free(ads_host);
741 *hp = ah;
743 return (SMB_ADS_SUCCESS);
747 * smb_ads_close
748 * Close connection to ADS server and free memory allocated for ADS handle.
749 * LDAP unbind is called here.
750 * Parameters:
751 * ah: handle to ADS server
752 * Returns:
753 * void
755 void
756 smb_ads_close(smb_ads_handle_t *ah)
758 if (ah == NULL)
759 return;
760 /* close and free connection resources */
761 if (ah->ld)
762 (void) ldap_unbind(ah->ld);
764 free(ah->domain);
765 free(ah->domain_dn);
766 free(ah->hostname);
767 free(ah->site);
768 free(ah);
772 * smb_ads_alloc_attr
774 * Since the attrs is a null-terminated array, all elements
775 * in the array (except the last one) will point to allocated
776 * memory.
778 static int
779 smb_ads_alloc_attr(LDAPMod *attrs[], int num)
781 int i;
783 bzero(attrs, num * sizeof (LDAPMod *));
784 for (i = 0; i < (num - 1); i++) {
785 attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod));
786 if (attrs[i] == NULL) {
787 smb_ads_free_attr(attrs);
788 return (-1);
792 return (0);
796 * smb_ads_free_attr
797 * Free memory allocated when publishing a share.
798 * Parameters:
799 * attrs: an array of LDAPMod pointers
800 * Returns:
801 * None
803 static void
804 smb_ads_free_attr(LDAPMod *attrs[])
806 int i;
807 for (i = 0; attrs[i]; i++) {
808 free(attrs[i]);
813 * Returns share DN in an allocated buffer. The format of the DN is
814 * cn=<sharename>,<container RDNs>,<domain DN>
816 * If the domain DN is not included in the container parameter,
817 * then it will be appended to create the share DN.
819 * The caller must free the allocated buffer.
821 static char *
822 smb_ads_get_sharedn(const char *sharename, const char *container,
823 const char *domain_dn)
825 char *share_dn;
826 int rc, offset, container_len, domain_len;
827 boolean_t append_domain = B_TRUE;
829 container_len = strlen(container);
830 domain_len = strlen(domain_dn);
832 if (container_len >= domain_len) {
834 /* offset to last domain_len characters */
835 offset = container_len - domain_len;
837 if (smb_strcasecmp(container + offset,
838 domain_dn, domain_len) == 0)
839 append_domain = B_FALSE;
842 if (append_domain)
843 rc = asprintf(&share_dn, "cn=%s,%s,%s", sharename,
844 container, domain_dn);
845 else
846 rc = asprintf(&share_dn, "cn=%s,%s", sharename,
847 container);
849 return ((rc == -1) ? NULL : share_dn);
853 * smb_ads_add_share
854 * Call by smb_ads_publish_share to create share object in ADS.
855 * This routine specifies the attributes of an ADS LDAP share object. The first
856 * attribute and values define the type of ADS object, the share object. The
857 * second attribute and value define the UNC of the share data for the share
858 * object. The LDAP synchronous add command is used to add the object into ADS.
859 * The container location to add the object needs to specified.
860 * Parameters:
861 * ah : handle to ADS server
862 * adsShareName: name of share object to be created in ADS
863 * shareUNC : share name on NetForce
864 * adsContainer: location in ADS to create share object
866 * Returns:
867 * -1 : error
868 * 0 : success
871 smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName,
872 const char *unc_name, const char *adsContainer)
874 LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR];
875 int j = 0;
876 char *share_dn;
877 int ret;
878 char *unc_names[] = {(char *)unc_name, NULL};
880 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
881 ah->domain_dn)) == NULL)
882 return (-1);
884 if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) {
885 free(share_dn);
886 return (-1);
889 attrs[j]->mod_op = LDAP_MOD_ADD;
890 attrs[j]->mod_type = "objectClass";
891 attrs[j]->mod_values = smb_ads_share_objcls;
893 attrs[++j]->mod_op = LDAP_MOD_ADD;
894 attrs[j]->mod_type = "uNCName";
895 attrs[j]->mod_values = unc_names;
897 if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) {
898 if (ret == LDAP_NO_SUCH_OBJECT) {
899 syslog(LOG_ERR, "Failed to publish share %s in" \
900 " AD. Container does not exist: %s.\n",
901 adsShareName, share_dn);
903 } else {
904 syslog(LOG_ERR, "Failed to publish share %s in" \
905 " AD: %s (%s).\n", adsShareName, share_dn,
906 ldap_err2string(ret));
908 smb_ads_free_attr(attrs);
909 free(share_dn);
910 return (ret);
912 free(share_dn);
913 smb_ads_free_attr(attrs);
915 return (0);
919 * smb_ads_del_share
920 * Call by smb_ads_remove_share to remove share object from ADS. The container
921 * location to remove the object needs to specified. The LDAP synchronous
922 * delete command is used.
923 * Parameters:
924 * ah : handle to ADS server
925 * adsShareName: name of share object in ADS to be removed
926 * adsContainer: location of share object in ADS
927 * Returns:
928 * -1 : error
929 * 0 : success
931 static int
932 smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName,
933 const char *adsContainer)
935 char *share_dn;
936 int ret;
938 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
939 ah->domain_dn)) == NULL)
940 return (-1);
942 if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) {
943 smb_tracef("ldap_delete: %s", ldap_err2string(ret));
944 free(share_dn);
945 return (-1);
947 free(share_dn);
949 return (0);
954 * smb_ads_escape_search_filter_chars
956 * This routine will escape the special characters found in a string
957 * that will later be passed to the ldap search filter.
959 * RFC 1960 - A String Representation of LDAP Search Filters
960 * 3. String Search Filter Definition
961 * If a value must contain one of the characters '*' OR '(' OR ')',
962 * these characters
963 * should be escaped by preceding them with the backslash '\' character.
965 * RFC 2252 - LDAP Attribute Syntax Definitions
966 * a backslash quoting mechanism is used to escape
967 * the following separator symbol character (such as "'", "$" or "#") if
968 * it should occur in that string.
970 static int
971 smb_ads_escape_search_filter_chars(const char *src, char *dst)
973 int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */
975 if (src == NULL || dst == NULL)
976 return (-1);
978 while (*src) {
979 if (!avail) {
980 *dst = 0;
981 return (-1);
984 switch (*src) {
985 case '\\':
986 case '\'':
987 case '$':
988 case '#':
989 case '*':
990 case '(':
991 case ')':
992 *dst++ = '\\';
993 avail--;
994 /* fall through */
996 default:
997 *dst++ = *src++;
998 avail--;
1002 *dst = 0;
1004 return (0);
1008 * smb_ads_lookup_share
1009 * The search filter is set to search for a specific share name in the
1010 * specified ADS container. The LDSAP synchronous search command is used.
1011 * Parameters:
1012 * ah : handle to ADS server
1013 * adsShareName: name of share object in ADS to be searched
1014 * adsContainer: location of share object in ADS
1015 * Returns:
1016 * -1 : error
1017 * 0 : not found
1018 * 1 : found
1021 smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName,
1022 const char *adsContainer, char *unc_name)
1024 char *attrs[4], filter[SMB_ADS_MAXBUFLEN];
1025 char *share_dn;
1026 int ret;
1027 LDAPMessage *res;
1028 char tmpbuf[SMB_ADS_MAXBUFLEN];
1030 if (adsShareName == NULL || adsContainer == NULL)
1031 return (-1);
1033 if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1034 ah->domain_dn)) == NULL)
1035 return (-1);
1037 res = NULL;
1038 attrs[0] = "cn";
1039 attrs[1] = "objectClass";
1040 attrs[2] = "uNCName";
1041 attrs[3] = NULL;
1043 if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) {
1044 free(share_dn);
1045 return (-1);
1048 (void) snprintf(filter, sizeof (filter),
1049 "(&(objectClass=volume)(uNCName=%s))", tmpbuf);
1051 if ((ret = ldap_search_s(ah->ld, share_dn,
1052 LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) {
1053 if (ret != LDAP_NO_SUCH_OBJECT)
1054 smb_tracef("%s: ldap_search: %s", share_dn,
1055 ldap_err2string(ret));
1057 (void) ldap_msgfree(res);
1058 free(share_dn);
1059 return (0);
1062 (void) free(share_dn);
1064 /* no match is found */
1065 if (ldap_count_entries(ah->ld, res) == 0) {
1066 (void) ldap_msgfree(res);
1067 return (0);
1070 /* free the search results */
1071 (void) ldap_msgfree(res);
1073 return (1);
1077 * smb_ads_publish_share
1078 * Publish share into ADS. If a share name already exist in ADS in the same
1079 * container then the existing share object is removed before adding the new
1080 * share object.
1081 * Parameters:
1082 * ah : handle return from smb_ads_open
1083 * adsShareName: name of share to be added to ADS directory
1084 * shareUNC : name of share on client, can be NULL to use the same name
1085 * as adsShareName
1086 * adsContainer: location for share to be added in ADS directory, ie
1087 * ou=share_folder
1088 * uncType : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR
1089 * to use host ip addr for UNC.
1090 * Returns:
1091 * -1 : error
1092 * 0 : success
1095 smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName,
1096 const char *shareUNC, const char *adsContainer, const char *hostname)
1098 int ret;
1099 char unc_name[SMB_ADS_MAXBUFLEN];
1101 if (adsShareName == NULL || adsContainer == NULL)
1102 return (-1);
1104 if (shareUNC == 0 || *shareUNC == 0)
1105 shareUNC = adsShareName;
1107 if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1108 hostname, shareUNC) < 0)
1109 return (-1);
1111 ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1113 switch (ret) {
1114 case 1:
1115 (void) smb_ads_del_share(ah, adsShareName, adsContainer);
1116 ret = smb_ads_add_share(ah, adsShareName, unc_name,
1117 adsContainer);
1118 break;
1120 case 0:
1121 ret = smb_ads_add_share(ah, adsShareName, unc_name,
1122 adsContainer);
1123 if (ret == LDAP_ALREADY_EXISTS)
1124 ret = -1;
1126 break;
1128 case -1:
1129 default:
1130 /* return with error code */
1131 ret = -1;
1134 return (ret);
1138 * smb_ads_remove_share
1139 * Remove share from ADS. A search is done first before explicitly removing
1140 * the share.
1141 * Parameters:
1142 * ah : handle return from smb_ads_open
1143 * adsShareName: name of share to be removed from ADS directory
1144 * adsContainer: location for share to be removed from ADS directory, ie
1145 * ou=share_folder
1146 * Returns:
1147 * -1 : error
1148 * 0 : success
1151 smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName,
1152 const char *shareUNC, const char *adsContainer, const char *hostname)
1154 int ret;
1155 char unc_name[SMB_ADS_MAXBUFLEN];
1157 if (adsShareName == NULL || adsContainer == NULL)
1158 return (-1);
1159 if (shareUNC == 0 || *shareUNC == 0)
1160 shareUNC = adsShareName;
1162 if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1163 hostname, shareUNC) < 0)
1164 return (-1);
1166 ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1167 if (ret == 0)
1168 return (0);
1169 if (ret == -1)
1170 return (-1);
1172 return (smb_ads_del_share(ah, adsShareName, adsContainer));
1176 * smb_ads_get_default_comp_container_dn
1178 * Build the distinguished name for the default computer conatiner (i.e. the
1179 * pre-defined Computers container).
1181 static void
1182 smb_ads_get_default_comp_container_dn(smb_ads_handle_t *ah, char *buf,
1183 size_t buflen)
1185 (void) snprintf(buf, buflen, "cn=%s,%s", SMB_ADS_COMPUTERS_CN,
1186 ah->domain_dn);
1190 * smb_ads_get_default_comp_dn
1192 * Build the distinguished name for this system.
1194 static void
1195 smb_ads_get_default_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen)
1197 char nbname[NETBIOS_NAME_SZ];
1198 char container_dn[SMB_ADS_DN_MAX];
1200 (void) smb_getnetbiosname(nbname, sizeof (nbname));
1201 smb_ads_get_default_comp_container_dn(ah, container_dn, SMB_ADS_DN_MAX);
1202 (void) snprintf(buf, buflen, "cn=%s,%s", nbname, container_dn);
1206 * smb_ads_add_computer
1208 * Returns 0 upon success. Otherwise, returns -1.
1210 static int
1211 smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1213 return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn));
1217 * smb_ads_modify_computer
1219 * Returns 0 upon success. Otherwise, returns -1.
1221 static int
1222 smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1224 return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn));
1228 * smb_ads_get_dc_level
1230 * Returns the functional level of the DC upon success.
1231 * Otherwise, -1 is returned.
1233 static int
1234 smb_ads_get_dc_level(smb_ads_handle_t *ah)
1236 LDAPMessage *res, *entry;
1237 char *attr[2];
1238 char **vals;
1239 int rc = -1;
1241 res = NULL;
1242 attr[0] = SMB_ADS_ATTR_DCLEVEL;
1243 attr[1] = NULL;
1244 if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr,
1245 0, &res) != LDAP_SUCCESS) {
1246 (void) ldap_msgfree(res);
1247 return (-1);
1250 /* no match for the specified attribute is found */
1251 if (ldap_count_entries(ah->ld, res) == 0) {
1252 (void) ldap_msgfree(res);
1253 return (-1);
1256 entry = ldap_first_entry(ah->ld, res);
1257 if (entry) {
1258 if ((vals = ldap_get_values(ah->ld, entry,
1259 SMB_ADS_ATTR_DCLEVEL)) == NULL) {
1261 * Observed the values aren't populated
1262 * by the Windows 2000 server.
1264 (void) ldap_msgfree(res);
1265 return (SMB_ADS_DCLEVEL_W2K);
1268 if (vals[0] != NULL)
1269 rc = atoi(vals[0]);
1271 ldap_value_free(vals);
1274 (void) ldap_msgfree(res);
1275 return (rc);
1279 * The fully-qualified hostname returned by this function is often used for
1280 * constructing service principal name. Return the fully-qualified hostname
1281 * in lower case for RFC 4120 section 6.2.1 conformance.
1283 static int
1284 smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len)
1286 if (smb_gethostname(fqhost, len, SMB_CASE_LOWER) != 0)
1287 return (-1);
1289 (void) snprintf(fqhost, len, "%s.%s", fqhost,
1290 ah->domain);
1292 return (0);
1295 static int
1296 smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
1298 LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR];
1299 char *sam_val[2];
1300 char *ctl_val[2], *fqh_val[2];
1301 char *encrypt_val[2];
1302 int j = -1;
1303 int ret, usrctl_flags = 0;
1304 char sam_acct[SMB_SAMACCT_MAXLEN];
1305 char fqhost[MAXHOSTNAMELEN];
1306 char usrctl_buf[16];
1307 char encrypt_buf[16];
1308 int max;
1309 smb_krb5_pn_set_t spn, upn;
1311 if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1312 return (-1);
1314 if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1315 return (-1);
1317 /* The SPN attribute is multi-valued and must be 1 or greater */
1318 if (smb_krb5_get_pn_set(&spn, SMB_PN_SPN_ATTR, ah->domain) == 0)
1319 return (-1);
1321 /* The UPN attribute is single-valued and cannot be zero */
1322 if (smb_krb5_get_pn_set(&upn, SMB_PN_UPN_ATTR, ah->domain) != 1) {
1323 smb_krb5_free_pn_set(&spn);
1324 smb_krb5_free_pn_set(&upn);
1325 return (-1);
1328 max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0))
1329 - (dclevel >= SMB_ADS_DCLEVEL_W2K8 ? 0 : 1);
1331 if (smb_ads_alloc_attr(attrs, max) != 0) {
1332 smb_krb5_free_pn_set(&spn);
1333 smb_krb5_free_pn_set(&upn);
1334 return (-1);
1337 /* objectClass attribute is not modifiable. */
1338 if (op == LDAP_MOD_ADD) {
1339 attrs[++j]->mod_op = op;
1340 attrs[j]->mod_type = "objectClass";
1341 attrs[j]->mod_values = smb_ads_computer_objcls;
1344 attrs[++j]->mod_op = op;
1345 attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT;
1346 sam_val[0] = sam_acct;
1347 sam_val[1] = 0;
1348 attrs[j]->mod_values = sam_val;
1350 attrs[++j]->mod_op = op;
1351 attrs[j]->mod_type = SMB_ADS_ATTR_UPN;
1352 attrs[j]->mod_values = upn.s_pns;
1354 attrs[++j]->mod_op = op;
1355 attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
1356 attrs[j]->mod_values = spn.s_pns;
1358 attrs[++j]->mod_op = op;
1359 attrs[j]->mod_type = SMB_ADS_ATTR_CTL;
1360 usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1361 SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD |
1362 SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE);
1363 (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
1364 ctl_val[0] = usrctl_buf;
1365 ctl_val[1] = 0;
1366 attrs[j]->mod_values = ctl_val;
1368 attrs[++j]->mod_op = op;
1369 attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST;
1370 fqh_val[0] = fqhost;
1371 fqh_val[1] = 0;
1372 attrs[j]->mod_values = fqh_val;
1374 /* enctypes support starting in Windows Server 2008 */
1375 if (dclevel > SMB_ADS_DCLEVEL_W2K3) {
1376 attrs[++j]->mod_op = op;
1377 attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES;
1378 (void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d",
1379 SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 +
1380 SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC);
1381 encrypt_val[0] = encrypt_buf;
1382 encrypt_val[1] = 0;
1383 attrs[j]->mod_values = encrypt_val;
1386 switch (op) {
1387 case LDAP_MOD_ADD:
1388 if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1389 syslog(LOG_NOTICE, "ldap_add: %s",
1390 ldap_err2string(ret));
1391 ret = -1;
1393 break;
1395 case LDAP_MOD_REPLACE:
1396 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1397 syslog(LOG_NOTICE, "ldap_modify: %s",
1398 ldap_err2string(ret));
1399 ret = -1;
1401 break;
1403 default:
1404 ret = -1;
1408 smb_ads_free_attr(attrs);
1409 smb_krb5_free_pn_set(&spn);
1410 smb_krb5_free_pn_set(&upn);
1412 return (ret);
1416 * Delete an ADS computer account.
1418 static void
1419 smb_ads_del_computer(smb_ads_handle_t *ah, char *dn)
1421 int rc;
1423 if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS)
1424 smb_tracef("ldap_delete: %s", ldap_err2string(rc));
1428 * Gets the value of the given attribute.
1430 static smb_ads_qstat_t
1431 smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair)
1433 char **vals;
1434 smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1436 assert(avpair);
1437 avpair->avp_val = NULL;
1438 vals = ldap_get_values(ld, entry, avpair->avp_attr);
1439 if (!vals)
1440 return (SMB_ADS_STAT_NOT_FOUND);
1442 if (!vals[0]) {
1443 ldap_value_free(vals);
1444 return (SMB_ADS_STAT_NOT_FOUND);
1447 avpair->avp_val = strdup(vals[0]);
1448 if (!avpair->avp_val)
1449 rc = SMB_ADS_STAT_ERR;
1451 ldap_value_free(vals);
1452 return (rc);
1456 * Process query's result.
1458 static smb_ads_qstat_t
1459 smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
1460 smb_ads_avpair_t *avpair)
1462 char fqhost[MAXHOSTNAMELEN];
1463 smb_ads_avpair_t dnshost_avp;
1464 smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1465 LDAPMessage *entry;
1467 if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1468 return (SMB_ADS_STAT_ERR);
1470 if (ldap_count_entries(ah->ld, res) == 0)
1471 return (SMB_ADS_STAT_NOT_FOUND);
1473 if ((entry = ldap_first_entry(ah->ld, res)) == NULL)
1474 return (SMB_ADS_STAT_ERR);
1476 dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
1477 rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
1479 switch (rc) {
1480 case SMB_ADS_STAT_FOUND:
1482 * Returns SMB_ADS_STAT_DUP to avoid overwriting
1483 * the computer account of another system whose
1484 * NetBIOS name collides with that of the current
1485 * system.
1487 if (strcasecmp(dnshost_avp.avp_val, fqhost))
1488 rc = SMB_ADS_STAT_DUP;
1490 free(dnshost_avp.avp_val);
1491 break;
1493 case SMB_ADS_STAT_NOT_FOUND:
1495 * Pre-created computer account doesn't have
1496 * the dNSHostname attribute. It's been observed
1497 * that the dNSHostname attribute is only set after
1498 * a successful domain join.
1499 * Returns SMB_ADS_STAT_FOUND as the account is
1500 * pre-created for the current system.
1502 rc = SMB_ADS_STAT_FOUND;
1503 break;
1505 default:
1506 break;
1509 if (rc != SMB_ADS_STAT_FOUND)
1510 return (rc);
1512 if (avpair)
1513 rc = smb_ads_getattr(ah->ld, entry, avpair);
1515 return (rc);
1520 * smb_ads_lookup_computer_n_attr
1522 * If avpair is NULL, checks the status of the specified computer account.
1523 * Otherwise, looks up the value of the specified computer account's attribute.
1524 * If found, the value field of the avpair will be allocated and set. The
1525 * caller should free the allocated buffer.
1527 * Return:
1528 * SMB_ADS_STAT_FOUND - if both the computer and the specified attribute is
1529 * found.
1530 * SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute
1531 * is not found.
1532 * SMB_ADS_STAT_DUP - if the computer account is already used by other systems
1533 * in the AD. This could happen if the hostname of multiple
1534 * systems resolved to the same NetBIOS name.
1535 * SMB_ADS_STAT_ERR - any failure.
1537 static smb_ads_qstat_t
1538 smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair,
1539 int scope, char *dn)
1541 char *attrs[3], filter[SMB_ADS_MAXBUFLEN];
1542 LDAPMessage *res;
1543 char sam_acct[SMB_SAMACCT_MAXLEN], sam_acct2[SMB_SAMACCT_MAXLEN];
1544 smb_ads_qstat_t rc;
1546 if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1547 return (SMB_ADS_STAT_ERR);
1549 res = NULL;
1550 attrs[0] = SMB_ADS_ATTR_DNSHOST;
1551 attrs[1] = NULL;
1552 attrs[2] = NULL;
1554 if (avpair) {
1555 if (!avpair->avp_attr)
1556 return (SMB_ADS_STAT_ERR);
1558 attrs[1] = avpair->avp_attr;
1561 if (smb_ads_escape_search_filter_chars(sam_acct, sam_acct2) != 0)
1562 return (SMB_ADS_STAT_ERR);
1564 (void) snprintf(filter, sizeof (filter),
1565 "(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT,
1566 sam_acct2);
1568 if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0,
1569 &res) != LDAP_SUCCESS) {
1570 (void) ldap_msgfree(res);
1571 return (SMB_ADS_STAT_NOT_FOUND);
1574 rc = smb_ads_get_qstat(ah, res, avpair);
1575 /* free the search results */
1576 (void) ldap_msgfree(res);
1577 return (rc);
1581 * smb_ads_find_computer
1583 * Starts by searching for the system's AD computer object in the default
1584 * container (i.e. cn=Computers). If not found, searches the entire directory.
1585 * If found, 'dn' will be set to the distinguished name of the system's AD
1586 * computer object.
1588 static smb_ads_qstat_t
1589 smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
1591 smb_ads_qstat_t stat;
1592 smb_ads_avpair_t avpair;
1594 avpair.avp_attr = SMB_ADS_ATTR_DN;
1595 smb_ads_get_default_comp_container_dn(ah, dn, SMB_ADS_DN_MAX);
1596 stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_ONELEVEL,
1597 dn);
1599 if (stat == SMB_ADS_STAT_NOT_FOUND) {
1600 (void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX);
1601 stat = smb_ads_lookup_computer_n_attr(ah, &avpair,
1602 LDAP_SCOPE_SUBTREE, dn);
1605 if (stat == SMB_ADS_STAT_FOUND) {
1606 (void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX);
1607 free(avpair.avp_val);
1610 return (stat);
1614 * smb_ads_update_computer_cntrl_attr
1616 * Modify the user account control attribute of an existing computer
1617 * object on AD.
1619 * Returns LDAP error code.
1621 static int
1622 smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn)
1624 LDAPMod *attrs[2];
1625 char *ctl_val[2];
1626 int ret = 0;
1627 char usrctl_buf[16];
1629 if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
1630 return (LDAP_NO_MEMORY);
1632 attrs[0]->mod_op = LDAP_MOD_REPLACE;
1633 attrs[0]->mod_type = SMB_ADS_ATTR_CTL;
1635 (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags);
1636 ctl_val[0] = usrctl_buf;
1637 ctl_val[1] = 0;
1638 attrs[0]->mod_values = ctl_val;
1639 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1640 syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
1643 smb_ads_free_attr(attrs);
1644 return (ret);
1648 * smb_ads_lookup_computer_attr_kvno
1650 * Lookup the value of the Kerberos version number attribute of the computer
1651 * account.
1653 static krb5_kvno
1654 smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
1656 smb_ads_avpair_t avpair;
1657 int kvno = 1;
1659 avpair.avp_attr = SMB_ADS_ATTR_KVNO;
1660 if (smb_ads_lookup_computer_n_attr(ah, &avpair,
1661 LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) {
1662 kvno = atoi(avpair.avp_val);
1663 free(avpair.avp_val);
1666 return (kvno);
1670 * smb_ads_join
1672 * Besides the NT-4 style domain join (using MS-RPC), CIFS server also
1673 * provides the domain join using Kerberos Authentication, Keberos
1674 * Change & Set password, and LDAP protocols. Basically, AD join
1675 * operation would require the following tickets to be acquired for the
1676 * the user account that is provided for the domain join.
1678 * 1) a Keberos TGT ticket,
1679 * 2) a ldap service ticket, and
1680 * 3) kadmin/changpw service ticket
1682 * The ADS client first sends a ldap search request to find out whether
1683 * or not the workstation trust account already exists in the Active Directory.
1684 * The existing computer object for this workstation will be removed and
1685 * a new one will be added. The machine account password is randomly
1686 * generated and set for the newly created computer object using KPASSWD
1687 * protocol (See RFC 3244). Once the password is set, our ADS client
1688 * finalizes the machine account by modifying the user acount control
1689 * attribute of the computer object. Kerberos keys derived from the machine
1690 * account password will be stored locally in /etc/krb5/krb5.keytab file.
1691 * That would be needed while acquiring Kerberos TGT ticket for the host
1692 * principal after the domain join operation.
1694 smb_ads_status_t
1695 smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd)
1697 smb_ads_handle_t *ah = NULL;
1698 krb5_context ctx = NULL;
1699 krb5_principal *krb5princs = NULL;
1700 krb5_kvno kvno;
1701 boolean_t delete = B_TRUE;
1702 smb_ads_status_t rc;
1703 boolean_t new_acct;
1704 int dclevel, num, usrctl_flags = 0;
1705 smb_ads_qstat_t qstat;
1706 char dn[SMB_ADS_DN_MAX];
1707 char tmpfile[] = SMBNS_KRB5_KEYTAB_TMP;
1708 int cnt, x;
1709 smb_krb5_pn_set_t spns;
1710 krb5_enctype *encptr;
1712 rc = smb_ads_open_main(&ah, domain, user, usr_passwd);
1713 if (rc != 0) {
1714 smb_ccache_remove(SMB_CCACHE_PATH);
1715 return (rc);
1718 if ((dclevel = smb_ads_get_dc_level(ah)) == -1) {
1719 smb_ads_close(ah);
1720 smb_ccache_remove(SMB_CCACHE_PATH);
1721 return (SMB_ADJOIN_ERR_GET_DCLEVEL);
1724 qstat = smb_ads_find_computer(ah, dn);
1725 switch (qstat) {
1726 case SMB_ADS_STAT_FOUND:
1727 new_acct = B_FALSE;
1728 if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
1729 smb_ads_close(ah);
1730 smb_ccache_remove(SMB_CCACHE_PATH);
1731 return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT);
1733 break;
1735 case SMB_ADS_STAT_NOT_FOUND:
1736 new_acct = B_TRUE;
1737 smb_ads_get_default_comp_dn(ah, dn, SMB_ADS_DN_MAX);
1738 if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
1739 smb_ads_close(ah);
1740 smb_ccache_remove(SMB_CCACHE_PATH);
1741 return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT);
1743 break;
1745 default:
1746 if (qstat == SMB_ADS_STAT_DUP)
1747 rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT;
1748 else
1749 rc = SMB_ADJOIN_ERR_TRUST_ACCT;
1750 smb_ads_close(ah);
1751 smb_ccache_remove(SMB_CCACHE_PATH);
1752 return (rc);
1755 if (smb_krb5_ctx_init(&ctx) != 0) {
1756 rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
1757 goto adjoin_cleanup;
1760 if (smb_krb5_get_pn_set(&spns, SMB_PN_KEYTAB_ENTRY, ah->domain) == 0) {
1761 rc = SMB_ADJOIN_ERR_GET_SPNS;
1762 goto adjoin_cleanup;
1765 if (smb_krb5_get_kprincs(ctx, spns.s_pns, spns.s_cnt, &krb5princs)
1766 != 0) {
1767 smb_krb5_free_pn_set(&spns);
1768 rc = SMB_ADJOIN_ERR_GET_SPNS;
1769 goto adjoin_cleanup;
1772 cnt = spns.s_cnt;
1773 smb_krb5_free_pn_set(&spns);
1775 /* New machine_passwd was filled in by our caller. */
1776 if (smb_krb5_setpwd(ctx, ah->domain, machine_passwd) != 0) {
1777 rc = SMB_ADJOIN_ERR_KSETPWD;
1778 goto adjoin_cleanup;
1781 kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
1784 * Only members of Domain Admins and Enterprise Admins can set
1785 * the TRUSTED_FOR_DELEGATION userAccountControl flag.
1786 * Try to set this, but don't fail the join if we can't.
1787 * Look into just removing this...
1789 usrctl_flags = (
1790 SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1791 SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION |
1792 SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
1793 set_ctl_again:
1794 x = smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn);
1795 if (x != LDAP_SUCCESS && (usrctl_flags &
1796 SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION) != 0) {
1797 syslog(LOG_NOTICE, "Unable to set the "
1798 "TRUSTED_FOR_DELEGATION userAccountControl flag on the "
1799 "machine account in Active Directory. It may be necessary "
1800 "to set that via Active Directory administration.");
1801 usrctl_flags &=
1802 ~SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION;
1803 goto set_ctl_again;
1805 if (x != LDAP_SUCCESS) {
1806 rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
1807 goto adjoin_cleanup;
1810 if (mktemp(tmpfile) == NULL) {
1811 rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
1812 goto adjoin_cleanup;
1815 encptr = smb_ads_get_enctypes(dclevel, &num);
1816 if (smb_krb5_kt_populate(ctx, ah->domain, krb5princs, cnt,
1817 tmpfile, kvno, machine_passwd, encptr, num) != 0) {
1818 rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
1819 goto adjoin_cleanup;
1822 delete = B_FALSE;
1823 rc = SMB_ADS_SUCCESS;
1825 adjoin_cleanup:
1826 if (new_acct && delete)
1827 smb_ads_del_computer(ah, dn);
1829 if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) {
1830 if (rc != SMB_ADJOIN_ERR_GET_SPNS)
1831 smb_krb5_free_kprincs(ctx, krb5princs, cnt);
1832 smb_krb5_ctx_fini(ctx);
1835 /* commit keytab file */
1836 if (rc == SMB_ADS_SUCCESS) {
1837 if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) {
1838 (void) unlink(tmpfile);
1839 rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB;
1841 } else {
1842 (void) unlink(tmpfile);
1845 smb_ads_close(ah);
1846 smb_ccache_remove(SMB_CCACHE_PATH);
1847 return (rc);
1850 struct xlate_table {
1851 int err;
1852 const char const *msg;
1855 static const struct xlate_table
1856 adjoin_table[] = {
1857 { SMB_ADS_SUCCESS, "Success" },
1858 { SMB_ADS_KRB5_INIT_CTX,
1859 "Failed creating a Kerberos context." },
1860 { SMB_ADS_KRB5_CC_DEFAULT,
1861 "Failed to resolve default credential cache." },
1862 { SMB_ADS_KRB5_PARSE_PRINCIPAL,
1863 "Failed parsing the user principal name." },
1864 { SMB_ADS_KRB5_GET_INIT_CREDS_PW,
1865 "Failed getting initial credentials. (Wrong password?)" },
1866 { SMB_ADS_KRB5_CC_INITIALIZE,
1867 "Failed initializing the credential cache." },
1868 { SMB_ADS_KRB5_CC_STORE_CRED,
1869 "Failed to update the credential cache." },
1870 { SMB_ADS_CANT_LOCATE_DC,
1871 "Failed to locate a domain controller." },
1872 { SMB_ADS_LDAP_INIT,
1873 "Failed to create an LDAP handle." },
1874 { SMB_ADS_LDAP_SETOPT,
1875 "Failed to set an LDAP option." },
1876 { SMB_ADS_LDAP_SET_DOM,
1877 "Failed to set the LDAP handle DN." },
1878 { SMB_ADS_LDAP_SASL_BIND,
1879 "Failed to bind the LDAP handle. "
1880 "Usually indicates an authentication problem." },
1882 { SMB_ADJOIN_ERR_GEN_PWD,
1883 "Failed to generate machine password." },
1884 { SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of "
1885 "the domain controller. The rootDSE attribute named "
1886 "\"domainControllerFunctionality\" is missing from the "
1887 "Active Directory." },
1888 { SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the "
1889 "workstation trust account." },
1890 { SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the "
1891 "workstation trust account." },
1892 { SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the "
1893 "workstation trust account because its name is already "
1894 "in use." },
1895 { SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the "
1896 "workstation trust account" },
1897 { SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos "
1898 "context." },
1899 { SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos "
1900 "principals." },
1901 { SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." },
1902 { SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR, "Failed to modify "
1903 "userAccountControl attribute of the workstation trust "
1904 "account." },
1905 { SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local "
1906 "keytab file (i.e /etc/krb5/krb5.keytab)." },
1907 { SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap "
1908 "configuration." },
1909 { SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap "
1910 "service." },
1911 { SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to "
1912 "local keytab file (i.e. /etc/krb5/krb5.keytab)." },
1913 { SMB_ADJOIN_ERR_AUTH_NETLOGON,
1914 "Failed to authenticate using the new computer account." },
1915 { SMB_ADJOIN_ERR_STORE_PROPS,
1916 "Failed to store computer account information locally." },
1917 { 0, NULL }
1921 * smb_ads_strerror
1923 * Lookup an error message for the specific adjoin error code.
1925 const char *
1926 smb_ads_strerror(int err)
1928 const struct xlate_table *xt;
1930 if (err > 0 && err < SMB_ADS_ERRNO_GAP)
1931 return (strerror(err));
1933 for (xt = adjoin_table; xt->msg; xt++)
1934 if (xt->err == err)
1935 return (xt->msg);
1937 return ("Unknown error code.");
1940 void
1941 smb_ads_log_errmsg(smb_ads_status_t err)
1943 const char *s = smb_ads_strerror(err);
1944 syslog(LOG_NOTICE, "%s", s);
1949 * smb_ads_lookup_msdcs
1951 * If server argument is set, try to locate the specified DC.
1952 * If it is set to empty string, locate any DCs in the specified domain.
1953 * Returns the discovered DC via buf.
1955 * fqdn - fully-qualified domain name
1956 * dci - the name and address of the found DC
1958 uint32_t
1959 smb_ads_lookup_msdcs(char *fqdn, smb_dcinfo_t *dci)
1961 smb_ads_host_info_t *hinfo = NULL;
1962 char ipstr[INET6_ADDRSTRLEN];
1964 if (!fqdn || !dci)
1965 return (NT_STATUS_INTERNAL_ERROR);
1967 ipstr[0] = '\0';
1968 if ((hinfo = smb_ads_find_host(fqdn)) == NULL)
1969 return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
1971 (void) smb_inet_ntop(&hinfo->ipaddr, ipstr,
1972 SMB_IPSTRLEN(hinfo->ipaddr.a_family));
1973 smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr);
1975 (void) strlcpy(dci->dc_name, hinfo->name, sizeof (dci->dc_name));
1976 dci->dc_addr = hinfo->ipaddr;
1978 free(hinfo);
1979 return (NT_STATUS_SUCCESS);
1982 static krb5_enctype *
1983 smb_ads_get_enctypes(int dclevel, int *num)
1985 krb5_enctype *encptr;
1987 if (dclevel >= SMB_ADS_DCLEVEL_W2K8) {
1988 *num = sizeof (w2k8enctypes) / sizeof (krb5_enctype);
1989 encptr = w2k8enctypes;
1990 } else {
1991 *num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype);
1992 encptr = pre_w2k8enctypes;
1995 return (encptr);