view usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_logon.c @ 10717:fe0545fc3cdd

6612607 CIFS ADS client should use ldap_sasl_interactive_bind_s API 6877755 smbd should not route stderr, stdout to /dev/null 6882701 Wrong error message for attempt to map local user to Windows group, or vice versa 6885105 Potential for deadlock in smb_node_set_delete_on_close() 6881928 smbd core generated when running a script to join domain, set abe properties 6885538 Reduce dependencies on libsmbrdr 6820325 cifs service can't start on multi vlan+ipmp configuration
author Alan Wright <amw@Sun.COM>
date Mon, 05 Oct 2009 11:03:34 -0700
parents ee04788f8605
children 37e5dcdf36d3
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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <pthread.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <synch.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <smbsrv/wintypes.h>
#include <smbsrv/libsmbrdr.h>
#include <smbsrv/ntstatus.h>
#include <smbsrv/smb.h>
#include <smbrdr.h>

#define	SMBRDR_ANON_USER	"IPC$"

static int smbrdr_anonymous_logon(char *domain_controller, char *domain_name);
static int smbrdr_auth_logon(char *domain_controller, char *domain_name,
    char *username);
static int smbrdr_session_setupx(struct sdb_logon *logon);
static boolean_t smbrdr_logon_validate(char *server, char *username);
static struct sdb_logon *smbrdr_logon_init(struct sdb_session *session,
    char *username, unsigned char *pwd);
static int smbrdr_logon_user(char *server, char *username, unsigned char *pwd);
static int smbrdr_authenticate(char *, char *, char *, unsigned char *);

/*
 * If the username is SMBRDR_ANON_USER, an anonymous session will be
 * established. Otherwise, an authenticated session will be established
 * based on the specified credentials.
 */
int
smbrdr_logon(char *domain_controller, char *domain, char *username)
{
	int rc;

	if (strcmp(username, SMBRDR_ANON_USER) == 0)
		rc = smbrdr_anonymous_logon(domain_controller, domain);
	else
		rc = smbrdr_auth_logon(domain_controller, domain, username);

	return (rc);
}

/*
 * smbrdr_anonymous_logon
 *
 * Set up an anonymous session. If the session to the resource domain
 * controller appears to be okay we shouldn't need to do anything here.
 * Otherwise we clean up the stale session and create a new one.
 */
static int
smbrdr_anonymous_logon(char *domain_controller, char *domain_name)
{
	if (smbrdr_logon_validate(domain_controller, SMBRDR_ANON_USER))
		return (0);

	if (smbrdr_negotiate(domain_controller, domain_name) != 0) {
		syslog(LOG_DEBUG, "smbrdr_anonymous_logon: negotiate failed");
		return (-1);
	}

	if (smbrdr_logon_user(domain_controller, SMBRDR_ANON_USER, 0) < 0) {
		syslog(LOG_DEBUG, "smbrdr_anonymous_logon: logon failed");
		return (-1);
	}

	return (0);
}

/*
 * Get the user session key from an already open named pipe.
 * The RPC library needs this.  See ndr_rpc_get_ssnkey()
 *
 * Returns zero (success) or an errno.
 */
int
smbrdr_get_ssnkey(int fid, unsigned char *ssn_key, size_t key_len)
{
	struct sdb_logon *logon;
	struct sdb_session *session;
	struct sdb_netuse *netuse;
	struct sdb_ofile *ofile;

	if (ssn_key == NULL || key_len < SMBAUTH_SESSION_KEY_SZ)
		return (EINVAL);

	ofile = smbrdr_ofile_get(fid);
	if (ofile == NULL)
		return (EBADF);

	netuse = ofile->netuse;
	session = netuse->session;
	logon = &session->logon;

	if (key_len > SMBAUTH_SESSION_KEY_SZ)
		bzero(ssn_key, key_len);
	bcopy(logon->ssn_key, ssn_key,
	    SMBAUTH_SESSION_KEY_SZ);

	smbrdr_ofile_put(ofile);
	return (0);
}

/*
 * smbrdr_auth_logon
 *
 * Set up a user session. If the session to the resource domain controller
 * appears to be okay we shouldn't need to do anything here. Otherwise we
 * clean up the stale session and create a new one. Once a session is
 * established, we leave it intact. It should only need to be set up again
 * due to an inactivity timeout or a domain controller reset.
 */
