Files
amiga-smbfs/source_code/proc.c
T
Olaf Barthel ed8def5209 - Initial import into sourceforge.net CVS.
git-svn-id: file:///Users/olsen/Code/migration-svn-zu-git/logical-line-staging/amiga-smbfs/trunk@3 26594b9e-b914-4e86-b7a1-9402bd427170
2005-05-24 13:22:14 +00:00

2617 lines
64 KiB
C

/*
* $Id: proc.c,v 1.1.1.1 2005-05-24 13:22:12 obarthel Exp $
*
* :ts=8
*
* 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 <olsen@sourcery.han.de>
*/
#include "smbfs.h"
/*****************************************************************************/
#include <smb/smb.h>
#include <smb/smbno.h>
#include <smb/smb_fs.h>
/*****************************************************************************/
#define SMB_VWV(packet) ((packet) + SMB_HEADER_LEN)
#define SMB_CMD(packet) ((packet)[8])
#define SMB_WCT(packet) ((packet)[SMB_HEADER_LEN - 1])
#define SMB_BCC(packet) smb_bcc(packet)
#define SMB_BUF(packet) ((packet) + SMB_HEADER_LEN + SMB_WCT(packet) * 2 + 2)
#define SMB_DIRINFO_SIZE 43
#define SMB_STATUS_SIZE 21
/*****************************************************************************/
static INLINE byte * smb_encode_word(byte *p, word data);
static INLINE byte * smb_decode_word(byte *p, word *data);
static INLINE int utc2local(int time_value);
static INLINE int local2utc(int time_value);
static INLINE word smb_bcc(byte *packet);
static INLINE int smb_verify(byte *packet, int command, int wct, int bcc);
static byte *smb_encode_dialect(byte *p, const byte *name, int len);
static byte *smb_encode_ascii(byte *p, const byte *name, int len);
static void smb_encode_vblock(byte *p, const byte *data, word len, int unused_fs);
static byte *smb_decode_data(byte *p, byte *data, word *data_len, int fs);
static byte *smb_name_mangle(byte *p, const byte *name);
static int date_dos2unix(unsigned short time_value, unsigned short date);
static void date_unix2dos(int unix_date, unsigned short *time_value, unsigned short *date);
static INLINE int smb_valid_packet(byte *packet);
static int smb_errno(int errcls, int error);
static int smb_request_ok(struct smb_server *s, int command, int wct, int bcc);
static int smb_retry(struct smb_server *server);
static int smb_request_ok_unlock(struct smb_server *s, int command, int wct, int bcc);
static byte *smb_setup_header(struct smb_server *server, byte command, word wct, word bcc);
static byte *smb_setup_header_exclusive(struct smb_server *server, byte command, word wct, word bcc);
static int smb_proc_do_create(struct smb_server *server, const char *path, int len, struct smb_dirent *entry, word command);
static char *smb_decode_dirent(char *p, struct smb_dirent *entry);
static int smb_proc_readdir_short(struct smb_server *server, char *path, int fpos, int cache_size, struct smb_dirent *entry);
static char *smb_decode_long_dirent(char *p, struct smb_dirent *finfo, int level);
static int smb_proc_readdir_long(struct smb_server *server, char *path, int fpos, int cache_size, struct smb_dirent *entry);
static int smb_proc_reconnect(struct smb_server *server);
static void smb_printerr(int class, int num);
/*****************************************************************************/
/*****************************************************************************
*
* Encoding/Decoding section
*
*****************************************************************************/
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_decode_word (byte * p, word * data)
{
(*data) = (word) p[0] | p[1] << 8;
return &(p[2]);
}
byte *
smb_encode_smb_length (byte * p, dword len)
{
p[0] = p[1] = 0;
p[2] = (len & 0xFF00) >> 8;
p[3] = (len & 0xFF);
if (len > 0xFFFF)
p[1] |= 0x01;
return &p[4];
}
static byte *
smb_encode_dialect (byte * p, const byte * name, int len)
{
(*p++) = 2;
strcpy (p, name);
return p + len + 1;
}
static byte *
smb_encode_ascii (byte * p, const byte * name, int len)
{
(*p++) = 4;
strcpy (p, name);
return p + len + 1;
}
static void
smb_encode_vblock (byte * p, const byte * data, word len, int unused_fs)
{
(*p++) = 5;
p = smb_encode_word (p, len);
memcpy (p, data, len);
}
static byte *
smb_decode_data (byte * p, byte * data, word * data_len, int unused_fs)
{
word len;
if (!((*p) == 1 || (*p) == 5))
LOG (("Warning! Data block not starting with 1 or 5\n"));
len = WVAL (p, 1);
p += 3;
memcpy (data, p, len);
(*data_len) = len;
return p + 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;
}
static INLINE int
utc2local (int time_value)
{
int result;
result = time_value - GetTimeZoneDelta();
return result;
}
static INLINE int
local2utc (int time_value)
{
int result;
result = time_value + GetTimeZoneDelta();
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;
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 = MakeTime(&tm);
return(seconds);
}
/* Convert linear UNIX date to a MS-DOS time/date pair. */
static void
date_unix2dos (int unix_date, unsigned short *time_value, unsigned short *date)
{
struct tm tm;
LocalTime(unix_date,&tm);
(*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.
*
****************************************************************************/
dword
smb_len (byte * packet)
{
return (dword)( ((packet[1] & 0x1) << 16L) | (packet[2] << 8L) | (packet[3]) );
}
static INLINE word
smb_bcc (byte * packet)
{
int pos = SMB_HEADER_LEN + SMB_WCT (packet) * sizeof (word);
return (word)(packet[pos] | packet[pos + 1] << 8);
}
/* smb_valid_packet: We check if packet fulfills the basic
requirements of a smb packet */
static INLINE int
smb_valid_packet (byte * packet)
{
int result;
LOG (("len: %ld, wct: %ld, bcc: %ld\n", smb_len (packet), SMB_WCT (packet), SMB_BCC (packet)));
result = (packet[4] == 0xff
&& packet[5] == 'S'
&& packet[6] == 'M'
&& packet[7] == 'B'
&& (smb_len (packet) + 4 == (dword)(
SMB_HEADER_LEN + SMB_WCT (packet) * 2 + SMB_BCC (packet) + 2))) ? 0 : (-EIO);
return result;
}
/* 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 INLINE int
smb_verify (byte * packet, int command, int wct, int bcc)
{
return (SMB_CMD (packet) == command &&
SMB_WCT (packet) >= wct &&
(bcc == -1 || SMB_BCC (packet) >= bcc)) ? 0 : -EIO;
}
static int
smb_errno (int errcls, int error)
{
int result,i;
if (errcls)
smb_printerr (errcls, error);
if (errcls == ERRDOS)
{
static const int map[][2] =
{
{ ERRbadfunc,EINVAL },
{ ERRbadfile,ENOENT },
{ ERRbadpath,ENOENT },
{ ERRnofids,EMFILE },
{ ERRnoaccess,EACCES },
{ ERRbadfid,EBADF },
{ ERRbadmcb,EIO },
{ ERRnomem,ENOMEM },
{ ERRbadmem,EFAULT },
{ ERRbadenv,EIO },
{ ERRbadformat,EIO },
{ ERRbadaccess,EACCES },
{ ERRbaddata,E2BIG },
{ ERRbaddrive,ENXIO },
{ ERRremcd,EIO },
{ ERRdiffdevice,EXDEV },
{ ERRnofiles,0 },
{ ERRbadshare,ETXTBSY },
{ ERRlock,EDEADLK },
{ ERRfilexists,EEXIST },
{ 87,0 },/* Unknown error! */
{ 183,EEXIST },/* This next error seems to occur on an mv when
the destination exists */
{ -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 },
{ -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;
}
return(result);
}
#if DEBUG
static char
print_char (char c)
{
if ((c < ' ') || (c > '~'))
return '.';
return c;
}
static void
smb_dump_packet (byte * packet)
{
int i, j, len;
int errcls, error;
errcls = (int) packet[9];
error = (int) (int) (packet[11] | packet[12] << 8);
LOG (("smb_len = %ld valid = %ld \n", len = smb_len (packet), smb_valid_packet (packet)));
LOG (("smb_cmd = %ld smb_wct = %ld smb_bcc = %ld\n", packet[8], SMB_WCT (packet), SMB_BCC (packet)));
LOG (("smb_rcls = %ld smb_err = %ld\n", errcls, error));
if (errcls)
smb_printerr (errcls, error);
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 expect the server to be locked. Then we do the
request and check the answer completely. When smb_request_ok
returns 0, you can be quite sure that everything went well. When
the answer is <=0, the returned number is a valid unix errno. */
static int
smb_request_ok (struct smb_server *s, int command, int wct, int bcc)
{
int result;
int error;
s->rcls = 0;
s->err = 0;
result = smb_request (s);
if (result < 0)
{
LOG (("smb_request failed\n"));
}
else if ((error = smb_valid_packet (s->packet)) != 0)
{
LOG (("not a valid packet!\n"));
result = error;
}
else if (s->rcls != 0)
{
result = -smb_errno (s->rcls, s->err);
}
else if ((error = smb_verify (s->packet, command, wct, bcc)) != 0)
{
LOG (("smb_verify failed\n"));
result = error;
}
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 0,
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 result = 0;
if (server->state == CONN_VALID)
goto out;
if (smb_release (server) < 0)
{
LOG (("smb_retry: smb_release failed\n"));
server->state = CONN_RETRIED;
goto out;
}
if (smb_proc_reconnect (server) < 0)
{
LOG (("smb_proc_reconnect failed\n"));
server->state = CONN_RETRIED;
goto out;
}
server->state = CONN_VALID;
result = 1;
out:
return result;
}
static int
smb_request_ok_unlock (struct smb_server *s, int command, int wct, int bcc)
{
int result;
result = smb_request_ok (s, command, wct, bcc);
return result;
}
/* smb_setup_header: We completely set up the packet. You only have to
insert the command-specific fields */
static byte *
smb_setup_header (struct smb_server *server, byte command, word wct, word bcc)
{
dword xmit_len = SMB_HEADER_LEN + wct * sizeof (word) + bcc + 2;
byte *p = server->packet;
byte *buf = server->packet;
p = smb_encode_smb_length (p, xmit_len);
BSET (p, 0, 0xff);
BSET (p, 1, 'S');
BSET (p, 2, 'M');
BSET (p, 3, 'B');
BSET (p, 4, command);
p += 5;
memset (p, '\0', 19);
p += 19;
p += 8;
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, 0x8);
WSET (buf, smb_flg2, 0x3);
}
(*p++) = wct; /* wct */
p += 2 * wct;
WSET (p, 0, bcc);
return p + 2;
}
/* smb_setup_header_exclusive waits on server->lock and locks the
server, when it's free. You have to unlock it manually when you're
finished with server->packet! */
static byte *
smb_setup_header_exclusive (struct smb_server *server, byte command, word wct, word bcc)
{
byte * result;
result = smb_setup_header (server, command, wct, bcc);
return result;
}
/*****************************************************************************
*
* File operation section.
*
****************************************************************************/
int
smb_proc_open (struct smb_server *server, const char *pathname, int len, struct smb_dirent *entry)
{
int error;
char *p;
char *buf = server->packet;
const word o_attr = aSYSTEM | aHIDDEN | aDIR;
LOG (("path=%s\n", pathname));
retry:
p = smb_setup_header (server, SMBopen, 2, 2 + len);
WSET (buf, smb_vwv0, 0x42); /* read/write */
WSET (buf, smb_vwv1, o_attr);
smb_encode_ascii (p, pathname, len);
if ((error = smb_request_ok (server, SMBopen, 7, 0)) < 0)
{
if (smb_retry (server))
goto retry;
if (error != -EACCES)
goto out;
p = smb_setup_header (server, SMBopen, 2, 2 + len);
WSET (buf, smb_vwv0, 0x40); /* read only */
WSET (buf, smb_vwv1, o_attr);
smb_encode_ascii (p, pathname, len);
if ((error = smb_request_ok (server, SMBopen, 7, 0)) < 0)
{
if (smb_retry (server))
goto retry;
goto out;
}
}
/* We should now have data in vwv[0..6]. */
entry->fileid = WVAL (buf, smb_vwv0);
entry->attr = WVAL (buf, smb_vwv1);
entry->ctime = entry->atime = entry->mtime = local2utc (DVAL (buf, smb_vwv2));
entry->size = DVAL (buf, smb_vwv4);
entry->opened = 1;
out:
return error;
}
/* 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 mtime)
{
char *buf = server->packet;
int local_time;
int result;
if(mtime != 0 && mtime != 0xffffffff)
{
/* 0 and 0xffffffff mean: do not set mtime */
local_time = utc2local (mtime);
}
else
{
local_time = mtime;
}
smb_setup_header_exclusive (server, SMBclose, 3, 0);
WSET (buf, smb_vwv0, fileid);
DSET (buf, smb_vwv1, local_time);
result = smb_request_ok_unlock (server, SMBclose, 0, 0);
return result;
}
/* In smb_proc_read and smb_proc_write we do not retry, because the
file-id would not be valid after a reconnection. */
/* smb_proc_read: fs indicates if it should be copied with
memcpy_tofs. */
int
smb_proc_read (struct smb_server *server, struct smb_dirent *finfo, off_t offset, long count, char *data, int fs)
{
word returned_count, data_len;
char *buf = server->packet;
int result;
int error;
smb_setup_header_exclusive (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);
if ((error = smb_request_ok (server, SMBread, 5, -1)) < 0)
{
result = error;
goto out;
}
returned_count = WVAL (buf, smb_vwv0);
smb_decode_data (SMB_BUF (server->packet), data, &data_len, fs);
if (returned_count != data_len)
{
LOG (("Warning, returned_count != data_len\n"));
LOG (("ret_c=%ld, data_len=%ld\n", returned_count, data_len));
}
else
{
LOG (("ret_c=%ld, data_len=%ld\n", returned_count, data_len));
}
result = data_len;
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, off_t offset, long count, char *data)
{
char *buf = server->packet;
int result;
smb_setup_header_exclusive (server, SMBreadbraw, 8, 0);
WSET (buf, smb_vwv0, finfo->fileid);
DSET (buf, smb_vwv1, offset);
WSET (buf, smb_vwv3, count);
WSET (buf, smb_vwv4, 0);
DSET (buf, smb_vwv5, 0);
result = smb_request_read_raw (server, data, count);
return result;
}
int
smb_proc_write (struct smb_server *server, struct smb_dirent *finfo, off_t offset, long count, const char *data)
{
int res;
char *buf = server->packet;
byte *p;
p = smb_setup_header_exclusive (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;
WSET (p, 0, count);
memcpy (p + 2, data, count);
if ((res = smb_request_ok (server, SMBwrite, 1, 0)) >= 0)
res = WVAL (buf, smb_vwv0);
return res;
}
/* count must be <= 65535 */
int
smb_proc_write_raw (struct smb_server *server, struct smb_dirent *finfo, off_t offset, long count, const char *data)
{
char *buf = server->packet;
int result;
long len = server->max_xmit - 4;
byte *p;
if (len >= count)
len = 0;
else
len = count - len; /* transfer the larger part using the second packet */
p = smb_setup_header_exclusive (server, SMBwritebraw, 11, len);
WSET (buf, smb_vwv0, finfo->fileid);
DSET (buf, smb_vwv1, count);
DSET (buf, smb_vwv3, offset);
DSET (buf, smb_vwv5, 0); /* timeout */
WSET (buf, smb_vwv7, 1); /* send final result response */
DSET (buf, smb_vwv8, 0); /* reserved */
if (server->protocol > PROTOCOL_COREPLUS)
{
WSET (buf, smb_vwv10, len);
WSET (buf, smb_vwv11, p - smb_base(buf));
}
if (len > 0)
memcpy (p, data, len);
result = smb_request_ok (server, SMBwritebraw, 1, 0);
LOG (("first request returned %ld\n", result));
if (result < 0)
goto out;
result = smb_request_write_raw (server, data + len, count - len);
LOG(("raw request returned %ld\n", result));
if (result > 0)
{
int error;
/* We have to do the checks of smb_request_ok here as well */
if ((error = smb_valid_packet (server->packet)) != 0)
{
LOG (("not a valid packet!\n"));
result = error;
}
else if (server->rcls != 0)
{
result = -smb_errno (server->rcls, server->err);
}
else if ((error = smb_verify (server->packet, SMBwritec, 1, 0)) != 0)
{
LOG (("smb_verify failed\n"));
result = error;
}
/* If everything went fine, the whole block has been transfered. */
if (result == (count - len))
result = count;
}
out:
return result;
}
int
smb_proc_lseek (struct smb_server *server, struct smb_dirent *finfo, off_t offset, int mode, off_t * new_position_ptr)
{
char *buf = server->packet;
int error;
retry:
smb_setup_header (server, SMBlseek, 4,0);
WSET (buf, smb_vwv0, finfo->fileid);
WSET (buf, smb_vwv1, mode);
DSET (buf, smb_vwv2, offset);
if ((error = smb_request_ok (server, SMBlseek, 1, 0)) < 0)
{
if (smb_retry (server))
goto retry;
}
else
{
(*new_position_ptr) = DVAL(buf, smb_vwv0);
}
return error;
}
/* 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, struct smb_lkrng *locks, int num_entries, int mode, long timeout)
{
int result;
int num_locks, num_unlocks;
char *buf = server->packet;
char *data;
struct smb_lkrng *p;
int i;
num_locks = num_unlocks = 0;
if (mode & 2)
num_unlocks = num_entries;
else
num_locks = num_entries;
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);
BSET (buf, smb_vwv3, mode & 1);
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);
}
if ((result = smb_request_ok (server, SMBlockingX, 0, 0)) < 0)
{
if (smb_retry (server))
goto retry;
}
return result;
}
/* smb_proc_do_create: We expect entry->attry & entry->ctime to be set. */
static int
smb_proc_do_create (struct smb_server *server, const char *path, int len, struct smb_dirent *entry, word command)
{
int error;
char *p;
char *buf = server->packet;
int local_time;
retry:
p = smb_setup_header (server, command, 3, len + 2);
WSET (buf, smb_vwv0, entry->attr);
local_time = utc2local (entry->ctime);
DSET (buf, smb_vwv1, local_time);
smb_encode_ascii (p, path, len);
if ((error = smb_request_ok (server, command, 1, 0)) < 0)
{
if (smb_retry (server))
goto retry;
goto out;
}
entry->opened = 1;
entry->fileid = WVAL (buf, smb_vwv0);
smb_proc_close (server, entry->fileid, entry->mtime);
out:
return error;
}
int
smb_proc_create (struct smb_server *server, const char *path, int len, struct smb_dirent *entry)
{
return smb_proc_do_create (server, path, len, entry, SMBcreate);
}
int
smb_proc_mv (struct smb_server *server, const char *opath, const int olen, const char *npath, const int nlen)
{
char *p;
char *buf = server->packet;
int result;
retry:
p = smb_setup_header (server, SMBmv, 1, olen + nlen + 4);
WSET (buf, smb_vwv0, 0);
p = smb_encode_ascii (p, opath, olen);
smb_encode_ascii (p, npath, olen);
if ((result = smb_request_ok (server, SMBmv, 0, 0)) < 0)
{
if (smb_retry (server))
goto retry;
}
return result;
}
int
smb_proc_mkdir (struct smb_server *server, const char *path, const int len)
{
char *p;
int result;
retry:
p = smb_setup_header (server, SMBmkdir, 0, 2 + len);
smb_encode_ascii (p, path, len);
if ((result = smb_request_ok (server, SMBmkdir, 0, 0)) < 0)
{
if (smb_retry (server))
goto retry;
}
return result;
}
int
smb_proc_rmdir (struct smb_server *server, const char *path, const int len)
{
char *p;
int result;
retry:
p = smb_setup_header (server, SMBrmdir, 0, 2 + len);
smb_encode_ascii (p, path, len);
if ((result = smb_request_ok (server, SMBrmdir, 0, 0)) < 0)
{
if (smb_retry (server))
goto retry;
}
return result;
}
int
smb_proc_unlink (struct smb_server *server, const char *path, const int len)
{
char *p;
char *buf = server->packet;
int result;
retry:
p = smb_setup_header (server, SMBunlink, 1, 2 + len);
WSET (buf, smb_vwv0, 0);
smb_encode_ascii (p, path, len);
if ((result = smb_request_ok (server, SMBunlink, 0, 0)) < 0)
{
if (smb_retry (server))
goto retry;
}
return result;
}
int
smb_proc_trunc (struct smb_server *server, word fid, dword length)
{
char *p;
char *buf = server->packet;
int result;
p = smb_setup_header (server, SMBwrite, 5, 3);
WSET (buf, smb_vwv0, fid);
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);
if (result >= 0)
result = DVAL(buf, smb_vwv0);
return result;
}
static char *
smb_decode_dirent (char *p, struct smb_dirent *entry)
{
size_t name_size;
p += SMB_STATUS_SIZE; /* reserved (search_status) */
entry->attr = BVAL (p, 0);
entry->mtime = entry->atime = entry->ctime = date_dos2unix (WVAL (p, 1), WVAL (p, 3));
entry->size = DVAL (p, 5);
name_size = 13;
if(name_size > entry->complete_path_size-1)
name_size = entry->complete_path_size-1;
memcpy (entry->complete_path, p + 9, name_size);
entry->complete_path[name_size] = '\0';
LOG (("smb_decode_dirent: path = %s\n", entry->complete_path));
return p + 22;
}
/* 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, char *path, int fpos, int cache_size, struct smb_dirent *entry)
{
char *p;
char *buf;
int error;
int result = 0;
int i;
int first, total_count;
struct smb_dirent *current_entry;
word bcc;
word count;
char status[SMB_STATUS_SIZE];
int entries_asked = (server->max_xmit - 100) / SMB_DIRINFO_SIZE;
int dirlen = strlen (path);
char * mask;
mask = malloc(dirlen + 4 + 1);
if (mask == NULL)
{
result = (-ENOMEM);
goto out;
}
strcpy (mask, path);
strcat (mask, "\\*.*");
LOG (("SMB call readdir %ld @ %ld\n", cache_size, fpos));
LOG ((" mask = %s\n", mask));
buf = server->packet;
retry:
first = 1;
total_count = 0;
current_entry = entry;
while (1)
{
if (first == 1)
{
p = smb_setup_header (server, SMBsearch, 2, 5 + strlen (mask));
WSET (buf, smb_vwv0, entries_asked);
WSET (buf, smb_vwv1, aDIR);
p = smb_encode_ascii (p, mask, strlen (mask));
(*p++) = 5;
(void) smb_encode_word (p, 0);
}
else
{
p = smb_setup_header (server, SMBsearch, 2, 5 + SMB_STATUS_SIZE);
WSET (buf, smb_vwv0, entries_asked);
WSET (buf, smb_vwv1, aDIR);
p = smb_encode_ascii (p, "", 0);
(void) smb_encode_vblock (p, status, SMB_STATUS_SIZE, 0);
}
if ((error = smb_request_ok (server, SMBsearch, 1, -1)) < 0)
{
if ((server->rcls == ERRDOS) && (server->err == ERRnofiles))
{
result = total_count - fpos;
goto unlock_return;
}
else
{
if (smb_retry (server))
goto retry;
result = error;
goto unlock_return;
}
}
p = SMB_VWV (server->packet);
p = smb_decode_word (p, &count); /* vwv[0] = count-returned */
p = smb_decode_word (p, &bcc);
first = 0;
if (count <= 0)
{
result = total_count - fpos;
goto unlock_return;
}
if (bcc != count * SMB_DIRINFO_SIZE + 3)
{
result = -EIO;
goto unlock_return;
}
p += 3; /* Skipping VBLOCK header (5, length lo, length hi). */
/* Read the last entry into the status field. */
memcpy (status, SMB_BUF (server->packet) + 3 + (count - 1) * SMB_DIRINFO_SIZE, SMB_STATUS_SIZE);
/* Now we are ready to parse smb directory entries. */
for (i = 0; i < count; i++)
{
if (total_count < fpos)
{
p += SMB_DIRINFO_SIZE;
LOG (("smb_proc_readdir: skipped entry; total_count = %ld, i = %ld, fpos = %ld\n", total_count, i, fpos));
}
else if (total_count >= fpos + cache_size)
{
result = total_count - fpos;
goto unlock_return;
}
else
{
p = smb_decode_dirent (p, current_entry);
current_entry += 1;
}
total_count += 1;
}
}
unlock_return:
if(mask != NULL)
free(mask);
out:
return result;
}
/*****************************************************************************/
#define MAX_DIGITS 4 /* NOTE: this is actually a hard-coded value (see below) */
/* Multiprecision arithmetics for beginners; we use 64 bits, chopped
* into 16 bit pieces each. That allows us to comfortably execute all
* calculations with 'long' arguments and not get into trouble because
* of overflows and carry bits that don't exist. The assumption is that
* the size of a 'long' is twice as large as that of a 'short'.
*/
struct digits
{
unsigned short d[MAX_DIGITS];
};
/* Divide a multiprecision number by a positive integer. This is
* essentially the division by a single "digit", as described in
* Donald E. Knuth's "The Art of Computer Programming, Volume 2
* Seminumerical Algorithms", 3rd edition, page 625 as the answer
* to exercise 16 on page 282 in section 4.3.1. We don't really
* need the more complex version since we just need to divide by
* 10,000 and then by 1,000. The complex version is much more
* complex than calling this division routine twice.
*/
static void
divide_by(struct digits * q,unsigned long d)
{
int m;
m = MAX_DIGITS;
while(m > 0 && q->d[m-1] == 0)
m--;
if(m > 0)
{
unsigned long r,s;
int j;
r = 0;
j = m - 1;
do
{
r <<= 16;
s = (r + q->d[j]);
q->d[j] = s / d;
r = s % d;
}
while(--j >= 0);
}
}
/* This is an application of algorithm S ('Subtraction of
* non-negative integers') described in Donald E. Knuth's
* "The Art of Computer Programming, Volume 2 / Seminumerical
* Algorithms", 3rd edition, page 267.
*/
static long
subtract_number(struct digits * u,unsigned long v)
{
long d;
/* This implementation takes advantage of the fact that
* the 'width' of 'v' (64 bits) is just half the size of
* 'u' (32 bits), which allows us to unroll the loop and
* leave out some of the arguments which would not
* contribute to the result anyway.
*/
/* First round */
d = ((long)u->d[0]) - (long)(v & 0xFFFF);
u->d[0] = d;
/* Second round */
d = ((long)u->d[1]) - ((long)((v >> 16) & 0xFFFF)) - (d < 0);
u->d[1] = d;
/* Third round */
d = ((long)u->d[2]) - (d < 0);
u->d[2] = d;
/* Fourth round */
d = ((long)u->d[3]) - (d < 0);
u->d[3] = d;
return(d < 0);
}
/* Interpret 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
interpret_long_date(char * p)
{
unsigned long hi,lo;
struct digits d;
time_t result;
int i,k;
/* Extract the 64 bit time value. */
lo = DVAL(p,0);
hi = DVAL(p,4);
/* Chop the time into handy 16 bit chunks. */
d.d[3] = hi >> 16;
d.d[2] = hi & 0xFFFF;
d.d[1] = lo >> 16;
d.d[0] = lo & 0xFFFF;
/* Divide by 10,000,000 in two easy pieces. */
divide_by(&d,10000);
divide_by(&d,1000);
/* Adjust by 369 years (11,644,473,600 seconds). */
k = 0;
for(i = 0 ; k == 0 && i < 4 ; i++)
k += subtract_number(&d,2911118400UL);
/* If the result did not produce an underflow or overflow,
* return the number of seconds encoded in the two least
* significant 'digits'.
*/
if(k == 0 && d.d[3] == 0 && d.d[2] == 0)
result = (time_t)((((unsigned long)d.d[1]) << 16) + d.d[0]);
else
result = 0;
return(result);
}
/*****************************************************************************/
static void
smb_get_dirent_name(char *p,int level,char ** name_ptr,int * len_ptr)
{
switch (level)
{
case 1: /* OS/2 understands this */
(*name_ptr) = p + 27;
(*len_ptr) = strlen(p + 27);
break;
case 2: /* this is what OS/2 uses */
(*name_ptr) = p + 31;
(*len_ptr) = strlen(p + 31);
break;
case 260: /* NT uses this, but also accepts 2 */
(*name_ptr) = p + 94;
(*len_ptr) = min (DVAL (p+60, 0), SMB_MAXNAMELEN);
break;
default:
(*name_ptr) = NULL;
(*len_ptr) = 0;
break;
}
}
/* interpret a long filename structure - this is mostly guesses at the
moment. The length of the structure is returned. The structure of
a long filename depends on the info level. 260 is used by NT and 2
is used by OS/2. */
static char *
smb_decode_long_dirent (char *p, struct smb_dirent *finfo, int level)
{
char *result;
switch (level)
{
case 1: /* OS/2 understands this */
#if DEBUG
{
char buffer[255];
memcpy(buffer,p + 27,sizeof(buffer)-1);
buffer[sizeof(buffer)-1] = '\0';
LOG(("type=%ld, name='%s'\n",level,buffer));
}
#endif /* DEBUG */
if (finfo != NULL)
{
strlcpy (finfo->complete_path, p + 27, finfo->complete_path_size);
finfo->len = strlen (finfo->complete_path);
finfo->size = DVAL (p, 16);
finfo->attr = BVAL (p, 24);
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));
}
result = p + 28 + BVAL (p, 26);
break;
case 2: /* this is what OS/2 uses */
#if DEBUG
{
char buffer[255];
memcpy(buffer,p + 31,sizeof(buffer)-1);
buffer[sizeof(buffer)-1] = '\0';
LOG(("type=%ld, name='%s'\n",level,buffer));
}
#endif /* DEBUG */
if (finfo != NULL)
{
strlcpy (finfo->complete_path, p + 31, finfo->complete_path_size);
finfo->len = strlen (finfo->complete_path);
finfo->size = DVAL (p, 16);
finfo->attr = BVAL (p, 24);
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));
}
result = p + 32 + BVAL (p, 30);
break;
case 260: /* NT uses this, but also accepts 2 */
#if DEBUG
{
char buffer[255];
int len;
len = min (DVAL (p+60, 0), sizeof(buffer)-1);
memcpy(buffer,p+94,len);
buffer[len] = '\0';
LOG(("type=%ld, name='%s'\n",level,buffer));
}
#endif /* DEBUG */
result = p + WVAL (p, 0);
if (finfo != NULL)
{
int namelen;
p += 4; /* next entry offset */
p += 4; /* fileindex */
finfo->ctime = interpret_long_date(p);
p += 8;
finfo->atime = interpret_long_date(p);
p += 8;
p += 8; /* write time */
finfo->mtime = interpret_long_date(p);
p += 8;
finfo->size = DVAL (p, 0);
p += 8;
p += 8; /* alloc size */
finfo->attr = BVAL (p, 0);
p += 4;
namelen = min (DVAL (p, 0), SMB_MAXNAMELEN);
p += 4;
p += 4; /* EA size */
p += 2; /* short name len? */
p += 24; /* short name? */
if(namelen > (int)finfo->complete_path_size-1)
namelen = finfo->complete_path_size-1;
memcpy (finfo->complete_path, p, namelen);
finfo->complete_path[namelen] = '\0';
finfo->len = namelen;
}
break;
default:
if (finfo != NULL)
{
/* I have to set times to 0 here, because I do not
have specs about this for all info levels. */
finfo->ctime = finfo->mtime = finfo->atime = 0;
}
LOG (("Unknown long filename format %ld\n", level));
result = p + WVAL (p, 0);
break;
}
return result;
}
static int
smb_proc_readdir_long (struct smb_server *server, char *path, int fpos, int cache_size, struct smb_dirent *entry)
{
int max_matches = 512; /* this should actually be based on the max_xmit value */
/* NT uses 260, OS/2 uses 2. Both accept 1. */
int info_level = server->protocol < PROTOCOL_NT1 ? 1 : 260;
char *p;
int i;
int first;
int total_count = 0;
struct smb_dirent *current_entry;
char *resp_data;
char *resp_param;
int resp_data_len = 0;
int resp_param_len = 0;
int attribute = aSYSTEM | aHIDDEN | aDIR;
int result;
int error = 0;
int ff_searchcount;
int ff_eos = 0;
int ff_dir_handle = 0;
int ff_resume_key = 0;
int loop_count = 0;
unsigned char *outbuf = server->packet;
int dirlen = strlen (path) + 2 + 1;
char *mask;
int masklen;
ENTER();
/* ZZZ experimental 'max_matches' adjustment */
/*
if(info_level == 260)
max_matches = server->max_xmit / 360;
else
max_matches = server->max_xmit / 40;
*/
SHOWVALUE(server->max_xmit);
SHOWVALUE(max_matches);
mask = malloc (dirlen);
if (mask == NULL)
{
LOG (("Memory allocation failed\n"));
error = (-ENOMEM);
SHOWVALUE(error);
goto out;
}
strcpy (mask, path);
strcat (mask, "\\*");
masklen = strlen (mask);
LOG (("SMB call lreaddir %ld @ %ld\n", cache_size, fpos));
LOG ((" mask = %s\n", mask));
resp_param = NULL;
resp_data = NULL;
retry:
first = 1;
total_count = 0;
current_entry = entry;
while (ff_eos == 0)
{
loop_count++;
if (loop_count > 200)
{
LOG (("smb_proc_readdir_long: Looping in FIND_NEXT???\n"));
error = -EIO;
SHOWVALUE(error);
break;
}
memset (outbuf, 0, 39);
smb_setup_header (server, SMBtrans2, 15, 5 + 12 + masklen + 1);
WSET (outbuf, smb_tpscnt, 12 + masklen + 1);
WSET (outbuf, smb_tdscnt, 0);
WSET (outbuf, smb_mprcnt, 10);
WSET (outbuf, smb_mdrcnt, server->max_xmit);
WSET (outbuf, smb_msrcnt, 0);
WSET (outbuf, smb_flags, 0);
DSET (outbuf, smb_timeout, 0);
WSET (outbuf, smb_pscnt, WVAL (outbuf, smb_tpscnt));
WSET (outbuf, smb_psoff, ((SMB_BUF (outbuf) + 3) - outbuf) - 4);
WSET (outbuf, smb_dscnt, 0);
WSET (outbuf, smb_dsoff, 0);
WSET (outbuf, smb_suwcnt, 1);
WSET (outbuf, smb_setup0, first == 1 ? TRANSACT2_FINDFIRST : TRANSACT2_FINDNEXT);
p = SMB_BUF (outbuf);
(*p++) = 0; /* put in a null smb_name */
(*p++) = 'D';
(*p++) = ' '; /* this was added because OS/2 does it */
if (first != 0)
{
LOG (("first match\n"));
WSET (p, 0, attribute); /* attribute */
WSET (p, 2, max_matches); /* max count */
WSET (p, 4, 8 + 4 + 2); /* resume required + close on end + continue */
WSET (p, 6, info_level);
DSET (p, 8, 0);
}
else
{
LOG (("next match; ff_dir_handle=0x%lx ff_resume_key=%ld mask='%s'\n", ff_dir_handle, ff_resume_key, mask));
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, 8 + 4 + 2); /* resume required + close on end + continue */
}
p += 12;
if(masklen > 0)
memcpy (p, mask, masklen);
p += masklen;
(*p++) = 0;
(*p) = 0;
result = smb_trans2_request (server, &resp_data_len, &resp_param_len, &resp_data, &resp_param);
LOG (("smb_proc_readdir_long: smb_trans2_request returns %ld\n", result));
if (result < 0)
{
if (smb_retry (server))
goto retry;
LOG (("smb_proc_readdir_long: got error from trans2_request\n"));
error = result;
SHOWVALUE(error);
break;
}
/* 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");
Delay(TICKS_PER_SECOND / 5);
continue;
}
if (server->rcls != 0)
{
LOG (("server->rcls = %ld err = %ld\n",server->rcls, server->err));
error = smb_errno (server->rcls, server->err);
SHOWVALUE(error);
break;
}
/* ZZZ bail out if this is empty. */
if (resp_param == NULL)
break;
/* parse out some important return info */
p = resp_param;
if (first != 0)
{
ff_dir_handle = WVAL (p, 0);
ff_searchcount = WVAL (p, 2);
ff_eos = WVAL (p, 4);
}
else
{
ff_searchcount = WVAL (p, 0);
ff_eos = WVAL (p, 2);
}
LOG (("received %ld entries (eos=%ld)\n",ff_searchcount, ff_eos));
if (ff_searchcount == 0)
break;
/* ZZZ bail out if this is empty. */
if (resp_data == NULL)
break;
/* point to the data bytes */
p = resp_data;
/* Now we are ready to parse smb directory entries. */
for (i = 0; i < ff_searchcount; i++)
{
if(i == ff_searchcount - 1)
{
char * last_name;
int len;
ff_resume_key = DVAL(p, 0);
smb_get_dirent_name(p,info_level,&last_name,&len);
if(len > 0)
{
#if DEBUG
{
char buffer[SMB_MAXNAMELEN+1];
memcpy(buffer,last_name,len);
buffer[len] = '\0';
LOG(("last name = '%s'\n",buffer));
}
#endif /* DEBUG */
if(len + 1 > dirlen)
{
D(("increasing mask; old value = %ld new value = %ld",dirlen,len+1));
if(mask != NULL)
free (mask);
dirlen = len + 1;
SHOWVALUE(dirlen);
mask = malloc (dirlen);
if (mask == NULL)
{
LOG (("smb_proc_readdir_long: Memory allocation failed\n"));
error = -ENOMEM;
SHOWVALUE(error);
goto fail;
}
}
memcpy (mask, last_name, len);
mask[len] = '\0';
masklen = len;
}
else
{
masklen = 0;
}
}
if (total_count < fpos)
{
p = smb_decode_long_dirent (p, NULL, info_level);
LOG (("smb_proc_readdir: skipped entry; total_count = %ld, i = %ld, fpos = %ld\n",total_count, i, fpos));
}
else if (total_count >= fpos + cache_size)
{
p = smb_decode_long_dirent (p, NULL, info_level);
LOG (("smb_proc_readdir: skipped entry; total_count = %ld, i = %ld, fpos = %ld\n",total_count, i, fpos));
continue;
}
else
{
p = smb_decode_long_dirent (p, current_entry, info_level);
current_entry += 1;
}
total_count += 1;
}
SHOWVALUE(ff_resume_key);
if (resp_data != NULL)
{
free (resp_data);
resp_data = NULL;
}
if (resp_param != NULL)
{
free (resp_param);
resp_param = NULL;
}
first = 0;
if (ff_searchcount > 0)
loop_count = 0;
}
fail:
/* finished: not needed any more */
if (mask != NULL)
free (mask);
if (resp_data != NULL)
free (resp_data);
if (resp_param != NULL)
free (resp_param);
out:
if(error < 0)
{
RETURN(error);
return(error);
}
else
{
RETURN (total_count - fpos);
return (total_count - fpos);
}
}
int
smb_proc_readdir (struct smb_server *server, char *path, int fpos, int cache_size, struct smb_dirent *entry)
{
int result;
if (server->protocol >= PROTOCOL_LANMAN2)
result = smb_proc_readdir_long (server, path, fpos, cache_size, entry);
else
result = smb_proc_readdir_short (server, path, fpos, cache_size, entry);
return result;
}
int
smb_proc_getattr_core (struct smb_server *server, const char *path, int len, struct smb_dirent *entry)
{
int result;
char *p;
char *buf = server->packet;
LOG (("smb_proc_getattr: %s\n", path));
retry:
p = smb_setup_header (server, SMBgetatr, 0, 2 + len);
smb_encode_ascii (p, path, len);
if ((result = smb_request_ok (server, SMBgetatr, 10, 0)) < 0)
{
if (smb_retry (server))
goto retry;
goto out;
}
entry->attr = WVAL (buf, smb_vwv0);
/* The server only tells us 1 time */
entry->ctime = entry->atime = entry->mtime = local2utc (DVAL (buf, smb_vwv1));
entry->size = DVAL (buf, smb_vwv3);
out:
return result;
}
/* smb_proc_getattrE: entry->fid must be valid */
int
smb_proc_getattrE (struct smb_server *server, struct smb_dirent *entry)
{
char *buf = server->packet;
int result;
smb_setup_header_exclusive (server, SMBgetattrE, 1, 0);
WSET (buf, smb_vwv0, entry->fileid);
if ((result = smb_request_ok (server, SMBgetattrE, 11, 0)) < 0)
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->size = DVAL (buf, smb_vwv6);
entry->attr = WVAL (buf, smb_vwv10);
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, struct smb_dirent *new_finfo)
{
char *p;
char *buf = server->packet;
int result;
int local_time;
retry:
LOG (("smb_proc_setattr_core\n"));
p = smb_setup_header (server, SMBsetatr, 8, 4 + len);
WSET (buf, smb_vwv0, new_finfo->attr);
local_time = utc2local (new_finfo->mtime);
DSET (buf, smb_vwv1, local_time);
p = smb_encode_ascii (p, path, len);
(void) smb_encode_ascii (p, "", 0);
if ((result = smb_request_ok (server, SMBsetatr, 0, 0)) < 0)
{
if (smb_retry (server))
goto retry;
}
return result;
}
/* smb_proc_setattrE: we do not retry here, because we rely on fid,
which would not be valid after a retry. */
int
smb_proc_setattrE (struct smb_server *server, word fid, struct smb_dirent *new_entry)
{
char *buf = server->packet;
word date, time_value;
int result;
LOG (("smb_proc_setattrE\n"));
smb_setup_header_exclusive (server, SMBsetattrE, 7, 0);
WSET (buf, smb_vwv0, fid);
date_unix2dos (new_entry->ctime, &time_value, &date);
WSET (buf, smb_vwv1, date);
WSET (buf, smb_vwv2, time_value);
date_unix2dos (new_entry->atime, &time_value, &date);
WSET (buf, smb_vwv3, date);
WSET (buf, smb_vwv4, time_value);
date_unix2dos (new_entry->mtime, &time_value, &date);
WSET (buf, smb_vwv5, date);
WSET (buf, smb_vwv6, time_value);
result = smb_request_ok_unlock (server, SMBsetattrE, 0, 0);
return result;
}
int
smb_proc_dskattr (struct smb_server *server, struct smb_dskattr *attr)
{
int error;
char *p;
retry:
smb_setup_header (server, SMBdskattr, 0, 0);
if ((error = smb_request_ok (server, SMBdskattr, 5, 0)) < 0)
{
if (smb_retry (server))
goto retry;
goto out;
}
p = SMB_VWV (server->packet);
p = smb_decode_word (p, &attr->total);
p = smb_decode_word (p, &attr->allocblocks);
p = smb_decode_word (p, &attr->blocksize);
(void) smb_decode_word (p, &attr->free);
out:
return error;
}
/*****************************************************************************
*
* Mount/umount operations.
*
****************************************************************************/
struct smb_prots
{
enum smb_protocol prot;
const char *name;
};
/* smb_proc_reconnect: We expect the server to be locked, so that you
can call the routine from within smb_retry. The socket must be
created, like after a user-level socket()-call. It may not be
connected. */
static int
smb_proc_reconnect (struct smb_server *server)
{
static const struct smb_prots prots[] =
{
{PROTOCOL_CORE, "PC NETWORK PROGRAM 1.0"},
{PROTOCOL_COREPLUS, "MICROSOFT NETWORKS 1.03"},
{PROTOCOL_LANMAN1, "MICROSOFT NETWORKS 3.0"},
{PROTOCOL_LANMAN1, "LANMAN1.0"},
{PROTOCOL_LANMAN2, "LM1.2X002"},
{PROTOCOL_NT1, "NT LM 0.12"},
{PROTOCOL_NT1, "NT LANMAN 1.0"},
{-1, NULL}
};
char dev[] = "A:";
int i, plength;
int max_xmit = 1024; /* Space needed for first request. */
int given_max_xmit = server->mount_data.max_xmit;
int result;
word any_word;
byte *p;
unsigned char password[24];
int password_len;
unsigned char nt_password[24];
int nt_password_len;
unsigned char full_share[SMB_MAXNAMELEN+1];
int full_share_len;
byte *packet;
if (server->max_recv <= 0)
server->max_recv = given_max_xmit > 8000 ? given_max_xmit : 8000;
if ((result = smb_connect (server)) < 0)
{
LOG (("smb_proc_reconnect: could not smb_connect\n"));
goto fail;
}
/* Here we assume that the connection is valid */
server->state = CONN_VALID;
if (server->packet != NULL)
free (server->packet);
server->packet = malloc (server->max_recv);
if (server->packet == NULL)
{
LOG (("smb_proc_connect: No memory! Bailing out.\n"));
result = -ENOMEM;
goto fail;
}
packet = server->packet;
server->max_xmit = max_xmit;
/* Start with an RFC1002 session request packet. */
p = packet + 4;
p = smb_name_mangle (p, server->mount_data.server_name);
p = smb_name_mangle (p, server->mount_data.client_name);
smb_encode_smb_length (packet, (byte *) p - (byte *) (packet));
packet[0] = 0x81; /* SESSION REQUEST */
if ((result = smb_request (server)) < 0)
{
LOG (("smb_proc_connect: Failed to send SESSION REQUEST.\n"));
goto fail;
}
if (packet[0] != 0x82)
{
LOG (("smb_proc_connect: Did not receive positive response (err = %lx)\n",packet[0]));
#if DEBUG
{
smb_dump_packet (packet);
}
#endif /* DEBUG */
result = -EIO;
goto fail;
}
LOG (("smb_proc_connect: Passed SESSION REQUEST.\n"));
/* Now we are ready to send a SMB Negotiate Protocol packet. */
memset (packet, 0, SMB_HEADER_LEN);
plength = 0;
for (i = 0; prots[i].name != NULL; i++)
plength += strlen (prots[i].name) + 2;
smb_setup_header (server, SMBnegprot, 0, plength);
p = SMB_BUF (packet);
for (i = 0; prots[i].name != NULL; i++)
p = smb_encode_dialect (p, prots[i].name, strlen (prots[i].name));
LOG (("smb_proc_connect: Request SMBnegprot...\n"));
if ((result = smb_request_ok (server, SMBnegprot, 1, -1)) < 0)
{
LOG (("smb_proc_connect: Failure requesting SMBnegprot\n"));
goto fail;
}
LOG (("Verified!\n"));
p = SMB_VWV (packet);
p = smb_decode_word (p, &any_word);
i = any_word;
server->protocol = prots[i].prot;
LOG (("smb_proc_connect: Server wants %s protocol.\n",prots[i].name));
if (server->protocol > PROTOCOL_LANMAN1)
{
int user_len = strlen (server->mount_data.username)+1;
LOG (("smb_proc_connect: password = %s\n",server->mount_data.password));
LOG (("smb_proc_connect: usernam = %s\n",server->mount_data.username));
LOG (("smb_proc_connect: blkmode = %ld\n",WVAL (packet, smb_vwv5)));
if (server->protocol >= PROTOCOL_NT1)
{
server->maxxmt = DVAL (packet, smb_vwv3 + 1);
server->blkmode = DVAL (packet, smb_vwv9 + 1);
server->sesskey = DVAL (packet, smb_vwv7 + 1);
server->security_mode = BVAL(packet, smb_vwv1);
memcpy(server->crypt_key,SMB_BUF(packet),8);
}
else
{
server->maxxmt = WVAL (packet, smb_vwv2);
server->blkmode = WVAL (packet, smb_vwv5);
server->sesskey = DVAL (packet, smb_vwv6);
server->security_mode = BVAL(packet, smb_vwv1);
memcpy(server->crypt_key,SMB_BUF(packet),8);
}
SHOWVALUE(server->security_mode);
if(server->security_mode & 2)
{
SHOWMSG("encrypted passwords required");
memset(password,0,sizeof(password));
strlcpy(password,server->mount_data.password,sizeof(password));
smb_encrypt(password,server->crypt_key,password);
password_len = 24;
PRINTHEADER();
PRINTF(("password: "));
for(i = 0 ; i < 24 ; i++)
PRINTF(("%02lx ",password[i]));
PRINTF(("\n"));
memset(nt_password,0,sizeof(nt_password));
strlcpy(nt_password,server->mount_data.password,sizeof(nt_password));
smb_nt_encrypt(nt_password,server->crypt_key,nt_password);
nt_password_len = 24;
PRINTHEADER();
PRINTF(("nt_password: "));
for(i = 0 ; i < 24 ; i++)
PRINTF(("%02lx ",nt_password[i]));
PRINTF(("\n"));
PRINTHEADER();
PRINTF(("crypt_key: "));
for(i = 0 ; i < 8 ; i++)
PRINTF(("%02lx ",server->crypt_key[i]));
PRINTF(("\n"));
}
else
{
SHOWMSG("plain text passwords sufficient");
password_len = strlen(server->mount_data.password)+1;
nt_password_len = 0;
}
/* If in share level security then don't send a password now */
if((server->security_mode & 1) == 0)
{
SHOWMSG("share level security; zapping passwords");
strcpy(password,"");
password_len = 0;
strcpy(nt_password,"");
nt_password_len = 0;
}
SHOWVALUE(password_len);
SHOWVALUE(nt_password_len);
LOG (("smb_proc_connect: workgroup = %s\n", server->mount_data.workgroup_name));
if (server->protocol >= PROTOCOL_NT1)
{
char *OS_id = "AmigaOS";
char *client_id = "smbfs";
SHOWMSG("server->protocol >= PROTOCOL_NT1");
smb_setup_header (server, SMBsesssetupX, 13, user_len + password_len + nt_password_len + strlen (server->mount_data.workgroup_name)+1 + strlen (OS_id)+1 + strlen (client_id)+1);
WSET (packet, smb_vwv0, 0xff);
WSET (packet, smb_vwv2, given_max_xmit);
WSET (packet, smb_vwv3, 2);
WSET (packet, smb_vwv4, 0); /* server->pid */
DSET (packet, smb_vwv5, server->sesskey);
WSET (packet, smb_vwv7, password_len);
WSET (packet, smb_vwv8, nt_password_len);
p = SMB_BUF (packet);
if(nt_password_len != 0)
{
SHOWMSG("adding encrypted passwords");
memcpy (p, password, password_len);
p += password_len;
memcpy (p, nt_password, nt_password_len);
p += nt_password_len;
}
else
{
SHOWMSG("adding plain text password");
memcpy (p, server->mount_data.password, password_len);
p += password_len;
}
memcpy (p, server->mount_data.username, user_len);
p += user_len;
strcpy (p, server->mount_data.workgroup_name);
p += strlen (p) + 1;
strcpy (p, OS_id);
p += strlen (p) + 1;
strcpy (p, client_id);
}
else
{
smb_setup_header (server, SMBsesssetupX, 10, user_len + password_len);
WSET (packet, smb_vwv0, 0xff);
WSET (packet, smb_vwv1, 0);
WSET (packet, smb_vwv2, given_max_xmit);
WSET (packet, smb_vwv3, 2);
WSET (packet, smb_vwv4, 0); /* server->pid */
DSET (packet, smb_vwv5, server->sesskey);
WSET (packet, smb_vwv7, password_len);
WSET (packet, smb_vwv8, 0);
WSET (packet, smb_vwv9, 0);
p = SMB_BUF (packet);
memcpy (p, server->mount_data.password, password_len);
p += password_len;
memcpy (p, server->mount_data.username, user_len);
}
if ((result = smb_request_ok (server, SMBsesssetupX, 3, 0)) < 0)
{
LOG (("smb_proc_connect: SMBsessetupX failed\n"));
goto fail;
}
smb_decode_word (packet + 32, &(server->server_uid));
}
else
{
server->maxxmt = 0;
server->blkmode = 0;
server->sesskey = 0;
password_len = strlen(server->mount_data.password)+1;
nt_password_len = 0;
}
if(nt_password_len > 0)
{
strlcpy(full_share,"//",sizeof(full_share));
strlcat(full_share,server->mount_data.server_name,sizeof(full_share));
strlcat(full_share,"/",sizeof(full_share));
strlcat(full_share,server->mount_data.service,sizeof(full_share));
full_share_len = strlen(full_share);
for(i = 0 ; i < full_share_len ; i++)
{
if(full_share[i] == '/')
full_share[i] = '\\';
}
StringToUpper(full_share);
SHOWSTRING(full_share);
memset (packet, 0, SMB_HEADER_LEN);
smb_setup_header (server, SMBtconX, 4, password_len + full_share_len+1 + strlen(dev)+1);
WSET (packet, smb_vwv0, 0xFF);
WSET (packet, smb_vwv3, password_len);
p = SMB_BUF (packet);
if(nt_password_len > 0)
memcpy(p,password,password_len);
else
memcpy (p, server->mount_data.password, password_len);
p += password_len;
memcpy(p,full_share,full_share_len+1);
p += full_share_len+1;
strcpy(p,dev);
BSET(packet,smb_rcls,1);
if ((result = smb_request_ok (server, SMBtconX, 3, 0)) < 0)
{
SHOWVALUE(SMB_WCT(packet));
LOG (("smb_proc_connect: SMBtconX not verified.\n"));
goto fail;
}
SHOWVALUE(SMB_WCT(packet));
/* Changed, max_xmit hasn't been updated if a tconX message was send instead of tcon. */
if (server->maxxmt)
server->max_xmit = server->maxxmt;
server->tid = WVAL(packet,smb_tid);
}
else
{
/* 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));
if ((result = smb_request_ok (server, SMBtcon, 2, 0)) < 0)
{
LOG (("smb_proc_connect: SMBtcon not verified.\n"));
goto fail;
}
LOG (("OK! Managed to set up SMBtcon!\n"));
p = SMB_VWV (packet);
p = smb_decode_word (p, &server->max_xmit);
SHOWVALUE(server->max_xmit);
/* Added by Brian Willette - We were ignoring the server's initial
maxbuf value */
if (server->maxxmt != 0 && server->max_xmit > server->maxxmt)
server->max_xmit = server->maxxmt;
(void) smb_decode_word (p, &server->tid);
}
SHOWVALUE(server->max_xmit);
/* Changed, max_xmit hasn't been updated if a tconX message was send instead of tcon. */
if (server->max_xmit > given_max_xmit)
server->max_xmit = given_max_xmit;
/* Ok, everything is fine. max_xmit does not include
the TCP-SMB header of 4 bytes. */
if (server->max_xmit < 65535 - 4)
server->max_xmit += 4;
LOG (("max_xmit = %ld, tid = %ld\n", server->max_xmit, server->tid));
LOG (("smb_proc_connect: Normal exit\n"));
return 0;
fail:
server->state = CONN_INVALID;
return result;
}
/* smb_proc_reconnect: server->packet is allocated with
server->max_xmit bytes if and only if we return >= 0 */
int
smb_proc_connect (struct smb_server *server)
{
int result;
result = smb_proc_reconnect (server);
if ((result < 0) && (server->packet != NULL))
{
free (server->packet);
server->packet = NULL;
}
return result;
}
/* error code stuff - put together by Merik Karman
merik@blackadder.dsh.oz.au */
typedef struct
{
char *name;
int code;
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"},
{"ERRfilexists", 80, "The file named in a Create Directory, Make New File or Link request already exists"},
{"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"},
{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"},
{"ERRreserved", 254, "reserved"},
{"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, "Data error (CRC)"},
{"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"},
{NULL, -1, NULL}
};
typedef struct
{
int code;
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 void
smb_printerr (int class, int num)
{
int i, j;
err_code_struct *err;
for (i = 0; err_classes[i].class; i++)
{
if (err_classes[i].code != class)
continue;
if (!err_classes[i].err_msgs)
{
ReportError("%s - %ld.", err_classes[i].class, num);
LOG (("%s - %ld\n", 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;
ReportError ("%s - %s (%s).", err_classes[i].class, err[j].name, err[j].message);
LOG (("%s - %s (%s)\n",err_classes[i].class, err[j].name,err[j].message));
return;
}
}
ReportError ("Unknown error - (%ld, %ld).", class, num);
LOG (("Unknown error - (%ld, %ld)\n", class, num));
}