Files
amiga-smbfs/source_code/sock.c
T
obarthel 2f0c186ff6 Updated to version 2.11
The ACTION_SETDATE operation again changes both the file/directory creation date/time and the last modification date/time. Because some SMB servers will not return a valid modification date/time, the creation date/time will be substituted when directory entry information is converted into the appropriate AmigaDOS data structures. This is problematic if only the modification time/dated is updated by the ACTION_SETDATE operation because the change may appear not to "stick".

The SMB function which would be used to update both the modification and the creation time stamps did not take care to preserve the creation time stamp. This might have contributed to the ACTION_SETDATE operation not having a noticeable effect.

The debug code no longer uses FORMAT_DEF for converting AmigaDOS time stamp information into readable text, but uses FORMAT_DOS instead. This is an attempt to track down a hard to detect bug in the debug code which might be related to locale.library date conversion. Because the buffer size for the date and time information is limited, the locale settings might just produce too much text to fit into the buffers. But that's only a theory so far...

The functions which the debug code uses to prepare 64 bit integers and strings for display are now a bit more paranoid in watching the lengths of the resulting strings, reporting possible buffer overflows.

Went over all the debug output text format strings with a fine-toothed comb and actually found a few instances of the formatting parameters not being entirely correct.

The assert.c code makes a bit more of an effort to handle NULL strings for display.

The non-debug builds of smbfs now complain again if any of the debug parameters are being used.
2018-12-31 12:16:16 +01:00

1759 lines
42 KiB
C