static int
smbrdr_auth_logon(char *domain_controller, char *domain_name, char *username)
{
	int erc;
	uint8_t pwd_hash[SMBAUTH_HASH_SZ];

	if (username == NULL || *username == 0) {
		syslog(LOG_DEBUG, "smbrdr_auth_logon: no username");
		return (-1);
	}

	smb_ipc_get_passwd(pwd_hash, SMBAUTH_HASH_SZ);
	if (*pwd_hash == 0) {
		syslog(LOG_DEBUG, "smbrdr_auth_logon: no password");
		return (-1);
	}

	if (smbrdr_logon_validate(domain_controller, username))
		return (0);

	if (smbrdr_negotiate(domain_controller, domain_name) != 0) {
		syslog(LOG_DEBUG, "smbrdr_auth_logon: negotiate failed");
		return (-1);
	}

	erc = smbrdr_authenticate(domain_controller, domain_name, username,
	    pwd_hash);
	return ((erc == AUTH_USER_GRANT) ? 0 : -1);
}

/*
 * smbrdr_authenticate
 *
 * Authenticate primary_domain\account_name.
 *
 * Returns:
 * 0	User access granted
 * 1	Guest access granted
 * 2	IPC access granted
 * (<0) Error
 */
static int
smbrdr_authenticate(char *domain_controller, char *primary_domain,
    char *account_name, unsigned char *pwd)
{
	if (pwd == NULL)
		return (AUTH_USER_GRANT | AUTH_IPC_ONLY_GRANT);

	/*
	 * Ensure that the domain name is uppercase.
	 */
	(void) utf8_strupr(primary_domain);
	return (smbrdr_logon_user(domain_controller, account_name, pwd));
}

/*
 * smbrdr_logon_user
 *
 * This is the entry point for logging  a user onto the domain. The
 * session structure should have been obtained via a successful call
 * to smbrdr_smb_connect. We allocate a logon structure to hold the
 * user details and attempt to logon using smbrdr_session_setupx.
 * Note that we expect the password fields to have been encrypted
 * before this call.
 *
 * On success, the logon structure will be returned. Otherwise a null
 * pointer will be returned.
 */
static int
smbrdr_logon_user(char *server, char *username, unsigned char *pwd)
{
	struct sdb_session *session;
	struct sdb_logon *logon;
	struct sdb_logon old_logon;
	int ret;

	if ((server == NULL) || (username == NULL) ||
	    ((strcmp(username, SMBRDR_ANON_USER) != 0) && (pwd == NULL)))
		return (-1);

	session = smbrdr_session_lock(server, 0, SDB_SLCK_WRITE);
	if (session == NULL) {
		syslog(LOG_DEBUG, "smbrdr_logon_user: %s: no session with %s",
		    username, server);
		return (-1);
	}

	bzero(&old_logon, sizeof (struct sdb_logon));

	logon = &session->logon;
	if (logon->type != SDB_LOGON_NONE) {
		if (strcasecmp(logon->username, username) == 0) {
			/* The requested user has already been logged in */
			smbrdr_session_unlock(session);
			return ((logon->type == SDB_LOGON_GUEST)
			    ? AUTH_GUEST_GRANT : AUTH_USER_GRANT);
		}

		old_logon = *logon;
	}

	logon = smbrdr_logon_init(session, username, pwd);

	if (logon == NULL) {
		syslog(LOG_DEBUG, "smbrdr_logon_user: %s: %s",
		    username, strerror(ENOMEM));
		smbrdr_session_unlock(session);
		return (-1);
	}

	if (smbrdr_session_setupx(logon) < 0) {
		free(logon);
		smbrdr_session_unlock(session);
		return (-1);
	}


	ret = (logon->type == SDB_LOGON_GUEST)
	    ? AUTH_GUEST_GRANT : AUTH_USER_GRANT;

	session->logon = *logon;
	free(logon);

	if (old_logon.type != SDB_LOGON_NONE)
		(void) smbrdr_logoffx(&old_logon);

	smbrdr_session_unlock(session);
	return (ret);
}


