Home | History | Annotate | Download | only in smbd
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #include <syslog.h>
     27 #include <synch.h>
     28 #include <pthread.h>
     29 #include <unistd.h>
     30 #include <string.h>
     31 #include <strings.h>
     32 #include <sys/errno.h>
     33 
     34 #include <smbsrv/libsmb.h>
     35 #include <smbsrv/libsmbns.h>
     36 #include <smbsrv/libmlsvc.h>
     37 #include <smbsrv/smbinfo.h>
     38 #include <smbsrv/ntstatus.h>
     39 #include "smbd.h"
     40 
     41 
     42 /*
     43  * This is a short-lived thread that triggers the initial DC discovery
     44  * at startup.
     45  */
     46 static pthread_t smb_locate_dc_thr;
     47 
     48 static void *smbd_locate_dc_thread(void *);
     49 static int smbd_get_kpasswd_srv(char *, size_t);
     50 static uint32_t smbd_join_workgroup(smb_joininfo_t *);
     51 static uint32_t smbd_join_domain(smb_joininfo_t *);
     52 
     53 /*
     54  * smbd_join
     55  *
     56  * Joins the specified domain/workgroup.
     57  *
     58  * If the security mode or domain name is being changed,
     59  * the caller must restart the service.
     60  */
     61 uint32_t
     62 smbd_join(smb_joininfo_t *info)
     63 {
     64 	uint32_t status;
     65 
     66 	dssetup_clear_domain_info();
     67 	if (info->mode == SMB_SECMODE_WORKGRP)
     68 		status = smbd_join_workgroup(info);
     69 	else
     70 		status = smbd_join_domain(info);
     71 
     72 	return (status);
     73 }
     74 
     75 /*
     76  * smbd_set_netlogon_cred
     77  *
     78  * If the system is joined to an AD domain via kclient, SMB daemon will need
     79  * to establish the NETLOGON credential chain.
     80  *
     81  * Since the kclient has updated the machine password stored in SMF
     82  * repository, the cached ipc_info must be updated accordingly by calling
     83  * smb_ipc_commit.
     84  *
     85  * Due to potential replication delays in a multiple DC environment, the
     86  * NETLOGON rpc request must be sent to the DC, to which the KPASSWD request
     87  * is sent. If the DC discovered by the SMB daemon is different than the
     88  * kpasswd server, the current connection with the DC will be torn down
     89  * and a DC discovery process will be triggered to locate the kpasswd
     90  * server.
     91  *
     92  * If joining a new domain, the domain_name property must be set after a
     93  * successful credential chain setup.
     94  */
     95 boolean_t
     96 smbd_set_netlogon_cred(void)
     97 {
     98 	char kpasswd_srv[MAXHOSTNAMELEN];
     99 	char kpasswd_domain[MAXHOSTNAMELEN];
    100 	char sam_acct[SMB_SAMACCT_MAXLEN];
    101 	char ipc_usr[SMB_USERNAME_MAXLEN];
    102 	char *dom;
    103 	boolean_t new_domain = B_FALSE;
    104 	smb_domainex_t dxi;
    105 	smb_domain_t *di;
    106 
    107 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
    108 		return (B_FALSE);
    109 
    110 	if (smb_match_netlogon_seqnum())
    111 		return (B_FALSE);
    112 
    113 	(void) smb_config_getstr(SMB_CI_KPASSWD_SRV, kpasswd_srv,
    114 	    sizeof (kpasswd_srv));
    115 
    116 	if (*kpasswd_srv == '\0')
    117 		return (B_FALSE);
    118 
    119 	/*
    120 	 * If the domain join initiated by smbadm join CLI is in
    121 	 * progress, don't do anything.
    122 	 */
    123 	(void) smb_getsamaccount(sam_acct, sizeof (sam_acct));
    124 	smb_ipc_get_user(ipc_usr, SMB_USERNAME_MAXLEN);
    125 	if (smb_strcasecmp(ipc_usr, sam_acct, 0))
    126 		return (B_FALSE);
    127 
    128 	di = &dxi.d_primary;
    129 	if (!smb_domain_getinfo(&dxi))
    130 		(void) smb_getfqdomainname(di->di_fqname, MAXHOSTNAMELEN);
    131 
    132 	(void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain,
    133 	    sizeof (kpasswd_domain));
    134 
    135 	if (*kpasswd_domain != '\0' &&
    136 	    smb_strcasecmp(kpasswd_domain, di->di_fqname, 0)) {
    137 		dom = kpasswd_domain;
    138 		new_domain = B_TRUE;
    139 	} else {
    140 		dom = di->di_fqname;
    141 	}
    142 
    143 	/*
    144 	 * DC discovery will be triggered if the domain info is not
    145 	 * currently cached or the SMB daemon has previously discovered a DC
    146 	 * that is different than the kpasswd server.
    147 	 */
    148 	if (new_domain || smb_strcasecmp(dxi.d_dc, kpasswd_srv, 0) != 0) {
    149 		if (*dxi.d_dc != '\0')
    150 			mlsvc_disconnect(dxi.d_dc);
    151 
    152 		if (!smb_locate_dc(dom, kpasswd_srv, &dxi)) {
    153 			if (!smb_locate_dc(di->di_fqname, "", &dxi)) {
    154 				smb_ipc_commit();
    155 				return (B_FALSE);
    156 			}
    157 		}
    158 	}
    159 
    160 	smb_ipc_commit();
    161 	if (mlsvc_netlogon(dxi.d_dc, di->di_nbname)) {
    162 		syslog(LOG_ERR,
    163 		    "failed to establish NETLOGON credential chain");
    164 		return (B_TRUE);
    165 	} else {
    166 		if (new_domain) {
    167 			smb_config_setdomaininfo(di->di_nbname, di->di_fqname,
    168 			    di->di_sid,
    169 			    di->di_u.di_dns.ddi_forest,
    170 			    di->di_u.di_dns.ddi_guid);
    171 			(void) smb_config_setstr(SMB_CI_KPASSWD_DOMAIN, "");
    172 		}
    173 	}
    174 
    175 	return (new_domain);
    176 }
    177 
    178 /*
    179  * smbd_locate_dc_start()
    180  *
    181  * Initialization of the thread that triggers the initial DC discovery
    182  * when SMB daemon starts up.
    183  * Returns 0 on success, an error number if thread creation fails.
    184  */
    185 int
    186 smbd_locate_dc_start(void)
    187 {
    188 	pthread_attr_t tattr;
    189 	int rc;
    190 
    191 	(void) pthread_attr_init(&tattr);
    192 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
    193 	rc = pthread_create(&smb_locate_dc_thr, &tattr, smbd_locate_dc_thread,
    194 	    NULL);
    195 	(void) pthread_attr_destroy(&tattr);
    196 	return (rc);
    197 }
    198 
    199 /*
    200  * smbd_locate_dc_thread()
    201  *
    202  * If necessary, set up Netlogon credential chain and locate a
    203  * domain controller in the given resource domain.
    204  *
    205  * The domain configuration will be updated upon a successful DC discovery.
    206  */
    207 /*ARGSUSED*/
    208 static void *
    209 smbd_locate_dc_thread(void *arg)
    210 {
    211 	char domain[MAXHOSTNAMELEN];
    212 	smb_domainex_t new_domain;
    213 	smb_domain_t *di;
    214 
    215 	if (!smb_match_netlogon_seqnum()) {
    216 		(void) smbd_set_netlogon_cred();
    217 	} else {
    218 		if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0) {
    219 			(void) smb_getdomainname(domain, MAXHOSTNAMELEN);
    220 			(void) smb_strupr(domain);
    221 		}
    222 
    223 		if (smb_locate_dc(domain, "", &new_domain)) {
    224 			di = &new_domain.d_primary;
    225 			smb_config_setdomaininfo(di->di_nbname, di->di_fqname,
    226 			    di->di_sid,
    227 			    di->di_u.di_dns.ddi_forest,
    228 			    di->di_u.di_dns.ddi_guid);
    229 		}
    230 	}
    231 
    232 	return (NULL);
    233 }
    234 
    235 
    236 /*
    237  * Retrieve the kpasswd server from krb5.conf.
    238  *
    239  * Initialization of the locate dc thread.
    240  * Returns 0 on success, an error number if thread creation fails.
    241  */
    242 static int
    243 smbd_get_kpasswd_srv(char *srv, size_t len)
    244 {
    245 	FILE *fp;
    246 	static char buf[512];
    247 	char *p;
    248 
    249 	*srv = '\0';
    250 	p = getenv("KRB5_CONFIG");
    251 	if (p == NULL || *p == '\0')
    252 		p = "/etc/krb5/krb5.conf";
    253 
    254 	if ((fp = fopen(p, "r")) == NULL)
    255 		return (-1);
    256 
    257 	while (fgets(buf, sizeof (buf), fp)) {
    258 
    259 		/* Weed out any comment text */
    260 		(void) trim_whitespace(buf);
    261 		if (*buf == '#')
    262 			continue;
    263 
    264 		if ((p = strstr(buf, "kpasswd_server")) != NULL) {
    265 			if ((p = strchr(p, '=')) != NULL) {
    266 				(void) trim_whitespace(++p);
    267 				(void) strlcpy(srv, p, len);
    268 			}
    269 			break;
    270 		}
    271 	}
    272 
    273 
    274 	(void) fclose(fp);
    275 	return ((*srv == '\0') ? -1 : 0);
    276 }
    277 
    278 static uint32_t
    279 smbd_join_workgroup(smb_joininfo_t *info)
    280 {
    281 	char nb_domain[SMB_PI_MAX_DOMAIN];
    282 
    283 	(void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nb_domain,
    284 	    sizeof (nb_domain));
    285 
    286 	smbd_set_secmode(SMB_SECMODE_WORKGRP);
    287 	smb_config_setdomaininfo(info->domain_name, "", "", "", "");
    288 
    289 	if (strcasecmp(nb_domain, info->domain_name))
    290 		smb_browser_reconfig();
    291 
    292 	return (NT_STATUS_SUCCESS);
    293 }
    294 
    295 static uint32_t
    296 smbd_join_domain(smb_joininfo_t *info)
    297 {
    298 	uint32_t status;
    299 	unsigned char passwd_hash[SMBAUTH_HASH_SZ];
    300 	char dc[MAXHOSTNAMELEN];
    301 	smb_domainex_t dxi;
    302 	smb_domain_t *di;
    303 
    304 	/*
    305 	 * Ensure that any previous membership of this domain has
    306 	 * been cleared from the environment before we start. This
    307 	 * will ensure that we don't attempt a NETLOGON_SAMLOGON
    308 	 * when attempting to find the PDC.
    309 	 */
    310 
    311 	(void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE);
    312 
    313 	if (smb_auth_ntlm_hash(info->domain_passwd, passwd_hash)
    314 	    != SMBAUTH_SUCCESS) {
    315 		syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'",
    316 		    info->domain_username);
    317 		return (NT_STATUS_INTERNAL_ERROR);
    318 	}
    319 
    320 	smb_ipc_set(info->domain_username, passwd_hash);
    321 
    322 	(void) smbd_get_kpasswd_srv(dc, sizeof (dc));
    323 	/* info->domain_name could either be NetBIOS domain name or FQDN */
    324 	if (smb_locate_dc(info->domain_name, dc, &dxi)) {
    325 		status = mlsvc_join(&dxi, info->domain_username,
    326 		    info->domain_passwd);
    327 
    328 		if (status == NT_STATUS_SUCCESS) {
    329 			di = &dxi.d_primary;
    330 			smbd_set_secmode(SMB_SECMODE_DOMAIN);
    331 			smb_config_setdomaininfo(di->di_nbname, di->di_fqname,
    332 			    di->di_sid,
    333 			    di->di_u.di_dns.ddi_forest,
    334 			    di->di_u.di_dns.ddi_guid);
    335 			smb_ipc_commit();
    336 			return (status);
    337 		}
    338 
    339 		smb_ipc_rollback();
    340 		syslog(LOG_ERR, "smbd: failed joining %s (%s)",
    341 		    info->domain_name, xlate_nt_status(status));
    342 		return (status);
    343 	}
    344 
    345 	smb_ipc_rollback();
    346 	syslog(LOG_ERR, "smbd: failed locating domain controller for %s",
    347 	    info->domain_name);
    348 	return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
    349 }
    350