/*
* :ts=4
*
* sock.c
*
* Copyright (C) 1995 by Paal-Kr. Engstad and Volker Lendecke
* Modified by Christian Starkjohann <cs -at- hal -dot- kph -dot- tuwien -dot- ac -dot- at>
* Modified for use with AmigaOS by Olaf Barthel <obarthel -at- gmx -dot- net>
*/
#include "smbfs.h"
/*****************************************************************************/
#include <smb/smb_fs.h>
#include <smb/smb.h>
#include <smb/smbno.h>
/*****************************************************************************/
/* Some message size calculations include the size of the NetBIOS session
* header, which may not be necessary. The "message size" in question is not
* the same as the underlying transport layer, which in this case is
* NetBIOS over TCP.
*/
#define NETBIOS_HEADER_SIZE 4
/*****************************************************************************/
#include "smb_abstraction.h"
#include "dump_smb.h"
#include "errors.h"
/*****************************************************************************/
/* Byte offsets into the packet buffer reference the following data
* (with the first four octets used by the NetBIOS session header):
*
* 0: WORD netbios_session[2] = NetBIOS session header
* 4: BYTE smb_idf[4] = contains 0xFF, 'SMB'
* 8: BYTE smb_com = command code
* 9: BYTE smb_rcls = error code class
* 10: BYTE smb_reh = reserved (contains AH if DOS INT-24 ERR)
* 11: WORD smb_err = error code
* 13: BYTE smb_reb = reserved
* 14: WORD smb_res[7] = reserved
* 28: WORD smb_tid = tree id #
* 30: WORD smb_pid = caller's process id #
* 32: WORD smb_uid = user id #
* 34: WORD smb_mid = mutiplex id #
* 36: BYTE smb_wct = count of parameter words
* 37: WORD smb_vwv[] = variable # words of params
* 39: WORD smb_bcc = # bytes of data following
* 41: BYTE smb_data[] = data bytes
*/
/*****************************************************************************/
/* Attempt to receive all the incoming data, even if recv() returns only
* parts of the total number of bytes requested. Returns the number of bytes
* read or, in case of error a negative number. A negative result is the
* value of -errno.
*/
static int
receive_all(int fd, void * _data, int len, int * error_ptr)
{
int num_bytes_received;
char * data = _data;
int result;
ASSERT( data != NULL || len == 0 );
ASSERT( error_ptr != NULL );
for(num_bytes_received = 0 ; num_bytes_received < len ; num_bytes_received += result)
{
result = recv(fd, &data[num_bytes_received], len - num_bytes_received, 0);
if(result == 0)
break;
if(result < 0)
{
(*error_ptr) = errno;
goto out;
}
}
result = num_bytes_received;
out:
return(result);
}
/*****************************************************************************/
/* Read the next NetBIOS frame and discard it. The primary purpose
* of this function is to receive and ignore NetBIOS "keep alive"
* messages.
*/
int
smb_discard_netbios_frames(struct smb_server *server, int sock_fd, int * error_ptr)
{
unsigned char netbios_session_buf[NETBIOS_HEADER_SIZE];
int netbios_session_payload_size;
int result;
ENTER();
ASSERT( server != NULL );
ASSERT( error_ptr != NULL );
/* Read the NetBIOS session header (rfc-1002, section 4.3.1) */
result = receive_all (sock_fd, netbios_session_buf, NETBIOS_HEADER_SIZE, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if (result < NETBIOS_HEADER_SIZE)
{
LOG (("expected %ld bytes, got %ld\n", NETBIOS_HEADER_SIZE, result));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
/* Check the session type. */
switch (netbios_session_buf[0])
{
/* 0x00 == session message */
case 0x00:
SHOWMSG("Got session message");
break;
/* 0x85 == session keepalive */
case 0x85:
SHOWMSG("Got session keepalive");
break;
/* 0x81 == session request */
/* 0x82 == positive session response */
/* 0x83 == negative session response */
/* 0x84 == retarget session response */
default:
/* The session setup may need to know about the
* NetBIOS session response, but for any command
* these message types are invalid.
*/
LOG(("Invalid session header type 0x%02lx\n", netbios_session_buf[0]));
(*error_ptr) = error_invalid_netbios_session;
result = -1;
goto out;
}
netbios_session_payload_size = (int)smb_len(netbios_session_buf);
SHOWVALUE(netbios_session_payload_size);
if(netbios_session_payload_size > 0)
{
/* The length in the NetBIOS header is the raw data length (17 bits) */
if (netbios_session_payload_size > server->transmit_buffer_allocation_size)
{
LOG(("Received length (%ld) > max_xmit (%ld)!\n", netbios_session_payload_size, server->transmit_buffer_allocation_size));
(*error_ptr) = error_message_exceeds_buffer_size;
result = -1;
goto out;
}
result = receive_all (sock_fd, server->transmit_buffer, netbios_session_payload_size, error_ptr);
if (result < 0)
{
LOG(("recv error = %ld\n", (*error_ptr)));
goto out;
}
}
out:
RETURN(result);
return(result);
}
/*****************************************************************************/
/* smb_receive_raw: The NetBIOS header is only stored if want_header != 0. */
static int
smb_receive_raw (
struct smb_server * server,
int command,
int sock_fd,
unsigned char * target,
int max_raw_length,
char * input_payload,
int input_payload_size,
int want_header,
int * error_ptr)
{
unsigned char netbios_session_buf[NETBIOS_HEADER_SIZE];
int netbios_session_payload_size = 0;
int len, result;
ASSERT( server != NULL );
ASSERT( error_ptr != NULL );
/* We need to read the NetBIOS session header before we can move
* on and read the SMB data. Because the NetBIOS session header
* may be a keepalive message or something else we can safely
* ignore, we will retry reading the header until we get to the
* point where it is safe to read the SMB data.
*/
while(TRUE)
{
server->rcls = 0;
server->err = 0;
/* Read the NetBIOS session header (rfc-1002, section 4.3.1) */
result = receive_all (sock_fd, netbios_session_buf, NETBIOS_HEADER_SIZE, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if (result < NETBIOS_HEADER_SIZE)
{
LOG (("expected %ld bytes, got %ld for the NetBIOS header\n", NETBIOS_HEADER_SIZE, result));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
netbios_session_payload_size = (int)smb_len(netbios_session_buf);
SHOWVALUE(netbios_session_payload_size);
#if defined(DUMP_SMB)
{
if(command != 0 && netbios_session_buf[0] != 0x00 && netbios_session_payload_size > 0)
{
unsigned char netbios_session_payload[256];
/* We only want to show what's in the first few
* bytes of a session packet. Since we only support
* two session packet types (session message and
* session keep alive) we will abort processing
* anyway so it doesn't matter if we ignore any
* data beyond the first 256 bytes.
*/
if(netbios_session_payload_size > (int)sizeof(netbios_session_payload))
netbios_session_payload_size = sizeof(netbios_session_payload);
result = receive_all (sock_fd, netbios_session_payload, netbios_session_payload_size, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if(result < netbios_session_payload_size)
{
LOG (("result (%ld) < %ld\n", result, netbios_session_payload_size));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
dump_netbios_header(__FILE__,__LINE__,netbios_session_buf,netbios_session_payload,netbios_session_payload_size);
}
else
{
dump_netbios_header(__FILE__,__LINE__,netbios_session_buf,NULL,0);
}
}
#endif /* defined(DUMP_SMB) */
/* Is this a session keepalive message? If so,
* read the next frame.
*/
if (netbios_session_buf[0] == 0x85)
{
LOG (("Got SESSION KEEP ALIVE\n"));
continue;
}
/* Is this a regular session message? This is what
* we came for.
*/
else if (netbios_session_buf[0] == 0x00)
{
break;
}
/* Check the session type again, looking for
* anything peculiar.
*/
switch (netbios_session_buf[0])
{
/* 0x00 == session message */
case 0x00:
/* This is what we came for. */
break;
/* 0x81 == session request */
/* 0x82 == positive session response */
/* 0x83 == negative session response */
/* 0x84 == retarget session response */
default:
/* The session setup may need to know about the
* NetBIOS session response, but for any command
* these message types are invalid.
*/
if(command != 0)
{
LOG (("Invalid session header type 0x%02lx\n", netbios_session_buf[0]));
(*error_ptr) = error_invalid_netbios_session;
result = -1;
goto out;
}
/* Ignore this message type. */
break;
}
break;
}
/* The length in the NetBIOS header is the raw data length (17 bits) */
len = netbios_session_payload_size;
if (len > max_raw_length)
{
LOG (("Received length (%ld) > max_xmit (%ld)!\n", len, max_raw_length));
(*error_ptr) = error_message_exceeds_buffer_size;
result = -1;
goto out;
}
/* Prepend the NetBIOS header to what is read? */
if (want_header)
{
ASSERT( target != NULL );
memcpy (target, netbios_session_buf, NETBIOS_HEADER_SIZE);
target += NETBIOS_HEADER_SIZE;
}
/* This is an optimization for the SMB_COM_READ and SMB_COM_READ_ANDX
* commands, which tries to avoid copying the received data twice. To
* this end we receive the command response up to the point at which
* the message header ends and the data returned by the server
* begins. Then we read the data, storing it directly in the
* receive buffer rather than in the packet buffer, from which
* it would otherwise have to be retrieved later.
*
* Note that this optimization may still not take effect because the
* amount of data expected to be received can be so small that it
* may make little sense to break up reception into two separate
* recv() calls.
*/
if(input_payload != NULL)
{
/* Receive SMB message header and payload separately? */
if(len > server->smb_read_threshold)
{
int num_bytes_received = 0;
LOG(("receiving SMB message and payload separately\n"));
LOG(("input_payload=0x%08lx, payload_size=%ld\n", input_payload, input_payload_size));
if(command == SMBreadX)
{
int data_length;
int data_offset;
/* We need to read the following data:
*
* 0: 32 bytes of SMB message header
* 32: 1 byte of word count
* 33: 1 byte of andxcommand
* 34: 1 byte of andxreserved
* 35: 2 bytes of andxoffset
* 37: 2 bytes of available
* 39: 2 bytes of datacompactionmode
* 41: 2 bytes of reserved
* 43: 2 bytes of datalength
* 45: 2 bytes of dataoffset
* 47: 10 bytes of reserved
* 57: 2 bytes of bytecount
*
* This adds up to 59 bytes.
*/
LOG(("SMBreadX: reading the first %ld bytes\n", 59));
ASSERT( target != NULL );
result = receive_all (sock_fd, target, 59, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
num_bytes_received += result;
ASSERT( num_bytes_received == 59 );
/* End of file reached? */
if(num_bytes_received < 59)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
data_offset = WVAL(target, 45);
SHOWVALUE(data_offset);
/* Skip the padding bytes, if any. */
if(data_offset > 59)
{
LOG (("skipping %ld padding bytes\n", data_offset - 59));
result = receive_all (sock_fd, target + 59, data_offset - 59, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
/* End of file reached? */
if(result < data_offset - 59)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
num_bytes_received += result;
}
data_length = WVAL(target, 43);
SHOWVALUE(data_length);
ASSERT( data_length <= input_payload_size );
result = receive_all (sock_fd, input_payload, data_length, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if(result < data_length)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
num_bytes_received += result;
/* This should never happen, but then we better make sure to
* read the entire message.
*/
if(num_bytes_received < len)
{
LOG(("reading the remaining %ld bytes; this should never happen\n", len - num_bytes_received ));
result = receive_all (sock_fd, &target[num_bytes_received], len - num_bytes_received, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if(result < len - num_bytes_received)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
}
}
else
{
int count_of_bytes_returned;
int count_of_bytes_to_read;
int buffer_format;
ASSERT( command == SMBread );
/* We need to read the following data:
*
* 0: 32 bytes of SMB message header
* 32: 1 byte of word count
* 33: 2 bytes of 'count of bytes returned' (1 word)
* 35: 8 bytes of reserved data (4 words)
* 43: 2 bytes of 'byte count' (1 word)
* 45: 1 byte of 'buffer format'
* 46: 2 bytes of 'count of bytes to read' (1 word).
*
* This adds up to 48 bytes.
*/
LOG(("SMBread: reading the first %ld bytes\n", 48));
ASSERT( target != NULL );
result = receive_all (sock_fd, target, 48, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
num_bytes_received += result;
ASSERT( num_bytes_received == 48 );
/* End of file reached? */
if(num_bytes_received < 48)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
/* So we read the header. Now we need to figure out if the
* data is in the expected format, and how many bytes are
* waiting to be read.
*/
/* The buffer format must be 1. */
buffer_format = BVAL(target, 45);
LOG(("buffer format = %ld, should be %ld\n", buffer_format, 1));
if(buffer_format != 1)
{
LOG(("buffer format %ld not supported\n", buffer_format));
(*error_ptr) = error_invalid_buffer_format;
result = -1;
goto out;
}
count_of_bytes_returned = WVAL(target, 33);
count_of_bytes_to_read = WVAL(target, 46);
/* That should never be more data than the read buffer may hold. */
ASSERT( count_of_bytes_to_read <= input_payload_size );
ASSERT( count_of_bytes_to_read <= count_of_bytes_returned );
ASSERT( count_of_bytes_returned <= input_payload_size );
LOG(("count of bytes to read = %ld, should be <= %ld\n", count_of_bytes_to_read, input_payload_size));
if(count_of_bytes_returned > input_payload_size)
{
LOG(("this is too much data\n"));
(*error_ptr) = error_message_exceeds_buffer_size;
result = -1;
goto out;
}
result = receive_all (sock_fd, input_payload, count_of_bytes_to_read, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if(result < count_of_bytes_to_read)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
num_bytes_received += result;
ASSERT( count_of_bytes_to_read == count_of_bytes_returned );
if(count_of_bytes_to_read < count_of_bytes_returned)
{
LOG(("fewer data available than should be delivered; setting the remainder (%ld bytes) to 0.\n", count_of_bytes_returned - count_of_bytes_to_read));
memset(&input_payload[count_of_bytes_to_read],0,count_of_bytes_returned - count_of_bytes_to_read);
}
/* This should never happen, but then we better make sure to
* read the entire message.
*/
if(num_bytes_received < len)
{
LOG(("reading the remaining %ld bytes; this should never happen\n", len - num_bytes_received ));
result = receive_all (sock_fd, &target[num_bytes_received], len - num_bytes_received, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if(result < len - num_bytes_received)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
}
#if defined(DUMP_SMB)
{
/* If want_header==0 then this is the data returned by SMB_COM_READ_RAW. */
dump_smb(__FILE__,__LINE__,!want_header,target,48,smb_packet_to_consumer,server->max_recv);
if(buffer_format == 1)
dump_smb(__FILE__,__LINE__,!want_header,input_payload,num_bytes_received - 48,smb_packet_to_consumer,server->max_recv);
if(num_bytes_received < len && result > 0)
dump_smb(__FILE__,__LINE__,!want_header,&target[num_bytes_received],result,smb_packet_to_consumer,server->max_recv);
}
#endif /* defined(DUMP_SMB) */
}
result = num_bytes_received;
}
/* No, read both as a single chunk through recv() and pick
* the SMB message header and its payload apart later. This
* is intended to improve small read operation performance
* for which two separate recv() operations may introduce
* additional delays in processing.
*/
else
{
LOG(("receiving SMB message and payload in one chunk\n"));
ASSERT( target != NULL );
result = receive_all (sock_fd, target, len, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if(result < len)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
#if defined(DUMP_SMB)
{
/* If want_header==0 then this is the data returned by SMB_COM_READ_RAW. */
dump_smb(__FILE__,__LINE__,!want_header,target,result,smb_packet_to_consumer,server->max_recv);
}
#endif /* defined(DUMP_SMB) */
if(command == SMBreadX)
{
int data_length;
int data_offset;
data_offset = WVAL(target, 45);
SHOWVALUE(data_offset);
data_length = WVAL(target, 43);
SHOWVALUE(data_length);
ASSERT( data_offset < len );
ASSERT( data_offset + data_length <= len );
ASSERT( data_length <= input_payload_size );
memcpy(input_payload, &target[data_offset], data_length);
}
else
{
int count_of_bytes_returned;
int count_of_bytes_to_read;
int buffer_format;
ASSERT( command == SMBread );
/* The buffer format must be 1. */
buffer_format = BVAL(target, 45);
LOG(("buffer format = %ld, should be %ld\n", buffer_format, 1));
if(buffer_format != 1)
{
LOG(("buffer format %ld not supported\n", buffer_format));
(*error_ptr) = error_invalid_buffer_format;
result = -1;
goto out;
}
count_of_bytes_returned = WVAL(target, 33);
count_of_bytes_to_read = WVAL(target, 46);
/* That should never be more data than the read buffer may hold. */
ASSERT( count_of_bytes_to_read <= input_payload_size );
ASSERT( count_of_bytes_to_read <= count_of_bytes_returned );
ASSERT( count_of_bytes_returned <= input_payload_size );
LOG(("count of bytes to read = %ld, should be <= %ld\n", count_of_bytes_to_read, input_payload_size));
if(count_of_bytes_returned > input_payload_size)
{
LOG(("this is too much data\n"));
(*error_ptr) = error_message_exceeds_buffer_size;
result = -1;
goto out;
}
memcpy(input_payload, &target[48], count_of_bytes_to_read);
ASSERT( count_of_bytes_to_read == count_of_bytes_returned );
if(count_of_bytes_to_read < count_of_bytes_returned)
{
LOG(("fewer data available than should be delivered; setting the remainder (%ld bytes) to 0.\n", count_of_bytes_returned - count_of_bytes_to_read));
memset(&input_payload[count_of_bytes_to_read],0,count_of_bytes_returned - count_of_bytes_to_read);
}
}
}
}
else
{
ASSERT( target != NULL );
result = receive_all (sock_fd, target, len, error_ptr);
if (result < 0)
{
LOG (("recv error = %ld\n", (*error_ptr)));
goto out;
}
if(result < len)
{
/* End of file */
LOG (("EOF\n"));
(*error_ptr) = error_end_of_file;
result = -1;
goto out;
}
#if defined(DUMP_SMB)
{
/* If want_header==0 then this is the data returned by SMB_COM_READ_RAW. */
dump_smb(__FILE__,__LINE__,!want_header,target,result,smb_packet_to_consumer,server->max_recv);
}
#endif /* defined(DUMP_SMB) */
}
out:
return result;
}
static int
smb_receive (
struct smb_server *server,
int command,
int sock_fd,
void * input_payload,
int payload_size,
int * error_ptr)
{
byte * packet = server->transmit_buffer;
int result;
ASSERT( error_ptr != NULL );
ASSERT( server->max_recv <= server->transmit_buffer_allocation_size );
result = smb_receive_raw (
server,
command,
sock_fd,
packet,
/* Note: Worst case, if the inbound data has to go into the buffer, with
* no separate payload buffer provided. This is a "workaround" for
* SMB_COM_READ_ANDX which can end up receiving the regular
* SMB message header plus 65535 bytes of payload data, which
* easily breaks the limit set by server->max_recv (which only
* accounts for the in-band reception limit, not the out-of-band
* limit if the "large readx" capability is in effect).
* ZZZ this merits another check in smb_receive_raw().
*/
server->transmit_buffer_allocation_size,
input_payload, payload_size,
/* We want the NetBIOS session service header */
TRUE,
error_ptr);
if (result < 0)
{
LOG (("receive error: %ld\n", (*error_ptr)));
goto out;
}
/* The caller is responsible for dealing with the error
* information.
*/
server->rcls = BVAL (packet, 9);
server->err = WVAL (packet, 11);
out:
return result;
}
/* smb_receive's preconditions also apply here. */
static int
smb_receive_trans2 (
struct smb_server * server,
int command,
int sock_fd,
int * data_len_ptr,
int * param_len_ptr,
char ** data_ptr,
char ** param_ptr,
int * error_ptr)
{
unsigned char *inbuf = server->transmit_buffer;
int parameter_displacement;
int parameter_count;
int parameter_offset;
int data_displacement;
int data_count;
int data_offset;
int total_parameter_count;
int total_data_count;
int data_len;
int param_len;
char * data = NULL;
char * param = NULL;
int total_data;
int total_param;
int result;
ASSERT( error_ptr != NULL );
/* Careful: any of the "pass by reference" parameters may be NULL. */
if(data_len_ptr != NULL)
(*data_len_ptr) = 0;
if(param_len_ptr != NULL)
(*param_len_ptr) = 0;
if(param_ptr != NULL)
(*param_ptr) = NULL;
if(data_ptr != NULL)
(*data_ptr) = NULL;
result = smb_receive (server, command, sock_fd, NULL, 0, error_ptr);
if (result < 0)
goto out;
/* Is an error condition set? The caller is responsible
* for dealing with the error.
*/
if (server->rcls != 0)
goto out;
/* parse out the lengths */
total_data = WVAL (inbuf, smb_tdrcnt);
total_param = WVAL (inbuf, smb_tprcnt);
if ((total_data > server->max_recv) || (total_param > server->max_recv))
{
LOG (("data/param too long\n"));
(*error_ptr) = error_data_exceeds_buffer_size;
result = -1;
goto out;
}
/* Allocate it, but only if there is something to allocate
* in the first place.
*/
if(total_data > 0)
{
data = malloc (total_data);
if (data == NULL)
{
LOG (("could not alloc data area\n"));
(*error_ptr) = ENOMEM;
result = -1;
goto out;
}
}
/* Allocate it, but only if there is something to allocate
* in the first place.
*/
if(total_param > 0)
{
param = malloc(total_param);
if (param == NULL)
{
LOG (("could not alloc param area\n"));
(*error_ptr) = ENOMEM;
result = -1;
goto out;
}
}
LOG (("total_data/total_param: %ld/%ld\n", total_data, total_param));
param_len = 0;
data_len = 0;
while (TRUE)
{
parameter_displacement = WVAL (inbuf, smb_prdisp);
parameter_count = WVAL (inbuf, smb_prcnt);
parameter_offset = WVAL (inbuf, smb_proff);
data_displacement = WVAL (inbuf, smb_drdisp);
data_count = WVAL (inbuf, smb_drcnt);
data_offset = WVAL (inbuf, smb_droff);
total_parameter_count = WVAL (inbuf, smb_tprcnt);
total_data_count = WVAL (inbuf, smb_tdrcnt);
if (parameter_displacement + parameter_count > total_param)
{
LOG (("invalid parameters\n"));
(*error_ptr) = error_invalid_parameter_size;
result = -1;
goto out;
}
if(param != NULL)
memcpy (param + parameter_displacement, smb_base (inbuf) + parameter_offset, parameter_count);
param_len += parameter_count;
if (data_displacement + data_count > total_data)
{
LOG (("invalid data block\n"));
(*error_ptr) = error_invalid_parameter_size;
result = -1;
goto out;
}
if(data != NULL)
memcpy (data + data_displacement, smb_base (inbuf) + data_offset, data_count);
data_len += data_count;
LOG (("data count/parameter count: %ld/%ld\n", data_count, parameter_count));
/* parse out the total lengths again - they can shrink! */
if (total_data_count > total_data || total_parameter_count > total_param)
{
LOG (("data/params grew!\n"));
(*error_ptr) = error_data_exceeds_buffer_size;
result = -1;
goto out;
}
total_data = total_data_count;
total_param = total_parameter_count;
if (total_data <= data_len && total_param <= param_len)
break;
result = smb_receive (server, command, sock_fd, NULL, 0, error_ptr);
if (result < 0)
goto out;
if (server->rcls != 0)
{
(*error_ptr) = error_check_smb_error;
result = -1;
goto out;
}
}
if(param_ptr != NULL && param_len_ptr != NULL)
{
(*param_ptr) = param;
(*param_len_ptr) = param_len;
param = NULL;
}
if(data_ptr != NULL && data_len_ptr != NULL)
{
(*data_ptr) = data;
(*data_len_ptr) = data_len;
data = NULL;
}
result = 0;
out:
if(param != NULL)
free (param);
if(data != NULL)
free(data);
return result;
}
void
smb_release (struct smb_server *server)
{
if (server->mount_data.fd >= 0)
{
CloseSocket (server->mount_data.fd);
server->mount_data.fd = -1;
}
server->state = CONN_INVALID;
}
int
smb_connect (struct smb_server *server, int * error_ptr)
{
int enabled = TRUE;
int result;
ASSERT( server != NULL );
ASSERT( error_ptr != NULL );
if(server->mount_data.fd < 0)
{
LOG(("network socket needs to be opened\n"));
result = socket (AF_INET, SOCK_STREAM, 0);
if (result < 0)
{
server->state = CONN_INVALID;
(*error_ptr) = errno;
goto out;
}
server->mount_data.fd = result;
}
LOG(("connecting to server %s:%ld with socket %ld\n",
Inet_NtoA(server->mount_data.addr.sin_addr.s_addr),
ntohs(server->mount_data.addr.sin_port),
server->mount_data.fd));
/* Wait a certain time period for the connection attempt to succeed? */
if(server->timeout > 0)
{
int non_blocking_io;
struct timeval tv;
fd_set write_fds;
/* Switch the socket into non-blocking mode, so that we
* may start the connection attempt and wait for it to
* either succeed or fail.
*/
non_blocking_io = TRUE;
result = IoctlSocket(server->mount_data.fd, FIONBIO, &non_blocking_io);
if(result < 0)
{
server->state = CONN_INVALID;
(*error_ptr) = errno;
goto out;
}
FD_ZERO(&write_fds);
FD_SET(server->mount_data.fd,&write_fds);
memset(&tv,0,sizeof(tv));
tv.tv_secs = server->timeout;
/* Try to establish the connection and don't hang around until
* it either succeeds or fails.
*/
connect (server->mount_data.fd, (struct sockaddr *)&server->mount_data.addr, sizeof(struct sockaddr_in));
LOG(("will wait for up to %ld seconds for connection attempt to succeed\n",server->timeout));
/* Wait for the connection status to change (success/failure), or until
* the timeout has elapsed.
*/
result = WaitSelect(server->mount_data.fd+1, NULL, &write_fds, NULL, &tv, NULL);
/* Connection status is known? */
if (result == 1)
{
socklen_t len;
int error;
error = 0;
len = sizeof(error);
/* Check if it failed or succeeded. */
if(getsockopt(server->mount_data.fd,SOL_SOCKET,SO_ERROR,&error,&len) == 0)
{
/* Connection established? */
if(error == 0)
{
result = 0;
}
/* Connection could not be made. */
else
{
LOG(("connection could not be established (error=%ld)\n",errno));
(*error_ptr) = errno;
result = -1;
}
}
/* Well, that could happen, too. */
else
{
LOG(("connection could not be established (error=%ld)\n",errno));
(*error_ptr) = errno;
result = -1;
}
}
/* Connection attempt timed out? */
else if (result == 0)
{
LOG(("connection could not be established (timeout)\n"));
(*error_ptr) = EWOULDBLOCK;
result = -1;
}
/* Well, that could happen, too. */
else /* if (result < 0) */
{
LOG(("connection could not be established (error=%ld)\n",errno));
(*error_ptr) = errno;
}
/* Switch the socket back into blocking mode. */
non_blocking_io = FALSE;
IoctlSocket(server->mount_data.fd, FIONBIO, &non_blocking_io);
if(result < 0)
{
server->state = CONN_INVALID;
LOG(("connection is invalid.\n"));
goto out;
}
}
/* Wait almost indefinitely for the connection to
* be made.
*/
else
{
result = connect (server->mount_data.fd, (struct sockaddr *)&server->mount_data.addr, sizeof(struct sockaddr_in));
if(result < 0)
{
LOG(("connect() has failed (errno=%ld)\n",errno));
server->state = CONN_INVALID;
(*error_ptr) = errno;
goto out;
}
}
/* Enable socket keepalives, for good measure. */
setsockopt(server->mount_data.fd, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled));
/* Disable the Nagle algorithm for send() operations, causing the
* data to be sent as soon as possible, rather than being subjected
* to traffic control/smoothing?
*/
if(server->tcp_no_delay)
setsockopt(server->mount_data.fd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof(enabled));
/* Request specific socket receive/transmit buffer sizes? Note that
* this is a request, not a figure which the TCP/IP stack has to
* honour.
*/
if(server->socket_receive_buffer_size > 0)
setsockopt(server->mount_data.fd, SOL_SOCKET, SO_RCVBUF, &server->socket_receive_buffer_size, sizeof(server->socket_receive_buffer_size));
if(server->socket_send_buffer_size > 0)
setsockopt(server->mount_data.fd, SOL_SOCKET, SO_SNDBUF, &server->socket_send_buffer_size, sizeof(server->socket_send_buffer_size));
/* Configure the send/receive timeout (in seconds)? */
if(server->timeout > 0)
{
struct timeval tv;
memset(&tv,0,sizeof(tv));
tv.tv_secs = server->timeout;
LOG(("server timeout = %ld seconds\n", server->timeout));
setsockopt(server->mount_data.fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv));
setsockopt(server->mount_data.fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));
}
else
{
LOG(("no server read/write/connect timeout was set\n"));
}
out:
return(result);
}
/* If there was a network error, or data was left unread,
* the best option is to the close server connection and
* reopen it again.
*
* Here's where we decide whether closing the connection
* makes sense, and then close it if necessary.
*/
void
smb_check_server_connection(struct smb_server *server, int error)
{
int close_connection;
switch(error)
{
case error_end_of_file:
case error_invalid_netbios_session:
case error_message_exceeds_buffer_size:
case error_invalid_buffer_format:
case error_data_exceeds_buffer_size:
case error_invalid_parameter_size:
case error_server_setup_incomplete:
case error_server_connection_invalid:
case error_smb_message_signature_missing:
case error_smb_message_too_short:
case error_smb_message_invalid_command:
case error_smb_message_invalid_word_count:
case error_smb_message_invalid_byte_count:
close_connection = TRUE;
break;
default:
close_connection = (error < error_end_of_file);
break;
}
if(close_connection)
{
/* Stop means stop: EINTR is equivalent to Ctrl+C */
if(error == EINTR)
server->dont_retry = TRUE;
smb_invalidate_all_inodes (server);
SHOWMSG("closing the server connection.");
smb_release(server);
server->state = CONN_INVALID;
}
}
/* Returns number of bytes received (>= 0) or a negative value in
* case of error.
*/
int
smb_request (
struct smb_server *server,
int command,
void * input_payload,
const void * output_payload,
int payload_size,
int * error_ptr)
{
unsigned char *buffer = server->transmit_buffer;
int sock_fd = server->mount_data.fd;
int len, result;
ASSERT( error_ptr != NULL );
if ((sock_fd < 0) || (buffer == NULL))
{
LOG (("Bad server!\n"));
(*error_ptr) = error_server_setup_incomplete;
result = -1;
goto out;
}
if (server->state != CONN_VALID)
{
LOG (("Connection state is invalid\n"));
(*error_ptr) = error_server_connection_invalid;
result = -1;
goto out;
}
/* Length includes the NetBIOS session header (4 bytes), which
* is prepended to the packet to be sent.
*/
len = NETBIOS_HEADER_SIZE + smb_len (buffer);
/* If there is a separate payload, only send the header
* here and take care of the payload later.
*/
if(output_payload != NULL && payload_size > 0)
{
ASSERT( payload_size < smb_len(buffer) );
ASSERT( len > payload_size );
/* Send SMB message header and payload separately? */
if(len > server->smb_write_threshold)
{
LOG(("sending SMB message and payload separately\n"));
len -= payload_size;
}
/* No, combine both into a single chunk which will be
* transmitted with a single send(). This is intended
* to improve small write operation performance for
* which two separate send() operations may not succeed
* in nudging the TCP/IP stack to transmit the data just
* yet (Nagle algorithm, etc.).
*/
else
{
LOG(("sending SMB message and payload in one chunk\n"));
memcpy(&buffer[len - payload_size], output_payload, payload_size);
output_payload = NULL;
payload_size = 0;
}
}
LOG (("len = %ld, cmd = 0x%lx, input_payload=0x%08lx, output_payload=0x%08lx, payload_size=%ld\n",
len,
buffer[8],
input_payload,
output_payload,
payload_size));
#if defined(DUMP_SMB)
dump_netbios_header(__FILE__,__LINE__,buffer,&buffer[NETBIOS_HEADER_SIZE],len);
dump_smb(__FILE__,__LINE__,0,buffer+NETBIOS_HEADER_SIZE,len-NETBIOS_HEADER_SIZE,smb_packet_from_consumer,server->max_recv);
#endif /* defined(DUMP_SMB) */
if(output_payload != NULL && payload_size > 0)
{
/* Use two send() calls for header and payload? */
if(!server->scatter_gather)
{
LOG(("using two send() calls\n"));
result = send (sock_fd, (void *) buffer, len, 0);
if (result < 0)
{
LOG(("send() for %ld bytes failed (errno=%ld)\n", len, errno));
(*error_ptr) = errno;
goto out;
}
result = send (sock_fd, (void *)output_payload, payload_size, 0);
if (result < 0)
{
LOG(("payload send() for %ld bytes failed (errno=%ld)\n", payload_size, errno));
(*error_ptr) = errno;
goto out;
}
}
/* No, use sendmsg() to transmit both header and payload
* in one single step.
*/
else
{
struct msghdr msg;
struct iovec iov[2];
LOG(("using sendmsg() for %ld+%ld = %ld bytes\n", len, payload_size, len+payload_size));
memset(&msg,0,sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 2;
iov[0].iov_base = buffer;
iov[0].iov_len = len;
iov[1].iov_base = (void *)output_payload;
iov[1].iov_len = payload_size;
result = sendmsg (sock_fd, &msg, 0);
if (result < 0)
{
LOG(("sendmsg() for %ld+%ld bytes failed (errno=%ld)\n", len, payload_size, errno));
(*error_ptr) = errno;
goto out;
}
}
}
else
{
result = send (sock_fd, (void *) buffer, len, 0);
if (result < 0)
{
LOG(("send() for %ld bytes failed (errno=%ld)\n", len, errno));
(*error_ptr) = errno;
goto out;
}
}
result = smb_receive (server, command, sock_fd, input_payload, payload_size, error_ptr);
out:
if (result < 0)
smb_check_server_connection(server,(*error_ptr));
LOG (("result = %ld\n", result));
return (result);
}
/* This is not really a trans2 request, we assume that you only have
* one packet to send.
*/
int
smb_trans2_request (
struct smb_server *server,
int command,
int *data_len,
int *param_len,
char **data,
char **param,
int * error_ptr)
{
unsigned char *buffer = server->transmit_buffer;
int sock_fd = server->mount_data.fd;
int len, result;
ASSERT( error_ptr != NULL );
if (server->state != CONN_VALID)
{
LOG (("Connection state is invalid\n"));
(*error_ptr) = error_server_connection_invalid;
result = -1;
goto out;
}
/* Length includes the NetBIOS session header (4 bytes), which
* is prepended to the packet to be sent.
*/
len = NETBIOS_HEADER_SIZE + smb_len (buffer);
LOG (("len = %ld cmd = 0x%02lx\n", len, buffer[8]));
#if defined(DUMP_SMB)
dump_netbios_header(__FILE__,__LINE__,buffer,NULL,0);
dump_smb(__FILE__,__LINE__,0,buffer+NETBIOS_HEADER_SIZE,len-NETBIOS_HEADER_SIZE,smb_packet_from_consumer,server->max_recv);
#endif /* defined(DUMP_SMB) */
result = send (sock_fd, (void *) buffer, len, 0);
if (result < 0)
{
LOG(("send() for %ld bytes failed (errno=%ld)\n", len, errno));
(*error_ptr) = errno;
goto out;
}
result = smb_receive_trans2 (server, command, sock_fd, data_len, param_len, data, param, error_ptr);
out:
if (result < 0)
smb_check_server_connection(server,(*error_ptr));
LOG (("result = %ld\n", result));
return result;
}
/* Perform the actual read operation for the SMBreadbraw command, for which
* the transmit buffer has already been set up, ready to be used. This
* function is called by smb_proc_read_raw().
*/
int
smb_request_read_raw (struct smb_server *server, unsigned char *target, int max_len, int * error_ptr)
{
unsigned char *buffer = server->transmit_buffer;
int sock_fd = server->mount_data.fd;
int len, result;
ASSERT( error_ptr != NULL );
if (server->state != CONN_VALID)
{
LOG (("Connection state is invalid\n"));
(*error_ptr) = error_server_connection_invalid;
result = -1;
goto out;
}
/* Length includes the NetBIOS session header (4 bytes), which
* is prepended to the packet to be sent.
*/
len = NETBIOS_HEADER_SIZE + smb_len (buffer);
LOG (("len = %ld cmd = 0x%02lx\n", len, buffer[8]));
LOG (("target=%lx, max_len=%ld\n", (unsigned int) target, max_len));
LOG (("buffer=%lx, sock=%lx\n", (unsigned int) buffer, (unsigned int) sock_fd));
#if defined(DUMP_SMB)
dump_netbios_header(__FILE__,__LINE__,buffer,NULL,0);
dump_smb(__FILE__,__LINE__,0,buffer+NETBIOS_HEADER_SIZE,len-NETBIOS_HEADER_SIZE,smb_packet_from_consumer,server->max_recv);
#endif /* defined(DUMP_SMB) */
/* Request that data should be read in raw mode. */
result = send (sock_fd, (void *) buffer, len, 0);
if (result < 0)
{
LOG(("send() for %ld bytes failed (errno=%ld)\n", len, errno));
(*error_ptr) = errno;
goto out;
}
/* Wait for the raw data to be sent by the server. */
result = smb_receive_raw (server, SMBreadbraw, sock_fd, target, max_len, NULL, 0, FALSE, error_ptr);
out:
if (result < 0)
smb_check_server_connection(server,(*error_ptr));
LOG (("result = %ld\n", result));
return result;
}
/* smb_request_write_raw assumes that the request SMBwriteBraw has been
* completed successfully, so that we can send the raw data now.
*/
int
smb_request_write_raw (struct smb_server *server, unsigned const char *source, int length, int * error_ptr)
{
byte nb_header[NETBIOS_HEADER_SIZE];
int sock_fd = server->mount_data.fd;
int result;
if (server->state != CONN_VALID)
{
LOG (("Connection state is invalid\n"));
(*error_ptr) = error_server_connection_invalid;
result = -1;
goto out;
}
ASSERT( length <= 65535 );
/* Prepare the NetBIOS header, which in this case is
* providing the length of the data to follow it.
*/
smb_encode_smb_length (nb_header, length);
#if defined(DUMP_SMB)
dump_netbios_header(__FILE__,__LINE__,nb_header,NULL,0);
dump_smb(__FILE__,__LINE__,0,source,length,smb_packet_from_consumer,server->max_recv);
#endif /* defined(DUMP_SMB) */
/* Use two send() calls to transmit header and data? */
if(!server->scatter_gather)
{
LOG(("using two send() calls\n"));
/* Send the NetBIOS header. */
result = send (sock_fd, nb_header, NETBIOS_HEADER_SIZE, 0);
if(result < 0)
{
LOG(("send() for %ld bytes failed (errno=%ld)\n", NETBIOS_HEADER_SIZE, errno));
(*error_ptr) = errno;
goto out;
}
/* Now send the data to be written. */
result = send (sock_fd, (void *)source, length, 0);
if(result < 0)
{
LOG(("send() for %ld bytes failed (errno=%ld)\n", length, errno));
(*error_ptr) = errno;
goto out;
}
}
/* No, use sendmsg() to transmit header and data in
* one single step.
*/
else
{
struct msghdr msg;
struct iovec iov[2];
LOG(("using sendmsg() for %ld+%ld = %ld bytes\n", NETBIOS_HEADER_SIZE, length, NETBIOS_HEADER_SIZE+length));
memset(&msg,0,sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 2;
iov[0].iov_base = nb_header;
iov[0].iov_len = NETBIOS_HEADER_SIZE;
iov[1].iov_base = (void *)source;
iov[1].iov_len = length;
result = sendmsg (sock_fd, &msg, 0);
if (result < 0)
{
LOG(("sendmsg() for %ld+%ld bytes failed (errno=%ld)\n", NETBIOS_HEADER_SIZE, length, errno));
(*error_ptr) = errno;
goto out;
}
}
/* Wait for the server to respond. */
if(!server->write_behind)
{
result = smb_receive (server, SMBwritebraw, sock_fd, NULL, 0, error_ptr);
if(result < 0)
goto out;
}
else
{
LOG(("not waiting for server to respond\n"));
}
result = length;
out:
if (result < 0)
smb_check_server_connection(server,(*error_ptr));
LOG (("result = %ld\n", result));
return result;
}