#include <syslog.h>
#include <synch.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <assert.h>
#include <smbsrv/libsmb.h>
#include <smbsrv/libsmbns.h>
#include <smbsrv/libmlsvc.h>
#include <smbsrv/smbinfo.h>
#include <lsalib.h>
#include <mlsvc.h>
#define SMB_DCLOCATOR_TIMEOUT 45
#define SMB_IS_FQDN(domain) (strchr(domain, '.') != NULL)
int smb_ddiscover_failure_pause = 5;
typedef struct smb_dclocator {
smb_dcinfo_t sdl_dci;
char sdl_domain[SMB_PI_MAX_DOMAIN];
boolean_t sdl_locate;
boolean_t sdl_bad_dc;
boolean_t sdl_cfg_chg;
mutex_t sdl_mtx;
cond_t sdl_cv;
uint32_t sdl_status;
} smb_dclocator_t;
static smb_dclocator_t smb_dclocator;
static pthread_t smb_dclocator_thr;
static void *smb_ddiscover_service(void *);
static uint32_t smb_ddiscover_qinfo(char *, char *, smb_domainex_t *);
static void smb_ddiscover_enum_trusted(char *, char *, smb_domainex_t *);
static uint32_t smb_ddiscover_use_config(char *, smb_domainex_t *);
static void smb_domainex_free(smb_domainex_t *);
static void smb_set_krb5_realm(char *);
int
smb_dclocator_init(void)
{
pthread_attr_t tattr;
int rc;
smb_dclocator.sdl_locate = B_TRUE;
(void) pthread_attr_init(&tattr);
(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
rc = pthread_create(&smb_dclocator_thr, &tattr,
smb_ddiscover_service, &smb_dclocator);
(void) pthread_attr_destroy(&tattr);
return (rc);
}
boolean_t
smb_locate_dc(char *domain, smb_domainex_t *dp)
{
int rc;
boolean_t rv;
timestruc_t to;
smb_domainex_t domain_info;
if (domain == NULL || *domain == '\0') {
syslog(LOG_DEBUG, "smb_locate_dc NULL dom");
smb_set_krb5_realm(NULL);
return (B_FALSE);
}
(void) mutex_lock(&smb_dclocator.sdl_mtx);
if (strcmp(smb_dclocator.sdl_domain, domain)) {
(void) strlcpy(smb_dclocator.sdl_domain, domain,
sizeof (smb_dclocator.sdl_domain));
smb_dclocator.sdl_cfg_chg = B_TRUE;
syslog(LOG_DEBUG, "smb_locate_dc new dom=%s", domain);
smb_set_krb5_realm(domain);
}
if (!smb_dclocator.sdl_locate) {
smb_dclocator.sdl_locate = B_TRUE;
(void) cond_broadcast(&smb_dclocator.sdl_cv);
}
while (smb_dclocator.sdl_locate) {
to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
to.tv_nsec = 0;
rc = cond_reltimedwait(&smb_dclocator.sdl_cv,
&smb_dclocator.sdl_mtx, &to);
if (rc == ETIME) {
syslog(LOG_NOTICE, "smb_locate_dc timeout");
rv = B_FALSE;
goto out;
}
}
if (smb_dclocator.sdl_status != 0) {
syslog(LOG_NOTICE, "smb_locate_dc status 0x%x",
smb_dclocator.sdl_status);
rv = B_FALSE;
goto out;
}
if (dp == NULL)
dp = &domain_info;
rv = smb_domain_getinfo(dp);
out:
(void) mutex_unlock(&smb_dclocator.sdl_mtx);
return (rv);
}
void
smb_ddiscover_refresh()
{
(void) mutex_lock(&smb_dclocator.sdl_mtx);
if (smb_dclocator.sdl_cfg_chg == B_FALSE) {
smb_dclocator.sdl_cfg_chg = B_TRUE;
syslog(LOG_DEBUG, "smb_ddiscover_refresh set cfg changed");
}
if (!smb_dclocator.sdl_locate) {
smb_dclocator.sdl_locate = B_TRUE;
(void) cond_broadcast(&smb_dclocator.sdl_cv);
}
(void) mutex_unlock(&smb_dclocator.sdl_mtx);
}
void
smb_ddiscover_bad_dc(char *bad_dc)
{
assert(bad_dc[0] != '\0');
(void) mutex_lock(&smb_dclocator.sdl_mtx);
syslog(LOG_DEBUG, "smb_ddiscover_bad_dc, cur=%s, bad=%s",
smb_dclocator.sdl_dci.dc_name, bad_dc);
if (strcmp(smb_dclocator.sdl_dci.dc_name, bad_dc)) {
goto out;
}
if (smb_dclocator.sdl_bad_dc) {
syslog(LOG_DEBUG, "smb_ddiscover_bad_dc repeat");
goto out;
}
syslog(LOG_INFO, "smb_ddiscover, bad DC: %s", bad_dc);
smb_dclocator.sdl_bad_dc = B_TRUE;
smb_domain_bad_dc();
if (!smb_dclocator.sdl_locate) {
smb_dclocator.sdl_locate = B_TRUE;
(void) cond_broadcast(&smb_dclocator.sdl_cv);
}
out:
(void) mutex_unlock(&smb_dclocator.sdl_mtx);
}
static void *
smb_ddiscover_service(void *arg)
{
smb_domainex_t dxi;
smb_dclocator_t *sdl = arg;
uint32_t status;
boolean_t bad_dc;
boolean_t cfg_chg;
for (;;) {
syslog(LOG_DEBUG, "smb_ddiscover_service waiting");
(void) mutex_lock(&sdl->sdl_mtx);
while (!sdl->sdl_locate)
(void) cond_wait(&sdl->sdl_cv,
&sdl->sdl_mtx);
find_again:
if (!smb_config_getbool(SMB_CI_DOMAIN_MEMB)) {
sdl->sdl_status = NT_STATUS_INVALID_SERVER_STATE;
syslog(LOG_DEBUG, "smb_ddiscover_service: "
"not a domain member");
goto wait_again;
}
if (sdl->sdl_domain[0] == '\0') {
sdl->sdl_status = NT_STATUS_INVALID_SERVER_STATE;
syslog(LOG_DEBUG, "smb_ddiscover_service: "
"null domain name");
goto wait_again;
}
bad_dc = sdl->sdl_bad_dc;
sdl->sdl_bad_dc = B_FALSE;
if (bad_dc) {
sdl->sdl_dci.dc_name[0] = '\0';
}
cfg_chg = sdl->sdl_cfg_chg;
sdl->sdl_cfg_chg = B_FALSE;
(void) mutex_unlock(&sdl->sdl_mtx);
syslog(LOG_DEBUG, "smb_ddiscover_service running "
"cfg_chg=%d bad_dc=%d", (int)cfg_chg, (int)bad_dc);
smb_ads_refresh(bad_dc);
bzero(&dxi, sizeof (dxi));
status = smb_ddiscover_main(sdl->sdl_domain, &dxi);
if (status == 0)
smb_domain_save();
(void) mutex_lock(&sdl->sdl_mtx);
sdl->sdl_status = status;
if (status == 0) {
sdl->sdl_dci = dxi.d_dci;
} else {
syslog(LOG_DEBUG, "smb_ddiscover_service "
"retry after STATUS_%s",
xlate_nt_status(status));
(void) sleep(smb_ddiscover_failure_pause);
goto find_again;
}
if (sdl->sdl_bad_dc) {
syslog(LOG_DEBUG, "smb_ddiscover_service "
"restart because bad_dc was set");
goto find_again;
}
if (sdl->sdl_cfg_chg) {
syslog(LOG_DEBUG, "smb_ddiscover_service "
"restart because cfg_chg was set");
goto find_again;
}
wait_again:
sdl->sdl_locate = B_FALSE;
sdl->sdl_bad_dc = B_FALSE;
sdl->sdl_cfg_chg = B_FALSE;
(void) cond_broadcast(&sdl->sdl_cv);
(void) mutex_unlock(&sdl->sdl_mtx);
}
return (NULL);
}
uint32_t
smb_ddiscover_main(char *domain, smb_domainex_t *dxi)
{
uint32_t status;
if (domain[0] == '\0') {
syslog(LOG_DEBUG, "smb_ddiscover_main NULL domain");
return (NT_STATUS_INTERNAL_ERROR);
}
status = smb_ads_lookup_msdcs(domain, &dxi->d_dci);
if (status != 0) {
syslog(LOG_DEBUG, "smb_ddiscover_main can't find DC (%s)",
xlate_nt_status(status));
goto out;
}
status = smb_ddiscover_qinfo(domain, dxi->d_dci.dc_name, dxi);
if (status != 0) {
syslog(LOG_DEBUG,
"smb_ddiscover_main can't get domain info (%s)",
xlate_nt_status(status));
goto out;
}
if (smb_domain_start_update() != SMB_DOMAIN_SUCCESS) {
syslog(LOG_DEBUG, "smb_ddiscover_main can't get lock");
status = NT_STATUS_INTERNAL_ERROR;
} else {
smb_domain_update(dxi);
smb_domain_end_update();
}
out:
smb_domainex_free(dxi);
return (status);
}
static uint32_t
smb_ddiscover_qinfo(char *domain, char *server, smb_domainex_t *dxi)
{
uint32_t ret, tmp;
ret = lsa_query_dns_domain_info(server, domain, &dxi->d_primary);
if (ret == NT_STATUS_SUCCESS)
goto success;
tmp = smb_ddiscover_use_config(domain, dxi);
if (tmp == NT_STATUS_SUCCESS)
goto success;
tmp = lsa_query_primary_domain_info(server, domain, &dxi->d_primary);
if (tmp == NT_STATUS_SUCCESS)
goto success;
return (ret);
success:
smb_ddiscover_enum_trusted(domain, server, dxi);
return (NT_STATUS_SUCCESS);
}
static void
smb_ddiscover_enum_trusted(char *domain, char *server, smb_domainex_t *dxi)
{
smb_trusted_domains_t *list;
uint32_t status;
list = &dxi->d_trusted;
status = lsa_enum_trusted_domains_ex(server, domain, list);
if (status != NT_STATUS_SUCCESS)
(void) lsa_enum_trusted_domains(server, domain, list);
}
static uint32_t
smb_ddiscover_use_config(char *domain, smb_domainex_t *dxi)
{
boolean_t use;
smb_domain_t *dinfo;
dinfo = &dxi->d_primary;
bzero(dinfo, sizeof (smb_domain_t));
if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
return (NT_STATUS_UNSUCCESSFUL);
smb_config_getdomaininfo(dinfo->di_nbname, dinfo->di_fqname,
NULL, NULL, NULL);
if (SMB_IS_FQDN(domain))
use = (smb_strcasecmp(dinfo->di_fqname, domain, 0) == 0);
else
use = (smb_strcasecmp(dinfo->di_nbname, domain, 0) == 0);
if (use)
smb_config_getdomaininfo(NULL, NULL, dinfo->di_sid,
dinfo->di_u.di_dns.ddi_forest,
dinfo->di_u.di_dns.ddi_guid);
return ((use) ? NT_STATUS_SUCCESS : NT_STATUS_UNSUCCESSFUL);
}
static void
smb_domainex_free(smb_domainex_t *dxi)
{
free(dxi->d_trusted.td_domains);
dxi->d_trusted.td_domains = NULL;
}
static void
smb_set_krb5_realm(char *domain)
{
static char realm[MAXHOSTNAMELEN];
if (domain == NULL || domain[0] == '\0') {
(void) unsetenv("KRB5_DEFAULT_REALM");
return;
}
(void) strlcpy(realm, domain, sizeof (realm));
(void) smb_strupr(realm);
(void) setenv("KRB5_DEFAULT_REALM", realm, 1);
}