/*
 * smbrdr_session_setupx
 *
 * Build and send an SMB session setup command. This is used to log a
 * user onto the domain. See CIFS section 4.1.2.
 *
 * Returns 0 on success. Otherwise returns a -ve error code.
 */
static int
smbrdr_session_setupx(struct sdb_logon *logon)
{
	struct sdb_session *session;
	smb_hdr_t smb_hdr;
	smbrdr_handle_t srh;
	smb_msgbuf_t *mb;
	char *native_os;
	char *native_lanman;
	unsigned short data_bytes;
	unsigned short guest;
	unsigned long capabilities;
	unsigned short null_size;
	size_t (*strlen_fn)(const char *s);
	DWORD status;
	int rc;

	/*
	 * Paranoia check - we should never get this
	 * far without a valid session structure.
	 */
	if ((session = logon->session) == NULL)
		return (-1);

	if (session->remote_caps & CAP_UNICODE) {
		strlen_fn = mts_wcequiv_strlen;
		null_size = sizeof (mts_wchar_t);
		session->smb_flags2 |= SMB_FLAGS2_UNICODE;
	} else {
		strlen_fn = strlen;
		null_size = sizeof (char);
	}

	if (smbrdr_sign_init(session, logon) < 0) {
		syslog(LOG_DEBUG,
		    "smbrdr_session_setupx: smbrdr_sign_init failed");
		return (-1);
	}

	status = smbrdr_request_init(&srh, SMB_COM_SESSION_SETUP_ANDX,
	    session, 0, 0);

	if (status != NT_STATUS_SUCCESS) {
		smbrdr_sign_fini(session);
		syslog(LOG_DEBUG, "smbrdr_session_setupx: %s",
		    xlate_nt_status(status));
		return (-1);
	}
	mb = &srh.srh_mbuf;

	/*
	 * Regardless of the server's capabilities or what's
	 * reported in smb_flags2, we should report our full
	 * capabilities.
	 */
	capabilities = CAP_UNICODE | CAP_NT_SMBS | CAP_STATUS32;

	/*
	 * Compute the BCC for unicode or ASCII strings.
	 */
	data_bytes  = logon->auth.ci_len + logon->auth.cs_len + null_size;
	data_bytes += strlen_fn(session->native_os) + null_size;
	data_bytes += strlen_fn(session->native_lanman) + null_size;

	if (logon->type == SDB_LOGON_ANONYMOUS) {
		/*
		 * Anonymous logon: no username or domain name.
		 * We still need to include two null characters.
		 */
		data_bytes += (2 * null_size);

		rc = smb_msgbuf_encode(mb, "bb.wwwwlwwllwlu.u.",
		    13,				/* smb_wct */
		    0xff,			/* AndXCommand (none) */
		    32 + 26 + 3 + data_bytes,	/* AndXOffset */
		    SMBRDR_REQ_BUFSZ,		/* MaxBufferSize */
		    1,				/* MaxMpxCount */
		    0,				/* VcNumber */
		    0,				/* SessionKey */
		    1,				/* CaseInsensitivePassLength */
		    0,				/* CaseSensitivePassLength */
		    0,				/* Reserved */
		    capabilities,		/* Capabilities */
		    data_bytes,			/* smb_bcc */
		    0,				/* No user or domain */
		    session->native_os,		/* NativeOS */
		    session->native_lanman);	/* NativeLanMan */
	} else {
		data_bytes += strlen_fn(logon->username) + null_size;
		data_bytes += strlen_fn(session->domain) + null_size;

		rc = smb_msgbuf_encode(mb, "bb.wwwwlwwllw#c#cuuu.u.",
		    13,				/* smb_wct */
		    0xff,			/* AndXCommand (none) */
		    32 + 26 + 3 + data_bytes,	/* AndXOffset */
		    SMBRDR_REQ_BUFSZ,		/* MaxBufferSize */
		    1,				/* MaxMpxCount */
		    session->vc,		/* VcNumber */
		    session->sesskey,		/* SessionKey */
		    logon->auth.ci_len,		/* CaseInsensitivePassLength */
		    logon->auth.cs_len,		/* CaseSensitivePassLength */
		    0,				/* Reserved */
		    capabilities,		/* Capabilities */
		    data_bytes,			/* smb_bcc */
		    logon->auth.ci_len,		/* ci length spec */
		    logon->auth.ci,		/* CaseInsensitivePassword */
		    logon->auth.cs_len,		/* cs length spec */
		    logon->auth.cs,		/* CaseSensitivePassword */
		    logon->username,		/* AccountName */
		    session->domain,		/* PrimaryDomain */
		    session->native_os,		/* NativeOS */
		    session->native_lanman);	/* NativeLanMan */
	}

	if (rc <= 0) {
		syslog(LOG_DEBUG, "smbrdr_session_setupx: encode failed");
		smbrdr_handle_free(&srh);
		smbrdr_sign_fini(session);
		return (-1);
	}

	status = smbrdr_exchange(&srh, &smb_hdr, 0);
	if (status != NT_STATUS_SUCCESS) {
		syslog(LOG_DEBUG, "smbrdr_session_setupx: %s",
		    xlate_nt_status(status));
		smbrdr_handle_free(&srh);
		smbrdr_sign_fini(session);
		return (-1);
	}

	rc = smb_msgbuf_decode(mb, "5.w2.u", &guest, &native_os);

	/*
	 * There was a problem in decoding response from
	 * a Samba 2.x PDC. This server sends strings in ASCII
	 * format and there is one byte with value 0 between
	 * native_os and native_lm:
	 *
	 *				 FF 53 4D 42 73 00		.SMBs.
	 * 00 00 00 88 01 00 00 00 00 00 00 00 00 00 00 00 ................
	 * 00 00 00 00 BB 00 64 00 00 00 03 FF 00 00 00 01 ......d.........
	 * 00 1C 00 55 6E 69 78 00 53 61 6D 62 61 20 32 2E ...Unix.Samba.2.
	 * 32 2E 38 61 00 53 41 4D 42 41 5F 44 4F 4D 00    2.8a.SAMBA_DOM.
	 *
	 * The byte doesn't seem to be padding because when change in
	 * native OS from Unix to Unix1 the 0 byte is still there:
	 *
	 *				 FF 53 4D 42 73 00		.SMBs.
	 * 00 00 00 88 01 00 00 00 00 00 00 00 00 00 00 00 ................
	 * 00 00 00 00 BB 00 64 00 00 00 03 FF 00 00 00 00 ......d.........
	 * 00 1D 00 55 6E 69 78 31 00 53 61 6D 62 61 20 32 ...Unix1.Samba.2
	 * 2E 32 2E 38 61 00 53 41 4D 42 41 5F 44 4F 4D 00 .2.8a.SAMBA_DOM.
	 */
	if (rc > 0) {
		if (session->remote_caps & CAP_UNICODE)
			rc = smb_msgbuf_decode(mb, "u", &native_lanman);
		else
			rc = smb_msgbuf_decode(mb, ".u", &native_lanman);
	}

	if (rc <= 0) {
		syslog(LOG_DEBUG, "smbrdr_session_setupx: decode failed");
		smbrdr_handle_free(&srh);
		smbrdr_sign_fini(session);
		return (-1);
	}

	session->remote_os = smbnative_os_value(native_os);
	session->remote_lm = smbnative_lm_value(native_lanman);
	session->pdc_type  = smbnative_pdc_value(native_lanman);

	logon->uid = smb_hdr.uid;
	if (guest)
		logon->type = SDB_LOGON_GUEST;

	smbrdr_handle_free(&srh);
	smbrdr_sign_unset_key(session);

	logon->state = SDB_LSTATE_SETUP;

	return (0);
}

