2011-12-26 22:53:34 +04:00
/*
* fs / cifs / smb2transport . c
*
* Copyright ( C ) International Business Machines Corp . , 2002 , 2011
* Etersoft , 2012
* Author ( s ) : Steve French ( sfrench @ us . ibm . com )
* Jeremy Allison ( jra @ samba . org ) 2006
* Pavel Shilovsky ( pshilovsky @ samba . org ) 2012
*
* This library is free software ; you can redistribute it and / or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation ; either version 2.1 of the License , or
* ( at your option ) any later version .
*
* 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 <linux/fs.h>
# include <linux/list.h>
# include <linux/wait.h>
# include <linux/net.h>
# include <linux/delay.h>
# include <linux/uaccess.h>
# include <asm/processor.h>
# include <linux/mempool.h>
2012-09-19 03:20:35 +04:00
# include <linux/highmem.h>
2011-12-26 22:53:34 +04:00
# include "smb2pdu.h"
# include "cifsglob.h"
# include "cifsproto.h"
# include "smb2proto.h"
# include "cifs_debug.h"
# include "smb2status.h"
2012-09-19 03:20:30 +04:00
# include "smb2glob.h"
2012-12-09 08:08:06 +04:00
int
2012-09-19 03:20:34 +04:00
smb2_calc_signature ( struct smb_rqst * rqst , struct TCP_Server_Info * server )
2012-09-19 03:20:30 +04:00
{
int i , rc ;
unsigned char smb2_signature [ SMB2_HMACSHA256_SIZE ] ;
unsigned char * sigptr = smb2_signature ;
2012-09-19 03:20:34 +04:00
struct kvec * iov = rqst - > rq_iov ;
int n_vec = rqst - > rq_nvec ;
2012-09-19 03:20:30 +04:00
struct smb2_hdr * smb2_pdu = ( struct smb2_hdr * ) iov [ 0 ] . iov_base ;
memset ( smb2_signature , 0x0 , SMB2_HMACSHA256_SIZE ) ;
memset ( smb2_pdu - > Signature , 0x0 , SMB2_SIGNATURE_SIZE ) ;
rc = crypto_shash_setkey ( server - > secmech . hmacsha256 ,
server - > session_key . response , SMB2_NTLMV2_SESSKEY_SIZE ) ;
if ( rc ) {
cERROR ( 1 , " %s: Could not update with response \n " , __func__ ) ;
return rc ;
}
rc = crypto_shash_init ( & server - > secmech . sdeschmacsha256 - > shash ) ;
if ( rc ) {
cERROR ( 1 , " %s: Could not init md5 \n " , __func__ ) ;
return rc ;
}
for ( i = 0 ; i < n_vec ; i + + ) {
if ( iov [ i ] . iov_len = = 0 )
continue ;
if ( iov [ i ] . iov_base = = NULL ) {
cERROR ( 1 , " null iovec entry " ) ;
return - EIO ;
}
/*
* The first entry includes a length field ( which does not get
* signed that occupies the first 4 bytes before the header ) .
*/
if ( i = = 0 ) {
if ( iov [ 0 ] . iov_len < = 8 ) /* cmd field at offset 9 */
break ; /* nothing to sign or corrupt header */
rc =
crypto_shash_update (
& server - > secmech . sdeschmacsha256 - > shash ,
iov [ i ] . iov_base + 4 , iov [ i ] . iov_len - 4 ) ;
} else {
rc =
crypto_shash_update (
& server - > secmech . sdeschmacsha256 - > shash ,
iov [ i ] . iov_base , iov [ i ] . iov_len ) ;
}
if ( rc ) {
cERROR ( 1 , " %s: Could not update with payload \n " ,
__func__ ) ;
return rc ;
}
}
2012-09-19 03:20:35 +04:00
/* now hash over the rq_pages array */
for ( i = 0 ; i < rqst - > rq_npages ; i + + ) {
struct kvec p_iov ;
cifs_rqst_page_to_kvec ( rqst , i , & p_iov ) ;
crypto_shash_update ( & server - > secmech . sdeschmacsha256 - > shash ,
p_iov . iov_base , p_iov . iov_len ) ;
kunmap ( rqst - > rq_pages [ i ] ) ;
}
2012-09-19 03:20:30 +04:00
rc = crypto_shash_final ( & server - > secmech . sdeschmacsha256 - > shash ,
sigptr ) ;
if ( rc )
cERROR ( 1 , " %s: Could not generate sha256 hash \n " , __func__ ) ;
memcpy ( smb2_pdu - > Signature , sigptr , SMB2_SIGNATURE_SIZE ) ;
return rc ;
}
2012-12-09 08:08:06 +04:00
int
smb3_calc_signature ( struct smb_rqst * rqst , struct TCP_Server_Info * server )
{
cFYI ( 1 , " smb3 signatures not supported yet " ) ;
return - EOPNOTSUPP ;
}
2012-09-19 03:20:30 +04:00
/* must be called with server->srv_mutex held */
static int
2012-09-19 03:20:34 +04:00
smb2_sign_rqst ( struct smb_rqst * rqst , struct TCP_Server_Info * server )
2012-09-19 03:20:30 +04:00
{
int rc = 0 ;
2012-09-19 03:20:34 +04:00
struct smb2_hdr * smb2_pdu = rqst - > rq_iov [ 0 ] . iov_base ;
2012-09-19 03:20:30 +04:00
if ( ! ( smb2_pdu - > Flags & SMB2_FLAGS_SIGNED ) | |
server - > tcpStatus = = CifsNeedNegotiate )
return rc ;
if ( ! server - > session_estab ) {
strncpy ( smb2_pdu - > Signature , " BSRSPYL " , 8 ) ;
return rc ;
}
2012-12-09 08:08:06 +04:00
rc = server - > ops - > calc_signature ( rqst , server ) ;
2012-09-19 03:20:30 +04:00
return rc ;
}
int
2012-09-19 03:20:34 +04:00
smb2_verify_signature ( struct smb_rqst * rqst , struct TCP_Server_Info * server )
2012-09-19 03:20:30 +04:00
{
unsigned int rc ;
char server_response_sig [ 16 ] ;
2012-09-19 03:20:34 +04:00
struct smb2_hdr * smb2_pdu = ( struct smb2_hdr * ) rqst - > rq_iov [ 0 ] . iov_base ;
2012-09-19 03:20:30 +04:00
if ( ( smb2_pdu - > Command = = SMB2_NEGOTIATE ) | |
( smb2_pdu - > Command = = SMB2_OPLOCK_BREAK ) | |
( ! server - > session_estab ) )
return 0 ;
/*
* BB what if signatures are supposed to be on for session but
* server does not send one ? BB
*/
/* Do not need to verify session setups with signature "BSRSPYL " */
if ( memcmp ( smb2_pdu - > Signature , " BSRSPYL " , 8 ) = = 0 )
cFYI ( 1 , " dummy signature received for smb command 0x%x " ,
smb2_pdu - > Command ) ;
/*
* Save off the origiginal signature so we can modify the smb and check
* our calculated signature against what the server sent .
*/
memcpy ( server_response_sig , smb2_pdu - > Signature , SMB2_SIGNATURE_SIZE ) ;
memset ( smb2_pdu - > Signature , 0 , SMB2_SIGNATURE_SIZE ) ;
mutex_lock ( & server - > srv_mutex ) ;
2012-12-09 08:08:06 +04:00
rc = server - > ops - > calc_signature ( rqst , server ) ;
2012-09-19 03:20:30 +04:00
mutex_unlock ( & server - > srv_mutex ) ;
if ( rc )
return rc ;
if ( memcmp ( server_response_sig , smb2_pdu - > Signature ,
SMB2_SIGNATURE_SIZE ) )
return - EACCES ;
else
return 0 ;
}
2011-12-26 22:53:34 +04:00
/*
* Set message id for the request . Should be called after wait_for_free_request
* and when srv_mutex is held .
*/
static inline void
smb2_seq_num_into_buf ( struct TCP_Server_Info * server , struct smb2_hdr * hdr )
{
hdr - > MessageId = get_next_mid ( server ) ;
}
static struct mid_q_entry *
smb2_mid_entry_alloc ( const struct smb2_hdr * smb_buffer ,
struct TCP_Server_Info * server )
{
struct mid_q_entry * temp ;
if ( server = = NULL ) {
cERROR ( 1 , " Null TCP session in smb2_mid_entry_alloc " ) ;
return NULL ;
}
temp = mempool_alloc ( cifs_mid_poolp , GFP_NOFS ) ;
if ( temp = = NULL )
return temp ;
else {
memset ( temp , 0 , sizeof ( struct mid_q_entry ) ) ;
temp - > mid = smb_buffer - > MessageId ; /* always LE */
temp - > pid = current - > pid ;
temp - > command = smb_buffer - > Command ; /* Always LE */
temp - > when_alloc = jiffies ;
temp - > server = server ;
/*
* The default is for the mid to be synchronous , so the
* default callback just wakes up the current task .
*/
temp - > callback = cifs_wake_up_task ;
temp - > callback_data = current ;
}
atomic_inc ( & midCount ) ;
temp - > mid_state = MID_REQUEST_ALLOCATED ;
return temp ;
}
static int
smb2_get_mid_entry ( struct cifs_ses * ses , struct smb2_hdr * buf ,
struct mid_q_entry * * mid )
{
if ( ses - > server - > tcpStatus = = CifsExiting )
return - ENOENT ;
if ( ses - > server - > tcpStatus = = CifsNeedReconnect ) {
cFYI ( 1 , " tcp session dead - return to caller to retry " ) ;
return - EAGAIN ;
}
if ( ses - > status ! = CifsGood ) {
/* check if SMB2 session is bad because we are setting it up */
if ( ( buf - > Command ! = SMB2_SESSION_SETUP ) & &
( buf - > Command ! = SMB2_NEGOTIATE ) )
return - EAGAIN ;
/* else ok - we are setting up session */
}
* mid = smb2_mid_entry_alloc ( buf , ses - > server ) ;
if ( * mid = = NULL )
return - ENOMEM ;
spin_lock ( & GlobalMid_Lock ) ;
list_add_tail ( & ( * mid ) - > qhead , & ses - > server - > pending_mid_q ) ;
spin_unlock ( & GlobalMid_Lock ) ;
return 0 ;
}
int
smb2_check_receive ( struct mid_q_entry * mid , struct TCP_Server_Info * server ,
bool log_error )
{
unsigned int len = get_rfc1002_length ( mid - > resp_buf ) ;
2012-09-19 03:20:34 +04:00
struct kvec iov ;
struct smb_rqst rqst = { . rq_iov = & iov ,
. rq_nvec = 1 } ;
iov . iov_base = ( char * ) mid - > resp_buf ;
iov . iov_len = get_rfc1002_length ( mid - > resp_buf ) + 4 ;
2011-12-26 22:53:34 +04:00
dump_smb ( mid - > resp_buf , min_t ( u32 , 80 , len ) ) ;
/* convert the length into a more usable form */
2012-09-19 03:20:30 +04:00
if ( ( len > 24 ) & &
2011-12-26 22:53:34 +04:00
( server - > sec_mode & ( SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED ) ) ) {
2012-09-19 03:20:30 +04:00
int rc ;
2012-09-19 03:20:34 +04:00
rc = smb2_verify_signature ( & rqst , server ) ;
2012-09-19 03:20:30 +04:00
if ( rc )
cERROR ( 1 , " SMB signature verification returned error = "
" %d " , rc ) ;
}
2011-12-26 22:53:34 +04:00
return map_smb2_to_linux_error ( mid - > resp_buf , log_error ) ;
}
2012-09-19 03:20:35 +04:00
struct mid_q_entry *
smb2_setup_request ( struct cifs_ses * ses , struct smb_rqst * rqst )
2011-12-26 22:53:34 +04:00
{
int rc ;
2012-09-19 03:20:35 +04:00
struct smb2_hdr * hdr = ( struct smb2_hdr * ) rqst - > rq_iov [ 0 ] . iov_base ;
2011-12-26 22:53:34 +04:00
struct mid_q_entry * mid ;
smb2_seq_num_into_buf ( ses - > server , hdr ) ;
rc = smb2_get_mid_entry ( ses , hdr , & mid ) ;
if ( rc )
2012-09-19 03:20:35 +04:00
return ERR_PTR ( rc ) ;
rc = smb2_sign_rqst ( rqst , ses - > server ) ;
if ( rc ) {
2012-09-19 03:20:30 +04:00
cifs_delete_mid ( mid ) ;
2012-09-19 03:20:35 +04:00
return ERR_PTR ( rc ) ;
}
return mid ;
2011-12-26 22:53:34 +04:00
}
2012-09-19 03:20:35 +04:00
struct mid_q_entry *
smb2_setup_async_request ( struct TCP_Server_Info * server , struct smb_rqst * rqst )
2012-07-11 14:45:28 +04:00
{
2012-09-19 03:20:35 +04:00
int rc ;
struct smb2_hdr * hdr = ( struct smb2_hdr * ) rqst - > rq_iov [ 0 ] . iov_base ;
2012-07-11 14:45:28 +04:00
struct mid_q_entry * mid ;
smb2_seq_num_into_buf ( server , hdr ) ;
mid = smb2_mid_entry_alloc ( hdr , server ) ;
if ( mid = = NULL )
2012-09-19 03:20:35 +04:00
return ERR_PTR ( - ENOMEM ) ;
2012-07-11 14:45:28 +04:00
2012-09-19 03:20:35 +04:00
rc = smb2_sign_rqst ( rqst , server ) ;
2012-07-11 14:45:28 +04:00
if ( rc ) {
DeleteMidQEntry ( mid ) ;
2012-09-19 03:20:35 +04:00
return ERR_PTR ( rc ) ;
2012-09-19 03:20:30 +04:00
}
2012-09-19 03:20:35 +04:00
return mid ;
2012-07-11 14:45:28 +04:00
}