/* * 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 2017 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * DC Locator */ #define SMB_DCLOCATOR_TIMEOUT 45 /* seconds */ #define SMB_IS_FQDN(domain) (strchr(domain, '.') != NULL) /* How long to pause after a failure to find any domain controllers. */ int smb_ddiscover_failure_pause = 5; /* sec. */ typedef struct smb_dclocator { smb_dcinfo_t sdl_dci; /* .dc_name .dc_addr */ 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 *); /* * =================================================================== * 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; /* * We need the smb_ddiscover_service to run on startup, * so it will enter smb_ddiscover_main() and put the * SMB "domain cache" into "updating" state so clients * trying to logon will wait while we're finding a DC. */ 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); } /* * This is the entry point for discovering a domain controller for the * specified domain. Called during join domain, and then periodically * by smbd_dc_update (the "DC monitor" thread). * * 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) * * 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, 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); } /* * Tell the domain discovery service to run again now, * and assume changed configuration (i.e. a new DC). * Like the first part of smb_locate_dc(). * * Note: This is called from the service refresh handler * and the door handler to tell the ddiscover thread to * request the new DC from idmap. Therefore, we must not * trigger a new idmap discovery run from here, or that * would start a ping-pong match. */ /* ARGSUSED */ 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); } /* * Called by our client-side threads after they fail to connect to * the DC given to them by smb_locate_dc(). This is often called * after some delay, because the connection timeout delays these * threads for a while, so it's quite common that the DC locator * service has already started looking for a new DC. These late * notifications should not continually restart the DC locator. */ 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)) { /* * The "bad" DC is no longer the current one. * Probably a late "bad DC" report. */ goto out; } if (smb_dclocator.sdl_bad_dc) { /* Someone already marked the current DC as "bad". */ syslog(LOG_DEBUG, "smb_ddiscover_bad_dc repeat"); goto out; } /* * Mark the current DC as "bad" and let the DC Locator * run again if it's not already. */ syslog(LOG_INFO, "smb_ddiscover, bad DC: %s", bad_dc); smb_dclocator.sdl_bad_dc = B_TRUE; smb_domain_bad_dc(); /* In-line smb_ddiscover_kick */ 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); } /* * ========================================================== * 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) { smb_domainex_t dxi; smb_dclocator_t *sdl = arg; uint32_t status; boolean_t bad_dc; boolean_t cfg_chg; for (;;) { /* * Wait to be signaled for work by one of: * smb_locate_dc(), smb_ddiscover_refresh(), * smb_ddiscover_bad_dc() */ 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); 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; } /* * Want to know if these change below. * Note: mutex held here */ find_again: bad_dc = sdl->sdl_bad_dc; sdl->sdl_bad_dc = B_FALSE; if (bad_dc) { /* * Need to clear the current DC name or * ddiscover_bad_dc will keep setting 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); /* * Clear the cached DC now so that we'll ask idmap again. * If our current DC gave us errors, force rediscovery. */ smb_ads_refresh(bad_dc); /* * Search for the DC, save the result. */ 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; } /* * Run again if either of cfg_chg or bad_dc * was turned on during smb_ddiscover_main(). * Note: mutex held here. */ 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); } /*NOTREACHED*/ return (NULL); } /* * Discovers a domain controller for the specified domain via DNS. * After the domain controller is discovered successfully primary and * trusted domain infromation will be queried using RPC queries. * * Caller should zero out *dxi before calling, and after a * successful return should call: smb_domain_save() */ 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: /* Don't need the trusted domain list anymore. */ smb_domainex_free(dxi); return (status); } /* * Obtain primary and trusted domain information using LSA queries. * * domain - either NetBIOS or fully-qualified domain name */ static uint32_t smb_ddiscover_qinfo(char *domain, char *server, smb_domainex_t *dxi) { uint32_t ret, tmp; /* If we must return failure, use this first one. */ 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; /* All of the above failed. */ return (ret); success: smb_ddiscover_enum_trusted(domain, server, dxi); return (NT_STATUS_SUCCESS); } /* * 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); 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; } /* In case krb5.conf is not configured, set the default realm. */ (void) strlcpy(realm, domain, sizeof (realm)); (void) smb_strupr(realm); (void) setenv("KRB5_DEFAULT_REALM", realm, 1); }