/*
 * smbrdr_logoffx
 *
 * Build and send an SMB session logoff (SMB_COM_LOGOFF_ANDX) command.
 * This is the inverse of an SMB_COM_SESSION_SETUP_ANDX. See CIFS
 * section 4.1.3. The logon structure should have been obtained from a
 * successful call to smbrdr_logon_user.
 *
 * Returns 0 on success. Otherwise returns a -ve error code.
 */
int
smbrdr_logoffx(struct sdb_logon *logon)
{
	struct sdb_session *session;
	smbrdr_handle_t srh;
	smb_hdr_t smb_hdr;
	DWORD status;
	int rc;

	if (logon->state != SDB_LSTATE_SETUP) {
		/* No user to logoff */
		bzero(logon, sizeof (struct sdb_logon));
		return (0);
	}

	if ((session = logon->session) == 0) {
		bzero(logon, sizeof (struct sdb_logon));
		return (0);
	}

	logon->state = SDB_LSTATE_LOGGING_OFF;
	smbrdr_netuse_logoff(logon->uid);

	if ((session->state != SDB_SSTATE_NEGOTIATED) &&
	    (session->state != SDB_SSTATE_DISCONNECTING)) {
		bzero(logon, sizeof (struct sdb_logon));
		return (0);
	}

	status = smbrdr_request_init(&srh, SMB_COM_LOGOFF_ANDX,
	    session, logon, 0);

	if (status != NT_STATUS_SUCCESS) {
		logon->state = SDB_LSTATE_SETUP;
		syslog(LOG_DEBUG, "smbrdr_logoffx: %s: %s", logon->username,
		    xlate_nt_status(status));
		return (-1);
	}

	rc = smb_msgbuf_encode(&srh.srh_mbuf, "bbbww", 2, 0xff, 0, 0, 0);
	if (rc < 0) {
		logon->state = SDB_LSTATE_SETUP;
		smbrdr_handle_free(&srh);
		syslog(LOG_DEBUG, "smbrdr_logoffx: %s: encode failed",
		    logon->username);
		return (rc);
	}

	status = smbrdr_exchange(&srh, &smb_hdr, 0);
	if (status != NT_STATUS_SUCCESS) {
		syslog(LOG_DEBUG, "smbrdr_logoffx: %s: %s", logon->username,
		    xlate_nt_status(status));
		rc = -1;
	} else {
		rc = 0;
	}

	bzero(logon, sizeof (struct sdb_logon));
	smbrdr_handle_free(&srh);
	return (rc);
}


