Commit 34f598ca authored by Steve French's avatar Steve French
Browse files
parents dcd169b7 789b4588
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -676,14 +676,23 @@ static ssize_t cifs_multiuser_mount_proc_write(struct file *file,
{
	char c;
	int rc;
	static bool warned;

	rc = get_user(c, buffer);
	if (rc)
		return rc;
	if (c == '0' || c == 'n' || c == 'N')
		multiuser_mount = 0;
	else if (c == '1' || c == 'y' || c == 'Y')
	else if (c == '1' || c == 'y' || c == 'Y') {
		multiuser_mount = 1;
		if (!warned) {
			warned = true;
			printk(KERN_WARNING "CIFS VFS: The legacy multiuser "
				"mount code is scheduled to be deprecated in "
				"3.5. Please switch to using the multiuser "
				"mount option.");
		}
	}

	return count;
}
+7 −3
Original line number Diff line number Diff line
@@ -113,9 +113,11 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo)
		   MAX_MECH_STR_LEN +
		   UID_KEY_LEN + (sizeof(uid_t) * 2) +
		   CREDUID_KEY_LEN + (sizeof(uid_t) * 2) +
		   USER_KEY_LEN + strlen(sesInfo->user_name) +
		   PID_KEY_LEN + (sizeof(pid_t) * 2) + 1;

	if (sesInfo->user_name)
		desc_len += USER_KEY_LEN + strlen(sesInfo->user_name);

	spnego_key = ERR_PTR(-ENOMEM);
	description = kzalloc(desc_len, GFP_KERNEL);
	if (description == NULL)
@@ -152,8 +154,10 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo)
	dp = description + strlen(description);
	sprintf(dp, ";creduid=0x%x", sesInfo->cred_uid);

	if (sesInfo->user_name) {
		dp = description + strlen(description);
		sprintf(dp, ";user=%s", sesInfo->user_name);
	}

	dp = description + strlen(description);
	sprintf(dp, ";pid=0x%x", current->pid);
+8 −3
Original line number Diff line number Diff line
@@ -420,15 +420,20 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash,
	}

	/* convert ses->user_name to unicode and uppercase */
	len = strlen(ses->user_name);
	len = ses->user_name ? strlen(ses->user_name) : 0;
	user = kmalloc(2 + (len * 2), GFP_KERNEL);
	if (user == NULL) {
		cERROR(1, "calc_ntlmv2_hash: user mem alloc failure\n");
		rc = -ENOMEM;
		return rc;
	}

	if (len) {
		len = cifs_strtoUCS((__le16 *)user, ses->user_name, len, nls_cp);
		UniStrupr(user);
	} else {
		memset(user, '\0', 2);
	}

	rc = crypto_shash_update(&ses->server->secmech.sdeschmacmd5->shash,
				(char *)user, 2 * len);
+246 −55
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@
#include <asm/processor.h>
#include <linux/inet.h>
#include <linux/module.h>
#include <keys/user-type.h>
#include <net/ipv6.h>
#include "cifspdu.h"
#include "cifsglob.h"
@@ -225,76 +226,92 @@ static int check2ndT2(struct smb_hdr *pSMB)

