2005-11-11 07:45:38 +03:00
/*
Unix SMB / CIFS implementation .
SMB2 client transport context management functions
Copyright ( C ) Andrew Tridgell 2005
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
2007-07-10 06:07:03 +04:00
the Free Software Foundation ; either version 3 of the License , or
2005-11-11 07:45:38 +03:00
( at your option ) any later version .
This program 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 General Public License for more details .
You should have received a copy of the GNU General Public License
2007-07-10 06:07:03 +04:00
along with this program . If not , see < http : //www.gnu.org/licenses/>.
2005-11-11 07:45:38 +03:00
*/
# include "includes.h"
# include "libcli/raw/libcliraw.h"
2008-04-02 06:53:27 +04:00
# include "libcli/raw/raw_proto.h"
2005-11-11 07:45:38 +03:00
# include "libcli/smb2/smb2.h"
2006-01-03 20:27:33 +03:00
# include "libcli/smb2/smb2_calls.h"
2005-11-11 07:45:38 +03:00
# include "lib/socket/socket.h"
# include "lib/events/events.h"
# include "lib/stream/packet.h"
2006-08-30 15:29:34 +04:00
# include "lib/util/dlinklist.h"
2005-11-11 07:45:38 +03:00
/*
an event has happened on the socket
*/
static void smb2_transport_event_handler ( struct event_context * ev ,
struct fd_event * fde ,
uint16_t flags , void * private )
{
struct smb2_transport * transport = talloc_get_type ( private ,
struct smb2_transport ) ;
if ( flags & EVENT_FD_READ ) {
packet_recv ( transport - > packet ) ;
return ;
}
if ( flags & EVENT_FD_WRITE ) {
packet_queue_run ( transport - > packet ) ;
}
}
/*
destroy a transport
*/
2006-05-24 11:34:11 +04:00
static int transport_destructor ( struct smb2_transport * transport )
2005-11-11 07:45:38 +03:00
{
2006-07-13 21:37:45 +04:00
smb2_transport_dead ( transport , NT_STATUS_LOCAL_DISCONNECT ) ;
2005-11-11 07:45:38 +03:00
return 0 ;
}
/*
handle receive errors
*/
static void smb2_transport_error ( void * private , NTSTATUS status )
{
struct smb2_transport * transport = talloc_get_type ( private ,
struct smb2_transport ) ;
2006-07-13 21:37:45 +04:00
smb2_transport_dead ( transport , status ) ;
2005-11-11 07:45:38 +03:00
}
static NTSTATUS smb2_transport_finish_recv ( void * private , DATA_BLOB blob ) ;
/*
create a transport structure based on an established socket
*/
struct smb2_transport * smb2_transport_init ( struct smbcli_socket * sock ,
TALLOC_CTX * parent_ctx )
{
struct smb2_transport * transport ;
transport = talloc_zero ( parent_ctx , struct smb2_transport ) ;
if ( ! transport ) return NULL ;
transport - > socket = talloc_steal ( transport , sock ) ;
/* setup the stream -> packet parser */
transport - > packet = packet_init ( transport ) ;
if ( transport - > packet = = NULL ) {
talloc_free ( transport ) ;
return NULL ;
}
packet_set_private ( transport - > packet , transport ) ;
packet_set_socket ( transport - > packet , transport - > socket - > sock ) ;
packet_set_callback ( transport - > packet , smb2_transport_finish_recv ) ;
packet_set_full_request ( transport - > packet , packet_full_request_nbt ) ;
packet_set_error_handler ( transport - > packet , smb2_transport_error ) ;
packet_set_event_context ( transport - > packet , transport - > socket - > event . ctx ) ;
packet_set_nofree ( transport - > packet ) ;
/* take over event handling from the socket layer - it only
handles events up until we are connected */
talloc_free ( transport - > socket - > event . fde ) ;
transport - > socket - > event . fde = event_add_fd ( transport - > socket - > event . ctx ,
transport - > socket ,
socket_get_fd ( transport - > socket - > sock ) ,
EVENT_FD_READ ,
smb2_transport_event_handler ,
transport ) ;
2005-11-14 06:45:57 +03:00
packet_set_fde ( transport - > packet , transport - > socket - > event . fde ) ;
packet_set_serialise ( transport - > packet ) ;
2005-11-11 07:45:38 +03:00
talloc_set_destructor ( transport , transport_destructor ) ;
transport - > options . timeout = 30 ;
return transport ;
}
/*
mark the transport as dead
*/
2006-07-13 21:37:45 +04:00
void smb2_transport_dead ( struct smb2_transport * transport , NTSTATUS status )
2005-11-11 07:45:38 +03:00
{
smbcli_sock_dead ( transport - > socket ) ;
2006-07-13 21:37:45 +04:00
if ( NT_STATUS_EQUAL ( NT_STATUS_UNSUCCESSFUL , status ) ) {
status = NT_STATUS_UNEXPECTED_NETWORK_ERROR ;
}
2005-11-11 07:45:38 +03:00
/* kill all pending receives */
while ( transport - > pending_recv ) {
struct smb2_request * req = transport - > pending_recv ;
req - > state = SMB2_REQUEST_ERROR ;
2006-07-13 21:37:45 +04:00
req - > status = status ;
2005-11-11 07:45:38 +03:00
DLIST_REMOVE ( transport - > pending_recv , req ) ;
if ( req - > async . fn ) {
req - > async . fn ( req ) ;
}
}
}
2008-04-19 00:27:24 +04:00
static bool smb2_handle_oplock_break ( struct smb2_transport * transport ,
const DATA_BLOB * blob )
{
uint8_t * hdr ;
uint16_t opcode ;
uint64_t seqnum ;
hdr = blob - > data + NBT_HDR_SIZE ;
if ( blob - > length < ( SMB2_MIN_SIZE + 0x18 ) ) {
DEBUG ( 1 , ( " Discarding smb2 oplock reply of size %u \n " ,
blob - > length ) ) ;
return false ;
}
opcode = SVAL ( hdr , SMB2_HDR_OPCODE ) ;
seqnum = BVAL ( hdr , SMB2_HDR_MESSAGE_ID ) ;
if ( ( opcode ! = SMB2_OP_BREAK ) | |
( seqnum ! = UINT64_MAX ) ) {
return false ;
}
if ( transport - > oplock . handler ) {
uint8_t * body = hdr + SMB2_HDR_BODY ;
struct smb2_handle h ;
uint8_t level ;
level = CVAL ( body , 0x02 ) ;
smb2_pull_handle ( body + 0x08 , & h ) ;
transport - > oplock . handler ( transport , & h , level ,
transport - > oplock . private_data ) ;
}
return true ;
}
2005-11-11 07:45:38 +03:00
/*
we have a full request in our receive buffer - match it to a pending request
and process
*/
static NTSTATUS smb2_transport_finish_recv ( void * private , DATA_BLOB blob )
{
struct smb2_transport * transport = talloc_get_type ( private ,
struct smb2_transport ) ;
uint8_t * buffer , * hdr ;
int len ;
2005-11-11 11:00:51 +03:00
struct smb2_request * req = NULL ;
2005-11-11 07:45:38 +03:00
uint64_t seqnum ;
2006-07-17 13:36:52 +04:00
uint32_t flags ;
2005-11-17 03:48:24 +03:00
uint16_t buffer_code ;
uint32_t dynamic_size ;
2006-07-17 13:36:52 +04:00
uint32_t i ;
2005-11-11 07:45:38 +03:00
buffer = blob . data ;
len = blob . length ;
hdr = buffer + NBT_HDR_SIZE ;
if ( len < SMB2_MIN_SIZE ) {
DEBUG ( 1 , ( " Discarding smb2 reply of size %d \n " , len ) ) ;
goto error ;
}
2008-04-19 00:27:24 +04:00
if ( smb2_handle_oplock_break ( transport , & blob ) ) {
talloc_free ( buffer ) ;
return NT_STATUS_OK ;
}
2006-07-17 13:36:52 +04:00
flags = IVAL ( hdr , SMB2_HDR_FLAGS ) ;
2008-02-12 09:00:35 +03:00
seqnum = BVAL ( hdr , SMB2_HDR_MESSAGE_ID ) ;
2005-11-11 07:45:38 +03:00
/* match the incoming request against the list of pending requests */
for ( req = transport - > pending_recv ; req ; req = req - > next ) {
if ( req - > seqnum = = seqnum ) break ;
}
if ( ! req ) {
DEBUG ( 1 , ( " Discarding unmatched reply with seqnum 0x%llx op %d \n " ,
2005-11-30 05:08:15 +03:00
( long long ) seqnum , SVAL ( hdr , SMB2_HDR_OPCODE ) ) ) ;
2005-11-11 07:45:38 +03:00
goto error ;
}
/* fill in the 'in' portion of the matching request */
req - > in . buffer = buffer ;
talloc_steal ( req , buffer ) ;
req - > in . size = len ;
req - > in . allocated = req - > in . size ;
req - > in . hdr = hdr ;
req - > in . body = hdr + SMB2_HDR_BODY ;
req - > in . body_size = req - > in . size - ( SMB2_HDR_BODY + NBT_HDR_SIZE ) ;
req - > status = NT_STATUS ( IVAL ( hdr , SMB2_HDR_STATUS ) ) ;
2005-11-25 08:46:46 +03:00
if ( NT_STATUS_EQUAL ( req - > status , STATUS_PENDING ) ) {
2006-07-17 13:36:52 +04:00
if ( flags & 0x00000002 ) {
2007-10-07 02:28:14 +04:00
req - > cancel . can_cancel = true ;
2006-07-17 13:36:52 +04:00
req - > cancel . pending_id = IVAL ( hdr , SMB2_HDR_PID ) ;
for ( i = 0 ; i < req - > cancel . do_cancel ; i + + ) {
smb2_cancel ( req ) ;
}
}
2005-11-25 08:46:46 +03:00
talloc_free ( buffer ) ;
return NT_STATUS_OK ;
}
2005-11-17 03:48:24 +03:00
buffer_code = SVAL ( req - > in . body , 0 ) ;
2006-06-30 03:11:07 +04:00
req - > in . body_fixed = ( buffer_code & ~ 1 ) ;
2005-11-17 03:48:24 +03:00
req - > in . dynamic = NULL ;
2006-06-30 03:11:07 +04:00
dynamic_size = req - > in . body_size - req - > in . body_fixed ;
2005-11-17 03:48:24 +03:00
if ( dynamic_size ! = 0 & & ( buffer_code & 1 ) ) {
2006-06-30 03:11:07 +04:00
req - > in . dynamic = req - > in . body + req - > in . body_fixed ;
2005-11-17 03:48:24 +03:00
if ( smb2_oob ( & req - > in , req - > in . dynamic , dynamic_size ) ) {
DEBUG ( 1 , ( " SMB2 request invalid dynamic size 0x%x \n " ,
dynamic_size ) ) ;
goto error ;
}
}
2008-02-14 02:12:33 +03:00
smb2_setup_bufinfo ( req ) ;
2005-11-30 05:08:15 +03:00
DEBUG ( 2 , ( " SMB2 RECV seqnum=0x%llx \n " , ( long long ) req - > seqnum ) ) ;
2005-11-12 10:48:56 +03:00
dump_data ( 5 , req - > in . body , req - > in . body_size ) ;
2005-11-11 08:53:54 +03:00
2005-11-11 07:45:38 +03:00
/* if this request has an async handler then call that to
notify that the reply has been received . This might destroy
the request so it must happen last */
DLIST_REMOVE ( transport - > pending_recv , req ) ;
req - > state = SMB2_REQUEST_DONE ;
if ( req - > async . fn ) {
req - > async . fn ( req ) ;
}
return NT_STATUS_OK ;
error :
2005-11-12 10:48:56 +03:00
dump_data ( 5 , buffer , len ) ;
2005-11-11 07:45:38 +03:00
if ( req ) {
DLIST_REMOVE ( transport - > pending_recv , req ) ;
req - > state = SMB2_REQUEST_ERROR ;
2005-11-11 11:00:51 +03:00
if ( req - > async . fn ) {
req - > async . fn ( req ) ;
}
} else {
talloc_free ( buffer ) ;
2005-11-11 07:45:38 +03:00
}
return NT_STATUS_UNSUCCESSFUL ;
}
/*
handle timeouts of individual smb requests
*/
static void smb2_timeout_handler ( struct event_context * ev , struct timed_event * te ,
2005-11-11 11:00:51 +03:00
struct timeval t , void * private )
2005-11-11 07:45:38 +03:00
{
struct smb2_request * req = talloc_get_type ( private , struct smb2_request ) ;
if ( req - > state = = SMB2_REQUEST_RECV ) {
DLIST_REMOVE ( req - > transport - > pending_recv , req ) ;
}
req - > status = NT_STATUS_IO_TIMEOUT ;
req - > state = SMB2_REQUEST_ERROR ;
if ( req - > async . fn ) {
req - > async . fn ( req ) ;
}
}
/*
destroy a request
*/
2006-05-24 11:34:11 +04:00
static int smb2_request_destructor ( struct smb2_request * req )
2005-11-11 07:45:38 +03:00
{
if ( req - > state = = SMB2_REQUEST_RECV ) {
DLIST_REMOVE ( req - > transport - > pending_recv , req ) ;
}
return 0 ;
}
/*
put a request into the send queue
*/
void smb2_transport_send ( struct smb2_request * req )
{
DATA_BLOB blob ;
NTSTATUS status ;
2006-01-05 19:43:56 +03:00
_smb2_setlen ( req - > out . buffer , req - > out . size - NBT_HDR_SIZE ) ;
2005-11-11 07:45:38 +03:00
2005-11-30 05:08:15 +03:00
DEBUG ( 2 , ( " SMB2 send seqnum=0x%llx \n " , ( long long ) req - > seqnum ) ) ;
2005-11-12 10:48:56 +03:00
dump_data ( 5 , req - > out . body , req - > out . body_size ) ;
2005-11-11 10:23:45 +03:00
2005-11-11 07:45:38 +03:00
/* check if the transport is dead */
if ( req - > transport - > socket - > sock = = NULL ) {
req - > state = SMB2_REQUEST_ERROR ;
req - > status = NT_STATUS_NET_WRITE_FAULT ;
return ;
}
blob = data_blob_const ( req - > out . buffer , req - > out . size ) ;
status = packet_send ( req - > transport - > packet , blob ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
req - > state = SMB2_REQUEST_ERROR ;
req - > status = status ;
return ;
}
req - > state = SMB2_REQUEST_RECV ;
DLIST_ADD ( req - > transport - > pending_recv , req ) ;
/* add a timeout */
if ( req - > transport - > options . timeout ) {
event_add_timed ( req - > transport - > socket - > event . ctx , req ,
timeval_current_ofs ( req - > transport - > options . timeout , 0 ) ,
smb2_timeout_handler , req ) ;
}
talloc_set_destructor ( req , smb2_request_destructor ) ;
}
2006-07-17 11:45:23 +04:00
static void idle_handler ( struct event_context * ev ,
struct timed_event * te , struct timeval t , void * private )
{
struct smb2_transport * transport = talloc_get_type ( private ,
struct smb2_transport ) ;
struct timeval next = timeval_add ( & t , 0 , transport - > idle . period ) ;
transport - > socket - > event . te = event_add_timed ( transport - > socket - > event . ctx ,
transport ,
next ,
idle_handler , transport ) ;
transport - > idle . func ( transport , transport - > idle . private ) ;
}
/*
setup the idle handler for a transport
the period is in microseconds
*/
void smb2_transport_idle_handler ( struct smb2_transport * transport ,
void ( * idle_func ) ( struct smb2_transport * , void * ) ,
uint64_t period ,
void * private )
{
transport - > idle . func = idle_func ;
transport - > idle . private = private ;
transport - > idle . period = period ;
if ( transport - > socket - > event . te ! = NULL ) {
talloc_free ( transport - > socket - > event . te ) ;
}
transport - > socket - > event . te = event_add_timed ( transport - > socket - > event . ctx ,
transport ,
timeval_current_ofs ( 0 , period ) ,
idle_handler , transport ) ;
}