/*
 * smbrdr_logon_init
 *
 * Find a slot for account logon information. The account information
 * is associated with a session so we need a valid session slot before
 * calling this function. If we already have a record of the specified
 * account, a pointer to that record is returned. Otherwise we attempt
 * to allocate a new one.
 */
static struct sdb_logon *
smbrdr_logon_init(struct sdb_session *session, char *username,
    unsigned char *pwd)
{
	struct sdb_logon *logon;
	int64_t smbrdr_lmcompl;
	int rc;

	logon = (struct sdb_logon *)malloc(sizeof (sdb_logon_t));
	if (logon == 0)
		return (0);

	bzero(logon, sizeof (struct sdb_logon));
	logon->session = session;

	(void) smb_config_getnum(SMB_CI_LM_LEVEL, &smbrdr_lmcompl);

	if (strcmp(username, "IPC$") == 0) {
		logon->type = SDB_LOGON_ANONYMOUS;
		logon->auth.ci_len = 1;
		*(logon->auth.ci) = 0;
		logon->auth.cs_len = 0;
	} else {
		logon->type = SDB_LOGON_USER;
		rc = smb_auth_set_info(username, 0, pwd,
		    session->domain, session->challenge_key,
		    session->challenge_len, smbrdr_lmcompl, &logon->auth);

		/* Generate (and save) the session key. */
		if (rc == 0) {
			rc = smb_auth_gen_session_key(&logon->auth,
			    logon->ssn_key);
		}

		if (rc != 0) {
			free(logon);
			return (0);
		}
	}

	(void) strlcpy(logon->username, username, MAX_ACCOUNT_NAME);
	logon->state = SDB_LSTATE_INIT;
	return (logon);
}

/*
 * smbrdr_logon_validate
 *
 * if session is there and it's alive and also the required
 * user is already logged in don't need to do anything
 * otherwise clear the session structure.
 */
static boolean_t
smbrdr_logon_validate(char *server, char *username)
{
	struct sdb_session *session;
	boolean_t valid = B_FALSE;

	session = smbrdr_session_lock(server, username, SDB_SLCK_WRITE);
	if (session) {
		if (nb_keep_alive(session->sock, session->port) == 0) {
			valid = B_TRUE;
		} else {
			session->state = SDB_SSTATE_STALE;
			syslog(LOG_DEBUG,
			    "smbrdr_logon_validate: stale session");
		}

		smbrdr_session_unlock(session);
	}

	return (valid);
}