2012-05-15 20:20:51 +04:00
/*
* SMB1 ( CIFS ) version specific operations
*
* Copyright ( c ) 2012 , Jeff Layton < jlayton @ redhat . com >
*
* This library is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License v2 as published
* by the Free Software Foundation .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See
* the GNU Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include "cifsglob.h"
2012-05-15 20:21:10 +04:00
# include "cifsproto.h"
# include "cifs_debug.h"
2012-02-28 15:23:34 +04:00
# include "cifspdu.h"
2012-05-15 20:21:10 +04:00
/*
* An NT cancel request header looks just like the original request except :
*
* The Command is SMB_COM_NT_CANCEL
* The WordCount is zeroed out
* The ByteCount is zeroed out
*
* This function mangles an existing request buffer into a
* SMB_COM_NT_CANCEL request and then sends it .
*/
static int
send_nt_cancel ( struct TCP_Server_Info * server , void * buf ,
struct mid_q_entry * mid )
{
int rc = 0 ;
struct smb_hdr * in_buf = ( struct smb_hdr * ) buf ;
/* -4 for RFC1001 length and +2 for BCC field */
in_buf - > smb_buf_length = cpu_to_be32 ( sizeof ( struct smb_hdr ) - 4 + 2 ) ;
in_buf - > Command = SMB_COM_NT_CANCEL ;
in_buf - > WordCount = 0 ;
put_bcc ( 0 , in_buf ) ;
mutex_lock ( & server - > srv_mutex ) ;
rc = cifs_sign_smb ( in_buf , server , & mid - > sequence_number ) ;
if ( rc ) {
mutex_unlock ( & server - > srv_mutex ) ;
return rc ;
}
rc = smb_send ( server , in_buf , be32_to_cpu ( in_buf - > smb_buf_length ) ) ;
mutex_unlock ( & server - > srv_mutex ) ;
cFYI ( 1 , " issued NT_CANCEL for mid %u, rc = %d " ,
in_buf - > Mid , rc ) ;
return rc ;
}
2012-05-15 20:20:51 +04:00
2012-02-28 15:04:17 +04:00
static bool
cifs_compare_fids ( struct cifsFileInfo * ob1 , struct cifsFileInfo * ob2 )
{
return ob1 - > netfid = = ob2 - > netfid ;
}
2012-05-17 13:02:51 +04:00
static unsigned int
cifs_read_data_offset ( char * buf )
{
READ_RSP * rsp = ( READ_RSP * ) buf ;
return le16_to_cpu ( rsp - > DataOffset ) ;
}
static unsigned int
cifs_read_data_length ( char * buf )
{
READ_RSP * rsp = ( READ_RSP * ) buf ;
return ( le16_to_cpu ( rsp - > DataLengthHigh ) < < 16 ) +
le16_to_cpu ( rsp - > DataLength ) ;
}
2012-05-17 13:25:35 +04:00
static struct mid_q_entry *
cifs_find_mid ( struct TCP_Server_Info * server , char * buffer )
{
struct smb_hdr * buf = ( struct smb_hdr * ) buffer ;
struct mid_q_entry * mid ;
spin_lock ( & GlobalMid_Lock ) ;
list_for_each_entry ( mid , & server - > pending_mid_q , qhead ) {
if ( mid - > mid = = buf - > Mid & &
mid - > mid_state = = MID_REQUEST_SUBMITTED & &
le16_to_cpu ( mid - > command ) = = buf - > Command ) {
spin_unlock ( & GlobalMid_Lock ) ;
return mid ;
}
}
spin_unlock ( & GlobalMid_Lock ) ;
return NULL ;
}
2012-05-17 17:53:29 +04:00
static void
2012-05-23 16:14:34 +04:00
cifs_add_credits ( struct TCP_Server_Info * server , const unsigned int add ,
const int optype )
2012-05-17 17:53:29 +04:00
{
spin_lock ( & server - > req_lock ) ;
server - > credits + = add ;
server - > in_flight - - ;
spin_unlock ( & server - > req_lock ) ;
wake_up ( & server - > request_q ) ;
}
static void
cifs_set_credits ( struct TCP_Server_Info * server , const int val )
{
spin_lock ( & server - > req_lock ) ;
server - > credits = val ;
server - > oplocks = val > 1 ? enable_oplocks : false ;
spin_unlock ( & server - > req_lock ) ;
}
static int *
2012-05-23 16:14:34 +04:00
cifs_get_credits_field ( struct TCP_Server_Info * server , const int optype )
2012-05-17 17:53:29 +04:00
{
return & server - > credits ;
}
2012-05-23 16:14:34 +04:00
static unsigned int
cifs_get_credits ( struct mid_q_entry * mid )
{
return 1 ;
}
2012-05-23 14:01:59 +04:00
/*
* Find a free multiplex id ( SMB mid ) . Otherwise there could be
* mid collisions which might cause problems , demultiplexing the
* wrong response to this request . Multiplex ids could collide if
* one of a series requests takes much longer than the others , or
* if a very large number of long lived requests ( byte range
* locks or FindNotify requests ) are pending . No more than
* 64 K - 1 requests can be outstanding at one time . If no
* mids are available , return zero . A future optimization
* could make the combination of mids and uid the key we use
* to demultiplex on ( rather than mid alone ) .
* In addition to the above check , the cifs demultiplex
* code already used the command code as a secondary
* check of the frame and if signing is negotiated the
* response would be discarded if the mid were the same
* but the signature was wrong . Since the mid is not put in the
* pending queue until later ( when it is about to be dispatched )
* we do have to limit the number of outstanding requests
* to somewhat less than 64 K - 1 although it is hard to imagine
* so many threads being in the vfs at one time .
*/
static __u64
cifs_get_next_mid ( struct TCP_Server_Info * server )
{
__u64 mid = 0 ;
__u16 last_mid , cur_mid ;
bool collision ;
spin_lock ( & GlobalMid_Lock ) ;
/* mid is 16 bit only for CIFS/SMB */
cur_mid = ( __u16 ) ( ( server - > CurrentMid ) & 0xffff ) ;
/* we do not want to loop forever */
last_mid = cur_mid ;
cur_mid + + ;
/*
* This nested loop looks more expensive than it is .
* In practice the list of pending requests is short ,
* fewer than 50 , and the mids are likely to be unique
* on the first pass through the loop unless some request
* takes longer than the 64 thousand requests before it
* ( and it would also have to have been a request that
* did not time out ) .
*/
while ( cur_mid ! = last_mid ) {
struct mid_q_entry * mid_entry ;
unsigned int num_mids ;
collision = false ;
if ( cur_mid = = 0 )
cur_mid + + ;
num_mids = 0 ;
list_for_each_entry ( mid_entry , & server - > pending_mid_q , qhead ) {
+ + num_mids ;
if ( mid_entry - > mid = = cur_mid & &
mid_entry - > mid_state = = MID_REQUEST_SUBMITTED ) {
/* This mid is in use, try a different one */
collision = true ;
break ;
}
}
/*
* if we have more than 32 k mids in the list , then something
* is very wrong . Possibly a local user is trying to DoS the
* box by issuing long - running calls and SIGKILL ' ing them . If
* we get to 2 ^ 16 mids then we ' re in big trouble as this
* function could loop forever .
*
* Go ahead and assign out the mid in this situation , but force
* an eventual reconnect to clean out the pending_mid_q .
*/
if ( num_mids > 32768 )
server - > tcpStatus = CifsNeedReconnect ;
if ( ! collision ) {
mid = ( __u64 ) cur_mid ;
server - > CurrentMid = mid ;
break ;
}
cur_mid + + ;
}
spin_unlock ( & GlobalMid_Lock ) ;
return mid ;
}
2012-05-23 14:31:03 +04:00
/*
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 ;
}
2012-05-25 10:43:58 +04:00
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 ;
}
2012-05-27 20:48:35 +04:00
static void
cifs_qfs_tcon ( const unsigned int xid , struct cifs_tcon * tcon )
{
CIFSSMBQFSDeviceInfo ( xid , tcon ) ;
CIFSSMBQFSAttributeInfo ( xid , tcon ) ;
}
2012-05-25 14:40:22 +04:00
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 ;
}
2012-05-27 17:34:43 +04:00
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 ) ;
}
2012-05-27 20:39:52 +04:00
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 ;
}
2012-05-15 20:20:51 +04:00
struct smb_version_operations smb1_operations = {
2012-05-15 20:21:10 +04:00
. send_cancel = send_nt_cancel ,
2012-02-28 15:04:17 +04:00
. compare_fids = cifs_compare_fids ,
2012-05-17 12:18:21 +04:00
. setup_request = cifs_setup_request ,
. check_receive = cifs_check_receive ,
2012-05-17 17:53:29 +04:00
. add_credits = cifs_add_credits ,
. set_credits = cifs_set_credits ,
. get_credits_field = cifs_get_credits_field ,
2012-05-23 16:14:34 +04:00
. get_credits = cifs_get_credits ,
2012-05-23 14:01:59 +04:00
. get_next_mid = cifs_get_next_mid ,
2012-05-17 13:02:51 +04:00
. read_data_offset = cifs_read_data_offset ,
. read_data_length = cifs_read_data_length ,
. map_error = map_smb_to_linux_error ,
2012-05-17 13:25:35 +04:00
. find_mid = cifs_find_mid ,
. check_message = checkSMB ,
. dump_detail = cifs_dump_detail ,
. is_oplock_break = is_valid_oplock_break ,
2012-05-23 14:31:03 +04:00
. check_trans2 = cifs_check_trans2 ,
2012-05-25 10:43:58 +04:00
. need_neg = cifs_need_neg ,
. negotiate = cifs_negotiate ,
2012-05-25 10:54:49 +04:00
. sess_setup = CIFS_SessSetup ,
. logoff = CIFSSMBLogoff ,
2012-05-25 11:11:39 +04:00
. tree_connect = CIFSTCon ,
. tree_disconnect = CIFSSMBTDis ,
2012-05-27 20:21:53 +04:00
. get_dfs_refer = CIFSGetDFSRefer ,
2012-05-27 20:48:35 +04:00
. qfs_tcon = cifs_qfs_tcon ,
2012-05-25 14:40:22 +04:00
. is_path_accessible = cifs_is_path_accessible ,
2012-05-27 17:34:43 +04:00
. query_path_info = cifs_query_path_info ,
. get_srv_inum = cifs_get_srv_inum ,
2012-05-27 20:39:52 +04:00
. build_path_to_root = cifs_build_path_to_root ,
2012-05-15 20:20:51 +04:00
} ;
struct smb_version_values smb1_values = {
. version_string = SMB1_VERSION_STRING ,
2012-02-28 15:23:34 +04:00
. large_lock_type = LOCKING_ANDX_LARGE_FILES ,
. exclusive_lock_type = 0 ,
. shared_lock_type = LOCKING_ANDX_SHARED_LOCK ,
. unlock_lock_type = 0 ,
2012-05-17 12:45:31 +04:00
. header_size = sizeof ( struct smb_hdr ) ,
. max_header_size = MAX_CIFS_HDR_SIZE ,
2012-05-17 13:02:51 +04:00
. read_rsp_size = sizeof ( READ_RSP ) ,
2011-12-26 22:53:34 +04:00
. lock_cmd = cpu_to_le16 ( SMB_COM_LOCKING_ANDX ) ,
2012-05-15 20:20:51 +04:00
} ;