mirror of
https://github.com/obarthel/amiga-smbfs.git
synced 2025-12-08 14:58:35 +00:00
Added more diagnostic output and debug functionality, e.g. regarding how many directory caches are currently being used. Long name directory scanning is a bit more paranoid when retrying a scan operation which might have been triggered by the server connection getting dropped. Added the new READONLY option (why stop at 47 options when you can have 48?) which makes the file system read-only by default and does not require you to use the "Lock" shell command to achieve the same effect. Note that the shell "Lock" command cannot be used to turn the read-only mode off again. Added support for the smb:// URI scheme, which means that instead of using "smbfs username=barney password=secret domain=workgroup //nas:445/share" you can now use "smbfs smb://workgroup;barney:secret@nas:445/share" instead. It is a bit shorter than the alternative and has an advantage in allowing you to reuse smb:// URLs which you know work well on other systems.
5042 lines
125 KiB
C
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 occured 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 a 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 an 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 a 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 a 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, §ors_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, §ors_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 a 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));
|
|
}
|