static int coalesce_t2(struct smb_hdr *psecond, struct smb_hdr *pTargetSMB)
{
	struct smb_t2_rsp *pSMB2 = (struct smb_t2_rsp *)psecond;
	struct smb_t2_rsp *pSMBs = (struct smb_t2_rsp *)psecond;
	struct smb_t2_rsp *pSMBt  = (struct smb_t2_rsp *)pTargetSMB;
	char *data_area_of_target;
	char *data_area_of_buf2;
	char *data_area_of_tgt;
	char *data_area_of_src;
	int remaining;
	unsigned int byte_count, total_in_buf;
	__u16 total_data_size, total_in_buf2;
	unsigned int byte_count, total_in_tgt;
	__u16 tgt_total_cnt, src_total_cnt, total_in_src;

	total_data_size = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount);
	src_total_cnt = get_unaligned_le16(&pSMBs->t2_rsp.TotalDataCount);
	tgt_total_cnt = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount);

	if (total_data_size !=
	    get_unaligned_le16(&pSMB2->t2_rsp.TotalDataCount))
		cFYI(1, "total data size of primary and secondary t2 differ");
	if (tgt_total_cnt != src_total_cnt)
		cFYI(1, "total data count of primary and secondary t2 differ "
			"source=%hu target=%hu", src_total_cnt, tgt_total_cnt);

	total_in_buf = get_unaligned_le16(&pSMBt->t2_rsp.DataCount);
	total_in_tgt = get_unaligned_le16(&pSMBt->t2_rsp.DataCount);

	remaining = total_data_size - total_in_buf;
	remaining = tgt_total_cnt - total_in_tgt;

	if (remaining < 0)
	if (remaining < 0) {
		cFYI(1, "Server sent too much data. tgt_total_cnt=%hu "
			"total_in_tgt=%hu", tgt_total_cnt, total_in_tgt);
		return -EPROTO;
	}

	if (remaining == 0) /* nothing to do, ignore */
	if (remaining == 0) {
		/* nothing to do, ignore */
		cFYI(1, "no more data remains");
		return 0;
	}

	total_in_buf2 = get_unaligned_le16(&pSMB2->t2_rsp.DataCount);
	if (remaining < total_in_buf2) {
	total_in_src = get_unaligned_le16(&pSMBs->t2_rsp.DataCount);
	if (remaining < total_in_src)
		cFYI(1, "transact2 2nd response contains too much data");
	}

	/* find end of first SMB data area */
	data_area_of_target = (char *)&pSMBt->hdr.Protocol +
	data_area_of_tgt = (char *)&pSMBt->hdr.Protocol +
				get_unaligned_le16(&pSMBt->t2_rsp.DataOffset);
	/* validate target area */

	data_area_of_buf2 = (char *)&pSMB2->hdr.Protocol +
				get_unaligned_le16(&pSMB2->t2_rsp.DataOffset);
	/* validate target area */
	data_area_of_src = (char *)&pSMBs->hdr.Protocol +
				get_unaligned_le16(&pSMBs->t2_rsp.DataOffset);

	data_area_of_target += total_in_buf;
	data_area_of_tgt += total_in_tgt;

	/* copy second buffer into end of first buffer */
	total_in_buf += total_in_buf2;
	total_in_tgt += total_in_src;
	/* is the result too big for the field? */
	if (total_in_buf > USHRT_MAX)
	if (total_in_tgt > USHRT_MAX) {
		cFYI(1, "coalesced DataCount too large (%u)", total_in_tgt);
		return -EPROTO;
	put_unaligned_le16(total_in_buf, &pSMBt->t2_rsp.DataCount);
	}
	put_unaligned_le16(total_in_tgt, &pSMBt->t2_rsp.DataCount);

	/* fix up the BCC */
	byte_count = get_bcc(pTargetSMB);
	byte_count += total_in_buf2;
	byte_count += total_in_src;
	/* is the result too big for the field? */
	if (byte_count > USHRT_MAX)
	if (byte_count > USHRT_MAX) {
		cFYI(1, "coalesced BCC too large (%u)", byte_count);
		return -EPROTO;
	}
	put_bcc(byte_count, pTargetSMB);

	byte_count = be32_to_cpu(pTargetSMB->smb_buf_length);
	byte_count += total_in_buf2;
	byte_count += total_in_src;
	/* don't allow buffer to overflow */
	if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4)
	if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) {
		cFYI(1, "coalesced BCC exceeds buffer size (%u)", byte_count);
		return -ENOBUFS;
	}
	pTargetSMB->smb_buf_length = cpu_to_be32(byte_count);

	memcpy(data_area_of_target, data_area_of_buf2, total_in_buf2);
	/* copy second buffer into end of first buffer */
	memcpy(data_area_of_tgt, data_area_of_src, total_in_src);

	if (remaining == total_in_buf2) {
		cFYI(1, "found the last secondary response");
		return 0; /* we are done */
	} else /* more responses to go */
	if (remaining != total_in_src) {
		/* more responses to go */
		cFYI(1, "waiting for more secondary responses");
		return 1;
	}

	/* we are done */
	cFYI(1, "found the last secondary response");
	return 0;
}

