Files

5042 lines
125 KiB
C

/*
* :ts=4
*
* proc.c
*
* Copyright (C) 1995 by Paal-Kr. Engstad and Volker Lendecke
*
* 28/06/96 - Fixed long file name support (smb_proc_readdir_long) by Yuri Per
*
* Modified for big endian support by Christian Starkjohann.
* Modified for use with AmigaOS by Olaf Barthel <obarthel -at- gmx -dot- net>
*/
#include "smbfs.h"
#include "quad_math.h"
#include "errors.h"
/*****************************************************************************/
#include <smb/smb.h>
#include <smb/smbno.h>
#include <smb/smb_fs.h>
/*****************************************************************************/
#include "smbfs_rev.h"
/*****************************************************************************/
/* smbfs uses the following commands:
*
* <smbno.h> CIFS protocol documentation Number Protocol Status Purpose (-> Replacement)
* SMBclose SMB_COM_CLOSE 0x04 Core Protocol - close file
* SMBcreate SMB_COM_CREATE 0x03 Core Protocol deprecated create file (-> SMB_COM_NT_CREATE_ANDX)
* SMBdskattr SMB_COM_QUERY_INFORMATION_DISK 0x80 Core Protocol deprecated get disk attributes (-> SMB_COM_TRANSACTION2+TRANS2_QUERY_FS_INFORMATION)
* SMBgetatr SMB_COM_QUERY_INFORMATION 0x08 Core Protocol deprecated get file attributes (-> SMB_COM_TRANSACTION2+TRANS2_QUERY_PATH_INFORMATION)
* SMBgetattrE SMB_COM_QUERY_INFORMATION2 0x23 LAN Manager 1.0 deprecated get file attributes expanded (-> SMB_COM_TRANSACTION2+TRANS2_QUERY_PATH_INFORMATION)
* SMBlockingX SMB_COM_LOCKING_ANDX 0x24 LAN Manager 1.0 - lock/unlock byte ranges and X
* SMBlseek SMB_COM_SEEK 0x12 Core Protocol obsolete seek
* SMBmkdir SMB_COM_CREATE_DIRECTORY 0x00 Core Protocol deprecated create directory (-> SMB_COM_TRANSACTION2+TRANS2_CREATE_DIRECTORY)
* SMBmv SMB_COM_RENAME 0x07 Core Protocol - rename file
* SMBnegprot SMB_COM_NEGOTIATE 0x72 Core Protocol - negotiate protocol
* SMBopen SMB_COM_OPEN 0x02 Core Protocol deprecated open file (-> SMB_COM_NT_CREATE_ANDX)
* SMBread SMB_COM_READ 0x0A Core Protocol deprecated read from file (-> SMB_COM_READ_ANDX)
* SMBreadbraw SMB_COM_READ_RAW 0x1a Core Protocol deprecated read a block of data with no smb header (-> SMB_COM_READ_ANDX)
* SMBreadX SMB_COM_READ_ANDX 0x2E LAN Manager 1.0 - read and X
* SMBrmdir SMB_COM_DELETE_DIRECTORY 0x01 Core Protocol - delete directory
* SMBsearch SMB_COM_SEARCH 0x81 Core Protocol deprecated search directory (-> SMB_COM_TRANSACTION2+TRANS2_FIND_FIRST2)
* SMBsesssetupX SMB_COM_SESSION_SETUP_ANDX 0x73 LAN Manager 1.0 - Session Set Up & X (including User Logon)
* SMBsetatr SMB_COM_SET_INFORMATION 0x09 Core Protocol deprecated set file attributes (-> SMB_COM_TRANSACTION2+TRANS2_SET_PATH_INFORMATION)
* SMBsetattrE SMB_COM_SET_INFORMATION2 0x22 LAN Manager 1.0 deprecated set file attributes expanded (-> SMB_COM_TRANSACTION2+TRANS2_SET_PATH_INFORMATION)
* SMBtcon SMB_COM_TREE_CONNECT 0x70 Core Protocol deprecated tree connect (-> SMB_COM_TREE_CONNECT_ANDX)
* SMBtconX SMB_COM_TREE_CONNECT_ANDX 0x75 LAN Manager 1.0 - tree connect and X
* SMBtrans2 SMB_COM_TRANSACTION2 0x32 LAN Manager 1.2 - TRANS2 protocol set
* SMBunlink SMB_COM_DELETE 0x06 Core Protocol - delete file
* SMBwrite SMB_COM_WRITE 0x0B Core Protocol deprecated write to file (-> SMB_COM_WRITE_ANDX)
* SMBwritebraw SMB_COM_WRITE_RAW 0x1d LAN Manager 1.0 deprecated write a block of data with no smb header (-> SMB_COM_WRITE_ANDX)
* SMBwriteX SMB_COM_WRITE_ANDX 0x2F LAN Manager 1.0 - write and X
*/
/*****************************************************************************/
/* 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
*/
#define SMB_VWV(packet) (&(packet)[37])
#define SMB_CMD(packet) ((packet)[8])
#define SMB_WCT(packet) ((packet)[36])
#define SMB_BCC(packet) smb_bcc(packet)
#define SMB_BUF(packet) ((packet) + 37 + SMB_WCT(packet) * sizeof(word) + sizeof(word))
/*****************************************************************************/
#define SMB_DIRINFO_SIZE 43
#define SMB_RESUME_KEY_SIZE 21
/*****************************************************************************/
/* 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
/*****************************************************************************/
/* This is for testing if the message exchange still works if the
* server claims to accept less than 65535 bytes per message.
*/
/*#define OVERRIDE_SERVER_MAX_BUFFER_SIZE 16644*/
/*****************************************************************************/
static void smb_printerr(int class, int num);
static int smb_proc_reconnect (struct smb_server *server, int * error_ptr);
/*****************************************************************************/
/* error code stuff - put together by Merik Karman
* merik -at- blackadder -dot- dsh -dot- oz -dot- au
*/
typedef struct
{
const char * name;
int code;
const char * message;
} err_code_struct;
/* Dos Error Messages */
static const err_code_struct dos_msgs[] =
{
{"ERRbadfunc", 1, "Invalid function"},
{"ERRbadfile", 2, "File not found"},
{"ERRbadpath", 3, "Directory invalid"},
{"ERRnofids", 4, "No file descriptors available"},
{"ERRnoaccess", 5, "Access denied"},
{"ERRbadfid", 6, "Invalid file handle"},
{"ERRbadmcb", 7, "Memory control blocks destroyed"},
{"ERRnomem", 8, "Insufficient server memory to perform the requested function"},
{"ERRbadmem", 9, "Invalid memory block address"},
{"ERRbadenv", 10, "Invalid environment"},
{"ERRbadformat", 11, "Invalid format"},
{"ERRbadaccess", 12, "Invalid open mode"},
{"ERRbaddata", 13, "Invalid data"},
{"ERR", 14, "reserved"},
{"ERRbaddrive", 15, "Invalid drive specified"},
{"ERRremcd", 16, "A Delete Directory request attempted to remove the server's current directory"},
{"ERRdiffdevice", 17, "Not same device"},
{"ERRnofiles", 18, "A File Search command can find no more files matching the specified criteria"},
{"ERRbadshare", 32, "The sharing mode specified for an Open conflicts with existing FIDs on the file"},
{"ERRlock", 33, "A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process"},
{"ERRnosuchshare", 67, "Share name not found"},
{"ERRfilexists", 80, "The file named in a Create Directory, Make New File or Link request already exists"},
{"ERRpaused", 81, "The server is temporarily paused"},
{"ERRinvalidparam", 87, "One of the requested values is out of range"},
{"ERRtimeout", 88, "The requested operation on a named pipe or an I/O device has timed out"},
{"ERRnoresource", 89, "No resources currently available for this SMB request"},
{"ERRtoomanyuids", 90, "Too many UIDs active for this SMB connection"},
{"ERRbaduid", 91, "The UID supplied is not known to the session, or the user identified by the UID does not have sufficient privileges"},
{"ERROR_DIRECTORY_NOT_EMPTY", 145, "The directory is not empty"},
{"OBJECT_NAME_COLLISION", 183, "Object name collision"},
{"ERRinvalidname", 123, "Invalid name"},
{"ERRbadpipe", 230, "Pipe invalid"},
{"ERRpipebusy", 231, "All instances of the requested pipe are busy"},
{"ERRpipeclosing", 232, "Pipe close in progress"},
{"ERRnotconnected", 233, "No process on other end of pipe"},
{"ERRmoredata", 234, "There is more data to be returned"},
{"ERROR_EAS_DIDNT_FIT", 275, "Either there are no extended attributes, or the available extended attributes did not fit into the response"},
{"ERROR_EAS_NOT_SUPPORTED", 282, "The server file system does not support Extended Attributes"},
{NULL, -1, NULL}
};
/* Server Error Messages */
static const err_code_struct server_msgs[] =
{
{"ERRerror", 1, "Non-specific error code"},
{"ERRbadpw", 2, "Bad password - name/password pair in a Tree Connect or Session Setup are invalid"},
{"ERRbadtype", 3, "reserved"},
{"ERRaccess", 4, "The requester does not have the necessary access rights within the specified context for the requested function. The context is defined by the TID or the UID"},
{"ERRinvnid", 5, "The tree ID (TID) specified in a command was invalid"},
{"ERRinvnetname", 6, "Invalid network name in tree connect"},
{"ERRinvdevice", 7, "Invalid device - printer request made to non-printer connection or non-printer request made to printer connection"},
{"ERRqfull", 49, "Print queue full (files) -- returned by open print file"},
{"ERRqtoobig", 50, "Print queue full -- no space"},
{"ERRqeof", 51, "EOF on print queue dump"},
{"ERRinvpfid", 52, "Invalid print file FID"},
{"ERRsmbcmd", 64, "The server did not recognize the command received"},
{"ERRsrverror", 65, "The server encountered an internal error, e.g., system file unavailable"},
{"ERRfilespecs", 67, "The file handle (FID) and pathname parameters contained an invalid combination of values"},
{"ERRreserved", 68, "reserved"},
{"ERRbadpermits", 69, "The access permissions specified for a file or directory are not a valid combination. The server cannot set the requested attribute"},
{"ERRreserved", 70, "reserved"},
{"ERRsetattrmode", 71, "The attribute mode in the Set File Attribute request is invalid"},
{"ERRpaused", 81, "Server is paused"},
{"ERRmsgoff", 82, "Not receiving messages"},
{"ERRnoroom", 83, "No room to buffer message"},
{"ERRrmuns", 87, "Too many remote user names"},
{"ERRtimeout", 88, "Operation timed out"},
{"ERRnoresource", 89, "No resources currently available for request"},
{"ERRtoomanyuids", 90, "Too many UIDs active on this session"},
{"ERRbaduid", 91, "The UID is not known as a valid ID on this session"},
{"ERRusempx", 250, "Temp unable to support Raw, use MPX mode"},
{"ERRusestd", 251, "Temp unable to support Raw, use standard read/write"},
{"ERRcontmpx", 252, "Continue in MPX mode"},
{"ERRreserved", 253, "reserved"},
{"ERRbadPW", 254, "Invalid password"},
{"ERRaccountExpired", 2239, "User account on the target machine is disabled or has expired"},
{"ERRbadClient", 2240, "The client does not have permission to access this server"},
{"ERRbadLogonTime", 2241, "Access to the server is not permitted at this time"},
{"ERRpasswordExpired", 2242, "The user's password has expired"},
{"ERRnosupport", 0xFFFF, "Function not supported"},
{NULL, -1, NULL}
};
/* Hard Error Messages */
static const err_code_struct hard_msgs[] =
{
{"ERRnowrite", 19, "Attempt to write on write-protected diskette"},
{"ERRbadunit", 20, "Unknown unit"},
{"ERRnotready", 21, "Drive not ready"},
{"ERRbadcmd", 22, "Unknown command"},
{"ERRdata", 23, "A problem has occurred in the physical I/O"},
{"ERRbadreq", 24, "Bad request structure length"},
{"ERRseek", 25, "Seek error"},
{"ERRbadmedia", 26, "Unknown media type"},
{"ERRbadsector", 27, "Sector not found"},
{"ERRnopaper", 28, "Printer out of paper"},
{"ERRwrite", 29, "Write fault"},
{"ERRread", 30, "Read fault"},
{"ERRgeneral", 31, "General failure"},
{"ERRbadshare", 32, "A open conflicts with an existing open"},
{"ERRlock", 33, "A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process"},
{"ERRwrongdisk", 34, "The wrong disk was found in a drive"},
{"ERRFCBUnavail", 35, "No FCBs are available to process request"},
{"ERRsharebufexc", 36, "A sharing buffer has been exceeded"},
{"ERRdiskfull", 39, "The file system is full"},
{NULL, -1, NULL}
};
typedef struct
{
int code;
const char * class;
const err_code_struct * err_msgs;
} err_class_struct;
static const err_class_struct err_classes[] =
{
{ 0, "SUCCESS", NULL },
{ 0x01, "ERRDOS", dos_msgs },
{ 0x02, "ERRSRV", server_msgs },
{ 0x03, "ERRHRD", hard_msgs },
{ 0x04, "ERRXOS", NULL },
{ 0xE1, "ERRRMX1", NULL },
{ 0xE2, "ERRRMX2", NULL },
{ 0xE3, "ERRRMX3", NULL },
{ 0xFF, "ERRCMD", NULL },
{ -1, NULL, NULL }
};
/*****************************************************************************/
static time_t convert_long_date_to_time_t(const char * p);
static void convert_time_t_to_long_date(time_t t, QUAD * long_date);
/*****************************************************************************/
/* Copy a string in ISO-Latin-1 form (8 bits per character) into a
* buffer, converting it into a little-endian 16-bit Unicode version
* of the string. This works because the ISO-Latin-1 character sits
* within the ASCII/BMP Latin-1 Unicode range.
*
* This function creates a NUL-terminated UTF16-LE Unicode string and
* returns how many bytes were written to the buffer, including the
* null-termination.
*
* To be on the safe side (the path name length can be a 32-bit
* integer), the whole copying operation stops as soon as the
* output buffer is filled.
*/
static int
copy_latin1_to_utf16le(byte * to,int to_size, const byte * from,int len)
{
int num_bytes_written = 0;
int i;
/* We have to have enough room to NUL-terminate the
* resulting converted string.
*/
if(to_size >= 2)
{
/* That takes care of the NUL. */
to_size -= 2;
for(i = 0 ; i < len ; i++)
{
if(num_bytes_written + 2 > to_size)
break;
(*to++) = (*from++);
(*to++) = '\0';
num_bytes_written += 2;
}
/* And we terminate that string... */
(*to++) = '\0';
(*to) = '\0';
num_bytes_written += 2;
}
return(num_bytes_written);
}
/*****************************************************************************/
/* Copy a string in little-endian 16-bit Unicode into a buffer, converting
* it into a ISO-Latin-1 (8 bits per character) version of the string, if
* possible. Code points beyond the ASCII/BMP Latin-1 character set are
* replaced by the control character 0x80.
*
* Note that the length is given as the number of 16-bit Unicode
* characters, not the number of bytes required to store the string.
*
* This function creates a NUL-terminated ISO-Latin-1 string and
* returns how many bytes were written to the buffer, including the
* null-termination.
*
* To be on the safe side (the path name length can be a 32-bit
* integer), the whole copying operation stops as soon as the
* output buffer is filled.
*/
static int
copy_utf16le_to_latin1(byte * to,int to_size,const byte * from,int len)
{
int num_bytes_written = 0;
word c;
int i;
/* We have to have enough room to NUL-terminate the
* resulting converted string.
*/
if(to_size >= 1)
{
/* That takes care of the NUL. */
to_size -= 1;
for(i = 0 ; i < len ; i++, from += 2)
{
if(num_bytes_written + 1 > to_size)
break;
/* Pick up the next UTF-16 code point,
* and just hopefully it will map to
* exactly one ASCII/BMP Latin-1
* character. If not, we substitute it
* with an "unusable" character which
* will be detected later, resulting in
* the file/directory name to be
* dropped.
*/
c = ((word)from[1] << 8) | from[0];
if(c >= 256)
c = 0x80;
(*to++) = c;
num_bytes_written++;
}
/* And we terminate that string... */
(*to) = '\0';
num_bytes_written++;
}
return(num_bytes_written);
}
/*****************************************************************************/
/* Works just like strlen(), but stops as soon as it hits
* the boundaries of the buffer which contains the string.
*/
static size_t strnlen(const char * s,size_t max_size)
{
const char * start = s;
size_t result;
if(max_size > 0)
{
while((*s) != '\0' && max_size-- > 0)
s++;
}
result = (size_t)(s - start);
return(result);
}
/*****************************************************************************/
/* Works just like strnlen(), but operates on 16-bit characters. */
static size_t strnlen_utf16(const char * s,size_t max_size)
{
size_t result = 0;
while(max_size >= 2)
{
if((s[0] | s[1]) == 0)
break;
s += 2;
max_size -= 2;
result++;
}
return(result);
}
/*****************************************************************************/
/*****************************************************************************
*
* Encoding/Decoding section
*
*****************************************************************************/
static INLINE byte *
smb_encode_byte(byte * p, byte data)
{
(*p) = data;
return &p[1];
}
static INLINE byte *
smb_encode_word (byte * p, word data)
{
p[0] = data & 0x00ffU;
p[1] = (data & 0xff00U) >> 8;
return &p[2];
}
static INLINE byte *
smb_encode_dword (byte * p, dword data)
{
p[0] = data & 0xffU;
p[1] = (data & 0xff00U) >> 8;
p[2] = (data & 0xff0000U) >> 16;
p[3] = (data & 0xff000000U) >> 24;
return &p[4];
}
static byte *
smb_copy_data (byte * p, const byte * data, int len)
{
memmove (p, data, len);
return &p[len];
}
static INLINE byte *
smb_decode_word (const byte * p, word * data)
{
(*data) = ((word)p[0]) | (((word)p[1]) << 8);
return (byte *)&p[2];
}
static INLINE byte *
smb_decode_dword (const byte * p, dword * data)
{
(*data) = ((dword)p[0]) | (((dword)p[1]) << 8) | (((dword)p[2]) << 16) | (((dword)p[3]) << 24);
return (byte *)&p[4];
}
static INLINE byte *
smb_encode_smb_length (byte * p, int len)
{
/* 0x00 = NetBIOS session message */
p[0] = 0;
/* Length is a 17-bit integer, the most significant
* bit of which goes into bit #0. The other 7 bits
* are reserved.
*/
p[1] = (len >> 16) & 1;
/* Payload length in network byte order
* (least significant 16 bits).
*/
p[2] = (len & 0xFF00) >> 8;
p[3] = (len & 0xFF);
return &p[4];
}
static byte *
smb_encode_dialect (byte * p, const byte * name, int len)
{
(*p++) = 2;
memcpy(p, name, len);
p[len] = '\0';
return p + len + 1;
}
static byte *
smb_encode_ascii (byte * p, const byte * name, int len)
{
(*p++) = 4; /* A NUL-terminated string follows. */
memcpy (p, name, len);
p[len] = '\0';
return p + len + 1;
}
static void
smb_encode_vblock (byte * p, const byte * data, int len)
{
ASSERT( 0 <= len && len <= 65535 );
(*p++) = 5; /* a variable block is to follow. */
p = smb_encode_word (p, len);
memcpy (p, data, len);
}
static byte *
smb_name_mangle (byte * p, const byte * name)
{
int len, pad = 0;
len = strlen (name);
if (len < 16)
pad = 16 - len;
(*p++) = 2 * (len + pad);
while ((*name) != '\0')
{
(*p++) = ((*name) >> 4) + 'A';
(*p++) = ((*name) & 0x0F) + 'A';
name++;
}
while (pad-- > 0)
{
(*p++) = 'C';
(*p++) = 'A';
}
(*p++) = '\0';
return p;
}
/* According to the core protocol documentation times are
* expressed as seconds past January 1st, 1970, local
* time zone.
*/
static int
utc2local (int time_value)
{
int result;
if(time_value > 0)
result = time_value - get_time_zone_delta();
else
result = time_value;
return result;
}
static int
local2utc (int time_value)
{
int result;
if(time_value > 0)
result = time_value + get_time_zone_delta();
else
result = time_value;
return result;
}
/* Convert an MS-DOS time/date pair to a UNIX date (seconds since January 1st 1970). */
static int
date_dos2unix (unsigned short time_value, unsigned short date)
{
time_t seconds;
time_t utc_seconds;
struct tm tm;
memset(&tm,0,sizeof(tm));
tm.tm_sec = 2 * (time_value & 0x1F);
tm.tm_min = (time_value >> 5) & 0x3F;
tm.tm_hour = (time_value >> 11) & 0x1F;
tm.tm_mday = date & 0x1F;
tm.tm_mon = ((date >> 5) & 0xF) - 1;
tm.tm_year = ((date >> 9) & 0x7F) + 80;
seconds = tm_to_seconds(&tm);
utc_seconds = local2utc(seconds);
#if DEBUG
{
struct tm tm_utc;
struct tm tm_local;
seconds_to_tm(utc_seconds,&tm_utc);
D(("time = %ld-%02ld-%02ld %02ld:%02ld:%02ld (UTC)",
tm_utc.tm_year + 1900,
tm_utc.tm_mon+1,
tm_utc.tm_mday,
tm_utc.tm_hour,
tm_utc.tm_min,
tm_utc.tm_sec));
seconds_to_tm(seconds,&tm_local);
D((" %ld-%02ld-%02ld %02ld:%02ld:%02ld (local)",
tm_local.tm_year + 1900,
tm_local.tm_mon+1,
tm_local.tm_mday,
tm_local.tm_hour,
tm_local.tm_min,
tm_local.tm_sec));
}
#endif /* DEBUG */
return(utc_seconds);
}
/* Convert linear UNIX date to a MS-DOS time/date pair. */
static void
date_unix2dos (int utc_seconds, unsigned short *time_value, unsigned short *date)
{
time_t seconds;
struct tm tm;
seconds = utc2local(utc_seconds);
seconds_to_tm(seconds,&tm);
#if DEBUG
{
struct tm tm_utc;
struct tm tm_local;
seconds_to_tm(utc_seconds,&tm_utc);
D(("time = %ld-%02ld-%02ld %02ld:%02ld:%02ld (UTC)",
tm_utc.tm_year + 1900,
tm_utc.tm_mon+1,
tm_utc.tm_mday,
tm_utc.tm_hour,
tm_utc.tm_min,
tm_utc.tm_sec));
seconds_to_tm(seconds,&tm_local);
D((" %ld-%02ld-%02ld %02ld:%02ld:%02ld (local)",
tm_local.tm_year + 1900,
tm_local.tm_mon+1,
tm_local.tm_mday,
tm_local.tm_hour,
tm_local.tm_min,
tm_local.tm_sec));
}
#endif /* DEBUG */
(*time_value) = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec / 2);
(*date) = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday;
}
/****************************************************************************
*
* Support section.
*
****************************************************************************/
static INLINE int
smb_len (const byte * packet)
{
/* This returns the payload length stored in the NetBIOS session header. */
return (((int)(packet[1] & 0x1)) << 16) | (((int)packet[2]) << 8) | (packet[3]);
}
static INLINE word
smb_bcc (const byte * packet)
{
int pos = 37 + SMB_WCT (packet) * sizeof (word);
return (word)(packet[pos] | ((word)packet[pos + 1]) << 8);
}
/* smb_valid_packet: We check if packet fulfills the basic
* requirements of a smb packet
*/
static int
smb_valid_packet (const byte * packet)
{
int error;
if (memcmp(&packet[4],"\xffSMB",4) != SAME)
error = error_smb_message_signature_missing;
else if (smb_len(packet) < (int)(32 + 1 + SMB_WCT (packet) * sizeof(word) + sizeof(word) + SMB_BCC (packet)) )
error = error_smb_message_too_short;
else
error = 0;
return error;
}
/* smb_verify: We check if we got the answer we expected, and if we
* got enough data. If bcc == -1, we don't care.
*/
static int
smb_verify (const byte * packet, int command, int wct, int bcc)
{
int error;
if (SMB_CMD (packet) != command)
error = error_smb_message_invalid_command;
else if (SMB_WCT (packet) < wct)
error = error_smb_message_invalid_word_count;
else if (bcc != -1 && SMB_BCC (packet) < bcc)
error = error_smb_message_invalid_byte_count;
else
error = 0;
return(error);
}
void
smb_translate_error_class_and_code(int errcls,int error,char ** class_ptr,char ** code_ptr)
{
const err_class_struct * err_class = NULL;
const err_code_struct * err_code = NULL;
int i;
for (i = 0; err_classes[i].class; i++)
{
if (err_classes[i].code == errcls)
{
err_class = &err_classes[i];
if(err_class->err_msgs != NULL)
{
const err_code_struct * err = err_class->err_msgs;
int j;
for (j = 0; err[j].name; j++)
{
if(err[j].code == error)
{
err_code = &err[j];
break;
}
}
}
break;
}
}
if(err_class != NULL && err_code != NULL)
{
(*class_ptr) = (char *)err_class->class;
(*code_ptr) = (char *)err_code->message;
}
else
{
(*class_ptr) = "?";
(*code_ptr) = "?";
}
}
int
smb_errno (int errcls, int error)
{
int result,i;
if (errcls == ERRDOS)
{
static const int map[][2] =
{
{ ERRbadfunc, EINVAL },
{ ERRbadfile, ENOENT },
{ ERRbadpath, ENOENT },
{ ERRnofids, EMFILE },
{ ERRnoaccess, EPERM },
{ ERRbadfid, EBADF },
{ ERRbadmcb, EIO },
{ ERRnomem, ENOMEM },
{ ERRbadmem, EFAULT },
{ ERRbadenv, EIO },
{ ERRbadformat, EIO },
{ ERRbadaccess, EACCES },
{ ERRbaddata, E2BIG },
{ ERRbaddrive, ENXIO },
{ ERRremcd, EIO },
{ ERRdiffdevice, EXDEV },
{ ERRbadshare, ETXTBSY },
{ ERRlock, EDEADLK },
{ ERRfilexists, EEXIST },
{ ERRinvalidparam, EINVAL },
{ 145, ENOTEMPTY}, /* Directory is not empty; this is what Samba reports (2016-04-23) */
{ 183, EEXIST }, /* This next error seems to occur on a "mv" when the destination exists ("object name collision") */
{ -1, -1 }
};
result = EIO;
for(i = 0 ; map[i][0] != -1 ; i++)
{
if(map[i][0] == error)
{
result = map[i][1];
break;
}
}
}
else if (errcls == ERRSRV)
{
static const int map[][2] =
{
{ ERRerror, ENFILE },
{ ERRbadpw, EINVAL },
{ ERRbadtype, EIO },
{ ERRaccess, EACCES },
{ -1, -1 }
};
result = EIO;
for(i = 0 ; map[i][0] != -1 ; i++)
{
if(map[i][0] == error)
{
result = map[i][1];
break;
}
}
}
else if (errcls == ERRHRD)
{
static const int map[][2] =
{
{ ERRnowrite, EROFS },
{ ERRbadunit, ENODEV },
{ ERRnotready, EBUSY },
{ ERRbadcmd, EIO },
{ ERRdata, EIO },
{ ERRbadreq, ERANGE },
{ ERRbadshare, ETXTBSY },
{ ERRlock, EDEADLK },
{ ERRdiskfull, ENOSPC },
{ -1, -1 }
};
result = EIO;
for(i = 0 ; map[i][0] != -1 ; i++)
{
if(map[i][0] == error)
{
result = map[i][1];
break;
}
}
}
else if (errcls == ERRCMD)
{
result = EIO;
}
else
{
result = 0;
}
#if DEBUG
{
const err_class_struct * err_class = NULL;
const err_code_struct * err_code = NULL;
int i;
for (i = 0; err_classes[i].class; i++)
{
if (err_classes[i].code == errcls)
{
err_class = &err_classes[i];
if(err_class->err_msgs != NULL)
{
const err_code_struct * err = err_class->err_msgs;
int j;
for (j = 0; err[j].name; j++)
{
if(err[j].code == error)
{
err_code = &err[j];
break;
}
}
}
break;
}
}
if(err_class != NULL && err_code != NULL)
{
D(("translated error code %ld/%ld (%s/%s) to %ld (%s)",
errcls,error,err_class->class,err_code->message,result,strerror(result)));
}
else
{
D(("no proper translation for error code %ld/%ld to %ld (%s)",
errcls,error,result,strerror(result)));
}
}
#endif /* DEBUG */
return(result);
}
#if DEBUG
static char
print_char (char c)
{
if ((c < ' ') || (c > '~'))
return '.';
return c;
}
static void
smb_dump_packet (const byte * packet)
{
int i, j, len;
int errcls, error;
errcls = (int) packet[9];
error = (int) (int) (packet[11] | ((int)packet[12]) << 8);
D(("smb_len = %ld valid = %ld", smb_len (packet), smb_valid_packet (packet)));
D(("smb_cmd = %ld smb_wct = %ld smb_bcc = %ld", packet[8], SMB_WCT (packet), SMB_BCC (packet)));
D(("smb_rcls = %ld smb_err = %ld", errcls, error));
if (errcls)
smb_printerr (errcls, error);
len = smb_len (packet);
if (len > 100)
len = 100;
PRINTHEADER();
for (i = 0; i < len; i += 10)
{
PRINTF (("%03ld:", i));
for (j = i; j < i + 10; j++)
{
if (j < len)
PRINTF (("%02lx ", packet[j]));
else
PRINTF ((" "));
}
PRINTF ((": "));
for (j = i; j < i + 10; j++)
{
if (j < len)
PRINTF (("%lc", print_char (packet[j])));
}
PRINTF (("\n"));
}
}
#endif /* DEBUG */
/* smb_request_ok: We do the request and check the answer
* completely. Returns 0 for success and -1 for failure.
*/
static int
smb_request_ok_with_payload (
struct smb_server * server,
int command,
int wct,
int bcc,
void * input_payload,
const void * output_payload,
int payload_size,
int * error_ptr)
{
int result;
int error;
server->rcls = 0;
server->err = 0;
/* Send the message and wait for the response to arrive. */
result = smb_request (server, command, input_payload, output_payload, payload_size, error_ptr);
/* smb_request() failed? */
if (result < 0)
{
SHOWMSG("smb_request failed");
}
/* The message received is inconsistent? */
else if ((error = smb_valid_packet (server->transmit_buffer)) != 0)
{
SHOWMSG("not a valid packet!");
(*error_ptr) = error;
result = -1;
}
/* An error condition was flagged? */
else if (server->rcls != 0)
{
smb_printerr (server->rcls, server->err);
(*error_ptr) = error_check_smb_error;
result = -1;
}
/* The message received does not contain the expected
* number of parameters and/or payload? A bcc value of
* -1 means that payload size should be ignored.
*/
else if ((error = smb_verify (server->transmit_buffer, command, wct, bcc)) != 0)
{
SHOWMSG("smb_verify failed");
(*error_ptr) = error;
result = -1;
}
else
{
D(("smb_request() returned %ld bytes", result));
/* Return 0 for success, rather than the number of
* bytes received.
*/
result = 0;
}
if(result < 0)
smb_check_server_connection(server, (*error_ptr));
return(result);
}
/* Same thing as smb_request_ok(), but without this payload
* business...
*/
static int
smb_request_ok (struct smb_server *s, int command, int wct, int bcc, int * error_ptr)
{
return(smb_request_ok_with_payload (s, command, wct, bcc, NULL, NULL, 0, error_ptr));
}
/* Try to reopen a file after the server has dropped the connection, or
* was disconnected.
*/
static int
reopen_entry(struct smb_server *server, struct smb_dirent *entry,int * error_ptr)
{
int result;
ASSERT( server != NULL && entry != NULL );
if(!entry->opened)
{
int ignored_error;
D(("trying to reopen file '%s'", escape_name(entry->complete_path)));
result = smb_proc_open (server, entry->complete_path, entry->len, entry->writable, FALSE, entry, error_ptr != NULL ? error_ptr : &ignored_error);
}
else
{
result = 0;
}
return(result);
}
/* smb_retry: This function should be called when smb_request_ok has
* indicated an error. If the error was indicated because the
* connection was killed, we try to reconnect. If smb_retry returns FALSE,
* the error was indicated for another reason, so a retry would not be
* of any use.
*/
static int
smb_retry (struct smb_server *server)
{
int success = FALSE;
int ignored_error;
if (server->state == CONN_VALID)
goto out;
smb_release (server);
/* Stop means stop. */
if(server->dont_retry)
goto out;
if (smb_proc_reconnect (server, &ignored_error) < 0)
{
SHOWMSG("smb_proc_reconnect failed");
server->state = CONN_RETRIED;
goto out;
}
server->state = CONN_VALID;
success = TRUE;
out:
return success;
}
/* smb_setup_header: We completely set up the packet. You only have to
* insert the command-specific fields.
*
* Returns a pointer to the SMB_Data part of the packet, which
* is exactly bcc bytes in size.
*/
static byte *
smb_setup_header (struct smb_server *server, byte command, int wct, int bcc)
{
/* The SMB frame size comes together as follows:
*
* 32 bytes for the SMB header
* 1 byte for the parameter count
* x words (2 bytes each) for the parameters
* 1 word (2 bytes) for the number of bytes to transmit (payload)
* x bytes to transmit (payload)
*/
const int smb_frame_size = 32 + 1 + wct * sizeof (word) + sizeof (word) + bcc;
byte *p = server->transmit_buffer;
byte *buf = server->transmit_buffer;
ASSERT( wct >= 0 );
ASSERT( bcc >= 0 );
ASSERT( smb_frame_size <= (int)server->transmit_buffer_allocation_size );
if(smb_frame_size > (int)server->transmit_buffer_allocation_size)
D(("Danger: total packet size (%ld) > transmit buffer allocation (%ld)!", smb_frame_size, server->transmit_buffer_allocation_size));
/* This sets up the NetBIOS session header. 'smb_frame_size'
* is the amount of data that follows the header, in this case
* the complete size of the SMB frame, including its payload.
*
* The NetBIOS session header takes up 4 bytes of room.
*/
p = smb_encode_smb_length (p, smb_frame_size);
BSET (p, 0, 0xff);
BSET (p, 1, 'S');
BSET (p, 2, 'M');
BSET (p, 3, 'B');
BSET (p, 4, command);
/* Fixed header length (32 bytes). */
memset (&p[5], 0, 32 - 5);
p += 32;
WSET (buf, smb_tid, server->tid);
WSET (buf, smb_pid, 0); /* server->pid */
WSET (buf, smb_uid, server->server_uid);
WSET (buf, smb_mid, 0); /* server->mid */
if (server->protocol > PROTOCOL_CORE)
{
BSET (buf, smb_flg, server->case_sensitive ? 0 : SMB_FLG_CASELESS_PATHNAMES);
WSET (buf, smb_flg2, SMB_FLG2_KNOWS_LONG_NAMES|SMB_FLG2_EAS|(server->unicode_enabled ? SMB_FLG2_UNICODE_STRINGS : 0));
}
(*p++) = wct;
p += wct * sizeof(word);
WSET (p, 0, bcc);
D(("total packet size=%ld, max receive size=%ld, packet buffer size=%ld",
p + sizeof(word) + bcc - server->transmit_buffer,
server->max_recv,
server->transmit_buffer_allocation_size
));
return p + sizeof(word);
}
/* Returns how much room is still left in the transmission
* buffer, just judging by the number of parameters and
* the payload size.
*/
int
smb_payload_size(const struct smb_server *server, int wct, int bcc)
{
return(server->transmit_buffer_size - (int)(32 + 1 + wct * sizeof(word) + sizeof(word) + bcc));
}
/*****************************************************************************
*
* File operation section.
*
****************************************************************************/
int
smb_proc_open (
struct smb_server *server,
const char *pathname,
int len,
int writable,
int truncate_file,
struct smb_dirent *entry,
int * error_ptr)
{
int result = 0;
char *p;
char *buf = server->transmit_buffer;
int retry_read_only;
ENTER();
D(("pathname = '%s'", escape_name(pathname)));
/* Because the original code opened every file/directory in
* read/write mode, we emulate the same behaviour. Why this
* is done is hard to tell. Presumably, some directories need
* to be writable for modifications to take place within.
*
* If read access is requested, we start with write access
* and disable truncation just to be safe. If that attempt
* fails due to access rights issues, we try again with read
* access.
*/
if(writable)
{
retry_read_only = FALSE;
}
else
{
writable = TRUE;
retry_read_only = TRUE;
truncate_file = FALSE;
}
if (!server->prefer_core_protocol && server->protocol >= PROTOCOL_NT1)
{
dword desired_access;
dword share_access;
dword create_disposition;
dword create_options;
dword ext_file_attributes;
dword end_of_file_low;
dword end_of_file_high;
char *params;
char *data;
int pathname_size;
int pathname_pad;
SHOWMSG("we'll try SMB_COM_NT_CREATE_ANDX");
if(server->unicode_enabled)
{
pathname_size = 2 * (len + 1);
pathname_pad = 1;
}
else
{
pathname_size = len + 1;
pathname_pad = 0;
}
ASSERT( smb_payload_size(server, 24, pathname_size + pathname_pad) >= 0 );
while(TRUE)
{
if(writable)
{
SHOWMSG("write access required");
/* The following does not work consistently with multiple
* SMB servers. Which is why we stick with generic read/write.
*/
/* desired_access = FILE_READ_DATA|FILE_WRITE_DATA|FILE_DELETE|FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES; */
desired_access = GENERIC_READ|GENERIC_WRITE;
}
else
{
SHOWMSG("read access is sufficient");
/* The following does not work consistently with multiple
* SMB servers. Which is why we stick with generic read.
*/
/* desired_access = FILE_READ_DATA|FILE_READ_ATTRIBUTES; */
desired_access = GENERIC_READ;
}
/* Allows others to read, write and delete the file just created.
* This may be useful if smbfs hangs, or you need to restart your
* System and you need to clean up after the file you just
* created.
*/
share_access = FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE;
if(writable && truncate_file)
{
create_disposition = FILE_OVERWRITE_IF;
create_options = FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS;
}
else
{
create_disposition = FILE_OPEN;
create_options = 0;
}
data = smb_setup_header (server, SMBntcreateX, 24, pathname_pad + pathname_size);
params = SMB_VWV (server->transmit_buffer);
params = smb_encode_byte(params, 0xFF); /* AndXCommand: no next command */
params = smb_encode_byte(params, 0); /* AndXReserved */
params = smb_encode_word(params, 0); /* AndXOffset */
params = smb_encode_byte(params, 0); /* Reserved */
params = smb_encode_word(params, pathname_pad + pathname_size); /* NameLength */
params = smb_encode_dword(params, 0); /* Flags */
params = smb_encode_dword(params, 0); /* RootDirectoryFID: 0 -> file name is relative to root directory */
params = smb_encode_dword(params, desired_access); /* DesiredAccess */
params = smb_encode_dword(params, 0); /* AllocationSize (low) */
params = smb_encode_dword(params, 0); /* AllocationSize (high) */
params = smb_encode_dword(params, ATTR_NORMAL); /* ExtFileAttributes */
params = smb_encode_dword(params, share_access); /* ShareAccess */
params = smb_encode_dword(params, create_disposition); /* CreateDisposition */
params = smb_encode_dword(params, create_options); /* CreateOptions */
params = smb_encode_dword(params, SEC_ANONYMOUS); /* ImpersonationLevel */
(void) smb_encode_byte(params, 0); /* SecurityFlags */
/* Now for the data portion of the message */
if(server->unicode_enabled)
{
/* Add a padding byte so that the following Unicode string
* will be word-aligned.
*/
(*data++) = 0;
(void) copy_latin1_to_utf16le(data, pathname_size, pathname, len);
}
else
{
(void) smb_copy_data(data, pathname, pathname_size); /* Length includes the terminating NUL byte. */
}
SHOWMSG("requesting SMBntcreateX");
result = smb_request_ok(server, SMBntcreateX, 34, 0, error_ptr);
if (result < 0)
{
int access_error;
SHOWMSG("that didn't work; retrying");
/* Try again in read-only mode? */
access_error = (*error_ptr);
if(access_error == error_check_smb_error)
access_error = smb_errno(server->rcls,server->err);
if (access_error == EACCES || access_error == EPERM)
{
if(retry_read_only)
{
SHOWMSG("retrying with read-only access");
retry_read_only = FALSE;
writable = FALSE;
continue;
}
else
{
goto out;
}
}
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
continue;
else
goto out;
}
break;
}
params = SMB_VWV (server->transmit_buffer);
params += 5; /* AndXCommand+AndXReserved+AndXOffset+OpLockLevel */
params = smb_decode_word(params, &entry->fileid);
params += sizeof(dword); /* CreateDisposition */
entry->ctime = convert_long_date_to_time_t(params);
params += 2 * sizeof(dword); /* CreateTime */
entry->atime = convert_long_date_to_time_t(params);
params += 2 * sizeof(dword); /* LastAccessTime */
entry->wtime = convert_long_date_to_time_t(params);
params += 2 * sizeof(dword); /* LastWriteTime */
entry->mtime = convert_long_date_to_time_t(params);
params += 2 * sizeof(dword); /* LastChangeTime */
params = smb_decode_dword(params, &ext_file_attributes);
entry->attr = ext_file_attributes;
params += 2 * sizeof(dword); /* AllocationSize */
params = smb_decode_dword(params, &end_of_file_low);
(void) smb_decode_dword(params, &end_of_file_high);
entry->size_low = end_of_file_low;
entry->size_high = end_of_file_high;
}
else
{
word access_and_share_modes;
int path_size;
if(server->unicode_enabled)
path_size = 1 + 2 * (len+1);
else
path_size = 1 + len+1;
ASSERT( smb_payload_size(server, 2, path_size) >= 0 );
SHOWMSG("using the old SMB_COM_OPEN");
while(TRUE)
{
if(writable)
access_and_share_modes = SMB_OPEN_SHARE_DENY_NOTHING|SMB_OPEN_ACCESS_READ_WRITE;
else
access_and_share_modes = SMB_OPEN_SHARE_DENY_NOTHING|SMB_OPEN_ACCESS_READ_ONLY;
p = smb_setup_header (server, SMBopen, 2, path_size);
WSET (buf, smb_vwv0, access_and_share_modes);
WSET (buf, smb_vwv1, SMB_FILE_ATTRIBUTE_HIDDEN|SMB_FILE_ATTRIBUTE_SYSTEM|SMB_FILE_ATTRIBUTE_DIRECTORY);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
copy_latin1_to_utf16le(p,2 * (len+1),pathname,len);
}
else
{
smb_encode_ascii (p, pathname, len);
}
result = smb_request_ok (server, SMBopen, 7, 0, error_ptr);
if (result < 0)
{
int access_error;
SHOWMSG("that didn't work; retrying");
/* Try again in read-only mode? */
access_error = (*error_ptr);
if(access_error == error_check_smb_error)
access_error = smb_errno(server->rcls,server->err);
if (access_error == EACCES || access_error == EPERM)
{
if(retry_read_only)
{
SHOWMSG("retrying with read-only access");
retry_read_only = FALSE;
writable = FALSE;
continue;
}
goto out;
}
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
continue;
else
goto out;
}
break;
}
/* We should now have data in vwv[0..6]. */
entry->fileid = WVAL (buf, smb_vwv0);
entry->attr = WVAL (buf, smb_vwv1);
/* This is actually just the wtime value, but we use it
* in all other places as well.
*/
entry->ctime = entry->atime = entry->mtime = entry->wtime = local2utc(DVAL (buf, smb_vwv2)); /* Note: this is UTIME, and should be the server's local time. */
#if DEBUG
{
struct tm tm_utc;
struct tm tm_local;
seconds_to_tm(entry->mtime,&tm_utc);
D(("modification time = %ld-%02ld-%02ld %02ld:%02ld:%02ld (UTC)",
tm_utc.tm_year + 1900,
tm_utc.tm_mon+1,
tm_utc.tm_mday,
tm_utc.tm_hour,
tm_utc.tm_min,
tm_utc.tm_sec));
seconds_to_tm(DVAL (buf, smb_vwv2),&tm_local);
D((" %ld-%02ld-%02ld %02ld:%02ld:%02ld (local)",
tm_local.tm_year + 1900,
tm_local.tm_mon+1,
tm_local.tm_mday,
tm_local.tm_hour,
tm_local.tm_min,
tm_local.tm_sec));
}
#endif /* DEBUG */
entry->size_low = DVAL (buf, smb_vwv4);
entry->size_high = 0;
}
entry->opened = TRUE;
entry->writable = writable;
out:
RETURN(result);
return(result);
}
/* smb_proc_close: in finfo->mtime we can send a modification time to
* the server
*/
int
smb_proc_close (struct smb_server *server, word fileid, dword wtime, int * error_ptr)
{
char *buf = server->transmit_buffer;
int result;
/* Note: wtime is UTIME, and should be the server's local time. */
if(wtime != 0 && wtime != (dword)-1)
wtime = utc2local(wtime);
smb_setup_header (server, SMBclose, 3, 0);
WSET (buf, smb_vwv0, fileid);
DSET (buf, smb_vwv1, wtime);
result = smb_request_ok (server, SMBclose, 0, 0, error_ptr);
return result;
}
/* In smb_proc_read and smb_proc_write we retry, but we update
* the file-id to be valid again after a reconnection.
*/
int
smb_proc_read (
struct smb_server *server,
struct smb_dirent *finfo,
off_t offset,
long count,
char *data,
int * error_ptr)
{
char *buf = server->transmit_buffer;
int result;
ASSERT( count <= 65535 );
retry:
smb_setup_header (server, SMBread, 5, 0);
WSET (buf, smb_vwv0, finfo->fileid);
WSET (buf, smb_vwv1, count);
DSET (buf, smb_vwv2, offset);
WSET (buf, smb_vwv4, 0);
D(("requesting %ld bytes", count));
result = smb_request_ok_with_payload (server, SMBread, 5, -1, data, NULL, count, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,finfo,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
goto out;
}
/* The buffer format must be 1; smb_request_ok_with_payload() already checked this. */
D(("buffer_format=%ld, should be %ld", BVAL(buf, NETBIOS_HEADER_SIZE+45), 1));
result = WVAL (buf, NETBIOS_HEADER_SIZE+46); /* count of bytes to read */
ASSERT( result <= count );
D(("read %ld bytes (should be < %ld)", result, count));
out:
return result;
}
/* count must be <= 65535. No error number is returned. A result of 0
* indicates an error, which has to be investigated by a normal read
* call.
*/
int
smb_proc_read_raw (
struct smb_server *server,
struct smb_dirent *finfo,
const QUAD * const offset_quad,
long count,
char * data,
int * error_ptr)
{
char *buf = server->transmit_buffer;
int result;
ASSERT( count <= 65535 );
retry:
smb_setup_header (server, SMBreadbraw, 10, 0);
WSET (buf, smb_vwv0, finfo->fileid);
DSET (buf, smb_vwv1, offset_quad->Low);
WSET (buf, smb_vwv3, count); /* maxcnt */
WSET (buf, smb_vwv4, 0); /* mincnt */
DSET (buf, smb_vwv5, 0); /* timeout */
WSET (buf, smb_vwv7, 0); /* reserved */
DSET (buf, smb_vwv8, offset_quad->High);
result = smb_request_read_raw (server, data, count, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,finfo,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
}
return result;
}
int
smb_proc_write (
struct smb_server *server,
struct smb_dirent *finfo,
off_t offset,
long count,
const char *data,
int * error_ptr)
{
int result;
char *buf = server->transmit_buffer;
byte *p;
ASSERT( count <= 65535 );
retry:
p = smb_setup_header (server, SMBwrite, 5, count + 3);
WSET (buf, smb_vwv0, finfo->fileid);
WSET (buf, smb_vwv1, count);
DSET (buf, smb_vwv2, offset);
WSET (buf, smb_vwv4, 0);
(*p++) = 1; /* Buffer format - this field must be 1 */
WSET (p, 0, count); /* Data length - this field must match what the count field in the SMB header says */
result = smb_request_ok_with_payload (server, SMBwrite, 1, 0, NULL, data, count, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,finfo,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
}
else
{
result = WVAL (buf, smb_vwv0);
}
return result;
}
int
smb_proc_write_raw (
struct smb_server *server,
struct smb_dirent *finfo,
const QUAD * const offset_quad,
long count,
const char *data,
int * error_ptr)
{
char *buf = server->transmit_buffer;
int num_bytes_written = 0;
int result;
long max_len;
long len;
byte *p;
ASSERT( count <= 65535 );
D(("number of bytes to send = %ld", count));
/* Calculate the maximum number of bytes that could be transferred with
* a single SMB_COM_WRITE_RAW packet...
*
* 'max_buffer_size' is the maximum size of a complete SMB message
* including the message header, the parameter and data blocks.
*
* The message header accounts for
* 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+
* 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid)
* = 32 bytes
*
* The parameters of an SMB_COM_WRITE_RAW command account for
* 1(wordcount)+2(fid)+2(countofbytes)+2(reserved1)+4(offset)+
* 4(timeout)+2(writemode)+4(reserved2)+2(datalength)+2(dataoffset)+
* 4(offset high)
* = 29 bytes
*
* The data part of an SMB_COM_WRITE_RAW command account for
* 2(bytecount)+0(pad) = 2 bytes, not including
* the actual payload
*
* This leaves 'max_buffer_size' - 63 for the payload.
*/
max_len = server->max_buffer_size;
if(max_len > 65535)
max_len = 65535;
max_len -= 63;
D(("maximum length for payload = %ld bytes", max_len));
/* Number of bytes to write is smaller than the maximum
* number of bytes which may be sent in a single SMB
* message, including parameter and data fields?
*/
if (count <= max_len)
{
D(("count (%ld) <= max_len (%ld) -- send no data with the message.",count,max_len));
len = 0; /* Send a zero length SMB_COM_WRITE_RAW message, followed by the raw data. */
}
else
{
len = count - max_len; /* Send some of the data as part of the SMB_COM_WRITE_RAW message, followed by the remaining raw data. */
D(("count (%ld) > max_len (%ld) -- send %ld bytes with the message.",count,max_len,len));
}
retry:
p = smb_setup_header (server, SMBwritebraw, 14, len);
WSET (buf, smb_vwv0, finfo->fileid);
WSET (buf, smb_vwv1, count);
WSET (buf, smb_vwv2, 0); /* reserved */
DSET (buf, smb_vwv3, offset_quad->Low);
DSET (buf, smb_vwv5, 0); /* timeout */
if(server->write_behind)
WSET (buf, smb_vwv7, 0); /* do not send a final result response. */
else
WSET (buf, smb_vwv7, 1); /* send final result response */
DSET (buf, smb_vwv8, 0); /* reserved */
WSET (buf, smb_vwv10, len);
WSET (buf, smb_vwv11, p - smb_base(buf));
DSET (buf, smb_vwv12, offset_quad->High);
SHOWMSG("requesting SMBwritebraw");
result = smb_request_ok_with_payload (server, SMBwritebraw, 1, 0, NULL, data, len, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,finfo,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
goto out;
}
num_bytes_written += len;
data += len;
count -= len;
D(("bytes sent so far = %ld", num_bytes_written));
if(count > 0)
{
D(("sending %ld bytes of data (raw)",count));
ASSERT( count <= 65535 );
result = smb_request_write_raw (server, data, count, error_ptr);
if (result < 0)
{
/* Roll back the counters */
num_bytes_written -= len;
data -= len;
count += len;
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,finfo,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
goto out;
}
if(server->write_behind)
{
/* We just assume success; the next file operation to follow
* will set an error status if something went wrong.
*/
num_bytes_written += count;
}
else
{
int error;
/* We have to do the checks of smb_request_ok here as well */
if ((error = smb_valid_packet (server->transmit_buffer)) != 0)
{
SHOWMSG("not a valid packet!");
(*error_ptr) = error;
result = -1;
goto out;
}
else if (server->rcls != 0)
{
D(("server error %ld/%ld", server->rcls, server->err));
smb_printerr (server->rcls, server->err);
(*error_ptr) = error_check_smb_error;
result = -1;
goto out;
}
num_bytes_written += count;
}
D(("bytes sent so far = %ld", num_bytes_written));
}
result = num_bytes_written;
out:
return result;
}
int
smb_proc_writex (
struct smb_server *server,
struct smb_dirent *finfo,
const QUAD * const offset_quad,
long count,
const char *data,
int * error_ptr)
{
char *buf = server->transmit_buffer;
int result;
byte *p;
ASSERT( count <= 65535 );
D(("number of bytes to send = %ld", count));
retry:
p = smb_setup_header (server, SMBwriteX, server->protocol >= PROTOCOL_NT1 ? 14 : 12, 2+1+count);
BSET (buf, smb_vwv0, 0xFF); /* AndXCommand/AndXReserved - no additional SMB commands provided */
WSET (buf, smb_vwv1, 0); /* AndXOffset */
WSET (buf, smb_vwv2, finfo->fileid); /* fid */
DSET (buf, smb_vwv3, offset_quad->Low); /* offset */
DSET (buf, smb_vwv5, 0); /* timeout */
WSET (buf, smb_vwv7, 0); /* write mode */
WSET (buf, smb_vwv8, 0); /* remaining */
WSET (buf, smb_vwv9, 0); /* reserved */
WSET (buf, smb_vwv10, count); /* data length */
if(server->protocol >= PROTOCOL_NT1)
{
WSET (buf, smb_vwv11, 64+2); /* data offset */
DSET (buf, smb_vwv12, offset_quad->High); /* high offset */
}
else
{
WSET (buf, smb_vwv11, 60+2); /* data offset */
}
/* Now for the data portion of the message */
WSET (p, 0, 1+count); p += 2; /* Byte count (1 pad byte + data bytes) */
(*p) = 0; /* Padding byte that must be ignored */
D(("requesting SMBwriteX: offset=%s, count=%ld", convert_quad_to_string(offset_quad), count));
result = smb_request_ok_with_payload (server, SMBwriteX, 6, 0, NULL, data, count, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,finfo,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
}
else
{
word available;
p = SMB_VWV (server->transmit_buffer);
smb_decode_word (p + 4, &available);
D(("number of bytes written = %ld", available));
result = available;
}
return result;
}
int
smb_proc_readx (
struct smb_server *server,
struct smb_dirent *finfo,
const QUAD * const offset_quad,
long count,
char *data,
int * error_ptr)
{
char *buf = server->transmit_buffer;
int result;
byte *p;
ASSERT( count <= 65535 );
D(("number of bytes to read = %ld", count));
retry:
p = smb_setup_header (server, SMBreadX, server->protocol >= PROTOCOL_NT1 ? 12 : 10, 2);
BSET (buf, smb_vwv0, 0xFF); /* AndXCommand/AndXReserved - no additional SMB commands provided */
WSET (buf, smb_vwv1, 0); /* AndXOffset */
WSET (buf, smb_vwv2, finfo->fileid); /* fid */
DSET (buf, smb_vwv3, offset_quad->Low); /* offset */
WSET (buf, smb_vwv5, count); /* MaxCountOfBytesToReturn */
WSET (buf, smb_vwv6, 0); /* MinCountOfBytesToReturn */
DSET (buf, smb_vwv7, 0); /* timeout */
WSET (buf, smb_vwv9, 0); /* remaining */
if(server->protocol >= PROTOCOL_NT1)
DSET (buf, smb_vwv10, offset_quad->High); /* OffsetHigh */
/* Now for the data portion of the message */
WSET (p, 0, 0); /* Byte count */
D(("requesting SMBreadX: offset=%s, count=%ld", convert_quad_to_string(offset_quad), count));
result = smb_request_ok_with_payload (server, SMBreadX, 7, 0, data, NULL, count, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,finfo,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
}
else
{
word data_length;
p = SMB_VWV (server->transmit_buffer);
smb_decode_word (p + 10, &data_length);
D(("number of bytes read = %ld", data_length));
result = data_length;
}
return result;
}
/* smb_proc_lockingX: We don't chain any further packets to the initial one */
int
smb_proc_lockingX (
struct smb_server *server,
struct smb_dirent *finfo,
const struct smb_lkrng *locks,
int num_entries,
int mode,
long timeout,
int * error_ptr)
{
int result;
int num_locks, num_unlocks;
char *buf = server->transmit_buffer;
char *data;
const struct smb_lkrng *p;
int i;
num_locks = num_unlocks = 0;
if (mode & 2)
num_unlocks = num_entries;
else
num_locks = num_entries;
ASSERT( smb_payload_size(server, 8, num_entries * 10) >= 0 );
retry:
data = smb_setup_header(server, SMBlockingX, 8, num_entries * 10);
BSET (buf, smb_vwv0, 0xFF);
WSET (buf, smb_vwv1, 0);
WSET (buf, smb_vwv2, finfo->fileid);
WSET (buf, smb_vwv3, mode & 1); /* must be WSET() or new oplock level will be random */
DSET (buf, smb_vwv4, timeout);
WSET (buf, smb_vwv6, num_unlocks);
WSET (buf, smb_vwv7, num_locks);
for (i = 0, p = locks; i < num_entries; i++, p++)
{
WSET (data, SMB_LPID_OFFSET(i), 0); /* server->pid */
DSET (data, SMB_LKOFF_OFFSET(i), p->offset);
DSET (data, SMB_LKLEN_OFFSET(i), p->len);
}
result = smb_request_ok (server, SMBlockingX, 0, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,finfo,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
}
return result;
}
/* smb_proc_do_create: We expect entry->attry and entry->ctime to be set. */
int
smb_proc_create (struct smb_server *server, const char *path, int len, struct smb_dirent *entry, int * error_ptr)
{
dword local_time;
int result;
char *p;
char *buf = server->transmit_buffer;
int path_size;
local_time = utc2local(entry->ctime); /* Note: this is UTIME, and should be the server's local time. */
#if DEBUG
{
struct tm tm_local;
struct tm tm_utc;
seconds_to_tm(entry->ctime,&tm_utc);
D(("creation time = %ld-%02ld-%02ld %02ld:%02ld:%02ld (UTC)",
tm_utc.tm_year + 1900,
tm_utc.tm_mon+1,
tm_utc.tm_mday,
tm_utc.tm_hour,
tm_utc.tm_min,
tm_utc.tm_sec));
seconds_to_tm(local_time,&tm_local);
D((" %ld-%02ld-%02ld %02ld:%02ld:%02ld (local)",
tm_local.tm_year + 1900,
tm_local.tm_mon+1,
tm_local.tm_mday,
tm_local.tm_hour,
tm_local.tm_min,
tm_local.tm_sec));
}
#endif /* DEBUG */
if(server->unicode_enabled)
path_size = 1 + 2 * (len + 1);
else
path_size = 1 + len + 1;
retry:
ASSERT( smb_payload_size(server, 3, path_size) >= 0 );
p = smb_setup_header (server, SMBcreate, 3, path_size);
WSET (buf, smb_vwv0, entry->attr);
DSET (buf, smb_vwv1, local_time);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
copy_latin1_to_utf16le(p,2 * (len+1),path,len);
}
else
{
smb_encode_ascii (p, path, len);
}
result = smb_request_ok (server, SMBcreate, 1, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
else
goto out;
}
entry->opened = TRUE;
entry->fileid = WVAL (buf, smb_vwv0);
out:
return result;
}
int
smb_proc_mv (
struct smb_server *server,
const char *old_path,
const int old_path_len,
const char *new_path,
const int new_path_len,
int * error_ptr)
{
char *p;
char *buf = server->transmit_buffer;
int size;
int result;
if(server->unicode_enabled)
size = 2 + 1 + 2 * (old_path_len+1) + 2 * (new_path_len+1);
else
size = 2 + 1 + (old_path_len+1) + 1 + (new_path_len+1);
ASSERT( smb_payload_size(server, 1, size) >= 0 );
retry:
p = smb_setup_header (server, SMBmv, 1, size);
WSET (buf, smb_vwv0, SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_HIDDEN);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
p += copy_latin1_to_utf16le(p,2 * (old_path_len+1),old_path,old_path_len);
(*p++) = 4; /* A NUL-terminated string follows. */
(*p++) = 0; /* Padding byte, allowing for the string to be word-aligned. */
(void) copy_latin1_to_utf16le(p,2 * (new_path_len+1),new_path,new_path_len);
}
else
{
p = smb_encode_ascii (p, old_path, old_path_len);
(void) smb_encode_ascii (p, new_path, new_path_len);
}
result = smb_request_ok (server, SMBmv, 0, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
}
return result;
}
int
smb_proc_mkdir (struct smb_server *server, const char *path, const int len, int * error_ptr)
{
int path_size;
int result;
char *p;
if(server->unicode_enabled)
path_size = 2 * (len + 1);
else
path_size = len + 1;
retry:
ASSERT( smb_payload_size(server, 0, 1 + path_size) >= 0 );
p = smb_setup_header (server, SMBmkdir, 0, 1 + path_size);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
(void) copy_latin1_to_utf16le(p,path_size,path,len);
}
else
{
(void) smb_encode_ascii (p, path, len);
}
result = smb_request_ok (server, SMBmkdir, 0, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
}
out:
return result;
}
int
smb_proc_rmdir (struct smb_server *server, const char *path, const int len, int * error_ptr)
{
int path_size;
int result;
char *p;
if(server->unicode_enabled)
path_size = 2 * (len + 1);
else
path_size = len + 1;
ASSERT( smb_payload_size(server, 0, 1 + path_size) >= 0 );
retry:
p = smb_setup_header (server, SMBrmdir, 0, 1 + path_size);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
(void) copy_latin1_to_utf16le(p,path_size,path,len);
}
else
{
(void) smb_encode_ascii (p, path, len);
}
result = smb_request_ok (server, SMBrmdir, 0, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
}
return result;
}
int
smb_proc_unlink (struct smb_server *server, const char *path, const int len, int * error_ptr)
{
int path_size;
char *p;
char *buf = server->transmit_buffer;
int result;
if(server->unicode_enabled)
path_size = 2 * (len + 1);
else
path_size = len + 1;
ASSERT( smb_payload_size(server, 1, 1 + path_size) >= 0 );
retry:
p = smb_setup_header (server, SMBunlink, 1, 1 + path_size);
/* Allow for system and hidden files to be deleted, too. After
* all, we show these in the directory lists.
*/
WSET (buf, smb_vwv0, SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_HIDDEN);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
(void) copy_latin1_to_utf16le(p,path_size,path,len);
}
else
{
(void) smb_encode_ascii (p, path, len);
}
result = smb_request_ok (server, SMBunlink, 0, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
}
return result;
}
/* This does not really truncate the file, making it shorter. It just
* writes "enough" data to the file to make it as large as requested.
*/
int
smb_proc_trunc (struct smb_server *server, struct smb_dirent *entry, dword length, int * error_ptr)
{
char *p;
char *buf = server->transmit_buffer;
int result;
ASSERT( smb_payload_size(server, 5, 3) >= 0 );
retry:
p = smb_setup_header (server, SMBwrite, 5, 3);
WSET (buf, smb_vwv0, entry->fileid);
WSET (buf, smb_vwv1, 0);
DSET (buf, smb_vwv2, length);
WSET (buf, smb_vwv4, 0);
smb_encode_ascii (p, "", 0);
result = smb_request_ok (server, SMBwrite, 1, 0, error_ptr);
if(result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,entry,NULL) == 0)
goto retry;
}
}
else
{
result = DVAL(buf, smb_vwv0);
}
return result;
}
static void
smb_decode_short_dirent (dircache_t * dircache, const char *p, struct smb_dirent *entry)
{
int name_len;
int i;
ASSERT( sizeof(dircache->search_resume_key) >= SMB_RESUME_KEY_SIZE );
memcpy(dircache->search_resume_key, p, SMB_RESUME_KEY_SIZE);
p += SMB_RESUME_KEY_SIZE;
entry->attr = BVAL (p, 0);
entry->mtime = entry->atime = entry->ctime = entry->wtime = date_dos2unix (WVAL (p, 1), WVAL (p, 3));
entry->size_low = DVAL (p, 5);
entry->size_high = 0;
/* The name is given in 8.3 MS-DOS style format, including the "."
* delimiter. This is a NUL-terminated OEM string, with one byte per
* character. If the name is shorter than 12 characters, it is
* padded with " " (space) characters.
*/
name_len = 12;
p += 9;
/* How long is the NUL-terminated string really? */
for(i = 0 ; i < name_len ; i++)
{
if(p[i] == '\0')
{
name_len = i;
break;
}
}
/* Remove padding characters from the name. */
while(name_len > 0 && p[name_len-1] == ' ')
name_len--;
ASSERT( name_len < entry->complete_path_size );
memcpy (entry->complete_path, p, name_len);
entry->complete_path[name_len] = '\0';
D(("name = '%s'", escape_name(entry->complete_path)));
#if DEBUG
{
struct tm tm;
seconds_to_tm(entry->mtime,&tm);
D(("mtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",
tm.tm_year + 1900,
tm.tm_mon+1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec));
}
#endif /* DEBUG */
}
/* This routine is used to read in directory entries from the network.
* Note that it is for short directory name seeks, i.e.: protocol < PROTOCOL_LANMAN2
*/
static int
smb_proc_readdir_short (
struct smb_server *server,
const char *path,
dircache_t * dircache,
int * end_of_search_ptr,
int * error_ptr)
{
char *p;
char *buf;
int result = -1;
int i;
int is_first, total_count;
struct smb_dirent * entry;
word bcc;
word count;
word datalength;
char resume_key[SMB_RESUME_KEY_SIZE];
int entries_asked;
int path_len = strlen (path);
int mask_len;
int mask_size;
char * mask;
int end_of_search = 0;
mask = malloc(path_len + 4 + 1);
if (mask == NULL)
{
(*error_ptr) = ENOMEM;
goto out;
}
memcpy(mask, path, path_len);
memcpy(&mask[path_len], "\\*.*", 5); /* Length includes terminating NUL. */
mask_len = strlen (mask);
if(server->unicode_enabled)
mask_size = 1 + 2 * (mask_len+1) + 3;
else
mask_size = 1 + mask_len+1 + 3;
SHOWMSG("SMB call readdir_short");
D((" mask = '%s'", escape_name(mask)));
buf = server->transmit_buffer;
memset(resume_key, 0, sizeof(resume_key));
retry:
if(dircache->sid == -1)
{
is_first = TRUE;
SHOWMSG("start scanning from the top");
}
else
{
is_first = FALSE;
memcpy(resume_key, dircache->search_resume_key, SMB_RESUME_KEY_SIZE);
}
total_count = 0;
entry = get_first_dircache_entry(dircache);
while (entry != NULL)
{
entries_asked = get_dircache_entries_available(dircache);
if (is_first)
{
SHOWMSG("reading first directory entries");
ASSERT( smb_payload_size(server, 2, mask_size) >= 0 );
p = smb_setup_header (server, SMBsearch, 2, mask_size);
WSET (buf, smb_vwv0, entries_asked);
WSET (buf, smb_vwv1, SMB_FILE_ATTRIBUTE_DIRECTORY);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
p += copy_latin1_to_utf16le(p,2 * (mask_len+1),mask,mask_len);
}
else
{
p = smb_encode_ascii (p, mask, strlen (mask));
}
(*p++) = 5; /* a variable block follows. */
(void) smb_encode_word (p, 0); /* resume key length = 0, i.e. this is the initial search command. */
is_first = FALSE;
}
else
{
int size;
SHOWMSG("reading next directory entries");
if(server->unicode_enabled)
size = 1 + 2 * (1) + 3 + SMB_RESUME_KEY_SIZE;
else
size = 1 + 1 + 3 + SMB_RESUME_KEY_SIZE;
ASSERT( smb_payload_size(server, 2, size) >= 0 );
p = smb_setup_header (server, SMBsearch, 2, size);
WSET (buf, smb_vwv0, entries_asked);
WSET (buf, smb_vwv1, SMB_FILE_ATTRIBUTE_DIRECTORY);
/* Add an empty file name. */
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
p += copy_latin1_to_utf16le(p,2 * (1),"",0);
}
else
{
p = smb_encode_ascii (p, "", 0);
}
/* Add the resume key. */
(void) smb_encode_vblock (p, resume_key, SMB_RESUME_KEY_SIZE);
}
if (smb_request_ok (server, SMBsearch, 1, -1, error_ptr) < 0)
{
if (server->rcls == ERRDOS && server->err == ERRnofiles)
{
SHOWMSG("not resuming this scan operation");
end_of_search = TRUE;
dircache->sid = -1;
break;
}
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
SHOWMSG("retrying...");
goto retry;
}
else
{
goto out;
}
}
p = SMB_VWV (server->transmit_buffer);
p = smb_decode_word (p, &count); /* vwv[0] = count-returned */
p = smb_decode_word (p, &bcc);
D(("number of directory entries returned = %ld", count));
if (count == 0)
break;
ASSERT( count <= entries_asked );
ASSERT (bcc == count * SMB_DIRINFO_SIZE + 3);
if (bcc != count * SMB_DIRINFO_SIZE + 3)
{
D(("byte count (%ld) does not match expected size (%ld)", bcc, count * SMB_DIRINFO_SIZE + 3));
(*error_ptr) = error_invalid_directory_size;
goto out;
}
if((*p++) != 5)
{
D(("buffer format mismatch (should be 5, is %ld)", p[-1]));
(*error_ptr) = error_invalid_directory_size;
goto out;
}
p = smb_decode_word (p, &datalength);
if(datalength != SMB_DIRINFO_SIZE * count)
{
D(("data length (%ld) does not match expected size (%ld)", datalength, count * SMB_DIRINFO_SIZE));
(*error_ptr) = error_invalid_directory_size;
goto out;
}
/* Now we are ready to parse smb directory entries. */
for (i = 0; entry != NULL && i < count; i++)
{
smb_decode_short_dirent (dircache, p, entry);
p += SMB_DIRINFO_SIZE;
entry = get_next_dircache_entry(dircache);
ASSERT( entry == NULL || i+1 == count );
if(entry == NULL && i+1 < count)
SHOWMSG("ran out of directory cache entries, which should never happen...");
total_count++;
}
ASSERT( sizeof(dircache->search_resume_key) >= SMB_RESUME_KEY_SIZE );
memcpy(resume_key, dircache->search_resume_key, SMB_RESUME_KEY_SIZE);
dircache->sid = 0;
}
result = total_count;
out:
if(mask != NULL)
free(mask);
if(result == -1)
{
SHOWMSG("not resuming this scan operation");
dircache->sid = -1;
}
(*end_of_search_ptr) = end_of_search;
D(("number of entries read = %ld", result));
return result;
}
/*****************************************************************************/
/* Translate an 8 byte "filetime" structure to a 'time_t'.
* It's originally in "100ns units since jan 1st 1601".
*
* Unlike the Samba implementation of that date conversion
* algorithm this one tries to perform the entire
* calculation using integer operations only.
*/
static time_t
convert_long_date_to_time_t(const char * p)
{
QUAD adjust;
QUAD long_date;
ULONG underflow;
time_t result;
/* Extract the 64-bit time value. */
long_date.Low = DVAL(p,0);
long_date.High = DVAL(p,4);
/* Divide by 10,000,000 to convert the time from 100ns
* units into seconds.
*/
divide_64_by_32(&long_date,10000000,&long_date);
/* Adjust by 369 years (11,644,473,600 seconds) to convert
* from the epoch beginning on January 1st 1601 to the one
* beginning on January 1st 1970 (the Unix epoch).
*/
adjust.Low = 0xb6109100;
adjust.High = 0x00000002;
underflow = subtract_64_from_64_to_64(&long_date,&adjust,&long_date);
/* If the result did not produce an underflow or overflow,
* return the number of seconds encoded in the least
* significant word of the result.
*/
if(underflow == 0 && long_date.High == 0)
result = long_date.Low;
else
result = 0;
return(result);
}
/*****************************************************************************/
/* Translate a 'time_t' value into an SMB file time, which is
* the equivalent in "100ns units since jan 1st 1601".
*/
static void
convert_time_t_to_long_date(time_t t, QUAD * long_date)
{
QUAD adjust;
long_date->Low = t;
long_date->High = 0;
/* Adjust by 369 years (11,644,473,600 seconds) to convert
* from the epoch beginning on January 1st 1601 to the one
* beginning on January 1st 1970 (the Unix epoch).
*/
adjust.Low = 0xb6109100;
adjust.High = 0x00000002;
add_64_plus_64_to_64(&adjust,long_date,long_date);
/* Multiply by 10,000,000 to convert the time from
* seconds to 100ns units.
*/
multiply_64_by_32_to_64(long_date,10000000,long_date);
}
/*****************************************************************************/
/* interpret a long filename structure */
static int
smb_decode_long_dirent (
const struct smb_server *server,
const char *p,
struct smb_dirent *finfo,
int level,
int * entry_length_ptr)
{
int success = TRUE;
ENTER();
ASSERT( entry_length_ptr != NULL );
switch (level)
{
case SMB_INFO_STANDARD:
SHOWMSG("SMB_INFO_STANDARD");
(*entry_length_ptr) = 28 + BVAL (p, 26);
if (finfo != NULL)
{
const char * name;
int name_size;
int name_len;
name = &p[27];
name_size = BVAL (p, 26);
SHOWVALUE(name_size);
/* Figure out how long the string really is. It's
* supposed to be NUL-terminated.
*/
if(server->unicode_enabled)
name_len = strnlen_utf16(name, name_size);
else
name_len = strnlen(name, name_size);
SHOWVALUE(name_len);
/* Skip directory entries whose names we cannot store. */
if(name_len >= (int)finfo->complete_path_size)
{
D(("name length >= %lu (skipping it)", (unsigned long)finfo->complete_path_size));
success = FALSE;
break;
}
if(name_len == 0)
{
SHOWMSG("name length == 0 (skipping it)");
success = FALSE;
break;
}
finfo->ctime = date_dos2unix (WVAL (p, 6), WVAL (p, 4));
finfo->atime = date_dos2unix (WVAL (p, 10), WVAL (p, 8));
finfo->mtime = date_dos2unix (WVAL (p, 14), WVAL (p, 12));
finfo->wtime = finfo->mtime;
finfo->size_low = DVAL (p, 16);
finfo->size_high = DVAL (p, 16);
finfo->attr = WVAL (p, 24);
if(server->unicode_enabled)
{
copy_utf16le_to_latin1(finfo->complete_path, finfo->complete_path_size, name, name_len);
}
else
{
memcpy(finfo->complete_path, name, name_len);
finfo->complete_path[name_len] = '\0';
}
finfo->len = name_len;
D(("name = '%s', length=%ld, size=%ld",escape_name(finfo->complete_path),name_len,name_size));
#if DEBUG
{
struct tm tm;
seconds_to_tm(finfo->mtime,&tm);
D(("mtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(finfo->ctime,&tm);
D(("ctime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(finfo->atime,&tm);
D(("atime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(finfo->wtime,&tm);
D(("wtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
}
#endif /* DEBUG */
}
break;
case SMB_FILE_BOTH_DIRECTORY_INFO:
SHOWMSG("SMB_FILE_BOTH_DIRECTORY_INFO");
(*entry_length_ptr) = WVAL (p, 0);
if (finfo != NULL)
{
dword size_low,size_high;
int name_size;
int name_len;
p += 4; /* next entry offset */
p += 4; /* fileindex */
finfo->ctime = convert_long_date_to_time_t(p);
p += 8;
finfo->atime = convert_long_date_to_time_t(p);
p += 8;
finfo->wtime = convert_long_date_to_time_t(p);
p += 8;
finfo->mtime = convert_long_date_to_time_t(p);
p += 8;
/* If the modification time is not set, try to
* substitute the write time for it.
*/
if(finfo->mtime == 0)
finfo->mtime = finfo->wtime;
size_low = DVAL (p, 0);
size_high = DVAL (p, 4);
p += 8;
finfo->size_low = size_low;
finfo->size_high = size_high;
p += 8; /* alloc size */
finfo->attr = DVAL (p, 0);
p += 4;
name_size = DVAL (p, 0);
p += 4;
/* Skip directory entries whose names we cannot store. */
if(name_size == 0)
{
SHOWMSG("name is empty");
success = FALSE;
break;
}
SHOWVALUE(name_size);
p += 4; /* EA size */
p += 1; /* short name length */
p += 1; /* reserved */
p += 12*2; /* short name (12 WCHAR characters) */
/* Figure out how long the string really is. It's
* supposed to be NUL-terminated.
*/
if(server->unicode_enabled)
name_len = strnlen_utf16(p, name_size);
else
name_len = strnlen(p, name_size);
SHOWVALUE(name_len);
/* Skip directory entries whose names we cannot store. */
if(name_len >= (int)finfo->complete_path_size)
{
D(("name length >= %lu (skipping it)", (unsigned long)finfo->complete_path_size));
success = FALSE;
break;
}
if(name_len == 0)
{
SHOWMSG("name length == 0 (skipping it)");
success = FALSE;
break;
}
if(server->unicode_enabled)
{
copy_utf16le_to_latin1(finfo->complete_path, finfo->complete_path_size, p, name_len);
}
else
{
memcpy(finfo->complete_path, p, name_len);
finfo->complete_path[name_len] = '\0';
}
finfo->len = name_len;
D(("name = '%s', length=%ld, size=%ld",escape_name(finfo->complete_path),name_len,name_size));
#if DEBUG
{
struct tm tm;
seconds_to_tm(finfo->mtime,&tm);
D(("mtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(finfo->ctime,&tm);
D(("ctime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(finfo->atime,&tm);
D(("atime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(finfo->wtime,&tm);
D(("wtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
}
#endif /* DEBUG */
}
break;
/* This should never happen. */
default:
(*entry_length_ptr) = 0;
success = FALSE;
break;
}
D(("entry_length = %ld",(*entry_length_ptr)));
RETURN(success);
return(success);
}
static int
smb_proc_readdir_long (
struct smb_server *server,
const char *path,
dircache_t * dircache,
int * end_of_search_ptr,
int * error_ptr)
{
int max_matches;
int info_level = server->protocol < PROTOCOL_NT1 ? SMB_INFO_STANDARD : SMB_FILE_BOTH_DIRECTORY_INFO;
char *p;
int i;
int is_first;
int total_count = 0;
char *resp_data = NULL;
char *resp_param = NULL;
int resp_data_len = 0;
int resp_param_len = 0;
int attribute = SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_DIRECTORY;
int result = 0;
int ff_searchcount;
int ff_end_of_search = 0;
int ff_dir_handle = -1;
int ff_resume_key = 0;
int loop_count = 0;
unsigned char *outbuf = server->transmit_buffer;
struct smb_dirent * entry;
const int path_len = strlen(path);
const int pattern_len = path_len + 2;
const int pattern_size = (server->unicode_enabled) ? 2 * (pattern_len + 1) : pattern_len + 1;
char *pattern;
int entry_length;
ENTER();
pattern = malloc (pattern_len+1);
if (pattern == NULL)
{
SHOWMSG("Memory allocation failed");
(*error_ptr) = ENOMEM;
result = -1;
goto out;
}
memcpy(pattern,path,path_len);
memcpy(&pattern[path_len],"\\*",3); /* This includes the terminating NUL. */
SHOWMSG("SMB call readdir_long");
D((" pattern = '%s'", escape_name(pattern)));
resp_param = NULL;
resp_data = NULL;
if(dircache->sid == -1 && dircache->close_sid != -1)
{
int sid = dircache->close_sid;
D(("closing previous find-first/find-next session with sid=0x%04lx", sid));
dircache->close_sid = -1;
smb_setup_header (server, SMBfindclose, 1, 0);
WSET (server->transmit_buffer, smb_vwv0, sid);
smb_request_ok (server, SMBfindclose, 0, 0, error_ptr);
}
retry:
if(dircache->sid == -1)
{
is_first = TRUE;
ff_dir_handle = -1;
ff_resume_key = 0;
SHOWMSG("start scanning from the top");
}
else
{
is_first = FALSE;
ff_dir_handle = dircache->sid;
ff_resume_key = dircache->resume_key;
D(("trying to resume scanning with sid=0x%04lx and resume_key=0x%08lx", ff_dir_handle, ff_resume_key));
}
total_count = 0;
ff_end_of_search = 0;
entry = get_first_dircache_entry(dircache);
while (entry != NULL && ff_end_of_search == 0)
{
if (++loop_count > 200)
{
SHOWMSG("Looping in FIND_NEXT???");
(*error_ptr) = error_looping_in_find_next;
result = -1;
goto out;
}
memset (outbuf, 0, sizeof(word) * smb_setup1);
ASSERT( smb_payload_size(server, 15, 3 + 12 + pattern_size) >= 0 );
smb_setup_header (server, SMBtrans2, 15, 3 + 12 + pattern_size);
WSET (outbuf, smb_tpscnt, 12 + pattern_size); /* TotalParameterCount */
WSET (outbuf, smb_tdscnt, 0); /* TotalDataCount */
WSET (outbuf, smb_mprcnt, 10); /* MaxParameterCount */
WSET (outbuf, smb_mdrcnt, server->max_recv); /* MaxDataCount */
WSET (outbuf, smb_msrcnt, 0); /* MaxSetupCount+Reserved1 */
WSET (outbuf, smb_flags, 0); /* Flags */
DSET (outbuf, smb_timeout, 0); /* Timeout */
/* Reserved2 */
WSET (outbuf, smb_pscnt, WVAL (outbuf, smb_tpscnt)); /* ParameterCount */
WSET (outbuf, smb_psoff, ((SMB_BUF (outbuf) + 3) - outbuf) - NETBIOS_HEADER_SIZE); /* ParameterOffset */
WSET (outbuf, smb_dscnt, 0); /* DataCount */
WSET (outbuf, smb_dsoff, 0); /* DataOffset */
WSET (outbuf, smb_suwcnt, 1); /* SetupCount+Reserved3 */
WSET (outbuf, smb_setup0, is_first ? TRANSACT2_FINDFIRST : TRANSACT2_FINDNEXT); /* Setup[0] */
p = SMB_BUF (outbuf);
(*p++) = '\0'; /* put in a null smb_name */
/* Align the parameter data to a 4-byte boundary. */
(*p++) = '\0';
(*p++) = '\0';
max_matches = get_dircache_entries_available(dircache);
ASSERT( max_matches > 0 );
D(("max_matches=%ld", max_matches));
if (is_first)
{
SHOWMSG("first match");
WSET (p, 0, attribute); /* attribute */
WSET (p, 2, max_matches); /* max count */
WSET (p, 4, SMB_FIND_CLOSE_AT_EOS|SMB_FIND_RETURN_RESUME_KEYS|SMB_FIND_CONTINUE_FROM_LAST);
WSET (p, 6, info_level);
DSET (p, 8, 0); /* return every entry there is */
}
else
{
D(("next match; ff_dir_handle=0x%04lx ff_resume_key=0x%08lx pattern='%s'", ff_dir_handle, ff_resume_key, escape_name(pattern)));
WSET (p, 0, ff_dir_handle);
WSET (p, 2, max_matches); /* max count */
WSET (p, 4, info_level);
DSET (p, 6, ff_resume_key);
WSET (p, 10, SMB_FIND_CLOSE_AT_EOS|SMB_FIND_RETURN_RESUME_KEYS|SMB_FIND_CONTINUE_FROM_LAST);
}
p += 12;
if(server->unicode_enabled)
{
copy_latin1_to_utf16le(p,pattern_size,pattern,pattern_len);
}
else
{
memcpy (p, pattern, pattern_len);
p[pattern_len] = '\0';
}
result = smb_trans2_request (server, SMBtrans2, &resp_data_len, &resp_param_len, &resp_data, &resp_param, error_ptr);
D(("smb_trans2_request returns %ld", result));
/* If an error was flagged, check first if it's a protocol
* error which we could handle below. Otherwise, try again.
*/
if (result < 0 && (*error_ptr) != error_check_smb_error)
{
if (smb_retry (server))
goto retry;
SHOWMSG("got error from trans2_request");
goto out;
}
/* Apparently, there is a bug in Windows 95 and friends which
* causes the directory read attempt to fail if you're asking
* for too much data too fast...
*/
if(server->rcls == ERRSRV && server->err == ERRerror)
{
SHOWMSG("ouch; delaying and retrying");
PROFILE_OFF();
Delay(TICKS_PER_SECOND / 5);
PROFILE_ON();
continue;
}
/* If there's a protocol error, stop and abort. */
if (server->rcls != 0)
{
D(("server->rcls = %ld err = %ld",server->rcls, server->err));
smb_printerr (server->rcls, server->err);
(*error_ptr) = error_check_smb_error;
result = -1;
goto out;
}
/* Bail out if this is empty. */
ASSERT( resp_param != NULL );
if (resp_param == NULL)
{
SHOWMSG("no response parameters to process; stopping the search for now");
break;
}
/* parse out some important return info */
p = resp_param;
if (is_first)
{
ASSERT( resp_param_len >= 6 );
if(resp_param_len < 6)
{
SHOWMSG("not enough response parameter data to process; stopping the search for now");
break;
}
/* This is the "search identifier" which allows us to
* keep scanning this directory until we hit the
* last entry.
*/
ff_dir_handle = WVAL (p, 0);
ff_searchcount = WVAL (p, 2);
ff_end_of_search = WVAL (p, 4);
}
else
{
ASSERT( resp_param_len >= 4 );
if(resp_param_len < 4)
{
SHOWMSG("not enough response parameter data to process; stopping the search for now");
break;
}
ff_searchcount = WVAL (p, 0);
ff_end_of_search = WVAL (p, 2);
}
ASSERT( ff_searchcount <= max_matches );
D(("received %ld entries (end of search = %s)",ff_searchcount, ff_end_of_search ? "yes" : "no"));
if (ff_searchcount == 0)
{
SHOWMSG("no further entries available; stopping the search for now");
break;
}
/* Bail out if this is empty. */
ASSERT( resp_data != NULL );
if (resp_data == NULL)
{
SHOWMSG("no directory data to process; stopping the search for now");
break;
}
/* Now we are ready to parse smb directory entries. */
for (i = 0, p = resp_data, entry_length = 0 ;
i < ff_searchcount && entry != NULL && p < &resp_data[resp_data_len];
i++, p += entry_length)
{
ff_resume_key = DVAL(p, 0);
D(("sid=0x%04lx, resume_key=0x%08lx", ff_dir_handle, ff_resume_key));
/* Skip this entry if we cannot decode the name. This could happen
* if the name will no fit into the buffer.
*/
if(!smb_decode_long_dirent (server, p, entry, info_level, &entry_length))
{
if(entry_length == 0)
{
SHOWMSG("no more entries available; stopping.");
break;
}
D(("skipped entry; total_count = %ld, i = %ld", total_count, i));
continue;
}
entry = get_next_dircache_entry(dircache);
ASSERT( entry != NULL || i+1 == ff_searchcount );
if(entry == NULL && i+1 < ff_searchcount)
SHOWMSG("ran out of directory cache entries, which should never happen...");
total_count++;
}
if (resp_data != NULL)
{
free (resp_data);
resp_data = NULL;
}
if (resp_param != NULL)
{
free (resp_param);
resp_param = NULL;
}
is_first = FALSE;
if (ff_searchcount > 0)
loop_count = 0;
}
result = total_count;
out:
/* finished: not needed any more */
if (pattern != NULL)
free (pattern);
if (resp_data != NULL)
free (resp_data);
if (resp_param != NULL)
free (resp_param);
(*end_of_search_ptr) = ff_end_of_search;
if(result >= 0)
{
if(ff_end_of_search)
{
SHOWMSG("not resuming this scan operation");
dircache->sid = -1;
dircache->resume_key = 0;
}
else
{
dircache->sid = ff_dir_handle;
dircache->resume_key = ff_resume_key;
}
}
else
{
SHOWMSG("not resuming this scan operation");
dircache->sid = -1;
dircache->resume_key = 0;
}
RETURN(result);
return(result);
}
int
smb_proc_readdir (
struct smb_server *server,
const char *path,
dircache_t * dircache,
int * end_of_search_ptr,
int * error_ptr)
{
int result;
if (server->protocol >= PROTOCOL_LANMAN2)
result = smb_proc_readdir_long (server, path, dircache, end_of_search_ptr, error_ptr);
else
result = smb_proc_readdir_short (server, path, dircache, end_of_search_ptr, error_ptr);
return result;
}
int
smb_proc_getattr_core (struct smb_server *server, const char *path, int len, struct smb_dirent *entry, int * error_ptr)
{
int result;
char *p;
char *buf = server->transmit_buffer;
int path_size;
D(("path='%s'", escape_name(path)));
if(server->unicode_enabled)
path_size = 1 + 2 * (len + 1);
else
path_size = 1 + len + 1;
ASSERT( smb_payload_size(server, 0, path_size) >= 0 );
retry:
p = smb_setup_header (server, SMBgetatr, 0, path_size);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
copy_latin1_to_utf16le(p,2 * (len+1),path,len);
}
else
{
smb_encode_ascii (p, path, len);
}
result = smb_request_ok (server, SMBgetatr, 10, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
else
goto out;
}
entry->attr = WVAL (buf, smb_vwv0);
/* The server only tells us just the wtime */
entry->ctime = entry->atime = entry->mtime = entry->wtime = local2utc(DVAL (buf, smb_vwv1)); /* Note: this is UTIME, and should be the server's local time. */
#if DEBUG
{
struct tm tm_utc;
struct tm tm_local;
seconds_to_tm(entry->mtime,&tm_utc);
D(("modification time = %ld-%02ld-%02ld %02ld:%02ld:%02ld (UTC)",
tm_utc.tm_year + 1900,
tm_utc.tm_mon+1,
tm_utc.tm_mday,
tm_utc.tm_hour,
tm_utc.tm_min,
tm_utc.tm_sec));
seconds_to_tm(DVAL (buf, smb_vwv1),&tm_local);
D((" %ld-%02ld-%02ld %02ld:%02ld:%02ld (local)",
tm_local.tm_year + 1900,
tm_local.tm_mon+1,
tm_local.tm_mday,
tm_local.tm_hour,
tm_local.tm_min,
tm_local.tm_sec));
}
#endif /* DEBUG */
entry->size_low = DVAL (buf, smb_vwv3);
entry->size_high = 0;
out:
return result;
}
int
smb_query_path_information(
struct smb_server *server,
const char *path,
int len,
int fid,
struct smb_dirent *entry,
int * error_ptr)
{
unsigned char *outbuf = server->transmit_buffer;
dword ext_file_attributes;
dword end_of_file_low;
dword end_of_file_high;
int is_directory;
dword file_name_length;
int parameter_count;
const char * file_name;
char * p;
int result;
char *resp_data = NULL;
int resp_data_len = 0;
int path_size;
retry:
if(len > 0)
{
if(server->unicode_enabled)
path_size = 2 * (len+1);
else
path_size = len+1;
parameter_count = 2 + 4 + path_size;
}
else
{
parameter_count = 2 + 2;
path_size = 0;
}
ASSERT( smb_payload_size(server, 15, 3 + parameter_count) >= 0 );
smb_setup_header (server, SMBtrans2, 15, 3 + parameter_count);
WSET (outbuf, smb_tpscnt, parameter_count); /* TotalParameterCount */
WSET (outbuf, smb_tdscnt, 0); /* TotalDataCount */
WSET (outbuf, smb_mprcnt, 0); /* MaxParameterCount */
WSET (outbuf, smb_mdrcnt, server->max_recv); /* MaxDataCount */
WSET (outbuf, smb_msrcnt, 0); /* MaxSetupCount+Reserved1 */
WSET (outbuf, smb_flags, 0); /* Flags */
DSET (outbuf, smb_timeout, 0); /* Timeout */
/* Reserved2 */
WSET (outbuf, smb_pscnt, WVAL (outbuf, smb_tpscnt)); /* ParameterCount */
WSET (outbuf, smb_psoff, ((SMB_BUF (outbuf) + 3) - outbuf) - NETBIOS_HEADER_SIZE); /* ParameterOffset */
WSET (outbuf, smb_dscnt, 0); /* DataCount */
WSET (outbuf, smb_dsoff, 0); /* DataOffset */
WSET (outbuf, smb_suwcnt, 1); /* SetupCount+Reserved3 */
WSET (outbuf, smb_setup0, len > 0 ? TRANSACT2_QPATHINFO : TRANSACT2_QFILEINFO); /* Setup[0] */
p = SMB_BUF (outbuf);
/* Align to a 4-byte-boundary. */
(*p++) = '\0';
(*p++) = '\0';
(*p++) = '\0';
if(len > 0)
{
WSET (p, 0, SMB_QUERY_FILE_ALL_INFO);
DSET (p, 2, 0);
p += 2 + 4;
if(server->unicode_enabled)
{
copy_latin1_to_utf16le(p,path_size,path,len);
}
else
{
memcpy(p,path,len);
p[len] = '\0';
}
}
else
{
WSET (p, 0, fid);
WSET (p, 2, SMB_QUERY_FILE_ALL_INFO);
}
result = smb_trans2_request (server, SMBtrans2, &resp_data_len, NULL, &resp_data, NULL, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
/* We don't need the file id? */
if (len > 0)
{
goto retry;
}
/* We do need the file id. */
else if (reopen_entry(server,entry,NULL) == 0)
{
fid = entry->fileid;
goto retry;
}
}
goto out;
}
if(server->rcls != 0)
{
(*error_ptr) = error_check_smb_error;
result = -1;
goto out;
}
ASSERT( resp_data != NULL );
p = resp_data;
entry->ctime = convert_long_date_to_time_t(p);
p += 2 * sizeof(dword); /* CreateTime */
entry->atime = convert_long_date_to_time_t(p);
p += 2 * sizeof(dword); /* LastAccessTime */
entry->wtime = convert_long_date_to_time_t(p);
p += 2 * sizeof(dword); /* LastWriteTime */
entry->mtime = convert_long_date_to_time_t(p);
p += 2 * sizeof(dword); /* LastChangeTime */
p = smb_decode_dword(p, &ext_file_attributes);
entry->attr = ext_file_attributes;
p += sizeof(dword); /* Reserved */
p += 2 * sizeof(dword); /* AllocationSize */
p = smb_decode_dword(p, &end_of_file_low);
p = smb_decode_dword(p, &end_of_file_high);
entry->size_low = end_of_file_low;
entry->size_high = end_of_file_high;
p += sizeof(dword); /* Number of links */
p += 1; /* Delete pending */
is_directory = (*p);
p += 1; /* Directory */
p += sizeof(word); /* Reserved */
p += sizeof(dword); /* Extended attribute size */
/* File name follows the length field. */
file_name = smb_decode_dword(p, &file_name_length);
#if DEBUG
{
TEXT name[SMB_MAXNAMELEN+1];
struct tm tm;
QUAD entry_size_quad;
/* Let's show what the name of the object in
* question is supposed to look like. The
* text is provided as 16-bit characters,
* even if Unicode mode is not enabled.
*/
copy_utf16le_to_latin1(name, sizeof(name), file_name, file_name_length / sizeof(word));
entry_size_quad.Low = entry->size_low;
entry_size_quad.High = entry->size_high;
seconds_to_tm(entry->mtime,&tm);
D(("mtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(entry->ctime,&tm);
D(("ctime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(entry->atime,&tm);
D(("atime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(entry->wtime,&tm);
D(("wtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
D(("size = %s (0x%08lx%08lx)",convert_quad_to_string(&entry_size_quad),entry->size_high,entry->size_low));
D(("attr = 0x%08lx",entry->attr));
D(("name = '%s' (length in bytes = %ld)",escape_name(name),file_name_length));
}
#endif /* DEBUG */
out:
if(resp_data != NULL)
free(resp_data);
return result;
}
/* smb_proc_getattrE: entry->fid must be valid */
int
smb_proc_getattrE (struct smb_server *server, struct smb_dirent *entry, int * error_ptr)
{
char *buf = server->transmit_buffer;
int result;
retry:
smb_setup_header (server, SMBgetattrE, 1, 0);
WSET (buf, smb_vwv0, entry->fileid);
result = smb_request_ok (server, SMBgetattrE, 11, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
else
goto out;
}
entry->ctime = date_dos2unix (WVAL (buf, smb_vwv1), WVAL (buf, smb_vwv0));
entry->atime = date_dos2unix (WVAL (buf, smb_vwv3), WVAL (buf, smb_vwv2));
entry->mtime = date_dos2unix (WVAL (buf, smb_vwv5), WVAL (buf, smb_vwv4));
entry->wtime = entry->mtime;
entry->size_low = DVAL (buf, smb_vwv6);
entry->size_high = 0;
entry->attr = WVAL (buf, smb_vwv10);
#if DEBUG
{
struct tm tm;
seconds_to_tm(entry->ctime,&tm);
D(("ctime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(entry->atime,&tm);
D(("atime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(entry->mtime,&tm);
D(("mtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
seconds_to_tm(entry->wtime,&tm);
D(("wtime = %ld-%02ld-%02ld %ld:%02ld:%02ld",tm.tm_year + 1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec));
}
#endif /* DEBUG */
out:
return result;
}
int
smb_set_file_information(struct smb_server *server, struct smb_dirent *entry, const QUAD * const size_quad, int * error_ptr)
{
unsigned char *outbuf = server->transmit_buffer;
int data_size;
char * p;
int result;
QUAD change_time_quad;
retry:
data_size = (size_quad == NULL) ? 40 : 8;
ASSERT( smb_payload_size(server, 15, 3 + 6 + 2 + data_size) >= 0 );
smb_setup_header (server, SMBtrans2, 15, 3 + 6 + 2 + data_size);
WSET (outbuf, smb_tpscnt, 6); /* TotalParameterCount */
WSET (outbuf, smb_tdscnt, data_size); /* TotalDataCount */
WSET (outbuf, smb_mprcnt, 0); /* MaxParameterCount */
WSET (outbuf, smb_mdrcnt, server->max_recv); /* MaxDataCount */
WSET (outbuf, smb_msrcnt, 0); /* MaxSetupCount+Reserved1 */
WSET (outbuf, smb_flags, 0); /* Flags */
DSET (outbuf, smb_timeout, 0); /* Timeout */
/* Reserved2 */
WSET (outbuf, smb_pscnt, WVAL (outbuf, smb_tpscnt)); /* ParameterCount */
WSET (outbuf, smb_psoff, ((SMB_BUF (outbuf) + 3) - outbuf) - NETBIOS_HEADER_SIZE); /* ParameterOffset */
WSET (outbuf, smb_dscnt, WVAL (outbuf, smb_tdscnt)); /* DataCount */
WSET (outbuf, smb_dsoff, WVAL (outbuf, smb_psoff) + 6+2); /* DataOffset */
WSET (outbuf, smb_suwcnt, 1); /* SetupCount+Reserved3 */
WSET (outbuf, smb_setup0, TRANSACT2_SETFILEINFO); /* Setup[0] */
p = SMB_BUF (outbuf);
/* Align to a 4-byte-boundary. */
(*p++) = '\0';
(*p++) = '\0';
(*p++) = '\0';
WSET (p, 0, entry->fileid);
WSET (p, 2, size_quad == NULL ? SMB_SET_FILE_BASIC_INFO : SMB_SET_FILE_END_OF_FILE_INFO);
WSET (p, 4, 0);
p += 6;
/* Align to a 4-byte-boundary. */
(*p++) = '\0';
(*p++) = '\0';
if(size_quad == NULL)
{
/* Creation time (no change) */
DSET(p, 0, 0);
DSET(p, 4, 0);
/* Last access time (no change) */
DSET(p, 8, 0);
DSET(p, 12, 0);
/* Note that we update both mtime and wtime of the
* directory and not just mtime because some Samba
* versions will end up setting the mtime of the
* directory entry to the time given as the wtime
* value and ignore the mtime value. This happens
* even if the wtime value is given as zero and the
* mtime value is non-zero.
*/
D(("entry->mtime = %lu",entry->mtime));
convert_time_t_to_long_date(entry->mtime,&change_time_quad);
/* Last write time */
DSET(p, 16, change_time_quad.Low);
DSET(p, 20, change_time_quad.High);
/* Change time */
DSET(p, 24, change_time_quad.Low);
DSET(p, 28, change_time_quad.High);
/* Extended file attributes */
DSET(p, 32, entry->attr);
/* Reserved */
DSET(p, 36, 0);
}
else
{
/* End of file */
DSET(p, 0, size_quad->Low);
DSET(p, 4, size_quad->High);
}
result = smb_trans2_request (server, SMBtrans2, NULL, NULL, NULL, NULL, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,entry,NULL) == 0)
goto retry;
}
SHOWMSG("that didn't work.");
goto out;
}
if(server->rcls != 0)
{
(*error_ptr) = error_check_smb_error;
result = -1;
}
out:
return result;
}
/* In core protocol, there is only 1 time to be set, we use
* entry->mtime, to make touch work.
*/
int
smb_proc_setattr_core (struct smb_server *server, const char *path, int len, const struct smb_dirent *new_finfo, int * error_ptr)
{
dword local_time;
char *p;
char *buf = server->transmit_buffer;
int result;
int path_size;
dword attr;
/* Don't do anything if mtime is unset. */
if(new_finfo->mtime == 0 || new_finfo->mtime == (time_t)-1)
return(0);
if(server->unicode_enabled)
path_size = 1 + 2 * (len + 1);
else
path_size = 1 + len + 1;
ASSERT( smb_payload_size(server, 8, path_size) >= 0 );
/* We cache these because if the connection needs to be
* reestablished, the direntry values will all get
* overwritten.
*/
attr = new_finfo->attr;
local_time = utc2local(new_finfo->mtime); /* Note: this is UTIME, and should be the server's local time. */
#if DEBUG
{
struct tm tm_local;
struct tm tm_utc;
seconds_to_tm(new_finfo->mtime,&tm_utc);
D(("modification time = %ld-%02ld-%02ld %02ld:%02ld:%02ld (UTC)",
tm_utc.tm_year + 1900,
tm_utc.tm_mon+1,
tm_utc.tm_mday,
tm_utc.tm_hour,
tm_utc.tm_min,
tm_utc.tm_sec));
seconds_to_tm(local_time,&tm_local);
D((" %ld-%02ld-%02ld %02ld:%02ld:%02ld (local)",
tm_local.tm_year + 1900,
tm_local.tm_mon+1,
tm_local.tm_mday,
tm_local.tm_hour,
tm_local.tm_min,
tm_local.tm_sec));
}
#endif /* DEBUG */
retry:
p = smb_setup_header (server, SMBsetatr, 8, path_size);
WSET (buf, smb_vwv0, attr);
DSET (buf, smb_vwv1, local_time);
WSET (buf, smb_vwv3, 0);
WSET (buf, smb_vwv4, 0);
WSET (buf, smb_vwv5, 0);
WSET (buf, smb_vwv6, 0);
WSET (buf, smb_vwv7, 0);
if(server->unicode_enabled)
{
(*p++) = 4; /* A NUL-terminated string follows. */
p += copy_latin1_to_utf16le(p,2 * (len+1),path,len);
}
else
{
p = smb_encode_ascii (p, path, len);
}
result = smb_request_ok (server, SMBsetatr, 0, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
}
return result;
}
int
smb_proc_setattrE (struct smb_server *server, word fid, struct smb_dirent *new_entry, int * error_ptr)
{
char *buf = server->transmit_buffer;
word date, time_value;
time_t ctime, atime, mtime;
int num_changes;
int result;
/* We cache these because if the connection needs to be
* reestablished, the direntry values will all get
* overwritten.
*/
ctime = new_entry->ctime;
atime = new_entry->atime;
mtime = new_entry->mtime;
retry:
smb_setup_header (server, SMBsetattrE, 7, 0);
WSET (buf, smb_vwv0, fid);
num_changes = 0;
if(ctime != 0 && ctime != (time_t)-1)
{
date_unix2dos (ctime, &time_value, &date);
num_changes++;
}
else
{
date = time_value = 0;
}
WSET (buf, smb_vwv1, date);
WSET (buf, smb_vwv2, time_value);
if(atime != 0 && atime != (time_t)-1)
{
date_unix2dos (atime, &time_value, &date);
num_changes++;
}
else
{
date = time_value = 0;
}
WSET (buf, smb_vwv3, date);
WSET (buf, smb_vwv4, time_value);
if(mtime != 0 && mtime != (time_t)-1)
{
date_unix2dos (mtime, &time_value, &date);
num_changes++;
}
else
{
date = time_value = 0;
}
WSET (buf, smb_vwv5, date);
WSET (buf, smb_vwv6, time_value);
/* Do we actually have to change anything at all? */
if(num_changes > 0)
{
result = smb_request_ok (server, SMBsetattrE, 0, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
{
if(reopen_entry(server,new_entry,NULL) == 0)
{
fid = new_entry->fileid;
goto retry;
}
}
SHOWMSG("that didn't work.");
}
}
else
{
result = 0;
}
return result;
}
int
smb_proc_dskattr (struct smb_server *server, struct smb_dskattr *attr, int * error_ptr)
{
char *resp_data = NULL;
int resp_data_len = 0;
int result;
char *p;
retry:
if (!server->prefer_core_protocol && server->protocol >= PROTOCOL_NT1)
{
unsigned char *outbuf = server->transmit_buffer;
dword total_allocation_units;
dword total_free_allocation_units;
dword sectors_per_allocation_unit;
dword bytes_per_sector;
ASSERT( smb_payload_size(server, 15, 3 + 2) >= 0 );
smb_setup_header (server, SMBtrans2, 15, 3 + 2);
WSET (outbuf, smb_tpscnt, 2); /* TotalParameterCount */
WSET (outbuf, smb_tdscnt, 0); /* TotalDataCount */
WSET (outbuf, smb_mprcnt, 0); /* MaxParameterCount */
WSET (outbuf, smb_mdrcnt, server->max_recv); /* MaxDataCount */
WSET (outbuf, smb_msrcnt, 0); /* MaxSetupCount+Reserved1 */
WSET (outbuf, smb_flags, 0); /* Flags */
DSET (outbuf, smb_timeout, 0); /* Timeout */
/* Reserved2 */
WSET (outbuf, smb_pscnt, WVAL (outbuf, smb_tpscnt)); /* ParameterCount */
WSET (outbuf, smb_psoff, ((SMB_BUF (outbuf) + 3) - outbuf) - NETBIOS_HEADER_SIZE); /* ParameterOffset */
WSET (outbuf, smb_dscnt, 0); /* DataCount */
WSET (outbuf, smb_dsoff, 0); /* DataOffset */
WSET (outbuf, smb_suwcnt, 1); /* SetupCount+Reserved3 */
WSET (outbuf, smb_setup0, TRANSACT2_QFSINFO); /* Setup[0] */
p = SMB_BUF (outbuf);
/* Align to a 4-byte-boundary. */
(*p++) = '\0';
(*p++) = '\0';
(*p++) = '\0';
WSET (p, 0, SMB_QUERY_FS_SIZE_INFO);
result = smb_trans2_request (server, SMBtrans2, &resp_data_len, NULL, &resp_data, NULL, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
goto out;
}
if(server->rcls != 0)
{
(*error_ptr) = error_check_smb_error;
result = -1;
goto out;
}
ASSERT( resp_data != NULL );
p = resp_data;
p = smb_decode_dword (p, &total_allocation_units);
p += sizeof(dword);
SHOWVALUE(total_allocation_units);
p = smb_decode_dword (p, &total_free_allocation_units);
p += sizeof(dword);
SHOWVALUE(total_free_allocation_units);
p = smb_decode_dword (p, &sectors_per_allocation_unit);
(void) smb_decode_dword (p, &bytes_per_sector);
SHOWVALUE(bytes_per_sector);
SHOWVALUE(sectors_per_allocation_unit);
attr->total = total_allocation_units;
attr->free = total_free_allocation_units;
attr->blocksize = bytes_per_sector;
attr->allocblocks = sectors_per_allocation_unit;
}
else
{
word total_units;
word sectors_per_unit;
word bytes_per_sector;
word free_units;
ASSERT( smb_payload_size(server, 0, 0) >= 0 );
smb_setup_header (server, SMBdskattr, 0, 0);
result = smb_request_ok (server, SMBdskattr, 5, 0, error_ptr);
if (result < 0)
{
if ((*error_ptr) != error_check_smb_error && smb_retry (server))
goto retry;
goto out;
}
p = SMB_VWV (server->transmit_buffer);
p = smb_decode_word (p, &total_units);
p = smb_decode_word (p, &sectors_per_unit);
p = smb_decode_word (p, &bytes_per_sector);
(void) smb_decode_word (p, &free_units);
attr->total = total_units;
attr->allocblocks = sectors_per_unit;
attr->blocksize = bytes_per_sector;
attr->free = free_units;
}
out:
if (resp_data != NULL)
free (resp_data);
return result;
}
/*****************************************************************************
*
* Mount/umount operations.
*
****************************************************************************/
struct smb_prots
{
enum smb_protocol prot;
const char * name;
};
static int
smb_proc_reconnect (struct smb_server *server, int * error_ptr)
{
static const struct smb_prots prots[] =
{
{PROTOCOL_CORE, "PC NETWORK PROGRAM 1.0"}, /* Core Protocol */
{PROTOCOL_COREPLUS, "MICROSOFT NETWORKS 1.03"}, /* CorePlus */
{PROTOCOL_LANMAN1, "MICROSOFT NETWORKS 3.0"}, /* DOS LAN Manager 1.0 */
{PROTOCOL_LANMAN1, "LANMAN1.0"}, /* LAN Manager 1.0 */
{PROTOCOL_LANMAN2, "LM1.2X002"}, /* LAN Manager 2.0 */
{PROTOCOL_NT1, "NT LM 0.12"}, /* NT LAN Manager */
{PROTOCOL_NT1, "NT LANMAN 1.0"},
};
/* Add a bit of fudge to account for the NetBIOS session
* header and whatever else might show up. How much space is
* required breaks down as follows:
*
* 4 bytes for the NetBIOS session header
* 32 bytes for the SMB frame header
* 1 byte for the parameter count
* x words (2 bytes each) for the parameters
* 1 word (2 bytes) for the number of bytes to transmit (payload)
* y bytes to transmit (payload)
*
* The parameter count (x) and the number of bytes to
* transmit may vary.
*
* What follows the 4 byte NetBIOS session header must not be
* larger than that an unsigned 17-bit integer can hold. It should
* not be much larger than 65535 bytes, though, since the transmission
* buffer only has some extra 1024 bytes of room at the end.
*/
const int safety_margin = 1024;
const int num_prots = sizeof(prots) / sizeof(prots[0]);
const char dev[] = "A:";
int i, plength;
const int default_max_buffer_size = 1024; /* Space needed for first request. */
int given_max_xmit;
int result;
word dialect_index;
byte *p;
unsigned char oem_password[24];
int oem_password_len;
unsigned char unicode_password[24];
int unicode_password_len;
char * share_name = NULL;
byte *packet;
dword max_buffer_size;
int packet_size;
int enable_unicode = 0;
result = smb_connect (server, error_ptr);
if (result < 0)
{
SHOWMSG("could not connect to server");
goto out;
}
/* Unless we know better, we don't go for Unicode just yet. */
server->unicode_enabled = FALSE;
/* Here we assume that the connection is valid */
server->state = CONN_VALID;
server->max_buffer_size = default_max_buffer_size;
SHOWVALUE(server->max_buffer_size);
/* Minimum receive buffer size is 8000 bytes, but cannot
* be larger than 65535 bytes.
*/
if (0 < server->mount_data.given_max_xmit && server->mount_data.given_max_xmit < 8000)
given_max_xmit = 8000;
else if (0 < server->mount_data.given_max_xmit && server->mount_data.given_max_xmit < 65536)
given_max_xmit = server->mount_data.given_max_xmit;
else
given_max_xmit = 65535;
if (server->max_recv <= 0)
server->max_recv = given_max_xmit;
SHOWVALUE(server->max_recv);
/* We need to allocate memory for the buffer which is used both
* for reception and transmission of messages. This means that it has
* to be large enough to account for both uses. The maximum
* transmit buffer size reported by the server may be smaller than
* the maximum message size which smbfs is prepared to accept, or
* it could be the other way round.
*/
packet_size = server->max_recv;
if (packet_size < (int)server->max_buffer_size)
packet_size = (int)server->max_buffer_size;
SHOWVALUE(packet_size);
if (server->transmit_buffer != NULL)
free (server->transmit_buffer);
server->transmit_buffer_size = packet_size;
/* Add a bit of fudge to account for the NetBIOS session
* header and whatever else might show up...
*/
server->transmit_buffer_allocation_size = server->transmit_buffer_size + safety_margin;
server->transmit_buffer = malloc (server->transmit_buffer_allocation_size);
if (server->transmit_buffer == NULL)
{
SHOWMSG("No memory! Bailing out.");
(*error_ptr) = ENOMEM;
result = -1;
goto out;
}
packet = server->transmit_buffer;
/* Prepend a NetBIOS header? */
if(!server->raw_smb)
{
/* Start with an RFC1002 session request packet. */
p = packet + NETBIOS_HEADER_SIZE;
p = smb_name_mangle (p, server->mount_data.server_name);
p = smb_name_mangle (p, server->mount_data.client_name);
/* Careful: the SMB packet length does not include the
* size of the NetBIOS header itself.
*/
smb_encode_smb_length (packet, (byte *) p - (byte *) (packet + NETBIOS_HEADER_SIZE));
packet[0] = 0x81; /* SESSION REQUEST */
result = smb_request (server, 0, NULL, NULL, 0, error_ptr);
if (result < 0)
{
SHOWMSG("Failed to send SESSION REQUEST.");
goto out;
}
if (packet[0] != 0x82)
{
D(("Did not receive positive response (err = %lx)",packet[0]));
#if DEBUG
{
smb_dump_packet (packet);
}
#endif /* DEBUG */
(*error_ptr) = error_session_request_failed;
result = -1;
goto out;
}
SHOWMSG("Passed SESSION REQUEST.");
}
/* Now we are ready to send an SMB Negotiate Protocol packet. */
plength = 0;
for (i = 0; i < num_prots ; i++)
plength += 1 + strlen (prots[i].name) + 1;
ASSERT( smb_payload_size(server, 0, plength) >= 0 );
smb_setup_header (server, SMBnegprot, 0, plength);
p = SMB_BUF (packet);
for (i = 0; i < num_prots ; i++)
p = smb_encode_dialect (p, prots[i].name, strlen (prots[i].name));
SHOWMSG("Request SMBnegprot...");
result = smb_request_ok (server, SMBnegprot, 1, -1, error_ptr);
if (result < 0)
{
SHOWMSG("Failure requesting SMBnegprot");
goto out;
}
p = SMB_VWV (packet);
p = smb_decode_word (p, &dialect_index);
/* If the server does not support any of the listed
* dialects, ist must return a dialect index of 0xFFFF.
*/
if(dialect_index > num_prots || dialect_index == 0xFFFFU)
{
SHOWMSG("Unsupported dialect");
(*error_ptr) = error_unsupported_dialect;
result = -1;
goto out;
}
server->protocol = prots[dialect_index].prot;
D(("Server wants %s protocol.",prots[dialect_index].name));
if (server->protocol > PROTOCOL_LANMAN1)
{
int user_len = strlen (server->mount_data.username);
dword server_sesskey;
/*
D(("password = %s",server->mount_data.password));
*/
D(("usernam = %s",server->mount_data.username));
D(("blkmode = %ld",WVAL (packet, smb_vwv5)));
/* NT LAN Manager or newer. */
if (server->protocol >= PROTOCOL_NT1)
{
server->security_mode = (*p++);
SHOWMSG("NT LAN Manager or newer");
/* Skip "max mpx count" (1 word) and "max number vcs" (1 word). */
p += 2 * sizeof(word);
p = smb_decode_dword(p, &max_buffer_size);
SHOWVALUE (max_buffer_size);
#if defined(OVERRIDE_SERVER_MAX_BUFFER_SIZE)
{
max_buffer_size = OVERRIDE_SERVER_MAX_BUFFER_SIZE;
}
#endif /* OVERRIDE_SERVER_MAX_BUFFER_SIZE */
p = smb_decode_dword(p, &server->max_raw_size);
SHOWVALUE (server->max_raw_size);
p = smb_decode_dword(p, &server_sesskey);
p = smb_decode_dword(p, &server->capabilities);
/* Skip "system time" (1 qword) and "server time zone" (1 word). */
p += 2 * sizeof(dword) + sizeof(word);
server->crypt_key_length = (*p);
SHOWVALUE(server->crypt_key_length);
memcpy(server->crypt_key,SMB_BUF(packet),server->crypt_key_length);
}
/* LAN Manager 2.0 or older */
else
{
word blkmode;
SHOWMSG("LAN Manager 2.0 or older");
server->security_mode = BVAL(packet, smb_vwv1);
max_buffer_size = WVAL (packet, smb_vwv2);
SHOWVALUE (max_buffer_size);
#if defined(OVERRIDE_SERVER_MAX_BUFFER_SIZE)
{
max_buffer_size = OVERRIDE_SERVER_MAX_BUFFER_SIZE;
}
#endif /* OVERRIDE_SERVER_MAX_BUFFER_SIZE */
/* Maximum raw read/write size is fixed to 65535 bytes. */
server->max_raw_size = 65535;
blkmode = WVAL (packet, smb_vwv5);
server_sesskey = DVAL (packet, smb_vwv6);
/* Crypt key size is fixed to 8 bytes. */
server->crypt_key_length = 8;
memcpy(server->crypt_key,SMB_BUF(packet),server->crypt_key_length);
/* We translate this into capabilities. According to the
* LAN Manager 1.x/2.0 documentation both bits 0+1 being set
* means the same thing as CAP_RAW_MODE being set.
*/
if((blkmode & 3) == 3)
server->capabilities = CAP_RAW_MODE;
}
SHOWVALUE(server->security_mode);
if(server->security_mode & NEGOTIATE_ENCRYPT_PASSWORDS)
{
SHOWMSG("encrypted passwords required");
/* Maximum password length for smb_encrypt() is 14 characters, which
* does not include the terminating NUL byte.
*/
smb_encrypt(server->mount_data.password,server->crypt_key,oem_password);
oem_password_len = 24;
/* Maximum password length for smb_nt_encrypt() is 128 characters, which
* does not include the terminating NUL byte.
*/
smb_nt_encrypt(server->mount_data.password,server->crypt_key,unicode_password);
unicode_password_len = 24;
}
else
{
SHOWMSG("plain text passwords sufficient");
/* Note: the password is an array of bytes, not a NUL-terminated string. */
oem_password_len = strlen(server->mount_data.password);
unicode_password_len = 0;
}
/* If in share level security then don't send a password just now, it will follow later. */
if((server->security_mode & NEGOTIATE_USER_SECURITY) == 0)
{
SHOWMSG("share level security; not sending a password or user name just now");
oem_password_len = 0;
unicode_password_len = 0;
user_len = 0;
}
else
{
SHOWMSG("user share level security");
}
SHOWVALUE(oem_password_len);
SHOWVALUE(unicode_password_len);
D(("workgroup = %s", server->mount_data.workgroup_name));
/* NT LAN Manager or newer. */
if (server->protocol >= PROTOCOL_NT1)
{
const char *native_os = "AmigaOS";
const char *native_lanman = VERS;
dword client_capabilities;
int num_bytes;
SHOWMSG("server->protocol >= PROTOCOL_NT1");
client_capabilities = CAP_LARGE_READX|CAP_LARGE_WRITEX|CAP_NT_FIND|CAP_LARGE_FILES;
if(server->use_unicode && (server->capabilities & CAP_UNICODE) != 0)
{
/* We may be able to use Unicode strings during the session setup,
* but the server may not understand it (Windows 7 seems to be
* unable to decode the strings), so we delay setting the SMB message
* flag "this message may contain Unicode strings" until we have
* completed the session setup.
*/
if(server->session_setup_delay_unicode)
{
SHOWMSG("Unicode support possible; delaying enabling it...");
enable_unicode = TRUE;
}
else
{
SHOWMSG("Unicode support enabled");
server->unicode_enabled = TRUE;
}
client_capabilities |= CAP_UNICODE;
}
else
{
SHOWMSG("Unicode support disabled");
}
if(server->unicode_enabled)
{
/* Use plain text passwords? Remember to transmit the
* password in UTF16LE-encoding. Note that we have to
* use the UTF16LE-encoded form, and not the OEM
* form.
*/
if((server->security_mode & NEGOTIATE_ENCRYPT_PASSWORDS) == 0)
{
SHOWMSG("plain text passwords will be sent; must be unicode-encoded.");
unicode_password_len = 2 * oem_password_len;
oem_password_len = 0;
}
ASSERT( smb_payload_size(server, 13, oem_password_len + unicode_password_len + 2 * (user_len+1) + 2 * (strlen (server->mount_data.workgroup_name)+1) + 2 * (strlen (native_os)+1) + 2 * (strlen (native_lanman)+1)) >= 0 );
smb_setup_header (server, SMBsesssetupX, 13, oem_password_len + unicode_password_len + 2 * (user_len+1) + 2 * (strlen (server->mount_data.workgroup_name)+1) + 2 * (strlen (native_os)+1) + 2 * (strlen (native_lanman)+1));
}
else
{
ASSERT( smb_payload_size(server, 13, oem_password_len + unicode_password_len + user_len+1 + strlen (server->mount_data.workgroup_name)+1 + strlen (native_os)+1 + strlen (native_lanman)+1) >= 0 );
smb_setup_header (server, SMBsesssetupX, 13, oem_password_len + unicode_password_len + user_len+1 + strlen (server->mount_data.workgroup_name)+1 + strlen (native_os)+1 + strlen (native_lanman)+1);
}
WSET (packet, smb_vwv0, 0xff); /* AndXCommand+AndXReserved */
WSET (packet, smb_vwv1, 0); /* AndXOffset */
WSET (packet, smb_vwv2, given_max_xmit); /* MaxBufferSize */
WSET (packet, smb_vwv3, 2); /* MaxMpxCount */
WSET (packet, smb_vwv4, 0); /* VcNumber */
DSET (packet, smb_vwv5, server_sesskey); /* SessionKey */
WSET (packet, smb_vwv7, oem_password_len); /* OEMPasswordLen */
WSET (packet, smb_vwv8, unicode_password_len); /* UnicodePasswordLen */
DSET (packet, smb_vwv9, 0); /* Reserved */
DSET (packet, smb_vwv11, client_capabilities); /* Capabilities */
p = SMB_BUF (packet);
num_bytes = 0;
/* Add the encrypted passwords now? */
if(server->security_mode & NEGOTIATE_ENCRYPT_PASSWORDS)
{
SHOWMSG("adding encrypted passwords");
memcpy (&p[num_bytes], oem_password, oem_password_len);
num_bytes += oem_password_len;
memcpy (&p[num_bytes], unicode_password, unicode_password_len);
num_bytes += unicode_password_len;
}
/* No, just add the plain text password. */
else
{
SHOWMSG("adding plain text password");
/* Add either the Unicode password or the OEM password,
* but not both.
*/
if(server->unicode_enabled)
{
int i,j;
/* Simplistic conversion to UTF16LE -- we do this here
* because the password must not be NUL-terminated, which
* copy_latin1_to_utf16le() would do.
*/
for(i = j = 0 ; i < unicode_password_len ; i += 2, j++)
{
p[num_bytes++] = server->mount_data.password[j];
p[num_bytes++] = 0;
}
}
else
{
memcpy (&p[num_bytes], server->mount_data.password, oem_password_len);
num_bytes += oem_password_len;
}
}
/* Add a padding byte to align the following Unicode
* strings, if necessary.
*/
if(server->unicode_enabled && (num_bytes % 2) == 0)
{
SHOWMSG("adding a padding byte");
p[num_bytes++] = 0;
}
else
{
SHOWMSG("no padding byte necessary");
}
p += num_bytes;
/* Add the user name, the workgroup name, etc. as Unicode strings? */
if(server->unicode_enabled)
{
const char * s;
int l;
copy_latin1_to_utf16le(p,2 * (user_len + 1),server->mount_data.username,user_len);
p += 2 * (user_len + 1);
s = server->mount_data.workgroup_name;
l = strlen(s);
copy_latin1_to_utf16le(p,2 * (l + 1),s,l);
p += 2 * (l + 1);
s = native_os;
l = strlen(s);
copy_latin1_to_utf16le(p,2 * (l + 1),s,l);
p += 2 * (l + 1);
s = native_lanman;
l = strlen(s);
copy_latin1_to_utf16le(p,2 * (l + 1),s,l);
}
/* No, just use OEM strings. */
else
{
if(user_len == 0)
{
(*p++) = '\0';
}
else
{
memcpy (p, server->mount_data.username, user_len + 1);
p += user_len + 1;
}
strcpy (p, server->mount_data.workgroup_name);
p += strlen (p) + 1;
strcpy (p, native_os);
p += strlen (p) + 1;
strcpy (p, native_lanman);
}
}
/* LAN Manager 2.0 or older */
else
{
ASSERT( smb_payload_size(server, 10, oem_password_len + user_len+1) >= 0 );
smb_setup_header (server, SMBsesssetupX, 10, oem_password_len + user_len+1);
WSET (packet, smb_vwv0, 0xff); /* No further ANDX command */
WSET (packet, smb_vwv1, 0); /* ANDX offset = 0 */
WSET (packet, smb_vwv2, given_max_xmit); /* maximum buffer size */
WSET (packet, smb_vwv3, 2); /* maximum mpx count; should be copied from server */
WSET (packet, smb_vwv4, 0); /* server->pid */
DSET (packet, smb_vwv5, server_sesskey);
WSET (packet, smb_vwv7, oem_password_len); /* case-sensitive password length */
WSET (packet, smb_vwv8, 0); /* no encrypted password */
p = SMB_BUF (packet);
/* Password text is not NUL-terminated. */
memcpy (p, server->mount_data.password, oem_password_len);
p += oem_password_len;
/* User name must be NUL-terminated. */
if(user_len > 0)
memcpy (p, server->mount_data.username, user_len+1);
else
(*p) = 0;
}
result = smb_request_ok (server, SMBsesssetupX, 3, 0, error_ptr);
if (result < 0)
{
SHOWMSG("SMBsessetupX fail");
goto out;
}
smb_decode_word (packet + 32, &server->server_uid);
}
else
{
max_buffer_size = server->max_buffer_size;
#if defined(OVERRIDE_SERVER_MAX_BUFFER_SIZE)
{
max_buffer_size = OVERRIDE_SERVER_MAX_BUFFER_SIZE;
}
#endif /* OVERRIDE_SERVER_MAX_BUFFER_SIZE */
server->capabilities = 0;
oem_password_len = 0;
}
if(server->protocol > PROTOCOL_LANMAN1 &&
(server->security_mode & (NEGOTIATE_USER_SECURITY|NEGOTIATE_ENCRYPT_PASSWORDS)) == (NEGOTIATE_USER_SECURITY|NEGOTIATE_ENCRYPT_PASSWORDS))
{
int server_name_len;
int service_len;
int share_name_len;
int share_name_size;
int padding_needed;
SHOWMSG("protocol is LAN Manager 2.0 or better, user level access is enabled, encrypted passwords are used.");
server_name_len = strlen(server->mount_data.server_name);
service_len = strlen(server->mount_data.service);
share_name_len = 2 + server_name_len + 1 + service_len;
share_name = malloc(share_name_len+1);
if(share_name == NULL)
{
SHOWMSG("No memory! Bailing out.");
(*error_ptr) = ENOMEM;
result = -1;
goto out;
}
memcpy(share_name,"//",2);
memcpy(&share_name[2],server->mount_data.server_name,server_name_len);
share_name[2 + server_name_len] = '/';
memcpy(&share_name[2 + server_name_len + 1],server->mount_data.service,service_len+1); /* Length includes terminating NUL byte. */
/* Flip the slashes, so they become backslashes. */
for(i = 0 ; i < share_name_len ; i++)
{
if(share_name[i] == '/')
share_name[i] = '\\';
}
string_toupper(share_name);
D(("share_name = '%s'", escape_name(share_name)));
if(server->unicode_enabled)
share_name_size = 2 * (share_name_len + 1);
else
share_name_size = share_name_len + 1;
if(server->unicode_enabled && (oem_password_len % 2) == 0)
padding_needed = 1;
else
padding_needed = 0;
ASSERT( smb_payload_size(server, 4, oem_password_len + padding_needed + share_name_size + strlen(dev)+1) >= 0 );
smb_setup_header (server, SMBtconX, 4, oem_password_len + padding_needed + share_name_size + strlen(dev)+1);
WSET (packet, smb_vwv0, 0xFF);
WSET (packet, smb_vwv3, oem_password_len);
p = SMB_BUF (packet);
memcpy(p,oem_password,oem_password_len);
p += oem_password_len;
if(padding_needed)
(*p++) = 0;
if(server->unicode_enabled)
copy_latin1_to_utf16le(p,share_name_size,share_name,share_name_len);
else
memcpy(p,share_name,share_name_size);
p += share_name_size;
strcpy(p,dev);
SHOWMSG("Using SMB_TREE_CONNECT_ANDX");
result = smb_request_ok (server, SMBtconX, 3, 0, error_ptr);
if (result < 0)
{
SHOWVALUE(SMB_WCT(packet));
SHOWMSG("SMBtconX not verifie");
goto out;
}
SHOWVALUE(SMB_WCT(packet));
server->tid = WVAL(packet,smb_tid);
}
else
{
word decoded_max_xmit;
SHOWMSG("Using SMB_TREE_CONNECT");
ASSERT( NOT server->unicode_enabled );
ASSERT( smb_payload_size(server, 0, 6 + strlen (server->mount_data.service) + strlen (server->mount_data.password) + strlen (dev)) >= 0 );
/* Fine! We have a connection, send a tcon message. */
smb_setup_header (server, SMBtcon, 0, 6 + strlen (server->mount_data.service) + strlen (server->mount_data.password) + strlen (dev));
p = SMB_BUF (packet);
p = smb_encode_ascii (p, server->mount_data.service, strlen (server->mount_data.service));
p = smb_encode_ascii (p, server->mount_data.password, strlen (server->mount_data.password));
(void) smb_encode_ascii (p, dev, strlen (dev));
result = smb_request_ok (server, SMBtcon, 2, 0, error_ptr);
if (result < 0)
{
SHOWMSG("SMBtcon not verified.");
goto out;
}
SHOWMSG("OK! Managed to set up SMBtcon!");
p = SMB_VWV (packet);
p = smb_decode_word (p, &decoded_max_xmit);
max_buffer_size = decoded_max_xmit;
#if defined(OVERRIDE_SERVER_MAX_BUFFER_SIZE)
{
max_buffer_size = OVERRIDE_SERVER_MAX_BUFFER_SIZE;
}
#endif /* OVERRIDE_SERVER_MAX_BUFFER_SIZE */
(void) smb_decode_word (p, &server->tid);
}
D(("max_buffer_size = %ld, tid = %ld", max_buffer_size, server->tid));
/* Let's get paranoid. Make sure that we can actually receive
* as much data as the buffer size allows, in a single consecutive
* buffer which follows the SMB message header.
*/
if(packet_size < (int)max_buffer_size)
{
SHOWVALUE(packet_size);
/* We need to allocate a larger packet buffer. */
packet_size = max_buffer_size;
D(("packet size updated to %ld bytes", packet_size));
free (server->transmit_buffer);
server->transmit_buffer_size = packet_size;
/* Add a bit of fudge to account for the NetBIOS session
* header and whatever else might show up...
*/
server->transmit_buffer_allocation_size = server->transmit_buffer_size + safety_margin;
server->transmit_buffer = malloc (server->transmit_buffer_allocation_size);
if (server->transmit_buffer == NULL)
{
SHOWMSG("No memory! Bailing out.");
(*error_ptr) = ENOMEM;
result = -1;
goto out;
}
}
/* Finally, limit the amount of data to send to the server,
* if requested.
*/
if(8000 <= server->mount_data.given_max_xmit && server->mount_data.given_max_xmit < (int)max_buffer_size)
{
max_buffer_size = server->mount_data.given_max_xmit;
D(("maximum buffer size limited to %ld bytes", max_buffer_size));
}
server->max_buffer_size = max_buffer_size;
/* Now that the session has been set up properly,
* we may safely allow Unicode strings to be used
* in SMB messages.
*/
if(enable_unicode)
server->unicode_enabled = TRUE;
SHOWMSG("Normal exit");
result = 0;
out:
if(share_name != NULL)
free(share_name);
if(result < 0)
server->state = CONN_INVALID;
return result;
}
/* smb_proc_reconnect: server->transmit_buffer is allocated with
* server->max_buffer_size bytes if and only if we return >= 0
*/
int
smb_proc_connect (struct smb_server *server, int * error_ptr)
{
int result;
result = smb_proc_reconnect (server, error_ptr);
if ((result < 0) && (server->transmit_buffer != NULL))
{
free (server->transmit_buffer);
server->transmit_buffer = NULL;
}
return result;
}
static void
smb_printerr (int class, int num)
{
const err_code_struct *err;
int i, j;
for (i = 0; err_classes[i].class; i++)
{
if (err_classes[i].code != class)
continue;
if (!err_classes[i].err_msgs)
{
report_error("%s - %ld.", err_classes[i].class, num);
D(("%s - %ld", err_classes[i].class, num));
return;
}
err = (err_code_struct *)err_classes[i].err_msgs;
for (j = 0; err[j].name; j++)
{
if (num != err[j].code)
continue;
report_error ("%s - %s (%s).", err_classes[i].class, err[j].name, err[j].message);
D(("%s - %s (%s)", err_classes[i].class, err[j].name, err[j].message));
return;
}
}
report_error ("Unknown error - (%ld, %ld).", class, num);
D(("Unknown error - (%ld, %ld)", class, num));
}