#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 <errno.h>
#include <cryptoutil.h>
#include <ads/dsgetdc.h>
#include <smbsrv/libsmbns.h>
#include <smbns_dyndns.h>
#include <smbns_krb.h>
#define SMB_ADS_AF_UNKNOWN(x) (((x)->ipaddr.a_family != AF_INET) && \
((x)->ipaddr.a_family != AF_INET6))
#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"
#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
#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
static 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,
};
static krb5_enctype pre_w2k8enctypes[] = {
ENCTYPE_ARCFOUR_HMAC,
ENCTYPE_DES_CBC_CRC,
ENCTYPE_DES_CBC_MD5,
};
#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_UCPWD "unicodePwd"
#define SMB_ADS_ATTR_DNSHOST "dNSHostName"
#define SMB_ADS_ATTR_KVNO "msDS-KeyVersionNumber"
#define SMB_ADS_ATTR_DN "distinguishedName"
#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
#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
};
static smb_ads_host_info_t *smb_ads_cached_host_info = NULL;
static mutex_t smb_ads_cached_host_mtx;
typedef struct smb_ads_config {
char c_site[SMB_ADS_SITE_MAX];
mutex_t c_mtx;
} smb_ads_config_t;
static smb_ads_config_t smb_ads_cfg;
typedef struct smb_ads_avpair {
char *avp_attr;
char *avp_val;
} smb_ads_avpair_t;
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 int smb_ads_open_main(smb_ads_handle_t **, 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_free_cached_host(void);
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_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_is_same_domain(char *, char *);
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 *);
static krb5_enctype *smb_ads_get_enctypes(int, int *);
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) mutex_unlock(&smb_ads_cfg.c_mtx);
DsFreeDcInfo(NULL);
}
void
smb_ads_fini(void)
{
smb_ads_free_cached_host();
}
void
smb_ads_refresh(boolean_t force_rediscovery)
{
char new_site[SMB_ADS_SITE_MAX];
(void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX);
(void) mutex_lock(&smb_ads_cfg.c_mtx);
(void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX);
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
smb_ads_free_cached_host();
if (force_rediscovery) {
(void) _DsForceRediscovery(NULL, 0);
}
}
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);
}
static boolean_t
smb_ads_validate_cache_host(char *domain)
{
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);
return (B_TRUE);
}
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);
}
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_host_info_t *
smb_ads_find_host(char *domain)
{
smb_ads_host_info_t *host = NULL;
DOMAIN_CONTROLLER_INFO *dci = NULL;
struct sockaddr_storage *ss;
uint32_t flags = DS_DS_FLAG;
uint32_t status;
int tries;
(void) mutex_lock(&smb_ads_cached_host_mtx);
if (smb_ads_validate_cache_host(domain)) {
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();
tries = 15;
again:
status = _DsGetDcName(
NULL,
domain,
NULL,
NULL,
flags,
&dci);
switch (status) {
case 0:
break;
case NT_STATUS_NO_SUCH_DOMAIN:
case NT_STATUS_INVALID_SERVER_STATE:
if (--tries > 0) {
(void) sleep(1);
goto again;
}
case NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND:
case NT_STATUS_CANT_WAIT:
default:
return (NULL);
}
host = calloc(1, sizeof (*host));
if (host == NULL)
goto out;
(void) strlcpy(host->name, dci->DomainControllerName, MAXHOSTNAMELEN);
ss = (void *)dci->_sockaddr;
switch (ss->ss_family) {
case AF_INET: {
struct sockaddr_in *sin = (void *)ss;
host->port = ntohs(sin->sin_port);
host->ipaddr.a_family = AF_INET;
(void) memcpy(&host->ipaddr.a_ipv4, &sin->sin_addr,
sizeof (in_addr_t));
break;
}
case AF_INET6: {
struct sockaddr_in6 *sin6 = (void *)ss;
host->port = ntohs(sin6->sin6_port);
host->ipaddr.a_family = AF_INET6;
(void) memcpy(&host->ipaddr.a_ipv6, &sin6->sin6_addr,
sizeof (in6_addr_t));
break;
}
default:
syslog(LOG_ERR, "no addr for DC %s",
dci->DomainControllerName);
free(host);
host = NULL;
goto out;
}
host->flags = dci->Flags;
(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);
(void) mutex_unlock(&smb_ads_cached_host_mtx);
out:
DsFreeDcInfo(dci);
return (host);
}
static int
smb_ads_count_dots(const char *s)
{
int ndots = 0;
while (*s) {
if (*s++ == '.')
ndots++;
}
return (ndots);
}
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);
}
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_handle_t *
smb_ads_open(void)
{
char domain[MAXHOSTNAMELEN];
smb_ads_handle_t *h;
smb_ads_status_t err;
if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
return (NULL);
if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
return (NULL);
err = smb_ads_open_main(&h, domain, NULL, NULL);
if (err != 0) {
smb_ads_log_errmsg(err);
return (NULL);
}
return (h);
}
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);
for (interact = prompts; interact->id != SASL_CB_LIST_END;
interact++) {
interact->result = NULL;
interact->len = 0;
}
return (LDAP_SUCCESS);
}
static int
smb_ads_open_main(smb_ads_handle_t **hp, 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 err, rc;
*hp = NULL;
if (user != NULL) {
err = smb_kinit(domain, user, password);
if (err != 0) {
syslog(LOG_ERR, "smbns: kinit failed");
return (err);
}
user = NULL;
password = NULL;
}
ads_host = smb_ads_find_host(domain);
if (ads_host == NULL)
return (SMB_ADS_CANT_LOCATE_DC);
ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
if (ah == NULL) {
free(ads_host);
return (ENOMEM);
}
(void) memset(ah, 0, sizeof (smb_ads_handle_t));
if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) {
syslog(LOG_ERR, "smbns: ldap_init failed");
smb_ads_free_cached_host();
free(ah);
free(ads_host);
return (SMB_ADS_LDAP_INIT);
}
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 (SMB_ADS_LDAP_SETOPT);
}
(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 (SMB_ADS_LDAP_SETOPT);
}
(void) smb_strlwr(ah->domain);
ah->domain_dn = smb_ads_convert_domain(domain);
if (ah->domain_dn == NULL) {
smb_ads_close(ah);
free(ads_host);
return (SMB_ADS_LDAP_SET_DOM);
}
ah->hostname = strdup(ads_host->name);
if (ah->hostname == NULL) {
smb_ads_close(ah);
free(ads_host);
return (ENOMEM);
}
(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 (ENOMEM);
}
} else {
ah->site = NULL;
}
(void) mutex_unlock(&smb_ads_cfg.c_mtx);
syslog(LOG_DEBUG, "smbns: smb_ads_open_main");
syslog(LOG_DEBUG, "smbns: domain: %s", ah->domain);
syslog(LOG_DEBUG, "smbns: domain_dn: %s", ah->domain_dn);
syslog(LOG_DEBUG, "smbns: ip_addr: %s", ah->ip_addr);
syslog(LOG_DEBUG, "smbns: hostname: %s", ah->hostname);
syslog(LOG_DEBUG, "smbns: site: %s",
(ah->site != NULL) ? ah->site : "");
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, "smbns: ldap_sasl_..._bind_s failed (%s)",
ldap_err2string(rc));
smb_ads_close(ah);
free(ads_host);
return (SMB_ADS_LDAP_SASL_BIND);
}
syslog(LOG_DEBUG, "smbns: ldap_sasl_..._bind_s success");
free(ads_host);
*hp = ah;
return (SMB_ADS_SUCCESS);
}
void
smb_ads_close(smb_ads_handle_t *ah)
{
if (ah == NULL)
return;
if (ah->ld)
(void) ldap_unbind(ah->ld);
free(ah->domain);
free(ah->domain_dn);
free(ah->hostname);
free(ah->site);
free(ah);
}
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);
}
static void
smb_ads_free_attr(LDAPMod *attrs[])
{
int i;
for (i = 0; attrs[i]; i++) {
free(attrs[i]);
}
}
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 = 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);
}
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) {
if (ret == LDAP_NO_SUCH_OBJECT) {
syslog(LOG_ERR, "Failed to publish share %s in" \
" AD. Container does not exist: %s.\n",
adsShareName, share_dn);
} else {
syslog(LOG_ERR, "Failed to publish share %s in" \
" AD: %s (%s).\n", adsShareName, 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);
}
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);
}
static int
smb_ads_escape_search_filter_chars(const char *src, char *dst)
{
int avail = SMB_ADS_MAXBUFLEN - 1;
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--;
default:
*dst++ = *src++;
avail--;
}
}
*dst = 0;
return (0);
}
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);
if (ldap_count_entries(ah->ld, res) == 0) {
(void) ldap_msgfree(res);
return (0);
}
(void) ldap_msgfree(res);
return (1);
}
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:
ret = -1;
}
return (ret);
}
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));
}
static void
smb_ads_get_new_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen,
char *container)
{
char nbname[NETBIOS_NAME_SZ];
if (container == NULL)
container = "cn=" SMB_ADS_COMPUTERS_CN;
(void) smb_getnetbiosname(nbname, sizeof (nbname));
(void) snprintf(buf, buflen, "cn=%s,%s,%s",
nbname, container, ah->domain_dn);
}
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));
}
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));
}
static int
smb_ads_get_dc_level(smb_ads_handle_t *ah)
{
LDAPMessage *res, *entry;
char *attr[2];
char **vals;
int rc;
res = NULL;
attr[0] = SMB_ADS_ATTR_DCLEVEL;
attr[1] = NULL;
rc = ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr, 0, &res);
if (rc != LDAP_SUCCESS) {
syslog(LOG_ERR, "smb_ads_get_dc_level: "
"LDAP search, error %s", ldap_err2string(rc));
(void) ldap_msgfree(res);
return (-1);
}
if (ldap_count_entries(ah->ld, res) == 0) {
(void) ldap_msgfree(res);
return (-1);
}
rc = -1;
entry = ldap_first_entry(ah->ld, res);
if (entry) {
if ((vals = ldap_get_values(ah->ld, entry,
SMB_ADS_ATTR_DCLEVEL)) == NULL) {
syslog(LOG_DEBUG, "smb_ads_get_dc_level: "
"LDAP values missing, assume W2K");
(void) ldap_msgfree(res);
return (SMB_ADS_DCLEVEL_W2K);
}
if (vals[0] != NULL) {
rc = atoi(vals[0]);
syslog(LOG_DEBUG, "smb_ads_get_dc_level: "
"LDAP value %d", rc);
}
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, SMB_CASE_LOWER) != 0)
return (-1);
(void) strlcat(fqhost, ".", len);
(void) strlcat(fqhost, ah->domain, len);
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];
char *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 usrctl_buf[16];
char encrypt_buf[16];
int max;
smb_krb5_pn_set_t spn, upn;
syslog(LOG_DEBUG, "smb_ads_computer_op, op=%s dn=%s",
(op == LDAP_MOD_ADD) ? "add" : "replace", dn);
if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
return (-1);
if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
return (-1);
if (smb_krb5_get_pn_set(&spn, SMB_PN_SPN_ATTR, ah->domain) == 0)
return (-1);
if (smb_krb5_get_pn_set(&upn, SMB_PN_UPN_ATTR, ah->domain) != 1) {
smb_krb5_free_pn_set(&spn);
smb_krb5_free_pn_set(&upn);
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) {
smb_krb5_free_pn_set(&spn);
smb_krb5_free_pn_set(&upn);
return (-1);
}
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;
attrs[j]->mod_values = upn.s_pns;
attrs[++j]->mod_op = op;
attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
attrs[j]->mod_values = spn.s_pns;
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;
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);
smb_krb5_free_pn_set(&spn);
smb_krb5_free_pn_set(&upn);
return (ret);
}
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));
}
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;
syslog(LOG_DEBUG, "smbns: ads_getattr (%s)", avpair->avp_attr);
vals = ldap_get_values(ld, entry, avpair->avp_attr);
if (!vals) {
syslog(LOG_DEBUG, "smbns: ads_getattr err: no vals");
return (SMB_ADS_STAT_NOT_FOUND);
}
if (!vals[0]) {
syslog(LOG_DEBUG, "smbns: ads_getattr err: no vals[0]");
ldap_value_free(vals);
return (SMB_ADS_STAT_NOT_FOUND);
}
avpair->avp_val = strdup(vals[0]);
if (!avpair->avp_val) {
syslog(LOG_DEBUG, "smbns: ads_getattr err: no mem");
rc = SMB_ADS_STAT_ERR;
} else {
syslog(LOG_DEBUG, "smbns: ads_getattr (%s) OK, val=%s",
avpair->avp_attr, avpair->avp_val);
}
ldap_value_free(vals);
return (rc);
}
static smb_ads_qstat_t
smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
smb_ads_avpair_t *avpair)
{
smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
LDAPMessage *entry;
if (ldap_count_entries(ah->ld, res) == 0) {
syslog(LOG_DEBUG, "smbns: find_computer, "
"ldap_count_entries zero");
return (SMB_ADS_STAT_NOT_FOUND);
}
if ((entry = ldap_first_entry(ah->ld, res)) == NULL) {
syslog(LOG_DEBUG, "smbns: find_computer, "
"ldap_first_entry error");
return (SMB_ADS_STAT_ERR);
}
syslog(LOG_DEBUG, "smbns: find_computer, have LDAP resp.");
if (avpair != NULL &&
strcmp(avpair->avp_attr, SMB_ADS_ATTR_DN) == 0) {
char fqhost[MAXHOSTNAMELEN];
smb_ads_avpair_t dnshost_avp;
syslog(LOG_DEBUG, "smbns: find_computer, check DNS name");
if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
return (SMB_ADS_STAT_ERR);
dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
dnshost_avp.avp_val = NULL;
rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
switch (rc) {
case SMB_ADS_STAT_FOUND:
if (strcasecmp(dnshost_avp.avp_val, fqhost)) {
syslog(LOG_DEBUG, "smbns: find_computer, "
"duplicate name (%s)",
dnshost_avp.avp_val);
rc = SMB_ADS_STAT_DUP;
}
free(dnshost_avp.avp_val);
break;
case SMB_ADS_STAT_NOT_FOUND:
rc = SMB_ADS_STAT_FOUND;
break;
default:
break;
}
if (rc != SMB_ADS_STAT_FOUND)
return (rc);
}
if (avpair) {
syslog(LOG_DEBUG, "smbns: find_computer, check %s",
avpair->avp_attr);
rc = smb_ads_getattr(ah->ld, entry, avpair);
}
return (rc);
}
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];
char tmpbuf[SMB_ADS_MAXBUFLEN];
smb_ads_qstat_t rc;
int err;
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, tmpbuf) != 0)
return (SMB_ADS_STAT_ERR);
(void) snprintf(filter, sizeof (filter),
"(&(objectClass=computer)(%s=%s))",
SMB_ADS_ATTR_SAMACCT, tmpbuf);
syslog(LOG_DEBUG, "smbns: lookup_computer, "
"dn=%s, scope=%d", dn, scope);
syslog(LOG_DEBUG, "smbns: lookup_computer, "
"filter=%s", filter);
syslog(LOG_DEBUG, "smbns: lookup_computer, "
"attrs[0]=%s", attrs[0]);
syslog(LOG_DEBUG, "smbns: lookup_computer, "
"attrs[1]=%s", attrs[1] ? attrs[1] : "");
err = ldap_search_s(ah->ld, dn, scope, filter, attrs, 0, &res);
if (err != LDAP_SUCCESS) {
syslog(LOG_DEBUG, "smbns: lookup_computer, "
"LDAP search failed, dn=(%s), scope=%d, err=%s",
dn, scope, ldap_err2string(err));
(void) ldap_msgfree(res);
return (SMB_ADS_STAT_NOT_FOUND);
}
syslog(LOG_DEBUG, "smbns: find_computer, ldap_search OK");
rc = smb_ads_get_qstat(ah, res, avpair);
if (rc == SMB_ADS_STAT_FOUND) {
syslog(LOG_DEBUG, "smbns: find_computer, attr %s = %s",
avpair->avp_attr, avpair->avp_val);
} else {
syslog(LOG_DEBUG, "smbns: find_computer, "
"get query status, error %d", rc);
}
(void) ldap_msgfree(res);
return (rc);
}
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;
avpair.avp_val = NULL;
(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);
}
static int
smb_ads_update_acct_passwd(smb_ads_handle_t *ah, char *passwd, char *dn)
{
LDAPMod *attrs[2];
struct berval *bvp[2];
struct berval bval;
char *qpass_buf = NULL;
int qpass_len = 0;
smb_wchar_t *ucpwd_buf = NULL;
int ucpwd_len = 0;
int ret = 0;
if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
return (LDAP_NO_MEMORY);
qpass_len = asprintf(&qpass_buf, "\"%s\"", passwd);
if (qpass_len < 0) {
qpass_len = 0;
ret = LDAP_NO_MEMORY;
goto out;
}
ucpwd_len = smb_wcequiv_strlen(qpass_buf);
ucpwd_buf = calloc(1, ucpwd_len + sizeof (smb_wchar_t));
if (ucpwd_buf == NULL) {
ret = LDAP_NO_MEMORY;
goto out;
}
ret = smb_mbstowcs(ucpwd_buf, qpass_buf, ucpwd_len / 2);
if (ret < 0) {
ret = LDAP_ENCODING_ERROR;
goto out;
}
bval.bv_val = (char *)ucpwd_buf;
bval.bv_len = ucpwd_len;
bvp[0] = &bval;
bvp[1] = NULL;
attrs[0]->mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
attrs[0]->mod_type = SMB_ADS_ATTR_UCPWD;
attrs[0]->mod_bvalues = bvp;
if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
}
out:
freezero(ucpwd_buf, ucpwd_len);
freezero(qpass_buf, qpass_len);
smb_ads_free_attr(attrs);
return (ret);
}
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);
}
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;
avpair.avp_val = NULL;
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);
}
boolean_t smbns_ads_try_kpasswd = 0;
smb_ads_status_t
smb_ads_join(char *domain, char *container,
char *user, char *usr_passwd, char *machine_passwd)
{
smb_ads_handle_t *ah = NULL;
krb5_context ctx = NULL;
krb5_principal *krb5princs = NULL;
krb5_kvno kvno;
boolean_t delete = B_TRUE;
smb_ads_status_t rc;
boolean_t new_acct;
int dclevel, num, usrctl_flags = 0;
smb_ads_qstat_t qstat;
char dn[SMB_ADS_DN_MAX];
char tmpfile[] = SMBNS_KRB5_KEYTAB_TMP;
int cnt, x;
smb_krb5_pn_set_t spns;
krb5_enctype *encptr;
rc = smb_ads_open_main(&ah, domain, user, usr_passwd);
if (rc != 0) {
const char *s = smb_ads_strerror(rc);
syslog(LOG_ERR, "smb_ads_join: open_main, error %s", s);
smb_ccache_remove(SMB_CCACHE_PATH);
return (rc);
}
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;
syslog(LOG_INFO, "smb_ads_join: machine account found."
" Updating: %s", dn);
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_new_comp_dn(ah, dn, SMB_ADS_DN_MAX, container);
syslog(LOG_INFO, "smb_ads_join: machine account not found."
" Creating: %s", dn);
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:
syslog(LOG_INFO, "smb_ads_find_computer, rc=%d", qstat);
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);
}
if (smb_krb5_ctx_init(&ctx) != 0) {
rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
goto adjoin_cleanup;
}
if (smb_krb5_get_pn_set(&spns, SMB_PN_KEYTAB_ENTRY, ah->domain) == 0) {
rc = SMB_ADJOIN_ERR_GET_SPNS;
goto adjoin_cleanup;
}
if (smb_krb5_get_kprincs(ctx, spns.s_pns, spns.s_cnt, &krb5princs)
!= 0) {
smb_krb5_free_pn_set(&spns);
rc = SMB_ADJOIN_ERR_GET_SPNS;
goto adjoin_cleanup;
}
cnt = spns.s_cnt;
smb_krb5_free_pn_set(&spns);
x = smb_ads_update_acct_passwd(ah, machine_passwd, dn);
if (x != 0 && smbns_ads_try_kpasswd)
x = smb_krb5_setpwd(ctx, ah->domain, machine_passwd);
if (x != 0) {
rc = SMB_ADJOIN_ERR_KSETPWD;
goto adjoin_cleanup;
}
kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
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);
set_ctl_again:
x = smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn);
if (x != LDAP_SUCCESS && (usrctl_flags &
SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION) != 0) {
syslog(LOG_NOTICE, "Unable to set the "
"TRUSTED_FOR_DELEGATION userAccountControl flag on the "
"machine account in Active Directory. It may be necessary "
"to set that via Active Directory administration.");
usrctl_flags &=
~SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION;
goto set_ctl_again;
}
if (x != LDAP_SUCCESS) {
rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
goto adjoin_cleanup;
}
if (mktemp(tmpfile) == NULL) {
rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
goto adjoin_cleanup;
}
encptr = smb_ads_get_enctypes(dclevel, &num);
if (smb_krb5_kt_populate(ctx, ah->domain, krb5princs, cnt,
tmpfile, kvno, machine_passwd, encptr, num) != 0) {
rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
goto adjoin_cleanup;
}
delete = B_FALSE;
rc = SMB_ADS_SUCCESS;
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_kprincs(ctx, krb5princs, cnt);
smb_krb5_ctx_fini(ctx);
}
if (rc == SMB_ADS_SUCCESS) {
if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) {
(void) unlink(tmpfile);
rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB;
}
} else {
(void) unlink(tmpfile);
}
smb_ads_close(ah);
smb_ccache_remove(SMB_CCACHE_PATH);
return (rc);
}
struct xlate_table {
int err;
const char * const msg;
};
static const struct xlate_table
adjoin_table[] = {
{ SMB_ADS_SUCCESS, "Success" },
{ SMB_ADS_KRB5_INIT_CTX,
"Failed creating a Kerberos context." },
{ SMB_ADS_KRB5_CC_DEFAULT,
"Failed to resolve default credential cache." },
{ SMB_ADS_KRB5_PARSE_PRINCIPAL,
"Failed parsing the user principal name." },
{ SMB_ADS_KRB5_GET_INIT_CREDS_OTHER,
"Failed getting initial credentials. (See svc. log)" },
{ SMB_ADS_KRB5_GET_INIT_CREDS_PW,
"Failed getting initial credentials. (Wrong password?)" },
{ SMB_ADS_KRB5_GET_INIT_CREDS_SKEW,
"Failed getting initial credentials. (Clock skew too great)" },
{ SMB_ADS_KRB5_CC_INITIALIZE,
"Failed initializing the credential cache." },
{ SMB_ADS_KRB5_CC_STORE_CRED,
"Failed to update the credential cache." },
{ SMB_ADS_CANT_LOCATE_DC,
"Failed to locate a domain controller." },
{ SMB_ADS_LDAP_INIT,
"Failed to create an LDAP handle." },
{ SMB_ADS_LDAP_SETOPT,
"Failed to set an LDAP option." },
{ SMB_ADS_LDAP_SET_DOM,
"Failed to set the LDAP handle DN." },
{ SMB_ADS_LDAP_SASL_BIND,
"Failed to bind the LDAP handle. "
"Usually indicates an authentication problem." },
{ SMB_ADJOIN_ERR_GEN_PWD,
"Failed to generate machine password." },
{ 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)." },
{ SMB_ADJOIN_ERR_AUTH_NETLOGON,
"Failed to authenticate using the new computer account." },
{ SMB_ADJOIN_ERR_STORE_PROPS,
"Failed to store computer account information locally." },
{ SMB_ADJOIN_ERR_ALREADY_JOINED,
"Already joined to a domain. "
"Run \"smbadm join -w workgroup\" first." },
{ 0, NULL }
};
const char *
smb_ads_strerror(int err)
{
const struct xlate_table *xt;
if (err > 0 && err < SMB_ADS_ERRNO_GAP)
return (strerror(err));
for (xt = adjoin_table; xt->msg; xt++)
if (xt->err == err)
return (xt->msg);
return ("Unknown error code.");
}
void
smb_ads_log_errmsg(smb_ads_status_t err)
{
const char *s = smb_ads_strerror(err);
syslog(LOG_NOTICE, "%s", s);
}
uint32_t
smb_ads_lookup_msdcs(char *fqdn, smb_dcinfo_t *dci)
{
smb_ads_host_info_t *hinfo = NULL;
char ipstr[INET6_ADDRSTRLEN];
if (!fqdn || !dci)
return (NT_STATUS_INTERNAL_ERROR);
ipstr[0] = '\0';
if ((hinfo = smb_ads_find_host(fqdn)) == NULL)
return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
(void) smb_inet_ntop(&hinfo->ipaddr, ipstr,
SMB_IPSTRLEN(hinfo->ipaddr.a_family));
smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr);
(void) strlcpy(dci->dc_name, hinfo->name, sizeof (dci->dc_name));
dci->dc_addr = hinfo->ipaddr;
dci->dc_flags = hinfo->flags;
free(hinfo);
return (NT_STATUS_SUCCESS);
}
static krb5_enctype *
smb_ads_get_enctypes(int dclevel, int *num)
{
krb5_enctype *encptr;
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;
}
return (encptr);
}