view usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c @ 13328:2f33da224406

849 domain controller "hot fail over" can take forever Reviewed by: Albert Lee <trisk@nexenta.com> Reviewed by: Garrett D'Amore <garrett@nexenta.com> Reviewed by: Richard Lowe <richlowe@richlowe.net> Approved by: Richard Lowe <richlowe@richlowe.net>
author Gordon Ross <gwr@nexenta.com>
date Thu, 07 Apr 2011 19:44:19 -0400
parents edb7861a1533
children
line wrap: on
line source

/*
 * 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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
 */

#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>

/*
 * DC Locator
 */
#define	SMB_DCLOCATOR_TIMEOUT	45	/* seconds */
#define	SMB_IS_FQDN(domain)	(strchr(domain, '.') != NULL)

typedef struct smb_dclocator {
	char		sdl_domain[SMB_PI_MAX_DOMAIN];
	char		sdl_dc[MAXHOSTNAMELEN];
	boolean_t	sdl_locate;
	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 void smb_ddiscover_main(char *, char *);
static boolean_t smb_ddiscover_dns(char *, char *, smb_domainex_t *);
static boolean_t smb_ddiscover_nbt(char *, char *, smb_domainex_t *);
static boolean_t smb_ddiscover_domain_match(char *, char *, uint32_t);
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 *);

/*
 * ===================================================================
 * API to initialize DC locator thread, trigger DC discovery, and
 * get the discovered DC and/or domain information.
 * ===================================================================
 */

/*
 * Initialization of the DC locator thread.
 * Returns 0 on success, an error number if thread creation fails.
 */
int
smb_dclocator_init(void)
{
	pthread_attr_t tattr;
	int rc;

	(void) pthread_attr_init(&tattr);
	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
	rc = pthread_create(&smb_dclocator_thr, &tattr,
	    smb_ddiscover_service, 0);
	(void) pthread_attr_destroy(&tattr);
	return (rc);
}

/*
 * This is the entry point for discovering a domain controller for the
 * specified domain.
 *
 * The actual work of discovering a DC is handled by DC locator thread.
 * All we do here is signal the request and wait for a DC or a timeout.
 *
 * Input parameters:
 *  domain - domain to be discovered (can either be NetBIOS or DNS domain)
 *  dc - preferred DC. If the preferred DC is set to empty string, it
 *       will attempt to discover any DC in the specified domain.
 *
 * Output parameter:
 *  dp - on success, dp will be filled with the discovered DC and domain
 *       information.
 * Returns B_TRUE if the DC/domain info is available.
 */
boolean_t
smb_locate_dc(char *domain, char *dc, smb_domainex_t *dp)
{
	int rc;
	timestruc_t to;
	smb_domainex_t domain_info;

	if (domain == NULL || *domain == '\0')
		return (B_FALSE);

	(void) mutex_lock(&smb_dclocator.sdl_mtx);

	if (!smb_dclocator.sdl_locate) {
		smb_dclocator.sdl_locate = B_TRUE;
		(void) strlcpy(smb_dclocator.sdl_domain, domain,
		    SMB_PI_MAX_DOMAIN);
		(void) strlcpy(smb_dclocator.sdl_dc, dc, MAXHOSTNAMELEN);
		(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)
			break;
	}

	if (dp == NULL)
		dp = &domain_info;
	rc = smb_domain_getinfo(dp);

	(void) mutex_unlock(&smb_dclocator.sdl_mtx);

	return (rc);
}

/*
 * If domain discovery is running, wait for it to finish.
 */
int
smb_ddiscover_wait(void)
{
	timestruc_t to;
	int rc = 0;

	(void) mutex_lock(&smb_dclocator.sdl_mtx);

	if (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);
	}

	(void) mutex_unlock(&smb_dclocator.sdl_mtx);

	return (rc);
}


/*
 * ==========================================================
 * DC discovery functions
 * ==========================================================
 */

