smbns_ads.c revision fe1c642d06e14b412cd83ae2179303186ab08972
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/param.h>
#include <ldap.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <netdb.h>
#include <pthread.h>
#include <unistd.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <sys/synch.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <sasl/sasl.h>
#include <note.h>
#include <smbsrv/libsmbns.h>
#include <smbns_dyndns.h>
#include <smbns_krb.h>
#define SMB_ADS_MAXBUFLEN 100
#define SMB_ADS_DN_MAX 300
#define SMB_ADS_MAXMSGLEN 512
#define SMB_ADS_COMPUTERS_CN "Computers"
#define SMB_ADS_COMPUTER_NUM_ATTR 8
#define SMB_ADS_SHARE_NUM_ATTR 3
#define SMB_ADS_SITE_MAX MAXHOSTNAMELEN
#define SMB_ADS_MSDCS_SRV_DC_RR "_ldap._tcp.dc._msdcs"
#define SMB_ADS_MSDCS_SRV_SITE_RR "_ldap._tcp.%s._sites.dc._msdcs"
/*
* domainControllerFunctionality
*
* This rootDSE attribute indicates the functional level of the DC.
*/
#define SMB_ADS_ATTR_DCLEVEL "domainControllerFunctionality"
#define SMB_ADS_DCLEVEL_W2K 0
#define SMB_ADS_DCLEVEL_W2K3 2
#define SMB_ADS_DCLEVEL_W2K8 3
#define SMB_ADS_DCLEVEL_W2K8_R2 4
/*
* msDs-supportedEncryptionTypes (Windows Server 2008 only)
*
* This attribute defines the encryption types supported by the system.
* Encryption Types:
* - DES cbc mode with CRC-32
* - DES cbc mode with RSA-MD5
* - ArcFour with HMAC/md5
* - AES-128
* - AES-256
*/
#define SMB_ADS_ATTR_ENCTYPES "msDs-supportedEncryptionTypes"
#define SMB_ADS_ENC_DES_CRC 1
#define SMB_ADS_ENC_DES_MD5 2
#define SMB_ADS_ENC_RC4 4
#define SMB_ADS_ENC_AES128 8
#define SMB_ADS_ENC_AES256 16
#define SMB_ADS_ATTR_SAMACCT "sAMAccountName"
#define SMB_ADS_ATTR_UPN "userPrincipalName"
#define SMB_ADS_ATTR_SPN "servicePrincipalName"
#define SMB_ADS_ATTR_CTL "userAccountControl"
#define SMB_ADS_ATTR_DNSHOST "dNSHostName"
#define SMB_ADS_ATTR_KVNO "msDS-KeyVersionNumber"
#define SMB_ADS_ATTR_DN "distinguishedName"
/*
* UserAccountControl flags: manipulate user account properties.
*
* The hexadecimal value of the following property flags are based on MSDN
* article # 305144.
*/
#define SMB_ADS_USER_ACCT_CTL_SCRIPT 0x00000001
#define SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE 0x00000002
#define SMB_ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED 0x00000008
#define SMB_ADS_USER_ACCT_CTL_LOCKOUT 0x00000010
#define SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD 0x00000020
#define SMB_ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE 0x00000040
#define SMB_ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED 0x00000080
#define SMB_ADS_USER_ACCT_CTL_TMP_DUP_ACCT 0x00000100
#define SMB_ADS_USER_ACCT_CTL_NORMAL_ACCT 0x00000200
#define SMB_ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT 0x00000800
#define SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT 0x00001000
#define SMB_ADS_USER_ACCT_CTL_SRV_TRUST_ACCT 0x00002000
#define SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD 0x00010000
#define SMB_ADS_USER_ACCT_CTL_MNS_LOGON_ACCT 0x00020000
#define SMB_ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED 0x00040000
#define SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION 0x00080000
#define SMB_ADS_USER_ACCT_CTL_NOT_DELEGATED 0x00100000
#define SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY 0x00200000
#define SMB_ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH 0x00400000
#define SMB_ADS_USER_ACCT_CTL_PASSWD_EXPIRED 0x00800000
#define SMB_ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION 0x01000000
/*
* Length of "dc=" prefix.
*/
#define SMB_ADS_DN_PREFIX_LEN 3
static char *smb_ads_computer_objcls[] = {
"top", "person", "organizationalPerson",
"user", "computer", NULL
};
static char *smb_ads_share_objcls[] = {
"top", "leaf", "connectionPoint", "volume", NULL
};
/* Cached ADS server to communicate with */
static smb_ads_host_info_t *smb_ads_cached_host_info = NULL;
static mutex_t smb_ads_cached_host_mtx;
/*
* SMB ADS config cache is maintained to facilitate the detection of
* changes in configuration that is relevant to AD selection.
*/
typedef struct smb_ads_config {
char c_site[SMB_ADS_SITE_MAX];
smb_inaddr_t c_pdc;
mutex_t c_mtx;
} smb_ads_config_t;
static smb_ads_config_t smb_ads_cfg;
/* attribute/value pair */
typedef struct smb_ads_avpair {
char *avp_attr;
char *avp_val;
} smb_ads_avpair_t;
/* query status */
typedef enum smb_ads_qstat {
SMB_ADS_STAT_ERR = -2,
SMB_ADS_STAT_DUP,
SMB_ADS_STAT_NOT_FOUND,
SMB_ADS_STAT_FOUND
} smb_ads_qstat_t;
typedef struct smb_ads_host_list {
int ah_cnt;
smb_ads_host_info_t *ah_list;
} smb_ads_host_list_t;
static smb_ads_handle_t *smb_ads_open_main(char *, char *, char *);
static int smb_ads_add_computer(smb_ads_handle_t *, int, char *);
static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *);
static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *);
static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *,
smb_ads_avpair_t *, int, char *);
static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *);
static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *);
static void smb_ads_gen_machine_passwd(char *, int);
static void smb_ads_free_cached_host(void);
static int smb_ads_get_spnset(char *, char **);
static void smb_ads_free_spnset(char **);
static int smb_ads_alloc_attr(LDAPMod **, int);
static void smb_ads_free_attr(LDAPMod **);
static int smb_ads_get_dc_level(smb_ads_handle_t *);
static smb_ads_host_info_t *smb_ads_select_dc(smb_ads_host_list_t *);
static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *, char *);
static smb_ads_qstat_t smb_ads_getattr(LDAP *, LDAPMessage *,
smb_ads_avpair_t *);
static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *, LDAPMessage *,
smb_ads_avpair_t *);
static boolean_t smb_ads_match_pdc(smb_ads_host_info_t *);
static boolean_t smb_ads_is_sought_host(smb_ads_host_info_t *, char *);
static boolean_t smb_ads_is_same_domain(char *, char *);
static boolean_t smb_ads_is_pdc_configured(void);
static smb_ads_host_info_t *smb_ads_dup_host_info(smb_ads_host_info_t *);
static char *smb_ads_get_sharedn(const char *, const char *, const char *);
/*
* smb_ads_init
*
* Initializes the ADS config cache.
*/
void
smb_ads_init(void)
{
(void) mutex_lock(&smb_ads_cfg.c_mtx);
(void) smb_config_getstr(SMB_CI_ADS_SITE,
smb_ads_cfg.c_site, SMB_ADS_SITE_MAX);
(void) smb_config_getip(SMB_CI_DOMAIN_SRV, &smb_ads_cfg.c_pdc);
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
}
void
smb_ads_fini(void)
{
smb_ads_free_cached_host();
}
/*
* smb_ads_refresh
*
* This function will be called when smb/server SMF service is refreshed.
* Clearing the smb_ads_cached_host_info would allow the next DC
* discovery process to pick up an AD based on the new AD configuration.
*/
void
smb_ads_refresh(void)
{
char new_site[SMB_ADS_SITE_MAX];
smb_inaddr_t new_pdc;
boolean_t purge = B_FALSE;
(void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX);
(void) smb_config_getip(SMB_CI_DOMAIN_SRV, &new_pdc);
(void) mutex_lock(&smb_ads_cfg.c_mtx);
if (smb_strcasecmp(smb_ads_cfg.c_site, new_site, 0)) {
(void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX);
purge = B_TRUE;
}
smb_ads_cfg.c_pdc = new_pdc;
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
(void) mutex_lock(&smb_ads_cached_host_mtx);
if (smb_ads_cached_host_info &&
smb_ads_is_pdc_configured() &&
!smb_ads_match_pdc(smb_ads_cached_host_info))
purge = B_TRUE;
(void) mutex_unlock(&smb_ads_cached_host_mtx);
if (purge)
smb_ads_free_cached_host();
}
static boolean_t
smb_ads_is_pdc_configured(void)
{
boolean_t configured;
(void) mutex_lock(&smb_ads_cfg.c_mtx);
configured = !smb_inet_iszero(&smb_ads_cfg.c_pdc);
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
return (configured);
}
/*
* smb_ads_build_unc_name
*
* Construct the UNC name of the share object in the format of
* \\hostname.domain\shareUNC
*
* Returns 0 on success, -1 on error.
*/
int
smb_ads_build_unc_name(char *unc_name, int maxlen,
const char *hostname, const char *shareUNC)
{
char my_domain[MAXHOSTNAMELEN];
if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0)
return (-1);
(void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s",
hostname, my_domain, shareUNC);
return (0);
}
/*
* smb_ads_ldap_ping
*
* This is used to bind to an ADS server to see
* if it is still alive.
*
* Returns:
* -1: error
* 0: successful
*/
/*ARGSUSED*/
static int
smb_ads_ldap_ping(smb_ads_host_info_t *ads_host)
{
int ldversion = LDAP_VERSION3, status, timeoutms = 5 * 1000;
LDAP *ld = NULL;
ld = ldap_init(ads_host->name, ads_host->port);
if (ld == NULL)
return (-1);
ldversion = LDAP_VERSION3;
(void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion);
/* setup TCP/IP connect timeout */
(void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms);
status = ldap_bind_s(ld, "", NULL, LDAP_AUTH_SIMPLE);
if (status != LDAP_SUCCESS) {
(void) ldap_unbind(ld);
return (-1);
}
(void) ldap_unbind(ld);
return (0);
}
/*
* The cached ADS host is no longer valid if one of the following criteria
* is satisfied:
*
* 1) not in the specified domain
* 2) not the sought host (if specified)
* 3) not reachable
*
* The caller is responsible for acquiring the smb_ads_cached_host_mtx lock
* prior to calling this function.
*
* Return B_TRUE if the cache host is still valid. Otherwise, return B_FALSE.
*/
static boolean_t
smb_ads_validate_cache_host(char *domain, char *srv)
{
if (!smb_ads_cached_host_info)
return (B_FALSE);
if (!smb_ads_is_same_domain(smb_ads_cached_host_info->name, domain))
return (B_FALSE);
if (smb_ads_ldap_ping(smb_ads_cached_host_info) == 0) {
if (!srv)
return (B_TRUE);
if (smb_ads_is_sought_host(smb_ads_cached_host_info, srv))
return (B_TRUE);
}
return (B_FALSE);
}
/*
* smb_ads_is_sought_host
*
* Returns true, if the sought host name matches the input host (host) name.
* The sought host is expected to be in Fully Qualified Domain Name (FQDN)
* format.
*/
static boolean_t
smb_ads_is_sought_host(smb_ads_host_info_t *host, char *sought_host_name)
{
if ((host == NULL) || (sought_host_name == NULL))
return (B_FALSE);
if (smb_strcasecmp(host->name, sought_host_name, 0))
return (B_FALSE);
return (B_TRUE);
}
/*
* smb_ads_match_hosts_same_domain
*
* Returns true, if the cached ADS host is in the same domain as the
* current (given) domain.
*/
static boolean_t
smb_ads_is_same_domain(char *cached_host_name, char *current_domain)
{
char *cached_host_domain;
if ((cached_host_name == NULL) || (current_domain == NULL))
return (B_FALSE);
cached_host_domain = strchr(cached_host_name, '.');
if (cached_host_domain == NULL)
return (B_FALSE);
++cached_host_domain;
if (smb_strcasecmp(cached_host_domain, current_domain, 0))
return (B_FALSE);
return (B_TRUE);
}
/*
* smb_ads_skip_ques_sec
* Skips the question section.
*/
static int
smb_ads_skip_ques_sec(int qcnt, uchar_t **ptr, uchar_t *eom)
{
int i, len;
for (i = 0; i < qcnt; i++) {
if ((len = dn_skipname(*ptr, eom)) < 0)
return (-1);
*ptr += len + QFIXEDSZ;
}
return (0);
}
/*
* smb_ads_decode_host_ans_sec
* Decodes ADS hosts, priority, weight and port number from the answer
* section based on the current buffer pointer.
*/
static int
smb_ads_decode_host_ans_sec(int ans_cnt, uchar_t **ptr, uchar_t *eom,
uchar_t *buf, smb_ads_host_info_t *ads_host_list)
{
int i, len;
smb_ads_host_info_t *ads_host;
for (i = 0; i < ans_cnt; i++) {
ads_host = &ads_host_list[i];
if ((len = dn_skipname(*ptr, eom)) < 0)
return (-1);
*ptr += len;
/* skip type, class, ttl */
*ptr += 8;
/* data size */
*ptr += 2;
/* Get priority, weight */
/* LINTED: E_CONSTANT_CONDITION */
NS_GET16(ads_host->priority, *ptr);
/* LINTED: E_CONSTANT_CONDITION */
NS_GET16(ads_host->weight, *ptr);
/* port */
/* LINTED: E_CONSTANT_CONDITION */
NS_GET16(ads_host->port, *ptr);
/* domain name */
len = dn_expand(buf, eom, *ptr, ads_host->name, MAXHOSTNAMELEN);
if (len < 0)
return (-1);
*ptr += len;
}
return (0);
}
/*
* smb_ads_skip_auth_sec
* Skips the authority section.
*/
static int
smb_ads_skip_auth_sec(int ns_cnt, uchar_t **ptr, uchar_t *eom)
{
int i, len;
uint16_t size;
for (i = 0; i < ns_cnt; i++) {
if ((len = dn_skipname(*ptr, eom)) < 0)
return (-1);
*ptr += len;
/* skip type, class, ttl */
*ptr += 8;
/* get len of data */
/* LINTED: E_CONSTANT_CONDITION */
NS_GET16(size, *ptr);
if ((*ptr + size) > eom)
return (-1);
*ptr += size;
}
return (0);
}
/*
* smb_ads_decode_host_ip
*
* Decodes ADS hosts and IP Addresses from the additional section based
* on the current buffer pointer.
*/
static int
smb_ads_decode_host_ip(int addit_cnt, int ans_cnt, uchar_t **ptr,
uchar_t *eom, uchar_t *buf, smb_ads_host_info_t *ads_host_list)
{
int i, j, len;
smb_inaddr_t ipaddr;
char hostname[MAXHOSTNAMELEN];
char *name;
uint16_t size = 0;
for (i = 0; i < addit_cnt; i++) {
/* domain name */
len = dn_expand(buf, eom, *ptr, hostname, MAXHOSTNAMELEN);
if (len < 0)
return (-1);
*ptr += len;
/* skip type, class, TTL, data len */
*ptr += 8;
/* LINTED: E_CONSTANT_CONDITION */
NS_GET16(size, *ptr);
if (size == INADDRSZ) {
/* LINTED: E_CONSTANT_CONDITION */
NS_GET32(ipaddr.a_ipv4, *ptr);
ipaddr.a_ipv4 = htonl(ipaddr.a_ipv4);
ipaddr.a_family = AF_INET;
} else if (size == IN6ADDRSZ) {
#ifdef BIG_ENDIAN
bcopy(*ptr, &ipaddr.a_ipv6, IN6ADDRSZ);
#else
for (i = 0; i < IN6ADDRSZ; i++)
(uint8_t *)(ipaddr.a_ipv6)
[IN6ADDRSZ-1-i] = *(*ptr+i);
#endif
ipaddr.a_family = AF_INET6;
}
/*
* find the host in the list of DC records from
* the answer section, that matches the host in the
* additional section, and set its IP address.
*/
for (j = 0; j < ans_cnt; j++) {
if ((name = ads_host_list[j].name) == NULL)
continue;
if (smb_strcasecmp(name, hostname, 0) == 0) {
ads_host_list[j].ipaddr = ipaddr;
}
}
}
return (0);
}
/*
* smb_ads_dup_host_info
*
* Duplicates the passed smb_ads_host_info_t structure.
* Caller must free memory allocated by this method.
*
* Returns a reference to the duplicated smb_ads_host_info_t structure.
* Returns NULL on error.
*/
static smb_ads_host_info_t *
smb_ads_dup_host_info(smb_ads_host_info_t *ads_host)
{
smb_ads_host_info_t *dup_host;
if (ads_host == NULL)
return (NULL);
dup_host = malloc(sizeof (smb_ads_host_info_t));
if (dup_host != NULL)
bcopy(ads_host, dup_host, sizeof (smb_ads_host_info_t));
return (dup_host);
}
/*
* smb_ads_hlist_alloc
*/
static smb_ads_host_list_t *
smb_ads_hlist_alloc(int count)
{
int size;
smb_ads_host_list_t *hlist;
if (count == 0)
return (NULL);
size = sizeof (smb_ads_host_info_t) * count;
hlist = (smb_ads_host_list_t *)malloc(sizeof (smb_ads_host_list_t));
if (hlist == NULL)
return (NULL);
hlist->ah_cnt = count;
hlist->ah_list = (smb_ads_host_info_t *)malloc(size);
if (hlist->ah_list == NULL) {
free(hlist);
return (NULL);
}
bzero(hlist->ah_list, size);
return (hlist);
}
/*
* smb_ads_hlist_free
*/
static void
smb_ads_hlist_free(smb_ads_host_list_t *host_list)
{
if (host_list == NULL)
return;
free(host_list->ah_list);
free(host_list);
}
/*
* smb_ads_query_dns_server
*
* This routine sends a DNS service location (SRV) query message to the
* DNS server via TCP to query it for a list of ADS server(s). Once a reply
* is received, the reply message is parsed to get the hostname. If there are IP
* addresses populated in the additional section then the additional section
* is parsed to obtain the IP addresses.
*
* The service location of _ldap._tcp.dc.msdcs.<ADS domain> is used to
* guarantee that Microsoft domain controllers are returned. Microsoft domain
* controllers are also ADS servers.
*
* The ADS hostnames are stored in the answer section of the DNS reply message.
* The IP addresses are stored in the additional section.
*
* The DNS reply message may be in compress formed. The compression is done
* on repeating domain name label in the message. i.e hostname.
*
* Upon successful completion, host list of ADS server(s) is returned.
*/
static smb_ads_host_list_t *
smb_ads_query_dns_server(char *domain, char *msdcs_svc_name)
{
smb_ads_host_list_t *hlist = NULL;
int len, qcnt, ans_cnt, ns_cnt, addit_cnt;
uchar_t *ptr, *eom;
struct __res_state res_state;
union {
HEADER hdr;
uchar_t buf[NS_MAXMSG];
} msg;
bzero(&res_state, sizeof (struct __res_state));
if (res_ninit(&res_state) < 0)
return (NULL);
/* use TCP */
res_state.options |= RES_USEVC;
len = res_nquerydomain(&res_state, msdcs_svc_name, domain,
C_IN, T_SRV, msg.buf, sizeof (msg.buf));
if (len < 0) {
syslog(LOG_NOTICE, "DNS query for %s failed: %s",
msdcs_svc_name, hstrerror(res_state.res_h_errno));
res_ndestroy(&res_state);
return (NULL);
}
if (len > sizeof (msg.buf)) {
syslog(LOG_NOTICE,
"DNS query for %s failed: too big", msdcs_svc_name);
res_ndestroy(&res_state);
return (NULL);
}
/* parse the reply, skip header and question sections */
ptr = msg.buf + sizeof (msg.hdr);
eom = msg.buf + len;
/* check truncated message bit */
if (msg.hdr.tc)
syslog(LOG_NOTICE,
"DNS query for %s failed: truncated", msdcs_svc_name);
qcnt = ntohs(msg.hdr.qdcount);
ans_cnt = ntohs(msg.hdr.ancount);
ns_cnt = ntohs(msg.hdr.nscount);
addit_cnt = ntohs(msg.hdr.arcount);
if (smb_ads_skip_ques_sec(qcnt, &ptr, eom) != 0) {
res_ndestroy(&res_state);
return (NULL);
}
hlist = smb_ads_hlist_alloc(ans_cnt);
if (hlist == NULL) {
res_ndestroy(&res_state);
return (NULL);
}
/* walk through the answer section */
if (smb_ads_decode_host_ans_sec(ans_cnt, &ptr, eom, msg.buf,
hlist->ah_list) != 0) {
smb_ads_hlist_free(hlist);
res_ndestroy(&res_state);
return (NULL);
}
/* check authority section */
if (ns_cnt > 0) {
if (smb_ads_skip_auth_sec(ns_cnt, &ptr, eom) != 0) {
smb_ads_hlist_free(hlist);
res_ndestroy(&res_state);
return (NULL);
}
}
/*
* Check additional section to get IP address of ADS host.
*/
if (addit_cnt > 0) {
if (smb_ads_decode_host_ip(addit_cnt, ans_cnt,
&ptr, eom, msg.buf, hlist->ah_list) != 0) {
smb_ads_hlist_free(hlist);
res_ndestroy(&res_state);
return (NULL);
}
}
res_ndestroy(&res_state);
return (hlist);
}
/*
* smb_ads_get_site_service
*
* Gets the msdcs SRV RR for the specified site.
*/
static void
smb_ads_get_site_service(char *site_service, size_t len)
{
(void) mutex_lock(&smb_ads_cfg.c_mtx);
if (*smb_ads_cfg.c_site == '\0')
*site_service = '\0';
else
(void) snprintf(site_service, len,
SMB_ADS_MSDCS_SRV_SITE_RR, smb_ads_cfg.c_site);
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
}
/*
* smb_ads_getipnodebyname
*
* This method gets the IP address by doing a host name lookup.
*/
static int
smb_ads_getipnodebyname(smb_ads_host_info_t *hentry)
{
struct hostent *h;
int error;
switch (hentry->ipaddr.a_family) {
case AF_INET6:
h = getipnodebyname(hentry->name, hentry->ipaddr.a_family,
AI_DEFAULT, &error);
if (h == NULL || h->h_length != IPV6_ADDR_LEN)
return (-1);
break;
case AF_INET:
h = getipnodebyname(hentry->name, hentry->ipaddr.a_family,
0, &error);
if (h == NULL || h->h_length != INADDRSZ)
return (-1);
break;
default:
return (-1);
}
bcopy(*(h->h_addr_list), &hentry->ipaddr.a_ip, h->h_length);
freehostent(h);
return (0);
}
/*
* smb_ads_find_host
*
* Finds an ADS host in a given domain.
*
* If the cached host is valid, it will be used. Otherwise, a DC will
* be selected based on the following criteria:
*
* 1) pdc (aka preferred DC) configuration
* 2) AD site configuration - the scope of the DNS lookup will be
* restricted to the specified site.
* 3) DC on the same subnet
* 4) DC with the lowest priority/highest weight
*
* The above items are listed in decreasing preference order. The selected
* DC must be online.
*
* If this function is called during domain join, the specified kpasswd server
* takes precedence over preferred DC, AD site, and so on.
*
* Parameters:
* domain: fully-qualified domain name.
* kpasswd_srv: fully-quailifed hostname of the kpasswd server.
*
* Returns:
* A copy of the cached host info is returned. The caller is responsible
* for deallocating the memory returned by this function.
*/
/*ARGSUSED*/
smb_ads_host_info_t *
smb_ads_find_host(char *domain, char *kpasswd_srv)
{
int i;
char site_service[MAXHOSTNAMELEN];
smb_ads_host_list_t *hlist, *hlist2;
smb_ads_host_info_t *hlistp = NULL, *host = NULL;
smb_ads_host_info_t *found_kpasswd_srv = NULL;
smb_ads_host_info_t *found_pdc = NULL;
if ((kpasswd_srv) && (*kpasswd_srv == '\0'))
kpasswd_srv = NULL;
(void) mutex_lock(&smb_ads_cached_host_mtx);
if (smb_ads_validate_cache_host(domain, kpasswd_srv)) {
host = smb_ads_dup_host_info(smb_ads_cached_host_info);
(void) mutex_unlock(&smb_ads_cached_host_mtx);
return (host);
}
(void) mutex_unlock(&smb_ads_cached_host_mtx);
smb_ads_free_cached_host();
/*
* First look for ADS hosts in ADS site if configured. Then try
* without ADS site info.
*/
hlist = NULL;
smb_ads_get_site_service(site_service, MAXHOSTNAMELEN);
/*
* If we're given an AD, the DNS SRV RR lookup should not be restricted
* to the specified site since there is no guarantee that the specified
* AD is in the specified site.
*/
if (*site_service != '\0' && !kpasswd_srv &&
!smb_ads_is_pdc_configured())
hlist = smb_ads_query_dns_server(domain, site_service);
if (!hlist)
hlist = smb_ads_query_dns_server(domain,
SMB_ADS_MSDCS_SRV_DC_RR);
if ((hlist == NULL) || (hlist->ah_list == NULL) || (hlist->ah_cnt == 0))
return (NULL);
for (i = 0, hlistp = hlist->ah_list; i < hlist->ah_cnt; i++) {
/* Do a host lookup by hostname to get the IP address */
if (smb_inet_iszero(&hlistp[i].ipaddr)) {
if (smb_ads_getipnodebyname(&hlistp[i]) < 0)
continue;
}
if (smb_ads_is_sought_host(&hlistp[i], kpasswd_srv))
found_kpasswd_srv = &hlistp[i];
if (smb_ads_match_pdc(&hlistp[i]))
found_pdc = &hlistp[i];
}
if (found_kpasswd_srv && smb_ads_ldap_ping(found_kpasswd_srv) == 0) {
host = found_kpasswd_srv;
goto update_cache;
}
if (found_pdc && smb_ads_ldap_ping(found_pdc) == 0) {
host = found_pdc;
goto update_cache;
}
/*
* If the specified DC (kpasswd_srv or pdc) is not found, fallback
* to find a DC in the specified AD site.
*/
if (*site_service != '\0' &&
(kpasswd_srv || smb_ads_is_pdc_configured())) {
hlist2 = smb_ads_query_dns_server(domain, site_service);
if (hlist2 && hlist2->ah_list && hlist2->ah_cnt != 0) {
smb_ads_hlist_free(hlist);
hlist = hlist2;
hlistp = hlist->ah_list;
for (i = 0; i < hlist->ah_cnt; i++) {
if (smb_inet_iszero(&hlistp[i].ipaddr) &&
smb_ads_getipnodebyname(&hlistp[i]) < 0)
continue;
}
}
}
/* Select DC from DC list */
host = smb_ads_select_dc(hlist);
update_cache:
if (host) {
(void) mutex_lock(&smb_ads_cached_host_mtx);
if (!smb_ads_cached_host_info)
smb_ads_cached_host_info = smb_ads_dup_host_info(host);
host = smb_ads_dup_host_info(smb_ads_cached_host_info);
(void) mutex_unlock(&smb_ads_cached_host_mtx);
}
smb_ads_hlist_free(hlist);
return (host);
}
/*
* Return the number of dots in a string.
*/
static int
smb_ads_count_dots(const char *s)
{
int ndots = 0;
while (*s) {
if (*s++ == '.')
ndots++;
}
return (ndots);
}
/*
* Convert a domain name in dot notation to distinguished name format,
* for example: sun.com -> dc=sun,dc=com.
*
* Returns a pointer to an allocated buffer containing the distinguished
* name.
*/
static char *
smb_ads_convert_domain(const char *domain_name)
{
const char *s;
char *dn_name;
char buf[2];
int ndots;
int len;
if (domain_name == NULL || *domain_name == 0)
return (NULL);
ndots = smb_ads_count_dots(domain_name);
++ndots;
len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1;
if ((dn_name = malloc(len)) == NULL)
return (NULL);
bzero(dn_name, len);
(void) strlcpy(dn_name, "dc=", len);
buf[1] = '\0';
s = domain_name;
while (*s) {
if (*s == '.') {
(void) strlcat(dn_name, ",dc=", len);
} else {
buf[0] = *s;
(void) strlcat(dn_name, buf, len);
}
++s;
}
return (dn_name);
}
/*
* smb_ads_free_cached_host
*
* Free the memory use by the global smb_ads_cached_host_info & set it to NULL.
*/
static void
smb_ads_free_cached_host(void)
{
(void) mutex_lock(&smb_ads_cached_host_mtx);
if (smb_ads_cached_host_info) {
free(smb_ads_cached_host_info);
smb_ads_cached_host_info = NULL;
}
(void) mutex_unlock(&smb_ads_cached_host_mtx);
}
/*
* smb_ads_open
* Open a LDAP connection to an ADS server if the system is in domain mode.
* Acquire both Kerberos TGT and LDAP service tickets for the host principal.
*
* This function should only be called after the system is successfully joined
* to a domain.
*/
smb_ads_handle_t *
smb_ads_open(void)
{
char domain[MAXHOSTNAMELEN];
if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
return (NULL);
if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
return (NULL);
return (smb_ads_open_main(domain, NULL, NULL));
}
static int
smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts)
{
NOTE(ARGUNUSED(ld, defaults));
sasl_interact_t *interact;
if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
return (LDAP_PARAM_ERROR);
/* There should be no extra arguemnts for SASL/GSSAPI authentication */
for (interact = prompts; interact->id != SASL_CB_LIST_END;
interact++) {
interact->result = NULL;
interact->len = 0;
}
return (LDAP_SUCCESS);
}
/*
* smb_ads_open_main
* Open a LDAP connection to an ADS server.
* If ADS is enabled and the administrative username, password, and
* ADS domain are defined then query DNS to find an ADS server if this is the
* very first call to this routine. After an ADS server is found then this
* server will be used everytime this routine is called until the system is
* rebooted or the ADS server becomes unavailable then an ADS server will
* be queried again. After the connection is made then an ADS handle
* is created to be returned.
*
* After the LDAP connection, the LDAP version will be set to 3 using
* ldap_set_option().
*
* The LDAP connection is bound before the ADS handle is returned.
* Parameters:
* domain - fully-qualified domain name
* user - the user account for whom the Kerberos TGT ticket and ADS
* service tickets are acquired.
* password - password of the specified user
*
* Returns:
* NULL : can't connect to ADS server or other errors
* smb_ads_handle_t* : handle to ADS server
*/
static smb_ads_handle_t *
smb_ads_open_main(char *domain, char *user, char *password)
{
smb_ads_handle_t *ah;
LDAP *ld;
int version = 3;
smb_ads_host_info_t *ads_host = NULL;
int rc;
if (user != NULL) {
if (smb_kinit(user, password) == 0)
return (NULL);
user = NULL;
password = NULL;
}
ads_host = smb_ads_find_host(domain, NULL);
if (ads_host == NULL)
return (NULL);
ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
if (ah == NULL) {
free(ads_host);
return (NULL);
}
(void) memset(ah, 0, sizeof (smb_ads_handle_t));
if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) {
smb_ads_free_cached_host();
free(ah);
free(ads_host);
return (NULL);
}
if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version)
!= LDAP_SUCCESS) {
smb_ads_free_cached_host();
free(ah);
free(ads_host);
(void) ldap_unbind(ld);
return (NULL);
}
(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
ah->ld = ld;
ah->domain = strdup(domain);
if (ah->domain == NULL) {
smb_ads_close(ah);
free(ads_host);
return (NULL);
}
ah->domain_dn = smb_ads_convert_domain(domain);
if (ah->domain_dn == NULL) {
smb_ads_close(ah);
free(ads_host);
return (NULL);
}
ah->hostname = strdup(ads_host->name);
if (ah->hostname == NULL) {
smb_ads_close(ah);
free(ads_host);
return (NULL);
}
(void) mutex_lock(&smb_ads_cfg.c_mtx);
if (*smb_ads_cfg.c_site != '\0') {
if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) {
smb_ads_close(ah);
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
free(ads_host);
return (NULL);
}
} else {
ah->site = NULL;
}
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL,
LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL);
if (rc != LDAP_SUCCESS) {
syslog(LOG_ERR, "ldal_sasl_interactive_bind_s failed (%s)",
ldap_err2string(rc));
smb_ads_close(ah);
free(ads_host);
return (NULL);
}
free(ads_host);
return (ah);
}
/*
* smb_ads_close
* Close connection to ADS server and free memory allocated for ADS handle.
* LDAP unbind is called here.
* Parameters:
* ah: handle to ADS server
* Returns:
* void
*/
void
smb_ads_close(smb_ads_handle_t *ah)
{
if (ah == NULL)
return;
/* close and free connection resources */
if (ah->ld)
(void) ldap_unbind(ah->ld);
free(ah->domain);
free(ah->domain_dn);
free(ah->hostname);
free(ah->site);
free(ah);
}
/*
* smb_ads_alloc_attr
*
* Since the attrs is a null-terminated array, all elements
* in the array (except the last one) will point to allocated
* memory.
*/
static int
smb_ads_alloc_attr(LDAPMod *attrs[], int num)
{
int i;
bzero(attrs, num * sizeof (LDAPMod *));
for (i = 0; i < (num - 1); i++) {
attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod));
if (attrs[i] == NULL) {
smb_ads_free_attr(attrs);
return (-1);
}
}
return (0);
}
/*
* smb_ads_free_attr
* Free memory allocated when publishing a share.
* Parameters:
* attrs: an array of LDAPMod pointers
* Returns:
* None
*/
static void
smb_ads_free_attr(LDAPMod *attrs[])
{
int i;
for (i = 0; attrs[i]; i++) {
free(attrs[i]);
}
}
/*
* smb_ads_get_spnset
*
* Derives the core set of SPNs based on the FQHN.
* The spn_set is a null-terminated array of char pointers.
*
* Returns 0 upon success. Otherwise, returns -1.
*/
static int
smb_ads_get_spnset(char *fqhost, char **spn_set)
{
int i;
bzero(spn_set, (SMBKRB5_SPN_IDX_MAX + 1) * sizeof (char *));
for (i = 0; i < SMBKRB5_SPN_IDX_MAX; i++) {
if ((spn_set[i] = smb_krb5_get_spn(i, fqhost)) == NULL) {
smb_ads_free_spnset(spn_set);
return (-1);
}
}
return (0);
}
/*
* smb_ads_free_spnset
*
* Free the memory allocated for the set of SPNs.
*/
static void
smb_ads_free_spnset(char **spn_set)
{
int i;
for (i = 0; spn_set[i]; i++)
free(spn_set[i]);
}
/*
* Returns share DN in an allocated buffer. The format of the DN is
* cn=<sharename>,<container RDNs>,<domain DN>
*
* If the domain DN is not included in the container parameter,
* then it will be appended to create the share DN.
*
* The caller must free the allocated buffer.
*/
static char *
smb_ads_get_sharedn(const char *sharename, const char *container,
const char *domain_dn)
{
char *share_dn;
int rc, offset, container_len, domain_len;
boolean_t append_domain = B_TRUE;
container_len = strlen(container);
domain_len = strlen(domain_dn);
if (container_len >= domain_len) {
/* offset to last domain_len characters */
offset = container_len - domain_len;
if (smb_strcasecmp(container + offset,
domain_dn, domain_len) == 0)
append_domain = B_FALSE;
}
if (append_domain)
rc = asprintf(&share_dn, "cn=%s,%s,%s", sharename,
container, domain_dn);
else
rc = asprintf(&share_dn, "cn=%s,%s", sharename,
container);
return ((rc == -1) ? NULL : share_dn);
}
/*
* smb_ads_add_share
* Call by smb_ads_publish_share to create share object in ADS.
* This routine specifies the attributes of an ADS LDAP share object. The first
* attribute and values define the type of ADS object, the share object. The
* second attribute and value define the UNC of the share data for the share
* object. The LDAP synchronous add command is used to add the object into ADS.
* The container location to add the object needs to specified.
* Parameters:
* ah : handle to ADS server
* adsShareName: name of share object to be created in ADS
* shareUNC : share name on NetForce
* adsContainer: location in ADS to create share object
*
* Returns:
* -1 : error
* 0 : success
*/
int
smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName,
const char *unc_name, const char *adsContainer)
{
LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR];
int j = 0;
char *share_dn;
int ret;
char *unc_names[] = {(char *)unc_name, NULL};
if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
ah->domain_dn)) == NULL)
return (-1);
if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) {
free(share_dn);
return (-1);
}
attrs[j]->mod_op = LDAP_MOD_ADD;
attrs[j]->mod_type = "objectClass";
attrs[j]->mod_values = smb_ads_share_objcls;
attrs[++j]->mod_op = LDAP_MOD_ADD;
attrs[j]->mod_type = "uNCName";
attrs[j]->mod_values = unc_names;
if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) {
smb_tracef("%s: ldap_add: %s", share_dn, ldap_err2string(ret));
smb_ads_free_attr(attrs);
free(share_dn);
return (ret);
}
free(share_dn);
smb_ads_free_attr(attrs);
return (0);
}
/*
* smb_ads_del_share
* Call by smb_ads_remove_share to remove share object from ADS. The container
* location to remove the object needs to specified. The LDAP synchronous
* delete command is used.
* Parameters:
* ah : handle to ADS server
* adsShareName: name of share object in ADS to be removed
* adsContainer: location of share object in ADS
* Returns:
* -1 : error
* 0 : success
*/
static int
smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName,
const char *adsContainer)
{
char *share_dn;
int ret;
if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
ah->domain_dn)) == NULL)
return (-1);
if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) {
smb_tracef("ldap_delete: %s", ldap_err2string(ret));
free(share_dn);
return (-1);
}
free(share_dn);
return (0);
}
/*
* smb_ads_escape_search_filter_chars
*
* This routine will escape the special characters found in a string
* that will later be passed to the ldap search filter.
*
* RFC 1960 - A String Representation of LDAP Search Filters
* 3. String Search Filter Definition
* If a value must contain one of the characters '*' OR '(' OR ')',
* these characters
* should be escaped by preceding them with the backslash '\' character.
*
* RFC 2252 - LDAP Attribute Syntax Definitions
* a backslash quoting mechanism is used to escape
* the following separator symbol character (such as "'", "$" or "#") if
* it should occur in that string.
*/
static int
smb_ads_escape_search_filter_chars(const char *src, char *dst)
{
int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */
if (src == NULL || dst == NULL)
return (-1);
while (*src) {
if (!avail) {
*dst = 0;
return (-1);
}
switch (*src) {
case '\\':
case '\'':
case '$':
case '#':
case '*':
case '(':
case ')':
*dst++ = '\\';
avail--;
/* fall through */
default:
*dst++ = *src++;
avail--;
}
}
*dst = 0;
return (0);
}
/*
* smb_ads_lookup_share
* The search filter is set to search for a specific share name in the
* specified ADS container. The LDSAP synchronous search command is used.
* Parameters:
* ah : handle to ADS server
* adsShareName: name of share object in ADS to be searched
* adsContainer: location of share object in ADS
* Returns:
* -1 : error
* 0 : not found
* 1 : found
*/
int
smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName,
const char *adsContainer, char *unc_name)
{
char *attrs[4], filter[SMB_ADS_MAXBUFLEN];
char *share_dn;
int ret;
LDAPMessage *res;
char tmpbuf[SMB_ADS_MAXBUFLEN];
if (adsShareName == NULL || adsContainer == NULL)
return (-1);
if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
ah->domain_dn)) == NULL)
return (-1);
res = NULL;
attrs[0] = "cn";
attrs[1] = "objectClass";
attrs[2] = "uNCName";
attrs[3] = NULL;
if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) {
free(share_dn);
return (-1);
}
(void) snprintf(filter, sizeof (filter),
"(&(objectClass=volume)(uNCName=%s))", tmpbuf);
if ((ret = ldap_search_s(ah->ld, share_dn,
LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) {
if (ret != LDAP_NO_SUCH_OBJECT)
smb_tracef("%s: ldap_search: %s", share_dn,
ldap_err2string(ret));
(void) ldap_msgfree(res);
free(share_dn);
return (0);
}
(void) free(share_dn);
/* no match is found */
if (ldap_count_entries(ah->ld, res) == 0) {
(void) ldap_msgfree(res);
return (0);
}
/* free the search results */
(void) ldap_msgfree(res);
return (1);
}
/*
* smb_ads_publish_share
* Publish share into ADS. If a share name already exist in ADS in the same
* container then the existing share object is removed before adding the new
* share object.
* Parameters:
* ah : handle return from smb_ads_open
* adsShareName: name of share to be added to ADS directory
* shareUNC : name of share on client, can be NULL to use the same name
* as adsShareName
* adsContainer: location for share to be added in ADS directory, ie
* ou=share_folder
* uncType : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR
* to use host ip addr for UNC.
* Returns:
* -1 : error
* 0 : success
*/
int
smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName,
const char *shareUNC, const char *adsContainer, const char *hostname)
{
int ret;
char unc_name[SMB_ADS_MAXBUFLEN];
if (adsShareName == NULL || adsContainer == NULL)
return (-1);
if (shareUNC == 0 || *shareUNC == 0)
shareUNC = adsShareName;
if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
hostname, shareUNC) < 0)
return (-1);
ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
switch (ret) {
case 1:
(void) smb_ads_del_share(ah, adsShareName, adsContainer);
ret = smb_ads_add_share(ah, adsShareName, unc_name,
adsContainer);
break;
case 0:
ret = smb_ads_add_share(ah, adsShareName, unc_name,
adsContainer);
if (ret == LDAP_ALREADY_EXISTS)
ret = -1;
break;
case -1:
default:
/* return with error code */
ret = -1;
}
return (ret);
}
/*
* smb_ads_remove_share
* Remove share from ADS. A search is done first before explicitly removing
* the share.
* Parameters:
* ah : handle return from smb_ads_open
* adsShareName: name of share to be removed from ADS directory
* adsContainer: location for share to be removed from ADS directory, ie
* ou=share_folder
* Returns:
* -1 : error
* 0 : success
*/
int
smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName,
const char *shareUNC, const char *adsContainer, const char *hostname)
{
int ret;
char unc_name[SMB_ADS_MAXBUFLEN];
if (adsShareName == NULL || adsContainer == NULL)
return (-1);
if (shareUNC == 0 || *shareUNC == 0)
shareUNC = adsShareName;
if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
hostname, shareUNC) < 0)
return (-1);
ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
if (ret == 0)
return (0);
if (ret == -1)
return (-1);
return (smb_ads_del_share(ah, adsShareName, adsContainer));
}
/*
* smb_ads_get_default_comp_container_dn
*
* Build the distinguished name for the default computer conatiner (i.e. the
* pre-defined Computers container).
*/
static void
smb_ads_get_default_comp_container_dn(smb_ads_handle_t *ah, char *buf,
size_t buflen)
{
(void) snprintf(buf, buflen, "cn=%s,%s", SMB_ADS_COMPUTERS_CN,
ah->domain_dn);
}
/*
* smb_ads_get_default_comp_dn
*
* Build the distinguished name for this system.
*/
static void
smb_ads_get_default_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen)
{
char nbname[NETBIOS_NAME_SZ];
char container_dn[SMB_ADS_DN_MAX];
(void) smb_getnetbiosname(nbname, sizeof (nbname));
smb_ads_get_default_comp_container_dn(ah, container_dn, SMB_ADS_DN_MAX);
(void) snprintf(buf, buflen, "cn=%s,%s", nbname, container_dn);
}
/*
* smb_ads_add_computer
*
* Returns 0 upon success. Otherwise, returns -1.
*/
static int
smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
{
return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn));
}
/*
* smb_ads_modify_computer
*
* Returns 0 upon success. Otherwise, returns -1.
*/
static int
smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
{
return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn));
}
/*
* smb_ads_get_dc_level
*
* Returns the functional level of the DC upon success.
* Otherwise, -1 is returned.
*/
static int
smb_ads_get_dc_level(smb_ads_handle_t *ah)
{
LDAPMessage *res, *entry;
char *attr[2];
char **vals;
int rc = -1;
res = NULL;
attr[0] = SMB_ADS_ATTR_DCLEVEL;
attr[1] = NULL;
if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr,
0, &res) != LDAP_SUCCESS) {
(void) ldap_msgfree(res);
return (-1);
}
/* no match for the specified attribute is found */
if (ldap_count_entries(ah->ld, res) == 0) {
(void) ldap_msgfree(res);
return (-1);
}
entry = ldap_first_entry(ah->ld, res);
if (entry) {
if ((vals = ldap_get_values(ah->ld, entry,
SMB_ADS_ATTR_DCLEVEL)) == NULL) {
/*
* Observed the values aren't populated
* by the Windows 2000 server.
*/
(void) ldap_msgfree(res);
return (SMB_ADS_DCLEVEL_W2K);
}
if (vals[0] != NULL)
rc = atoi(vals[0]);
ldap_value_free(vals);
}
(void) ldap_msgfree(res);
return (rc);
}
static int
smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len)
{
if (smb_gethostname(fqhost, len, 0) != 0)
return (-1);
(void) snprintf(fqhost, len, "%s.%s", fqhost,
ah->domain);
return (0);
}
static int
smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
{
LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR];
char *sam_val[2], *usr_val[2];
char *spn_set[SMBKRB5_SPN_IDX_MAX + 1], *ctl_val[2], *fqh_val[2];
char *encrypt_val[2];
int j = -1;
int ret, usrctl_flags = 0;
char sam_acct[SMB_SAMACCT_MAXLEN];
char fqhost[MAXHOSTNAMELEN];
char *user_principal;
char usrctl_buf[16];
char encrypt_buf[16];
int max;
if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
return (-1);
if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
return (-1);
if (smb_ads_get_spnset(fqhost, spn_set) != 0)
return (-1);
user_principal = smb_krb5_get_upn(spn_set[SMBKRB5_SPN_IDX_HOST],
ah->domain);
if (user_principal == NULL) {
smb_ads_free_spnset(spn_set);
return (-1);
}
max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0))
- (dclevel >= SMB_ADS_DCLEVEL_W2K8 ? 0 : 1);
if (smb_ads_alloc_attr(attrs, max) != 0) {
free(user_principal);
smb_ads_free_spnset(spn_set);
return (-1);
}
/* objectClass attribute is not modifiable. */
if (op == LDAP_MOD_ADD) {
attrs[++j]->mod_op = op;
attrs[j]->mod_type = "objectClass";
attrs[j]->mod_values = smb_ads_computer_objcls;
}
attrs[++j]->mod_op = op;
attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT;
sam_val[0] = sam_acct;
sam_val[1] = 0;
attrs[j]->mod_values = sam_val;
attrs[++j]->mod_op = op;
attrs[j]->mod_type = SMB_ADS_ATTR_UPN;
usr_val[0] = user_principal;
usr_val[1] = 0;
attrs[j]->mod_values = usr_val;
attrs[++j]->mod_op = op;
attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
attrs[j]->mod_values = spn_set;
attrs[++j]->mod_op = op;
attrs[j]->mod_type = SMB_ADS_ATTR_CTL;
usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD |
SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE);
(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
ctl_val[0] = usrctl_buf;
ctl_val[1] = 0;
attrs[j]->mod_values = ctl_val;
attrs[++j]->mod_op = op;
attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST;
fqh_val[0] = fqhost;
fqh_val[1] = 0;
attrs[j]->mod_values = fqh_val;
/* enctypes support starting in Windows Server 2008 */
if (dclevel > SMB_ADS_DCLEVEL_W2K3) {
attrs[++j]->mod_op = op;
attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES;
(void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d",
SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 +
SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC);
encrypt_val[0] = encrypt_buf;
encrypt_val[1] = 0;
attrs[j]->mod_values = encrypt_val;
}
switch (op) {
case LDAP_MOD_ADD:
if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
syslog(LOG_NOTICE, "ldap_add: %s",
ldap_err2string(ret));
ret = -1;
}
break;
case LDAP_MOD_REPLACE:
if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
syslog(LOG_NOTICE, "ldap_modify: %s",
ldap_err2string(ret));
ret = -1;
}
break;
default:
ret = -1;
}
smb_ads_free_attr(attrs);
free(user_principal);
smb_ads_free_spnset(spn_set);
return (ret);
}
/*
* Delete an ADS computer account.
*/
static void
smb_ads_del_computer(smb_ads_handle_t *ah, char *dn)
{
int rc;
if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS)
smb_tracef("ldap_delete: %s", ldap_err2string(rc));
}
/*
* Gets the value of the given attribute.
*/
static smb_ads_qstat_t
smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair)
{
char **vals;
smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
assert(avpair);
avpair->avp_val = NULL;
vals = ldap_get_values(ld, entry, avpair->avp_attr);
if (!vals)
return (SMB_ADS_STAT_NOT_FOUND);
if (!vals[0]) {
ldap_value_free(vals);
return (SMB_ADS_STAT_NOT_FOUND);
}
avpair->avp_val = strdup(vals[0]);
if (!avpair->avp_val)
rc = SMB_ADS_STAT_ERR;
ldap_value_free(vals);
return (rc);
}
/*
* Process query's result.
*/
static smb_ads_qstat_t
smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
smb_ads_avpair_t *avpair)
{
char fqhost[MAXHOSTNAMELEN];
smb_ads_avpair_t dnshost_avp;
smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
LDAPMessage *entry;
if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
return (SMB_ADS_STAT_ERR);
if (ldap_count_entries(ah->ld, res) == 0)
return (SMB_ADS_STAT_NOT_FOUND);
if ((entry = ldap_first_entry(ah->ld, res)) == NULL)
return (SMB_ADS_STAT_ERR);
dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
switch (rc) {
case SMB_ADS_STAT_FOUND:
/*
* Returns SMB_ADS_STAT_DUP to avoid overwriting
* the computer account of another system whose
* NetBIOS name collides with that of the current
* system.
*/
if (strcasecmp(dnshost_avp.avp_val, fqhost))
rc = SMB_ADS_STAT_DUP;
free(dnshost_avp.avp_val);
break;
case SMB_ADS_STAT_NOT_FOUND:
/*
* Pre-created computer account doesn't have
* the dNSHostname attribute. It's been observed
* that the dNSHostname attribute is only set after
* a successful domain join.
* Returns SMB_ADS_STAT_FOUND as the account is
* pre-created for the current system.
*/
rc = SMB_ADS_STAT_FOUND;
break;
default:
break;
}
if (rc != SMB_ADS_STAT_FOUND)
return (rc);
if (avpair)
rc = smb_ads_getattr(ah->ld, entry, avpair);
return (rc);
}
/*
* smb_ads_lookup_computer_n_attr
*
* If avpair is NULL, checks the status of the specified computer account.
* Otherwise, looks up the value of the specified computer account's attribute.
* If found, the value field of the avpair will be allocated and set. The
* caller should free the allocated buffer.
*
* Return:
* SMB_ADS_STAT_FOUND - if both the computer and the specified attribute is
* found.
* SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute
* is not found.
* SMB_ADS_STAT_DUP - if the computer account is already used by other systems
* in the AD. This could happen if the hostname of multiple
* systems resolved to the same NetBIOS name.
* SMB_ADS_STAT_ERR - any failure.
*/
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)
{
char *attrs[3], filter[SMB_ADS_MAXBUFLEN];
LDAPMessage *res;
char sam_acct[SMB_SAMACCT_MAXLEN], sam_acct2[SMB_SAMACCT_MAXLEN];
smb_ads_qstat_t rc;
if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
return (SMB_ADS_STAT_ERR);
res = NULL;
attrs[0] = SMB_ADS_ATTR_DNSHOST;
attrs[1] = NULL;
attrs[2] = NULL;
if (avpair) {
if (!avpair->avp_attr)
return (SMB_ADS_STAT_ERR);
attrs[1] = avpair->avp_attr;
}
if (smb_ads_escape_search_filter_chars(sam_acct, sam_acct2) != 0)
return (SMB_ADS_STAT_ERR);
(void) snprintf(filter, sizeof (filter),
"(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT,
sam_acct2);
if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0,
&res) != LDAP_SUCCESS) {
(void) ldap_msgfree(res);
return (SMB_ADS_STAT_NOT_FOUND);
}
rc = smb_ads_get_qstat(ah, res, avpair);
/* free the search results */
(void) ldap_msgfree(res);
return (rc);
}
/*
* smb_ads_find_computer
*
* Starts by searching for the system's AD computer object in the default
* container (i.e. cn=Computers). If not found, searches the entire directory.
* If found, 'dn' will be set to the distinguished name of the system's AD
* computer object.
*/
static smb_ads_qstat_t
smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
{
smb_ads_qstat_t stat;
smb_ads_avpair_t avpair;
avpair.avp_attr = SMB_ADS_ATTR_DN;
smb_ads_get_default_comp_container_dn(ah, dn, SMB_ADS_DN_MAX);
stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_ONELEVEL,
dn);
if (stat == SMB_ADS_STAT_NOT_FOUND) {
(void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX);
stat = smb_ads_lookup_computer_n_attr(ah, &avpair,
LDAP_SCOPE_SUBTREE, dn);
}
if (stat == SMB_ADS_STAT_FOUND) {
(void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX);
free(avpair.avp_val);
}
return (stat);
}
/*
* smb_ads_update_computer_cntrl_attr
*
* Modify the user account control attribute of an existing computer
* object on AD.
*
* Returns LDAP error code.
*/
static int
smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn)
{
LDAPMod *attrs[2];
char *ctl_val[2];
int ret = 0;
char usrctl_buf[16];
if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
return (LDAP_NO_MEMORY);
attrs[0]->mod_op = LDAP_MOD_REPLACE;
attrs[0]->mod_type = SMB_ADS_ATTR_CTL;
(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags);
ctl_val[0] = usrctl_buf;
ctl_val[1] = 0;
attrs[0]->mod_values = ctl_val;
if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
}
smb_ads_free_attr(attrs);
return (ret);
}
/*
* smb_ads_lookup_computer_attr_kvno
*
* Lookup the value of the Kerberos version number attribute of the computer
* account.
*/
static krb5_kvno
smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
{
smb_ads_avpair_t avpair;
int kvno = 1;
avpair.avp_attr = SMB_ADS_ATTR_KVNO;
if (smb_ads_lookup_computer_n_attr(ah, &avpair,
LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) {
kvno = atoi(avpair.avp_val);
free(avpair.avp_val);
}
return (kvno);
}
/*
* smb_ads_gen_machine_passwd
*
* Returned a null-terminated machine password generated randomly
* from [0-9a-zA-Z] character set. In order to pass the password
* quality check (three character classes), an uppercase letter is
* used as the first character of the machine password.
*/
static void
smb_ads_gen_machine_passwd(char *machine_passwd, int bufsz)
{
char *data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJK"
"LMNOPQRSTUVWXYZ";
int datalen = strlen(data);
int i, data_idx;
assert(machine_passwd);
assert(bufsz > 0);
/*
* The decimal value of upper case 'A' is 65. Randomly pick
* an upper-case letter from the ascii table.
*/
machine_passwd[0] = (random() % 26) + 65;
for (i = 1; i < bufsz - 1; i++) {
data_idx = random() % datalen;
machine_passwd[i] = data[data_idx];
}
machine_passwd[bufsz - 1] = 0;
}
/*
* smb_ads_join
*
* Besides the NT-4 style domain join (using MS-RPC), CIFS server also
* provides the domain join using Kerberos Authentication, Keberos
* Change & Set password, and LDAP protocols. Basically, AD join
* operation would require the following tickets to be acquired for the
* the user account that is provided for the domain join.
*
* 1) a Keberos TGT ticket,
* 2) a ldap service ticket, and
* 3) kadmin/changpw service ticket
*
* The ADS client first sends a ldap search request to find out whether
* or not the workstation trust account already exists in the Active Directory.
* The existing computer object for this workstation will be removed and
* a new one will be added. The machine account password is randomly
* generated and set for the newly created computer object using KPASSWD
* protocol (See RFC 3244). Once the password is set, our ADS client
* finalizes the machine account by modifying the user acount control
* attribute of the computer object. Kerberos keys derived from the machine
* account password will be stored locally in /etc/krb5/krb5.keytab file.
* That would be needed while acquiring Kerberos TGT ticket for the host
* principal after the domain join operation.
*/
smb_adjoin_status_t
smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd,
int len)
{
smb_ads_handle_t *ah = NULL;
krb5_context ctx = NULL;
krb5_principal krb5princs[SMBKRB5_SPN_IDX_MAX];
krb5_kvno kvno;
boolean_t des_only, delete = B_TRUE;
smb_adjoin_status_t rc = SMB_ADJOIN_SUCCESS;
boolean_t new_acct;
int dclevel, num, usrctl_flags = 0;
smb_ads_qstat_t qstat;
char dn[SMB_ADS_DN_MAX];
char *tmpfile;
/*
* Call library functions that can be used to get
* the list of encryption algorithms available on the system.
* (similar to what 'encrypt -l' CLI does). For now,
* unless someone has modified the configuration of the
* cryptographic framework (very unlikely), the following is the
* list of algorithms available on any system running Nevada
* by default.
*/
krb5_enctype w2k8enctypes[] = {ENCTYPE_AES256_CTS_HMAC_SHA1_96,
ENCTYPE_AES128_CTS_HMAC_SHA1_96, ENCTYPE_ARCFOUR_HMAC,
ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5,
};
krb5_enctype pre_w2k8enctypes[] = {ENCTYPE_ARCFOUR_HMAC,
ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5,
};
krb5_enctype *encptr;
if ((ah = smb_ads_open_main(domain, user, usr_passwd)) == NULL) {
smb_ccache_remove(SMB_CCACHE_PATH);
return (SMB_ADJOIN_ERR_GET_HANDLE);
}
smb_ads_gen_machine_passwd(machine_passwd, len);
if ((dclevel = smb_ads_get_dc_level(ah)) == -1) {
smb_ads_close(ah);
smb_ccache_remove(SMB_CCACHE_PATH);
return (SMB_ADJOIN_ERR_GET_DCLEVEL);
}
qstat = smb_ads_find_computer(ah, dn);
switch (qstat) {
case SMB_ADS_STAT_FOUND:
new_acct = B_FALSE;
if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
smb_ads_close(ah);
smb_ccache_remove(SMB_CCACHE_PATH);
return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT);
}
break;
case SMB_ADS_STAT_NOT_FOUND:
new_acct = B_TRUE;
smb_ads_get_default_comp_dn(ah, dn, SMB_ADS_DN_MAX);
if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
smb_ads_close(ah);
smb_ccache_remove(SMB_CCACHE_PATH);
return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT);
}
break;
default:
if (qstat == SMB_ADS_STAT_DUP)
rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT;
else
rc = SMB_ADJOIN_ERR_TRUST_ACCT;
smb_ads_close(ah);
smb_ccache_remove(SMB_CCACHE_PATH);
return (rc);
}
des_only = B_FALSE;
if (smb_krb5_ctx_init(&ctx) != 0) {
rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
goto adjoin_cleanup;
}
if (smb_krb5_get_principals(ah->domain, ctx, krb5princs) != 0) {
rc = SMB_ADJOIN_ERR_GET_SPNS;
goto adjoin_cleanup;
}
if (smb_krb5_setpwd(ctx, krb5princs[SMBKRB5_SPN_IDX_HOST],
machine_passwd) != 0) {
rc = SMB_ADJOIN_ERR_KSETPWD;
goto adjoin_cleanup;
}
kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
/*
* Only members of Domain Admins and Enterprise Admins can set
* the TRUSTED_FOR_DELEGATION userAccountControl flag.
*/
if (smb_ads_update_computer_cntrl_attr(ah,
SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION, dn)
== LDAP_INSUFFICIENT_ACCESS) {
usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
syslog(LOG_NOTICE, "Unable to set the "
"TRUSTED_FOR_DELEGATION userAccountControl flag on "
"the machine account in Active Directory. Please refer "
"to the Troubleshooting guide for more information.");
} else {
usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION |
SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
}
if (des_only)
usrctl_flags |= SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY;
if (smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn)
!= 0) {
rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
goto adjoin_cleanup;
}
if (dclevel >= SMB_ADS_DCLEVEL_W2K8) {
num = sizeof (w2k8enctypes) / sizeof (krb5_enctype);
encptr = w2k8enctypes;
} else {
num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype);
encptr = pre_w2k8enctypes;
}
tmpfile = mktemp(SMBNS_KRB5_KEYTAB_TMP);
if (tmpfile == NULL)
tmpfile = SMBNS_KRB5_KEYTAB_TMP;
if (smb_krb5_add_keytab_entries(ctx, krb5princs, tmpfile,
kvno, machine_passwd, encptr, num) != 0) {
rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
goto adjoin_cleanup;
}
delete = B_FALSE;
adjoin_cleanup:
if (new_acct && delete)
smb_ads_del_computer(ah, dn);
if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) {
if (rc != SMB_ADJOIN_ERR_GET_SPNS)
smb_krb5_free_principals(ctx, krb5princs,
SMBKRB5_SPN_IDX_MAX);
smb_krb5_ctx_fini(ctx);
}
/* commit keytab file */
if (rc == SMB_ADJOIN_SUCCESS) {
if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) {
(void) unlink(tmpfile);
rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB;
} else {
/* Set IDMAP config */
if (smb_config_set_idmap_domain(ah->domain) != 0) {
rc = SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN;
} else {
/* Refresh IDMAP service */
if (smb_config_refresh_idmap() != 0)
rc = SMB_ADJOIN_ERR_IDMAP_REFRESH;
}
}
} else {
(void) unlink(tmpfile);
}
smb_ads_close(ah);
smb_ccache_remove(SMB_CCACHE_PATH);
return (rc);
}
/*
* smb_ads_join_errmsg
*
* Display error message for the specific adjoin error code.
*/
void
smb_ads_join_errmsg(smb_adjoin_status_t status)
{
int i;
struct xlate_table {
smb_adjoin_status_t status;
char *msg;
} adjoin_table[] = {
{ SMB_ADJOIN_ERR_GET_HANDLE, "Failed to connect to an "
"Active Directory server." },
{ SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of "
"the domain controller. The rootDSE attribute named "
"\"domainControllerFunctionality\" is missing from the "
"Active Directory." },
{ SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the "
"workstation trust account." },
{ SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the "
"workstation trust account." },
{ SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the "
"workstation trust account because its name is already "
"in use." },
{ SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the "
"workstation trust account" },
{ SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos "
"context." },
{ SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos "
"principals." },
{ SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." },
{ SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR, "Failed to modify "
"userAccountControl attribute of the workstation trust "
"account." },
{ SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local "
"keytab file (i.e /etc/krb5/krb5.keytab)." },
{ SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap "
"configuration." },
{ SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap "
"service." },
{ SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to "
"local keytab file (i.e. /etc/krb5/krb5.keytab)." }
};
for (i = 0; i < sizeof (adjoin_table) / sizeof (adjoin_table[0]); i++) {
if (adjoin_table[i].status == status)
syslog(LOG_NOTICE, "%s", adjoin_table[i].msg);
}
}
/*
* smb_ads_match_pdc
*
* Returns B_TRUE if the given host's IP address matches the preferred DC's
* IP address. Otherwise, returns B_FALSE.
*/
static boolean_t
smb_ads_match_pdc(smb_ads_host_info_t *host)
{
boolean_t match = B_FALSE;
if (!host)
return (match);
(void) mutex_lock(&smb_ads_cfg.c_mtx);
if (smb_inet_equal(&host->ipaddr, &smb_ads_cfg.c_pdc))
match = B_TRUE;
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
return (match);
}
/*
* smb_ads_select_dcfromsubnet
*
* This method walks the list of DCs and returns the first DC record that
* responds to ldap ping and is in the same subnet as the host.
*
* Returns a pointer to the found DC record.
* Returns NULL, on error or if no DC record is found.
*/
static smb_ads_host_info_t *
smb_ads_select_dcfromsubnet(smb_ads_host_list_t *hlist)
{
smb_ads_host_info_t *hentry;
smb_nic_t *lnic;
smb_niciter_t ni;
size_t cnt;
int i;
if (smb_nic_getfirst(&ni) != 0)
return (NULL);
do {
lnic = &ni.ni_nic;
cnt = hlist->ah_cnt;
for (i = 0; i < cnt; i++) {
hentry = &hlist->ah_list[i];
if ((hentry->ipaddr.a_family == AF_INET) &&
(lnic->nic_ip.a_family == AF_INET)) {
if ((hentry->ipaddr.a_ipv4 &
lnic->nic_mask) ==
(lnic->nic_ip.a_ipv4 &
lnic->nic_mask))
if (smb_ads_ldap_ping(hentry) == 0)
return (hentry);
}
}
} while (smb_nic_getnext(&ni) == 0);
return (NULL);
}
/*
* smb_ads_select_dcfromlist
*
* This method walks the list of DCs and returns the first DC that
* responds to ldap ping.
*
* Returns a pointer to the found DC record.
* Returns NULL if no DC record is found.
*/
static smb_ads_host_info_t *
smb_ads_select_dcfromlist(smb_ads_host_list_t *hlist)
{
smb_ads_host_info_t *hentry;
size_t cnt;
int i;
cnt = hlist->ah_cnt;
for (i = 0; i < cnt; i++) {
hentry = &hlist->ah_list[i];
if (smb_ads_ldap_ping(hentry) == 0)
return (hentry);
}
return (NULL);
}
/*
* smb_ads_dc_compare
*
* Comparision function for sorting host entries (SRV records of DC) via qsort.
* RFC 2052/2782 are taken as reference, while implementing this algorithm.
*
* Domain Controllers(DCs) with lowest priority in their SRV DNS records
* are selected first. If they have equal priorities, then DC with highest
* weight in its SRV DNS record is selected. If the priority and weight are
* both equal, then the DC at the top of the list is selected.
*/
static int
smb_ads_dc_compare(const void *p, const void *q)
{
smb_ads_host_info_t *h1 = (smb_ads_host_info_t *)p;
smb_ads_host_info_t *h2 = (smb_ads_host_info_t *)q;
if (h1->priority < h2->priority)
return (-1);
if (h1->priority > h2->priority)
return (1);
/* Priorities are equal */
if (h1->weight < h2->weight)
return (1);
if (h1->weight > h2->weight)
return (-1);
return (0);
}
/*
* smb_ads_select_dc
*
* The list of ADS hosts returned by ADS lookup, is sorted by lowest priority
* and highest weight. On this sorted list, following additional rules are
* applied, to select a DC.
*
* - If there is a DC in the same subnet, then return the DC,
* if it responds to ldap ping.
* - Else, return first DC that responds to ldap ping.
*
* A reference to the host entry from input host list is returned.
*
* Returns NULL on error.
*/
static smb_ads_host_info_t *
smb_ads_select_dc(smb_ads_host_list_t *hlist)
{
smb_ads_host_info_t *hentry = NULL;
if (hlist->ah_cnt == 0)
return (NULL);
if (hlist->ah_cnt == 1) {
hentry = hlist->ah_list;
if (smb_ads_ldap_ping(hentry) == 0)
return (hentry);
}
/* Sort the list by priority and weight */
qsort(hlist->ah_list, hlist->ah_cnt,
sizeof (smb_ads_host_info_t), smb_ads_dc_compare);
if ((hentry = smb_ads_select_dcfromsubnet(hlist)) != NULL)
return (hentry);
if ((hentry = smb_ads_select_dcfromlist(hlist)) != NULL)
return (hentry);
return (NULL);
}
/*
* smb_ads_lookup_msdcs
*
* If server argument is set, try to locate the specified DC.
* If it is set to empty string, locate any DCs in the specified domain.
* Returns the discovered DC via buf.
*
* fqdn - fully-qualified domain name
* server - fully-qualifed hostname of a DC
* buf - the hostname of the discovered DC
*/
boolean_t
smb_ads_lookup_msdcs(char *fqdn, char *server, char *buf, uint32_t buflen)
{
smb_ads_host_info_t *hinfo = NULL;
char *p;
char *sought_host;
char ipstr[INET6_ADDRSTRLEN];
if (!fqdn || !buf)
return (B_FALSE);
ipstr[0] = '\0';
*buf = '\0';
sought_host = (*server == 0 ? NULL : server);
if ((hinfo = smb_ads_find_host(fqdn, sought_host)) == NULL)
return (B_FALSE);
(void) smb_inet_ntop(&hinfo->ipaddr, ipstr,
SMB_IPSTRLEN(hinfo->ipaddr.a_family));
smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr);
(void) strlcpy(buf, hinfo->name, buflen);
/*
* Remove the domain extension
*/
if ((p = strchr(buf, '.')) != 0)
*p = '\0';
free(hinfo);
return (B_TRUE);
}