diff options
Diffstat (limited to 'fs/cifs/smb1ops.c')
| -rw-r--r-- | fs/cifs/smb1ops.c | 399 | 
1 files changed, 397 insertions, 2 deletions
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c index 6dec38f5522..c40356d24c5 100644 --- a/fs/cifs/smb1ops.c +++ b/fs/cifs/smb1ops.c @@ -101,7 +101,8 @@ cifs_find_mid(struct TCP_Server_Info *server, char *buffer)  }  static void -cifs_add_credits(struct TCP_Server_Info *server, const unsigned int add) +cifs_add_credits(struct TCP_Server_Info *server, const unsigned int add, +		 const int optype)  {  	spin_lock(&server->req_lock);  	server->credits += add; @@ -120,11 +121,17 @@ cifs_set_credits(struct TCP_Server_Info *server, const int val)  }  static int * -cifs_get_credits_field(struct TCP_Server_Info *server) +cifs_get_credits_field(struct TCP_Server_Info *server, const int optype)  {  	return &server->credits;  } +static unsigned int +cifs_get_credits(struct mid_q_entry *mid) +{ +	return 1; +} +  /*   * Find a free multiplex id (SMB mid). Otherwise there could be   * mid collisions which might cause problems, demultiplexing the @@ -213,14 +220,382 @@ cifs_get_next_mid(struct TCP_Server_Info *server)  	return mid;  } +/* +	return codes: +		0	not a transact2, or all data present +		>0	transact2 with that much data missing +		-EINVAL	invalid transact2 + */ +static int +check2ndT2(char *buf) +{ +	struct smb_hdr *pSMB = (struct smb_hdr *)buf; +	struct smb_t2_rsp *pSMBt; +	int remaining; +	__u16 total_data_size, data_in_this_rsp; + +	if (pSMB->Command != SMB_COM_TRANSACTION2) +		return 0; + +	/* check for plausible wct, bcc and t2 data and parm sizes */ +	/* check for parm and data offset going beyond end of smb */ +	if (pSMB->WordCount != 10) { /* coalesce_t2 depends on this */ +		cFYI(1, "invalid transact2 word count"); +		return -EINVAL; +	} + +	pSMBt = (struct smb_t2_rsp *)pSMB; + +	total_data_size = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount); +	data_in_this_rsp = get_unaligned_le16(&pSMBt->t2_rsp.DataCount); + +	if (total_data_size == data_in_this_rsp) +		return 0; +	else if (total_data_size < data_in_this_rsp) { +		cFYI(1, "total data %d smaller than data in frame %d", +			total_data_size, data_in_this_rsp); +		return -EINVAL; +	} + +	remaining = total_data_size - data_in_this_rsp; + +	cFYI(1, "missing %d bytes from transact2, check next response", +		remaining); +	if (total_data_size > CIFSMaxBufSize) { +		cERROR(1, "TotalDataSize %d is over maximum buffer %d", +			total_data_size, CIFSMaxBufSize); +		return -EINVAL; +	} +	return remaining; +} + +static int +coalesce_t2(char *second_buf, struct smb_hdr *target_hdr) +{ +	struct smb_t2_rsp *pSMBs = (struct smb_t2_rsp *)second_buf; +	struct smb_t2_rsp *pSMBt  = (struct smb_t2_rsp *)target_hdr; +	char *data_area_of_tgt; +	char *data_area_of_src; +	int remaining; +	unsigned int byte_count, total_in_tgt; +	__u16 tgt_total_cnt, src_total_cnt, total_in_src; + +	src_total_cnt = get_unaligned_le16(&pSMBs->t2_rsp.TotalDataCount); +	tgt_total_cnt = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount); + +	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_tgt = get_unaligned_le16(&pSMBt->t2_rsp.DataCount); + +	remaining = tgt_total_cnt - total_in_tgt; + +	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 */ +		cFYI(1, "no more data remains"); +		return 0; +	} + +	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_tgt = (char *)&pSMBt->hdr.Protocol + +				get_unaligned_le16(&pSMBt->t2_rsp.DataOffset); + +	/* validate target area */ +	data_area_of_src = (char *)&pSMBs->hdr.Protocol + +				get_unaligned_le16(&pSMBs->t2_rsp.DataOffset); + +	data_area_of_tgt += total_in_tgt; + +	total_in_tgt += total_in_src; +	/* is the result too big for the field? */ +	if (total_in_tgt > USHRT_MAX) { +		cFYI(1, "coalesced DataCount too large (%u)", total_in_tgt); +		return -EPROTO; +	} +	put_unaligned_le16(total_in_tgt, &pSMBt->t2_rsp.DataCount); + +	/* fix up the BCC */ +	byte_count = get_bcc(target_hdr); +	byte_count += total_in_src; +	/* is the result too big for the field? */ +	if (byte_count > USHRT_MAX) { +		cFYI(1, "coalesced BCC too large (%u)", byte_count); +		return -EPROTO; +	} +	put_bcc(byte_count, target_hdr); + +	byte_count = be32_to_cpu(target_hdr->smb_buf_length); +	byte_count += total_in_src; +	/* don't allow buffer to overflow */ +	if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { +		cFYI(1, "coalesced BCC exceeds buffer size (%u)", byte_count); +		return -ENOBUFS; +	} +	target_hdr->smb_buf_length = cpu_to_be32(byte_count); + +	/* copy second buffer into end of first buffer */ +	memcpy(data_area_of_tgt, data_area_of_src, total_in_src); + +	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 bool +cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server, +		  char *buf, int malformed) +{ +	if (malformed) +		return false; +	if (check2ndT2(buf) <= 0) +		return false; +	mid->multiRsp = true; +	if (mid->resp_buf) { +		/* merge response - fix up 1st*/ +		malformed = coalesce_t2(buf, mid->resp_buf); +		if (malformed > 0) +			return true; +		/* All parts received or packet is malformed. */ +		mid->multiEnd = true; +		dequeue_mid(mid, malformed); +		return true; +	} +	if (!server->large_buf) { +		/*FIXME: switch to already allocated largebuf?*/ +		cERROR(1, "1st trans2 resp needs bigbuf"); +	} else { +		/* Have first buffer */ +		mid->resp_buf = buf; +		mid->large_buf = true; +		server->bigbuf = NULL; +	} +	return true; +} + +static bool +cifs_need_neg(struct TCP_Server_Info *server) +{ +	return server->maxBuf == 0; +} + +static int +cifs_negotiate(const unsigned int xid, struct cifs_ses *ses) +{ +	int rc; +	rc = CIFSSMBNegotiate(xid, ses); +	if (rc == -EAGAIN) { +		/* retry only once on 1st time connection */ +		set_credits(ses->server, 1); +		rc = CIFSSMBNegotiate(xid, ses); +		if (rc == -EAGAIN) +			rc = -EHOSTDOWN; +	} +	return rc; +} + +static void +cifs_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon) +{ +	CIFSSMBQFSDeviceInfo(xid, tcon); +	CIFSSMBQFSAttributeInfo(xid, tcon); +} + +static int +cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon, +			struct cifs_sb_info *cifs_sb, const char *full_path) +{ +	int rc; +	FILE_ALL_INFO *file_info; + +	file_info = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL); +	if (file_info == NULL) +		return -ENOMEM; + +	rc = CIFSSMBQPathInfo(xid, tcon, full_path, file_info, +			      0 /* not legacy */, cifs_sb->local_nls, +			      cifs_sb->mnt_cifs_flags & +				CIFS_MOUNT_MAP_SPECIAL_CHR); + +	if (rc == -EOPNOTSUPP || rc == -EINVAL) +		rc = SMBQueryInformation(xid, tcon, full_path, file_info, +				cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & +				  CIFS_MOUNT_MAP_SPECIAL_CHR); +	kfree(file_info); +	return rc; +} + +static int +cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, +		     struct cifs_sb_info *cifs_sb, const char *full_path, +		     FILE_ALL_INFO *data, bool *adjustTZ) +{ +	int rc; + +	/* could do find first instead but this returns more info */ +	rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */, +			      cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & +						CIFS_MOUNT_MAP_SPECIAL_CHR); +	/* +	 * BB optimize code so we do not make the above call when server claims +	 * no NT SMB support and the above call failed at least once - set flag +	 * in tcon or mount. +	 */ +	if ((rc == -EOPNOTSUPP) || (rc == -EINVAL)) { +		rc = SMBQueryInformation(xid, tcon, full_path, data, +					 cifs_sb->local_nls, +					 cifs_sb->mnt_cifs_flags & +						CIFS_MOUNT_MAP_SPECIAL_CHR); +		*adjustTZ = true; +	} +	return rc; +} + +static int +cifs_get_srv_inum(const unsigned int xid, struct cifs_tcon *tcon, +		  struct cifs_sb_info *cifs_sb, const char *full_path, +		  u64 *uniqueid, FILE_ALL_INFO *data) +{ +	/* +	 * We can not use the IndexNumber field by default from Windows or +	 * Samba (in ALL_INFO buf) but we can request it explicitly. The SNIA +	 * CIFS spec claims that this value is unique within the scope of a +	 * share, and the windows docs hint that it's actually unique +	 * per-machine. +	 * +	 * There may be higher info levels that work but are there Windows +	 * server or network appliances for which IndexNumber field is not +	 * guaranteed unique? +	 */ +	return CIFSGetSrvInodeNumber(xid, tcon, full_path, uniqueid, +				     cifs_sb->local_nls, +				     cifs_sb->mnt_cifs_flags & +						CIFS_MOUNT_MAP_SPECIAL_CHR); +} + +static char * +cifs_build_path_to_root(struct smb_vol *vol, struct cifs_sb_info *cifs_sb, +			struct cifs_tcon *tcon) +{ +	int pplen = vol->prepath ? strlen(vol->prepath) : 0; +	int dfsplen; +	char *full_path = NULL; + +	/* if no prefix path, simply set path to the root of share to "" */ +	if (pplen == 0) { +		full_path = kzalloc(1, GFP_KERNEL); +		return full_path; +	} + +	if (tcon->Flags & SMB_SHARE_IS_IN_DFS) +		dfsplen = strnlen(tcon->treeName, MAX_TREE_SIZE + 1); +	else +		dfsplen = 0; + +	full_path = kmalloc(dfsplen + pplen + 1, GFP_KERNEL); +	if (full_path == NULL) +		return full_path; + +	if (dfsplen) +		strncpy(full_path, tcon->treeName, dfsplen); +	strncpy(full_path + dfsplen, vol->prepath, pplen); +	convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb)); +	full_path[dfsplen + pplen] = 0; /* add trailing null */ +	return full_path; +} + +static void +cifs_clear_stats(struct cifs_tcon *tcon) +{ +#ifdef CONFIG_CIFS_STATS +	atomic_set(&tcon->stats.cifs_stats.num_writes, 0); +	atomic_set(&tcon->stats.cifs_stats.num_reads, 0); +	atomic_set(&tcon->stats.cifs_stats.num_flushes, 0); +	atomic_set(&tcon->stats.cifs_stats.num_oplock_brks, 0); +	atomic_set(&tcon->stats.cifs_stats.num_opens, 0); +	atomic_set(&tcon->stats.cifs_stats.num_posixopens, 0); +	atomic_set(&tcon->stats.cifs_stats.num_posixmkdirs, 0); +	atomic_set(&tcon->stats.cifs_stats.num_closes, 0); +	atomic_set(&tcon->stats.cifs_stats.num_deletes, 0); +	atomic_set(&tcon->stats.cifs_stats.num_mkdirs, 0); +	atomic_set(&tcon->stats.cifs_stats.num_rmdirs, 0); +	atomic_set(&tcon->stats.cifs_stats.num_renames, 0); +	atomic_set(&tcon->stats.cifs_stats.num_t2renames, 0); +	atomic_set(&tcon->stats.cifs_stats.num_ffirst, 0); +	atomic_set(&tcon->stats.cifs_stats.num_fnext, 0); +	atomic_set(&tcon->stats.cifs_stats.num_fclose, 0); +	atomic_set(&tcon->stats.cifs_stats.num_hardlinks, 0); +	atomic_set(&tcon->stats.cifs_stats.num_symlinks, 0); +	atomic_set(&tcon->stats.cifs_stats.num_locks, 0); +	atomic_set(&tcon->stats.cifs_stats.num_acl_get, 0); +	atomic_set(&tcon->stats.cifs_stats.num_acl_set, 0); +#endif +} + +static void +cifs_print_stats(struct seq_file *m, struct cifs_tcon *tcon) +{ +#ifdef CONFIG_CIFS_STATS +	seq_printf(m, " Oplocks breaks: %d", +		   atomic_read(&tcon->stats.cifs_stats.num_oplock_brks)); +	seq_printf(m, "\nReads:  %d Bytes: %llu", +		   atomic_read(&tcon->stats.cifs_stats.num_reads), +		   (long long)(tcon->bytes_read)); +	seq_printf(m, "\nWrites: %d Bytes: %llu", +		   atomic_read(&tcon->stats.cifs_stats.num_writes), +		   (long long)(tcon->bytes_written)); +	seq_printf(m, "\nFlushes: %d", +		   atomic_read(&tcon->stats.cifs_stats.num_flushes)); +	seq_printf(m, "\nLocks: %d HardLinks: %d Symlinks: %d", +		   atomic_read(&tcon->stats.cifs_stats.num_locks), +		   atomic_read(&tcon->stats.cifs_stats.num_hardlinks), +		   atomic_read(&tcon->stats.cifs_stats.num_symlinks)); +	seq_printf(m, "\nOpens: %d Closes: %d Deletes: %d", +		   atomic_read(&tcon->stats.cifs_stats.num_opens), +		   atomic_read(&tcon->stats.cifs_stats.num_closes), +		   atomic_read(&tcon->stats.cifs_stats.num_deletes)); +	seq_printf(m, "\nPosix Opens: %d Posix Mkdirs: %d", +		   atomic_read(&tcon->stats.cifs_stats.num_posixopens), +		   atomic_read(&tcon->stats.cifs_stats.num_posixmkdirs)); +	seq_printf(m, "\nMkdirs: %d Rmdirs: %d", +		   atomic_read(&tcon->stats.cifs_stats.num_mkdirs), +		   atomic_read(&tcon->stats.cifs_stats.num_rmdirs)); +	seq_printf(m, "\nRenames: %d T2 Renames %d", +		   atomic_read(&tcon->stats.cifs_stats.num_renames), +		   atomic_read(&tcon->stats.cifs_stats.num_t2renames)); +	seq_printf(m, "\nFindFirst: %d FNext %d FClose %d", +		   atomic_read(&tcon->stats.cifs_stats.num_ffirst), +		   atomic_read(&tcon->stats.cifs_stats.num_fnext), +		   atomic_read(&tcon->stats.cifs_stats.num_fclose)); +#endif +} +  struct smb_version_operations smb1_operations = {  	.send_cancel = send_nt_cancel,  	.compare_fids = cifs_compare_fids,  	.setup_request = cifs_setup_request, +	.setup_async_request = cifs_setup_async_request,  	.check_receive = cifs_check_receive,  	.add_credits = cifs_add_credits,  	.set_credits = cifs_set_credits,  	.get_credits_field = cifs_get_credits_field, +	.get_credits = cifs_get_credits,  	.get_next_mid = cifs_get_next_mid,  	.read_data_offset = cifs_read_data_offset,  	.read_data_length = cifs_read_data_length, @@ -228,7 +603,23 @@ struct smb_version_operations smb1_operations = {  	.find_mid = cifs_find_mid,  	.check_message = checkSMB,  	.dump_detail = cifs_dump_detail, +	.clear_stats = cifs_clear_stats, +	.print_stats = cifs_print_stats,  	.is_oplock_break = is_valid_oplock_break, +	.check_trans2 = cifs_check_trans2, +	.need_neg = cifs_need_neg, +	.negotiate = cifs_negotiate, +	.sess_setup = CIFS_SessSetup, +	.logoff = CIFSSMBLogoff, +	.tree_connect = CIFSTCon, +	.tree_disconnect = CIFSSMBTDis, +	.get_dfs_refer = CIFSGetDFSRefer, +	.qfs_tcon = cifs_qfs_tcon, +	.is_path_accessible = cifs_is_path_accessible, +	.query_path_info = cifs_query_path_info, +	.get_srv_inum = cifs_get_srv_inum, +	.build_path_to_root = cifs_build_path_to_root, +	.echo = CIFSSMBEcho,  };  struct smb_version_values smb1_values = { @@ -240,4 +631,8 @@ struct smb_version_values smb1_values = {  	.header_size = sizeof(struct smb_hdr),  	.max_header_size = MAX_CIFS_HDR_SIZE,  	.read_rsp_size = sizeof(READ_RSP), +	.lock_cmd = cpu_to_le16(SMB_COM_LOCKING_ANDX), +	.cap_unix = CAP_UNIX, +	.cap_nt_find = CAP_NT_SMBS | CAP_NT_FIND, +	.cap_large_files = CAP_LARGE_FILES,  };  |