/* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Maximum time to wait for a domain controller (30 seconds). */ #define SMB_NETLOGON_TIMEOUT 30 /* * Flags used in conjunction with the location and query condition * variables. */ #define SMB_NETLF_LOCATE_DC 0x00000001 #define SMB_NETLF_LSA_QUERY 0x00000002 typedef struct smb_netlogon_info { char snli_domain[SMB_PI_MAX_DOMAIN]; char snli_dc[MAXHOSTNAMELEN]; unsigned snli_flags; mutex_t snli_locate_mtx; cond_t snli_locate_cv; mutex_t snli_query_mtx; cond_t snli_query_cv; uint32_t snli_status; } smb_netlogon_info_t; static smb_netlogon_info_t smb_netlogon_info; /* NT4 domain support is not yet available. */ static boolean_t nt4_domain_support = B_FALSE; static pthread_t lsa_monitor_thr; static pthread_t dc_browser_thr; static void *smb_netlogon_lsa_monitor(void *arg); static void *smb_netlogon_dc_browser(void *arg); /* * Inline convenience function to find out if the domain information is * valid. The caller can decide whether or not to wait. */ static boolean_t smb_ntdomain_is_valid(uint32_t timeout) { smb_ntdomain_t *info; if ((info = smb_getdomaininfo(timeout)) != 0) { if (info->ipaddr != 0) return (B_TRUE); } return (B_FALSE); } /* * Retrieve the kpasswd server from krb5.conf. */ static int smbd_get_kpasswd_srv(char *srv, size_t len) { FILE *fp; static char buf[512]; char *p; *srv = '\0'; p = getenv("KRB5_CONFIG"); if (p == NULL || *p == '\0') p = "/etc/krb5/krb5.conf"; if ((fp = fopen(p, "r")) == NULL) return (-1); while (fgets(buf, sizeof (buf), fp)) { /* Weed out any comment text */ (void) trim_whitespace(buf); if (*buf == '#') continue; if ((p = strstr(buf, "kpasswd_server")) != NULL) { if ((p = strchr(p, '=')) != NULL) { (void) trim_whitespace(++p); (void) strlcpy(srv, p, len); } break; } } (void) fclose(fp); return ((*srv == '\0') ? -1 : 0); } /* * smbd_join * * Joins the specified domain/workgroup */ uint32_t smbd_join(smb_joininfo_t *info) { smb_ntdomain_t *pi; uint32_t status; unsigned char passwd_hash[SMBAUTH_HASH_SZ]; char plain_passwd[PASS_LEN + 1]; char plain_user[PASS_LEN + 1]; char nbt_domain[SMB_PI_MAX_DOMAIN]; char fqdn[MAXHOSTNAMELEN]; char dc[MAXHOSTNAMELEN]; char kpasswd_domain[MAXHOSTNAMELEN]; (void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain, MAXHOSTNAMELEN); if (info->mode == SMB_SECMODE_WORKGRP) { if ((smb_config_get_secmode() == SMB_SECMODE_DOMAIN) && kpasswd_domain == '\0') { if (smb_ads_domain_change_cleanup("") != 0) { syslog(LOG_ERR, "smbd: unable to remove the" " old keys from the Kerberos keytab. " "Please remove the old keys for your " "host principal."); } } (void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nbt_domain, sizeof (nbt_domain)); (void) smb_config_set_secmode(info->mode); (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, info->domain_name); if (strcasecmp(nbt_domain, info->domain_name)) smb_browser_reconfig(); return (NT_STATUS_SUCCESS); } /* * Ensure that any previous membership of this domain has * been cleared from the environment before we start. This * will ensure that we don't attempt a NETLOGON_SAMLOGON * when attempting to find the PDC. */ (void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE); (void) strlcpy(plain_user, info->domain_username, sizeof (plain_user)); (void) strlcpy(plain_passwd, info->domain_passwd, sizeof (plain_passwd)); (void) smb_resolve_netbiosname(info->domain_name, nbt_domain, sizeof (nbt_domain)); if (smb_resolve_fqdn(info->domain_name, fqdn, sizeof (fqdn)) != 1) { syslog(LOG_ERR, "smbd: fully-qualified domain name is unknown"); return (NT_STATUS_INVALID_PARAMETER); } if (smb_ads_domain_change_cleanup(fqdn)) { syslog(LOG_ERR, "smbd: unable to remove the old keys from the" " Kerberos keytab. Please remove the old keys for your " "host principal."); return (NT_STATUS_INTERNAL_ERROR); } if (smb_auth_ntlm_hash(plain_passwd, passwd_hash) != SMBAUTH_SUCCESS) { status = NT_STATUS_INTERNAL_ERROR; syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'", plain_user); return (status); } smbrdr_ipc_set(plain_user, passwd_hash); (void) smbd_get_kpasswd_srv(dc, sizeof (dc)); if (smbd_locate_dc(nbt_domain, dc)) { if ((pi = smb_getdomaininfo(0)) == 0) { status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; if (*dc == '\0') syslog(LOG_ERR, "smbd: could not get domain " "controller information for '%s'", info->domain_name); else syslog(LOG_ERR, "smbd: could not get the " "specified domain controller information " "'%s'", dc); return (status); } /* * Temporary delay before creating * the workstation trust account. */ (void) sleep(2); status = mlsvc_join(pi->server, pi->domain, plain_user, plain_passwd); if (status == NT_STATUS_SUCCESS) { (void) smb_config_set_secmode(SMB_SECMODE_DOMAIN); (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, info->domain_name); smbrdr_ipc_commit(); return (status); } smbrdr_ipc_rollback(); syslog(LOG_ERR, "smbd: failed joining %s (%s)", info->domain_name, xlate_nt_status(status)); return (status); } smbrdr_ipc_rollback(); syslog(LOG_ERR, "smbd: failed locating domain controller for %s", info->domain_name); return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND); } /* * smbd_locate_dc * * This is the entry point for discovering a domain controller for the * specified domain. The caller may block here for around 30 seconds if * the system has to go to the network and find a domain controller. * Sometime it would be good to change this to smb_locate_pdc and allow * the caller to specify whether or not he wants to wait for a response. * * The actual work of discovering a DC is handled by other threads. * All we do here is signal the request and wait for a DC or a timeout. * * domain - domain to be discovered * dc - preferred DC. If the preferred DC is set to empty string, it * will attempt to discover any DC in the specified domain. * * Returns B_TRUE if a domain controller is available. */ boolean_t smbd_locate_dc(char *domain, char *dc) { int rc; timestruc_t to; if (domain == NULL || *domain == '\0') return (B_FALSE); (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); if ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) == 0) { smb_netlogon_info.snli_flags |= SMB_NETLF_LOCATE_DC; (void) strlcpy(smb_netlogon_info.snli_domain, domain, SMB_PI_MAX_DOMAIN); (void) strlcpy(smb_netlogon_info.snli_dc, dc, MAXHOSTNAMELEN); (void) cond_broadcast(&smb_netlogon_info.snli_locate_cv); } while (smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) { to.tv_sec = SMB_NETLOGON_TIMEOUT; to.tv_nsec = 0; rc = cond_reltimedwait(&smb_netlogon_info.snli_locate_cv, &smb_netlogon_info.snli_locate_mtx, &to); if (rc == ETIME) break; } (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); return (smb_ntdomain_is_valid(0)); } /* * smb_netlogon_init * * Initialization of the DC browser and LSA monitor threads. * Returns 0 on success, an error number if thread creation fails. */ int smb_netlogon_init(void) { pthread_attr_t tattr; int rc; (void) pthread_attr_init(&tattr); (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); rc = pthread_create(&lsa_monitor_thr, &tattr, smb_netlogon_lsa_monitor, 0); if (rc != 0) goto nli_exit; rc = pthread_create(&dc_browser_thr, &tattr, smb_netlogon_dc_browser, 0); if (rc != 0) { (void) pthread_cancel(lsa_monitor_thr); (void) pthread_join(lsa_monitor_thr, NULL); } nli_exit: (void) pthread_attr_destroy(&tattr); return (rc); } /* * smb_netlogon_dc_browser * * This is the DC browser thread: it gets woken up whenever someone * wants to locate a domain controller. * * With the introduction of Windows 2000, NetBIOS is no longer a * requirement for NT domains. If NetBIOS has been disabled on the * network there will be no browsers and we won't get any response * to netlogon requests. So we try to find a DC controller via ADS * first. If ADS is disabled or the DNS query fails, we drop back * to the netlogon protocol. * * This function will block for up to 30 seconds waiting for the PDC * to be discovered. Sometime it would be good to change this to * smb_locate_pdc and allow the caller to specify whether or not he * wants to wait for a response. * */ /*ARGSUSED*/ static void * smb_netlogon_dc_browser(void *arg) { boolean_t rc; char resource_domain[SMB_PI_MAX_DOMAIN]; char dc[MAXHOSTNAMELEN]; for (;;) { (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); while ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) == 0) { (void) cond_wait(&smb_netlogon_info.snli_locate_cv, &smb_netlogon_info.snli_locate_mtx); } (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); (void) strlcpy(resource_domain, smb_netlogon_info.snli_domain, SMB_PI_MAX_DOMAIN); (void) strlcpy(dc, smb_netlogon_info.snli_dc, MAXHOSTNAMELEN); smb_setdomaininfo(NULL, NULL, 0); if ((smb_msdcs_lookup_ads(resource_domain, dc) == 0) && (nt4_domain_support)) { /* Try to locate a DC via NetBIOS */ smb_browser_netlogon(resource_domain); } rc = smb_ntdomain_is_valid(SMB_NETLOGON_TIMEOUT); (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); smb_netlogon_info.snli_flags &= ~SMB_NETLF_LOCATE_DC; (void) cond_broadcast(&smb_netlogon_info.snli_locate_cv); (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); if (rc != B_TRUE) { /* * Notify the LSA monitor to update the * primary and trusted domain information. */ (void) mutex_lock(&smb_netlogon_info.snli_query_mtx); smb_netlogon_info.snli_flags |= SMB_NETLF_LSA_QUERY; (void) cond_broadcast(&smb_netlogon_info.snli_query_cv); (void) mutex_unlock(&smb_netlogon_info.snli_query_mtx); } } /*NOTREACHED*/ return (NULL); } /* * smb_netlogon_lsa_monitor * * This monitor should run as a separate thread. It waits on a condition * variable until someone indicates that the LSA domain information needs * to be refreshed. It then queries the DC for the NT domain information: * primary, account and trusted domains. The condition variable should be * signaled whenever a DC is selected. * * Note that the LSA query calls require the DC information and this task * may end up blocked on the DC location protocol, which is why this * monitor is run as a separate thread. This should only happen if the DC * goes down immediately after we located it. */ /*ARGSUSED*/ static void * smb_netlogon_lsa_monitor(void *arg) { uint32_t status; for (;;) { (void) mutex_lock(&smb_netlogon_info.snli_query_mtx); while ((smb_netlogon_info.snli_flags & SMB_NETLF_LSA_QUERY) == 0) { (void) cond_wait(&smb_netlogon_info.snli_query_cv, &smb_netlogon_info.snli_query_mtx); } smb_netlogon_info.snli_flags &= ~SMB_NETLF_LSA_QUERY; (void) mutex_unlock(&smb_netlogon_info.snli_query_mtx); /* * Skip the LSA query if Authenticated IPC is supported * and the credential is not yet set. */ if (smbrdr_ipc_skip_lsa_query() == 0) { status = lsa_query_primary_domain_info(); if (status == NT_STATUS_SUCCESS) { if (lsa_query_account_domain_info() != NT_STATUS_SUCCESS) { syslog(LOG_DEBUG, "NetlogonLSAMonitor: query " "account info failed"); } if (lsa_enum_trusted_domains() != NT_STATUS_SUCCESS) { syslog(LOG_DEBUG, "NetlogonLSAMonitor: enum " "trusted domain failed"); } } else { syslog(LOG_DEBUG, "NetlogonLSAMonitor: update failed"); } } } /*NOTREACHED*/ return (NULL); } /* * smb_set_netlogon_cred * * If the system is joined to an AD domain via kclient, SMB daemon will need * to establish the NETLOGON credential chain. * * Since the kclient has updated the machine password stored in SMF * repository, the cached ipc_info must be updated accordingly by calling * smbrdr_ipc_commit. * * Due to potential replication delays in a multiple DC environment, the * NETLOGON rpc request must be sent to the DC, to which the KPASSWD request * is sent. If the DC discovered by the SMB daemon is different than the * kpasswd server, the current connection with the DC will be torn down * and a DC discovery process will be triggered to locate the kpasswd * server. * * If joining a new domain, the domain_name property must be set after a * successful credential chain setup. */ void smb_set_netlogon_cred(void) { smb_ntdomain_t *dp; smb_ntdomain_t domain_info; char kpasswd_srv[MAXHOSTNAMELEN]; char kpasswd_domain[MAXHOSTNAMELEN]; char sam_acct[MLSVC_ACCOUNT_NAME_MAX]; char *ipc_usr, *dom; boolean_t new_domain = B_FALSE; if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) return; if (smb_match_netlogon_seqnum()) return; (void) smb_config_getstr(SMB_CI_KPASSWD_SRV, kpasswd_srv, sizeof (kpasswd_srv)); if (*kpasswd_srv == '\0') return; /* * If the domain join initiated by smbadm join CLI is in * progress, don't do anything. */ (void) smb_gethostname(sam_acct, MLSVC_ACCOUNT_NAME_MAX - 1, 0); (void) strlcat(sam_acct, "$", MLSVC_ACCOUNT_NAME_MAX); ipc_usr = smbrdr_ipc_get_user(); if (strcasecmp(ipc_usr, sam_acct)) return; if ((dp = smb_getdomaininfo(0)) == NULL) { *domain_info.server = '\0'; (void) smb_getdomainname(domain_info.domain, sizeof (domain_info.domain)); dp = &domain_info; } (void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain, sizeof (kpasswd_domain)); if (*kpasswd_domain != '\0' && strncasecmp(kpasswd_domain, dp->domain, strlen(dp->domain))) { dom = kpasswd_domain; new_domain = B_TRUE; } else { dom = dp->domain; } /* * DC discovery will be triggered if the domain info is not * currently cached or the SMB daemon has previously discovered a DC * that is different than the kpasswd server. */ if (new_domain || strcasecmp(dp->server, kpasswd_srv) != 0) { if (*dp->server != '\0') mlsvc_disconnect(dp->server); if (!smbd_locate_dc(dom, kpasswd_srv)) (void) smbd_locate_dc(dp->domain, ""); if ((dp = smb_getdomaininfo(0)) == NULL) { smbrdr_ipc_commit(); return; } } smbrdr_ipc_commit(); if (mlsvc_netlogon(dp->server, dp->domain)) { syslog(LOG_ERR, "NETLOGON credential chain establishment" " failed"); } else { if (new_domain) { (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, kpasswd_domain); (void) smb_config_setstr(SMB_CI_KPASSWD_DOMAIN, ""); } } }