/*
 * This is the domain and DC discovery service: it gets woken up whenever
 * there is need to locate a domain controller.
 *
 * Upon success, the SMB domain cache will be populated with the discovered
 * DC and domain info.
 */
/*ARGSUSED*/
static void *
smb_ddiscover_service(void *arg)
{
	char domain[SMB_PI_MAX_DOMAIN];
	char sought_dc[MAXHOSTNAMELEN];

	for (;;) {
		(void) mutex_lock(&smb_dclocator.sdl_mtx);

		while (!smb_dclocator.sdl_locate)
			(void) cond_wait(&smb_dclocator.sdl_cv,
			    &smb_dclocator.sdl_mtx);

		(void) strlcpy(domain, smb_dclocator.sdl_domain,
		    SMB_PI_MAX_DOMAIN);
		(void) strlcpy(sought_dc, smb_dclocator.sdl_dc, MAXHOSTNAMELEN);
		(void) mutex_unlock(&smb_dclocator.sdl_mtx);

		smb_ddiscover_main(domain, sought_dc);

		(void) mutex_lock(&smb_dclocator.sdl_mtx);
		smb_dclocator.sdl_locate = B_FALSE;
		(void) cond_broadcast(&smb_dclocator.sdl_cv);
		(void) mutex_unlock(&smb_dclocator.sdl_mtx);
	}

	/*NOTREACHED*/
	return (NULL);
}

/*
 * Discovers a domain controller for the specified domain either via
 * DNS or NetBIOS. After the domain controller is discovered successfully
 * primary and trusted domain infromation will be queried using RPC queries.
 * If the RPC queries fail, the domain information stored in SMF might be used
 * if the the discovered domain is the same as the previously joined domain.
 * If everything is successful domain cache will be updated with all the
 * obtained information.
 */
static void
smb_ddiscover_main(char *domain, char *server)
{
	smb_domainex_t dxi;
	boolean_t discovered;

	bzero(&dxi, sizeof (smb_domainex_t));

	if (smb_domain_start_update() != SMB_DOMAIN_SUCCESS)
		return;

	if (SMB_IS_FQDN(domain))
		discovered = smb_ddiscover_dns(domain, server, &dxi);
	else
		discovered = smb_ddiscover_nbt(domain, server, &dxi);

	if (discovered)
		smb_domain_update(&dxi);

	smb_domain_end_update();

	smb_domainex_free(&dxi);

	if (discovered)
		smb_domain_save();
}

/*
 * Discovers a DC for the specified domain via DNS. If a DC is found
 * primary and trusted domains information will be queried.
 */
static boolean_t
smb_ddiscover_dns(char *domain, char *server, smb_domainex_t *dxi)
{
	uint32_t status;

	if (!smb_ads_lookup_msdcs(domain, server, dxi->d_dc, MAXHOSTNAMELEN))
		return (B_FALSE);

	status = smb_ddiscover_qinfo(domain, dxi->d_dc, dxi);
	return (status == NT_STATUS_SUCCESS);
}

/*
 * Discovers a DC for the specified domain using NETLOGON protocol.
 * If a DC cannot be found using NETLOGON then it will
 * try to resolve it via DNS, i.e. find out if it is the first label
 * of a DNS domain name. If the corresponding DNS name is found, DC
 * discovery will be done via DNS query.
 *
 * If the fully-qualified domain name is derived from the DNS config
 * file, the NetBIOS domain name specified by the user will be compared
 * against the NetBIOS domain name obtained via LSA query.  If there is
 * a mismatch, the DC discovery will fail since the discovered DC is
 * actually for another domain, whose first label of its FQDN somehow
 * matches with the NetBIOS name of the domain we're interested in.
 */