static void
cifs_echo_request(struct work_struct *work)
{
@@ -1578,11 +1595,14 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
		}
	}

	if (vol->multiuser && !(vol->secFlg & CIFSSEC_MAY_KRB5)) {
		cERROR(1, "Multiuser mounts currently require krb5 "
			  "authentication!");
#ifndef CONFIG_KEYS
	/* Muliuser mounts require CONFIG_KEYS support */
	if (vol->multiuser) {
		cERROR(1, "Multiuser mounts require kernels with "
			  "CONFIG_KEYS enabled.");
		goto cifs_parse_mount_err;
	}
#endif

	if (vol->UNCip == NULL)
		vol->UNCip = &vol->UNC[2];
@@ -1981,10 +2001,16 @@ static int match_session(struct cifs_ses *ses, struct smb_vol *vol)
			return 0;
		break;
	default:
		/* anything else takes username/password */
		if (ses->user_name == NULL)
		/* NULL username means anonymous session */
		if (ses->user_name == NULL) {
			if (!vol->nullauth)
				return 0;
		if (strncmp(ses->user_name, vol->username,
			break;
		}

		/* anything else takes username/password */
		if (strncmp(ses->user_name,
			    vol->username ? vol->username : "",
			    MAX_USERNAME_SIZE))
			return 0;
		if (strlen(vol->username) != 0 &&
@@ -2039,6 +2065,132 @@ cifs_put_smb_ses(struct cifs_ses *ses)
	cifs_put_tcp_session(server);
}

#ifdef CONFIG_KEYS

/* strlen("cifs:a:") + INET6_ADDRSTRLEN + 1 */
#define CIFSCREDS_DESC_SIZE (7 + INET6_ADDRSTRLEN + 1)

/* Populate username and pw fields from keyring if possible */
static int
cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses)
{
	int rc = 0;
	char *desc, *delim, *payload;
	ssize_t len;
	struct key *key;
	struct TCP_Server_Info *server = ses->server;
	struct sockaddr_in *sa;
	struct sockaddr_in6 *sa6;
	struct user_key_payload *upayload;

	desc = kmalloc(CIFSCREDS_DESC_SIZE, GFP_KERNEL);
	if (!desc)
		return -ENOMEM;

	/* try to find an address key first */
	switch (server->dstaddr.ss_family) {
	case AF_INET:
		sa = (struct sockaddr_in *)&server->dstaddr;
		sprintf(desc, "cifs:a:%pI4", &sa->sin_addr.s_addr);
		break;
	case AF_INET6:
		sa6 = (struct sockaddr_in6 *)&server->dstaddr;
		sprintf(desc, "cifs:a:%pI6c", &sa6->sin6_addr.s6_addr);
		break;
	default:
		cFYI(1, "Bad ss_family (%hu)", server->dstaddr.ss_family);
		rc = -EINVAL;
		goto out_err;
	}

	cFYI(1, "%s: desc=%s", __func__, desc);
	key = request_key(&key_type_logon, desc, "");
	if (IS_ERR(key)) {
		if (!ses->domainName) {
			cFYI(1, "domainName is NULL");
			rc = PTR_ERR(key);
			goto out_err;
		}

		/* didn't work, try to find a domain key */
		sprintf(desc, "cifs:d:%s", ses->domainName);
		cFYI(1, "%s: desc=%s", __func__, desc);
		key = request_key(&key_type_logon, desc, "");
		if (IS_ERR(key)) {
			rc = PTR_ERR(key);
			goto out_err;
		}
	}

	down_read(&key->sem);
	upayload = key->payload.data;
	if (IS_ERR_OR_NULL(upayload)) {
		rc = PTR_ERR(key);
		goto out_key_put;
	}

	/* find first : in payload */
	payload = (char *)upayload->data;
	delim = strnchr(payload, upayload->datalen, ':');
	cFYI(1, "payload=%s", payload);
	if (!delim) {
		cFYI(1, "Unable to find ':' in payload (datalen=%d)",
				upayload->datalen);
		rc = -EINVAL;
		goto out_key_put;
	}

	len = delim - payload;
	if (len > MAX_USERNAME_SIZE || len <= 0) {
		cFYI(1, "Bad value from username search (len=%ld)", len);
		rc = -EINVAL;
		goto out_key_put;
	}

	vol->username = kstrndup(payload, len, GFP_KERNEL);
	if (!vol->username) {
		cFYI(1, "Unable to allocate %ld bytes for username", len);
		rc = -ENOMEM;
		goto out_key_put;
	}
	cFYI(1, "%s: username=%s", __func__, vol->username);

	len = key->datalen - (len + 1);
	if (len > MAX_PASSWORD_SIZE || len <= 0) {
		cFYI(1, "Bad len for password search (len=%ld)", len);
		rc = -EINVAL;
		kfree(vol->username);
		vol->username = NULL;
		goto out_key_put;
	}

	++delim;
	vol->password = kstrndup(delim, len, GFP_KERNEL);
	if (!vol->password) {
		cFYI(1, "Unable to allocate %ld bytes for password", len);
		rc = -ENOMEM;
		kfree(vol->username);
		vol->username = NULL;
		goto out_key_put;
	}

out_key_put:
	up_read(&key->sem);
	key_put(key);
out_err:
	kfree(desc);
	cFYI(1, "%s: returning %d", __func__, rc);
	return rc;
}
#else /* ! CONFIG_KEYS */
static inline int
cifs_set_cifscreds(struct smb_vol *vol __attribute__((unused)),
		   struct cifs_ses *ses __attribute__((unused)))
{
	return -ENOSYS;
}
#endif /* CONFIG_KEYS */

static bool warned_on_ntlm;  /* globals init to false automatically */

static struct cifs_ses *
@@ -2914,18 +3066,33 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
#define CIFS_DEFAULT_IOSIZE (1024 * 1024)

/*
 * Windows only supports a max of 60k reads. Default to that when posix
 * extensions aren't in force.
 * Windows only supports a max of 60kb reads and 65535 byte writes. Default to
 * those values when posix extensions aren't in force. In actuality here, we
 * use 65536 to allow for a write that is a multiple of 4k. Most servers seem
 * to be ok with the extra byte even though Windows doesn't send writes that
 * are that large.
 *
 * Citation:
 *
 * http://blogs.msdn.com/b/openspecification/archive/2009/04/10/smb-maximum-transmit-buffer-size-and-performance-tuning.aspx
 */
#define CIFS_DEFAULT_NON_POSIX_RSIZE (60 * 1024)
#define CIFS_DEFAULT_NON_POSIX_WSIZE (65536)

static unsigned int
cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info)
{
	__u64 unix_cap = le64_to_cpu(tcon->fsUnixInfo.Capability);
	struct TCP_Server_Info *server = tcon->ses->server;
	unsigned int wsize = pvolume_info->wsize ? pvolume_info->wsize :
				CIFS_DEFAULT_IOSIZE;
	unsigned int wsize;

	/* start with specified wsize, or default */
	if (pvolume_info->wsize)
		wsize = pvolume_info->wsize;
	else if (tcon->unix_ext && (unix_cap & CIFS_UNIX_LARGE_WRITE_CAP))
		wsize = CIFS_DEFAULT_IOSIZE;
	else
		wsize = CIFS_DEFAULT_NON_POSIX_WSIZE;

	/* can server support 24-bit write sizes? (via UNIX extensions) */
	if (!tcon->unix_ext || !(unix_cap & CIFS_UNIX_LARGE_WRITE_CAP))
@@ -3136,10 +3303,9 @@ cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
		return -EINVAL;

	if (volume_info->nullauth) {
		cFYI(1, "null user");
		volume_info->username = kzalloc(1, GFP_KERNEL);
		if (volume_info->username == NULL)
			return -ENOMEM;
		cFYI(1, "Anonymous login");
		kfree(volume_info->username);
		volume_info->username = NULL;
	} else if (volume_info->username) {
		/* BB fixme parse for domain name here */
		cFYI(1, "Username: %s", volume_info->username);
@@ -3657,16 +3823,38 @@ int cifs_setup_session(unsigned int xid, struct cifs_ses *ses,
	return rc;
}

static int
cifs_set_vol_auth(struct smb_vol *vol, struct cifs_ses *ses)
{
	switch (ses->server->secType) {
	case Kerberos:
		vol->secFlg = CIFSSEC_MUST_KRB5;
		return 0;
	case NTLMv2:
		vol->secFlg = CIFSSEC_MUST_NTLMV2;
		break;
	case NTLM:
		vol->secFlg = CIFSSEC_MUST_NTLM;
		break;
	case RawNTLMSSP:
		vol->secFlg = CIFSSEC_MUST_NTLMSSP;
		break;
	case LANMAN:
		vol->secFlg = CIFSSEC_MUST_LANMAN;
		break;
	}

	return cifs_set_cifscreds(vol, ses);
}

static struct cifs_tcon *
cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
{
	int rc;
	struct cifs_tcon *master_tcon = cifs_sb_master_tcon(cifs_sb);
	struct cifs_ses *ses;
	struct cifs_tcon *tcon = NULL;
	struct smb_vol *vol_info;
	char username[28]; /* big enough for "krb50x" + hex of ULONG_MAX 6+16 */
			   /* We used to have this as MAX_USERNAME which is   */
			   /* way too big now (256 instead of 32) */

	vol_info = kzalloc(sizeof(*vol_info), GFP_KERNEL);
	if (vol_info == NULL) {
@@ -3674,8 +3862,6 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
		goto out;
	}

	snprintf(username, sizeof(username), "krb50x%x", fsuid);
	vol_info->username = username;
	vol_info->local_nls = cifs_sb->local_nls;
	vol_info->linux_uid = fsuid;
	vol_info->cred_uid = fsuid;
@@ -3685,8 +3871,11 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
	vol_info->local_lease = master_tcon->local_lease;
	vol_info->no_linux_ext = !master_tcon->unix_ext;

	/* FIXME: allow for other secFlg settings */
	vol_info->secFlg = CIFSSEC_MUST_KRB5;
	rc = cifs_set_vol_auth(vol_info, master_tcon->ses);
	if (rc) {
		tcon = ERR_PTR(rc);
		goto out;
	}

	/* get a reference for the same TCP session */
	spin_lock(&cifs_tcp_ses_lock);
@@ -3709,6 +3898,8 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
	if (ses->capabilities & CAP_UNIX)
		reset_cifs_unix_caps(0, tcon, NULL, vol_info);
out:
	kfree(vol_info->username);
	kfree(vol_info->password);
	kfree(vol_info);

	return tcon;
+2 −1
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@

/*****************************************************************************/
/*
 * the payload for a key of type "user"
 * the payload for a key of type "user" or "logon"
 * - once filled in and attached to a key:
 *   - the payload struct is invariant may not be changed, only replaced
 *   - the payload must be read with RCU procedures or with the key semaphore
@@ -33,6 +33,7 @@ struct user_key_payload {
};

extern struct key_type key_type_user;
extern struct key_type key_type_logon;

extern int user_instantiate(struct key *key, const void *data, size_t datalen);
extern int user_update(struct key *key, const void *data, size_t datalen);
Loading