static boolean_t
smb_ddiscover_nbt(char *domain, char *server, smb_domainex_t *dxi)
{
	char dnsdomain[MAXHOSTNAMELEN];
	uint32_t status;

	*dnsdomain = '\0';

	if (!smb_browser_netlogon(domain, dxi->d_dc, MAXHOSTNAMELEN)) {
		if (!smb_ddiscover_domain_match(domain, dnsdomain,
		    MAXHOSTNAMELEN))
			return (B_FALSE);

		if (!smb_ads_lookup_msdcs(dnsdomain, server, dxi->d_dc,
		    MAXHOSTNAMELEN))
			return (B_FALSE);
	}

	status = smb_ddiscover_qinfo(domain, dxi->d_dc, dxi);
	if (status != NT_STATUS_SUCCESS)
		return (B_FALSE);

	if ((*dnsdomain != '\0') &&
	    smb_strcasecmp(domain, dxi->d_primary.di_nbname, 0))
		return (B_FALSE);

	/*
	 * Now that we get the fully-qualified DNS name of the
	 * domain via LSA query. Verifies ADS configuration
	 * if we previously locate a DC via NetBIOS. On success,
	 * ADS cache will be populated.
	 */
	if (smb_ads_lookup_msdcs(dxi->d_primary.di_fqname, server,
	    dxi->d_dc, MAXHOSTNAMELEN) == 0)
		return (B_FALSE);

	return (B_TRUE);
}

/*
 * Tries to find a matching DNS domain for the given NetBIOS domain
 * name by checking the first label of system's configured DNS domains.
 * If a match is found, it'll be returned in the passed buffer.
 */
static boolean_t
smb_ddiscover_domain_match(char *nb_domain, char *buf, uint32_t len)
{
	struct __res_state res_state;
	int i;
	char *entry, *p;
	char first_label[MAXHOSTNAMELEN];
	boolean_t found;

	if (!nb_domain || !buf)
		return (B_FALSE);

	*buf = '\0';
	bzero(&res_state, sizeof (struct __res_state));
	if (res_ninit(&res_state))
		return (B_FALSE);

	found = B_FALSE;
	entry = res_state.defdname;
	for (i = 0; entry != NULL; i++) {
		(void) strlcpy(first_label, entry, MAXHOSTNAMELEN);
		if ((p = strchr(first_label, '.')) != NULL) {
			*p = '\0';
			if (strlen(first_label) > 15)
				first_label[15] = '\0';
		}

		if (smb_strcasecmp(nb_domain, first_label, 0) == 0) {
			found = B_TRUE;
			(void) strlcpy(buf, entry, len);
			break;
		}

		entry = res_state.dnsrch[i];
	}


	res_ndestroy(&res_state);
	return (found);
}

/*
 * Obtain primary and trusted domain information using LSA queries.
 *
 * Disconnect any existing connection with the domain controller.
 * This will ensure that no stale connection will be used, it will
 * also pickup any configuration changes in either side by trying
 * to establish a new connection.
 *
 * domain - either NetBIOS or fully-qualified domain name
 */
static uint32_t
smb_ddiscover_qinfo(char *domain, char *server, smb_domainex_t *dxi)
{
	uint32_t status;

	mlsvc_disconnect(server);

	status = lsa_query_dns_domain_info(server, domain, &dxi->d_primary);
	if (status != NT_STATUS_SUCCESS) {
		status = smb_ddiscover_use_config(domain, dxi);
		if (status != NT_STATUS_SUCCESS)
			status = lsa_query_primary_domain_info(server, domain,
			    &dxi->d_primary);
	}

	if (status == NT_STATUS_SUCCESS)
		smb_ddiscover_enum_trusted(domain, server, dxi);

	return (status);
}

/*
 * Obtain trusted domains information using LSA queries.
 *
 * domain - either NetBIOS or fully-qualified domain name.
 */
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);
}

/*
 * If the domain to be discovered matches the current domain (i.e the
 * value of either domain or fqdn configuration), then get the primary
 * domain information from SMF.
 */
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);
}