plan9fox/sys/src/cmd/ip/cifsd/smb.c

1719 lines
40 KiB
C

#include <u.h>
#include <libc.h>
#include <auth.h>
#include "dat.h"
#include "fns.h"
enum {
NEGOTIATE_USER_SECURITY = 1,
NEGOTIATE_ENCRYPT_PASSWORDS = 2,
};
static Chalstate *smbcs;
static int sessionkey;
static int sessionuid;
static int negotiated;
void
smbnegotiate(Req *r, uchar *h, uchar *p, uchar *e)
{
uchar *d, *de, *c, *ce, dom[256];
int i, x, mode;
char *s;
if(!unpack(h, p, e, "#0b{*2}#1w[]", &d, &de)){
err:
r->respond(r, STATUS_INVALID_SMB);
return;
}
i = 0;
x = -1;
while(unpack(h, d, de, "_f.", smbstrunpack8, &s, &d)){
if(debug)
fprint(2, "[%d] %s\n", i, s);
if(x < 0 && !cistrcmp(s, "NT LM 0.12"))
x = i;
free(s);
i++;
}
if(x < 0)
x = i-1;
if(x < 0)
x = 0;
sessionkey = rand();
c = ce = nil;
mode = 0;
if(needauth){
if(smbcs != nil)
auth_freechal(smbcs);
if(smbcs = auth_challenge("proto=mschap role=server")){
c = (uchar*)smbcs->chal;
ce = c + smbcs->nchal;
mode = NEGOTIATE_USER_SECURITY | NEGOTIATE_ENCRYPT_PASSWORDS;
} else
logit("auth_challenge: %r");
}
/*
* <89> Section 2.2.4.52.2: Windows NT servers always send the DomainName
* field in Unicode characters and never add a padding byte for alignment.
* Windows clients ignore the DomainName field in the server response.
*/
d = dom;
de = dom + smbstrpack16(d, d, d + sizeof(dom), domain);
if(!pack(r->rh, r->rp, r->re, "#0b{*2wbwwllllvw#2b}#1w{[][]}.",
x, mode, 50, 1, BUFFERSIZE, 0x10000, sessionkey,
CAP_UNICODE | CAP_LARGEFILES |
CAP_NT_FIND | CAP_NT_SMBS | CAP_NT_STATUS,
tofiletime(time(0)), -tzoff/60, c, ce, d, de, &r->rp))
goto err;
negotiated = 1;
r->respond(r, 0);
}
enum {
SMB_SETUP_GUEST = 1,
SMB_SETUP_USE_LANMAN_KEY = 2,
};
void
smbsessionsetupandx(Req *r, uchar *h, uchar *p, uchar *e)
{
uchar *lm, *lme, *nt, *nte, *xp;
char *user, *dom, *os, *lanman;
int xcmd, cap, bs, sk;
AuthInfo *ai;
user = dom = os = lanman = nil;
if(!unpack(h, p, e, "#0b{*2b_@4ww____l#2w#3w____l}#1w{[][]ffff}{?.}",
&xcmd, &bs, &sk, &cap, &lm, &lme, &nt, &nte,
r->o->strunpack, &user, r->o->strunpack, &dom,
r->o->strunpack, &os, r->o->strunpack, &lanman, &xp)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if(debug)
fprint(2, "bs=%x cap=%x user=%s dom=%s os=%s lanman=%s\n",
bs, cap, user, dom, os, lanman);
if(sk != sessionkey)
logit("ignoring bad session key");
while(!remoteuser){
if(needauth){
MSchapreply *mcr;
if(smbcs == nil || strlen(user) == 0)
break;
smbcs->user = user;
smbcs->nresp = (nte - nt)+sizeof(*mcr)-sizeof(mcr->NTresp);
if(smbcs->nresp < sizeof(*mcr))
smbcs->nresp = sizeof(*mcr);
mcr = mallocz(smbcs->nresp, 1);
if((lme - lm) <= sizeof(mcr->LMresp))
memmove(mcr->LMresp, lm, lme - lm);
if((nte - nt) > 0)
memmove(mcr->NTresp, nt, nte - nt);
smbcs->resp = mcr;
ai = auth_response(smbcs);
if(ai == nil){
logit("auth_response: %r");
free(mcr);
break; /* allow retry with the same challenge */
}
if(auth_chuid(ai, nil) < 0)
logit("auth_chuid: %r");
auth_freeAI(ai);
auth_freechal(smbcs);
smbcs = nil;
free(mcr);
}
remoteuser = getuser();
logit("auth successfull");
break;
}
sessionuid = (namehash(getuser()) & 0x7FFF) | 1;
r->uid = sessionuid;
if(bs >= 1024 || bs <= BUFFERSIZE)
remotebuffersize = bs;
if(!pack(r->rh, r->rp, r->re, "#0b{*2b_@2ww}#1w{fff}{.}",
xcmd, remoteuser ? 0 : SMB_SETUP_GUEST,
r->o->strpack, osname, r->o->strpack, progname,
r->o->strpack, domain, &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
smbcmd(r, xcmd, h, xp, e);
out:
free(user);
free(dom);
free(lanman);
free(os);
}
void
smblogoffandx(Req *r, uchar *h, uchar *p, uchar *e)
{
int xcmd;
uchar *xp;
if(!unpack(h, p, e, "#0b{*2b_}#1w{}{?.}", &xcmd, &xp)){
unsup:
r->respond(r, STATUS_NOT_SUPPORTED);
return;
}
logit("logoff");
if(remoteuser && needauth)
goto unsup;
logoff();
remoteuser = nil;
r->tid = 0xFFFF;
r->uid = 0;
if(!pack(r->rh, r->rp, r->re, "#0b{*2b_}#1w{}{.}", xcmd, &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
smbcmd(r, xcmd, h, xp, e);
}
enum {
SMB_SUPPORT_SEARCH_BITS = 1,
};
void
smbtreeconnectandx(Req *r, uchar *h, uchar *p, uchar *e)
{
int err, xcmd, flags;
char *path, *service;
uchar *pw, *pwe, *xp;
Tree *t;
path = service = nil;
if(!unpack(h, p, e, "#0b{*2b_@3ww#2w}#1w{[]ff}{?.}",
&xcmd, &flags, &pw, &pwe, r->o->strunpack, &path,
smbstrunpack8, &service, &xp)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if(r->flags & 1){
disconnecttree(r->tid);
r->tid = 0xFFFF;
}
if((t = connecttree(service, path, &err)) == nil){
r->respond(r, err);
goto out;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2b_@2ww}#1w{ff}{.}",
xcmd, SMB_SUPPORT_SEARCH_BITS,
smbstrpack8, t->share->service,
r->o->strpack, t->share->fsname, &r->rp)){
disconnecttree(t->tid);
r->respond(r, STATUS_INVALID_SMB);
} else {
r->tid = t->tid;
smbcmd(r, xcmd, h, xp, e);
}
out:
free(service);
free(path);
}
enum {
READ_WRITE_LOCK = 0x00,
SHARED_LOCK = 0x01,
OPLOCK_RELEASE = 0x02,
CHANGE_LOCKTYPE = 0x04,
CANCEL_LOCK = 0x08,
LARGE_FILES = 0x10,
};
void
smblockingandx(Req *r, uchar *h, uchar *p, uchar *e)
{
int i, err, xcmd, fid, tol, timeout, nunlock, nlock, pid;
unsigned int loff, hoff, llen, hlen;
uchar *d, *de, *xp;
vlong off, len;
File *f;
f = nil;
if(!unpack(h, p, e, "#0b{*2b_@2wwb_lww}#1w[]{?.}",
&xcmd, &fid, &tol, &timeout, &nunlock, &nlock, &d, &de, &xp)){
unsup:
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((f = getfile(r->tid, fid, nil, &err)) == nil){
r->respond(r, err);
goto out;
}
if(debug)
fprint(2, "tol %x\ntimeout %d\nunnlock %d\nnlock %d\n", tol, timeout, nunlock, nlock);
if(tol & (SHARED_LOCK | CHANGE_LOCKTYPE))
goto unsup;
for(i=0; i<nunlock+nlock; i++){
if(tol & LARGE_FILES){
if(!unpack(d, d, de, "w__llll[]", &pid, &hoff, &loff, &hlen, &llen, &d, nil))
goto unsup;
} else {
if(!unpack(d, d, de, "wll[]", &pid, &loff, &llen, &d, nil))
goto unsup;
hoff = hlen = 0;
}
off = (vlong)hoff<<32 | loff;
len = (vlong)hlen<<32 | llen;
if(debug)
fprint(2, "%s %x %llux %llux\n", (i < nunlock) ? "unlock" : "lock", pid, off, len);
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2b_@2w}#1w{}{.}", xcmd, &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
smbcmd(r, xcmd, h, xp, e);
out:
putfile(f);
}
enum {
REQ_ATTRIB = 0x01,
REQ_OPLOCK = 0x02,
REQ_OPLOCK_BATCH = 0x04,
};
void
smbopenandx(Req *r, uchar *h, uchar *p, uchar *e)
{
int err, nfid, xcmd, flags, amode, omode, fattr, act, csize, ctime;
char *name, *path;
uchar *xp;
Tree *t;
File *f;
Dir *d;
static int amode2dacc[] = {
[0x00] GENERIC_READ,
[0x01] GENERIC_WRITE,
[0x02] GENERIC_READ | GENERIC_WRITE,
[0x03] GENERIC_EXECUTE,
}, amode2sacc[] = {
[0x00] FILE_SHARE_COMPAT, /* compat */
[0x01] FILE_SHARE_NONE, /* exclusive use */
[0x02] FILE_SHARE_READ, /* deny write */
[0x03] FILE_SHARE_WRITE, /* deny read */
[0x04] FILE_SHARE_READ | FILE_SHARE_WRITE, /* shared read/write */
[0x05] -1,
[0x06] -1,
[0x07] -1,
}, omode2cdisp[] = {
[0x00] -1,
[0x01] FILE_OPEN,
[0x02] FILE_OVERWRITE,
[0x03] -1,
[0x10] FILE_CREATE,
[0x11] FILE_OPEN_IF,
[0x12] FILE_OVERWRITE_IF,
[0x13] -1,
};
f = nil;
d = nil;
name = path = nil;
if(!unpack(h, p, e, "#0b{*2b_@2www__wlwl________}#1w{f}{?.}",
&xcmd, &flags, &amode, &fattr, &ctime, &omode,
&csize, r->o->nameunpack, &name, &xp)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(r->tid, name, &t, &err)) == nil)
goto errout;
if((f = createfile(path, r->namecmp,
amode2dacc[amode & 3], amode2sacc[(amode>>4) & 7], omode2cdisp[omode & 0x13],
FILE_NON_DIRECTORY_FILE, (ulong)csize, fattr,
&act, (flags & REQ_ATTRIB) ? &d : nil, &err)) == nil){
errout:
r->respond(r, err);
goto out;
}
nfid = newfid(t, f);
amode = -1;
if(f->dacc & READMASK)
amode += 1;
if(f->dacc & WRITEMASK)
amode += 2;
if(!pack(r->rh, r->rp, r->re, "#0b{*2b_@2wwwllww__w______}#1w{}{.}",
xcmd, nfid,
!d ? 0 : dosfileattr(d),
!d ? 0 : d->mtime+tzoff,
!d ? 0 : filesize32(d->length),
!d ? 0 : amode,
!d ? 0 : f->rtype,
!d ? 0 : act, &r->rp)){
delfid(t, nfid);
r->respond(r, STATUS_INVALID_SMB);
} else
smbcmd(r, xcmd, h, xp, e);
out:
free(name);
free(path);
putfile(f);
free(d);
}
enum {
NT_CREATE_REQUEST_OPLOCK = 0x02,
NT_CREATE_REQUEST_OPBATCH = 0x04,
NT_CREATE_OPEN_TARGET_DIR = 0x08,
};
void
smbntcreatendx(Req *r, uchar *h, uchar *p, uchar *e)
{
int err, nfid, xcmd, flags, rootfid, fattr, dacc, sacc, cdisp, copt, act;
char *name, *path;
vlong csize;
uchar *xp;
Tree *t;
File *f;
Dir *d;
f = nil;
d = nil;
name = path = nil;
if(!unpack(h, p, e, "#0b{*2b_@2w___lllvllll_____}#1w{f}{?.}",
&xcmd, &flags, &rootfid, &dacc, &csize, &fattr, &sacc, &cdisp, &copt,
r->o->nameunpack, &name, &xp)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if(rootfid){
if((f = getfile(r->tid, rootfid, &t, &err)) == nil)
goto errout;
path = conspath(f->path, name);
putfile(f);
} else if((path = getpath(r->tid, name, &t, &err)) == nil)
goto errout;
if((f = createfile(path, r->namecmp, dacc, sacc, cdisp, copt, csize, fattr, &act, &d, &err)) == nil){
errout:
r->respond(r, err);
goto out;
}
nfid = newfid(t, f);
if(!pack(r->rh, r->rp, r->re, "#0b{*2b_@2wbwlvvvvlvvw__b}#1w{}{.}",
xcmd, 0, nfid, act, tofiletime(d->mtime), tofiletime(d->atime),
tofiletime(d->mtime), tofiletime(d->mtime), extfileattr(d),
allocsize(d->length, t->share->blocksize),
d->length, f->rtype, (d->qid.type & QTDIR) != 0, &r->rp)){
delfid(t, nfid);
r->respond(r, STATUS_INVALID_SMB);
} else
smbcmd(r, xcmd, h, xp, e);
out:
free(name);
free(path);
putfile(f);
free(d);
}
void
smbreadandx(Req *r, uchar *h, uchar *p, uchar *e)
{
int n, xcmd, fid, mincount, maxcount;
unsigned int loff, hoff;
uchar *rb, *rp, *re, *xp;
vlong off;
File *f;
f = nil;
hoff = 0;
if((unpack(h, p, e, "#0b{*2b_@2wwlww______l}#1w{}{?.}",
&xcmd, &fid, &loff, &mincount, &maxcount, &hoff, &xp) == 0) &&
(unpack(h, p, e, "#0b{*2b_@2wwlww______}#1w{}{?.}",
&xcmd, &fid, &loff, &mincount, &maxcount, &xp) == 0)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((f = getfile(r->tid, fid, nil, &n)) == nil){
r->respond(r, n);
goto out;
}
if((f->fd < 0) || (f->dacc & READMASK) == 0){
r->respond(r, STATUS_ACCESS_DENIED);
goto out;
}
/* dont really pack, just to get the pointer to the response data */
if(!pack(r->rh, r->rp, r->re, "#0b{*2________________________}#1w{%2.}", &rb)){
badsmb:
r->respond(r, STATUS_INVALID_SMB);
goto out;
}
re = rb + mincount;
if(re > r->re)
goto badsmb;
if(maxcount > mincount){
re = rb + maxcount;
if(re > r->re)
re = r->re;
}
n = 0;
rp = rb;
off = (vlong)hoff<<32 | loff;
while(rp < re){
if((n = pread(f->fd, rp, re - rp, off)) <= 0)
break;
off += n;
rp += n;
}
if(n < 0){
r->respond(r, smbmkerror());
goto out;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2b_@3www__#2w@2w__________}#1w{%2[]}{.}",
xcmd, 0xFFFF, 0x0000, rb, rp, &r->rp))
goto badsmb;
smbcmd(r, xcmd, h, xp, e);
out:
putfile(f);
}
void
smbwriteandx(Req *r, uchar *h, uchar *p, uchar *e)
{
int n, xcmd, fid, bufoff, buflen;
unsigned int loff, hoff;
uchar *d, *de, *xp;
File *f;
f = nil;
hoff = 0;
if((unpack(h, p, e, "#0b{*2b_@2wwl__________wwl}#1w{}{?.}",
&xcmd, &fid, &loff, &buflen, &bufoff, &hoff, &xp) == 0) &&
(unpack(h, p, e, "#0b{*2b_@2wwl__________ww}#1w{}{?.}",
&xcmd, &fid, &loff, &buflen, &bufoff, &xp) == 0)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
d = h + bufoff;
de = d + buflen;
if(d < h || de > e){
badsmb:
r->respond(r, STATUS_INVALID_SMB);
goto out;
}
if((f = getfile(r->tid, fid, nil, &n)) == nil){
r->respond(r, n);
goto out;
}
if((f->fd < 0) || (f->dacc & WRITEMASK) == 0){
r->respond(r, STATUS_ACCESS_DENIED);
goto out;
}
if((n = pwrite(f->fd, d, de - d, (vlong)hoff<<32 | loff)) < 0){
r->respond(r, smbmkerror());
goto out;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2b_@2www____}#1w{}{.}", xcmd, n, 0xFFFF, &r->rp))
goto badsmb;
smbcmd(r, xcmd, h, xp, e);
out:
putfile(f);
}
void
smbwrite(Req *r, uchar *h, uchar *p, uchar *e)
{
int n, fid, count, bf;
unsigned int off;
uchar *d, *de;
File *f;
f = nil;
if(!unpack(h, p, e, "#0b{*2wwl__}#1w{b#2w[]}", &fid, &count, &off, &bf, &d, &de)){
unsup:
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if(bf != 0x1)
goto unsup;
if((f = getfile(r->tid, fid, nil, &n)) == nil){
r->respond(r, n);
goto out;
}
if((f->fd < 0) || (f->dacc & WRITEMASK) == 0){
r->respond(r, STATUS_ACCESS_DENIED);
goto out;
}
if(count != (de - d)){
r->respond(r, STATUS_INVALID_SMB);
goto out;
}
if((n = pwrite(f->fd, d, count, off)) < 0){
r->respond(r, smbmkerror());
goto out;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2w}#1w{}.", n, &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
out:
putfile(f);
}
void
smbcloseflush(Req *r, uchar *h, uchar *p, uchar *e)
{
int err, fid;
Tree *t;
Find *s;
File *f;
f = nil;
s = nil;
if(!unpack(h, p, e, "#0b{*2w}#1w{}", &fid)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
switch(r->cmd){
case 0x05: /* SMB_COM_FLUSH */
if(fid == 0xFFFF){
if(gettree(r->tid) == nil){
r->respond(r, STATUS_SMB_BAD_TID);
goto out;
}
break;
}
/* no break */
case 0x04: /* SMB_COM_CLOSE */
if((f = getfile(r->tid, fid, &t, &err)) == nil){
r->respond(r, err);
goto out;
}
if(r->cmd == 0x04)
delfid(t, fid);
break;
case 0x34: /* SMB_COM_FIND_CLOSE2 */
if((s = getfind(r->tid, fid, &t, &err)) == nil){
r->respond(r, err);
goto out;
}
delsid(t, fid);
break;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}.", &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
out:
putfile(f);
putfind(s);
}
void
smbcreatedirectory(Req *r, uchar *h, uchar *p, uchar *e)
{
char *name, *path;
int err, fd;
name = path = nil;
if(!unpack(h, p, e, "#0b{*2}#1w{_f}", r->o->nameunpack, &name)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(r->tid, name, nil, &err)) == nil){
r->respond(r, err);
goto out;
}
if(access(path, AEXIST) == 0){
r->respond(r, STATUS_OBJECT_NAME_COLLISION);
goto out;
}
if((fd = create(path, OREAD, DMDIR | 0777)) < 0){
r->respond(r, smbmkerror());
goto out;
}
close(fd);
if(!pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}.", &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
out:
free(name);
free(path);
}
void
smbrename(Req *r, uchar *h, uchar *p, uchar *e)
{
char *name1, *name2, *path1, *path2, *x, *y;
int err, sattr;
Dir *d, nd;
d = nil;
name1 = name2 = path1 = path2 = nil;
if(!unpack(h, p, e, "#0b{*2w}#1w{_f_f}", &sattr,
r->o->nameunpack, &name1, r->o->nameunpack, &name2)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((path1 = getpath(r->tid, name1, nil, &err)) == nil){
r->respond(r, err);
goto out;
}
if((path2 = getpath(r->tid, name2, nil, &err)) == nil){
r->respond(r, err);
goto out;
}
if((d = xdirstat(&path1, r->namecmp)) == nil){
r->respond(r, smbmkerror());
goto out;
}
if(!matchattr(d, sattr)){
r->respond(r, STATUS_NO_SUCH_FILE);
goto out;
}
if(x = strrchr(path1, '/')){
*x = 0;
} else {
badpath:
r->respond(r, STATUS_OBJECT_PATH_SYNTAX_BAD);
goto out;
}
if(y = strrchr(path2, '/'))
*y++ = 0;
else
goto badpath;
if(r->namecmp(path1, path2)){
r->respond(r, STATUS_NOT_SAME_DEVICE);
goto out;
}
*x = '/';
nulldir(&nd);
nd.name = y;
if(dirwstat(path1, &nd) < 0){
r->respond(r, smbmkerror());
goto out;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}.", &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
xdirflush(path1, r->namecmp);
out:
free(name1);
free(name2);
free(path1);
free(path2);
free(d);
}
void
smbdelete(Req *r, uchar *h, uchar *p, uchar *e)
{
char *name, *path, *tmp;
int n, err, i, sattr;
Find *f;
Dir *d;
f = nil;
name = path = nil;
if(!unpack(h, p, e, "#0b{*2w}#1w{_f}", &sattr, r->o->nameunpack, &name)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(r->tid, name, nil, &err)) == nil){
errout:
r->respond(r, err);
goto out;
}
if((f = openfind(path, r->namecmp, sattr, 0, &err)) == nil)
goto errout;
n = 0;
while((i = readfind(f, f->index, &d)) >= 0){
tmp = conspath(f->base, d->name);
if(remove(tmp) < 0){
err = smbmkerror();
free(tmp);
goto errout;
}
free(tmp);
f->index = i+1;
n++;
}
if(n == 0){
err = STATUS_NO_SUCH_FILE;
goto errout;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}.", &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
out:
free(name);
free(path);
putfind(f);
}
void
smbdeletedirectory(Req *r, uchar *h, uchar *p, uchar *e)
{
char *name, *path;
Dir *d;
int err;
name = path = nil;
if(!unpack(h, p, e, "#0b{*2}#1w{_f}", r->o->nameunpack, &name)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(r->tid, name, nil, &err)) == nil){
r->respond(r, err);
goto out;
}
if(remove(path) < 0){
err = smbmkerror();
if((d = xdirstat(&path, r->namecmp)) == nil){
r->respond(r, err);
goto out;
}
free(d);
if(remove(path) < 0){
r->respond(r, smbmkerror());
goto out;
}
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}.", &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
out:
free(name);
free(path);
}
void
smbecho(Req *r, uchar *h, uchar *p, uchar *e)
{
uchar *t, *d, *de;
int i, n;
if(!unpack(h, p, e, "#0b{*2w}#1w[]", &n, &d, &de)){
r->respond(r, STATUS_NOT_SUPPORTED);
return;
}
if((r->tid != 0xFFFF) && (gettree(r->tid) == nil)){
r->respond(r, STATUS_SMB_BAD_TID);
return;
}
t = r->rp;
for(i=0; i < n; i++){
if(!pack(r->rh, r->rp, r->re, "#0b{*2w}#1w[].", i, d, de, &r->rp)){
r->respond(r, STATUS_INVALID_SMB);
break;
}
r->respond(r, 0);
r->rp = t;
}
}
void
smbdisconnecttree(Req *r, uchar *h, uchar *p, uchar *e)
{
int err;
if(!unpack(h, p, e, "#0b{*2}#1w{}")){
r->respond(r, STATUS_NOT_SUPPORTED);
return;
}
if(err = disconnecttree(r->tid)){
r->respond(r, err);
return;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}.", &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
}
void
smbqueryinformation(Req *r, uchar *h, uchar *p, uchar *e)
{
char *name, *path;
int err, mtime;
Dir *d;
d = nil;
name = path = nil;
if(!unpack(h, p, e, "#0b{*2}#1w{_f}", r->o->nameunpack, &name)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(r->tid, name, nil, &err)) == nil){
r->respond(r, err);
goto out;
}
if((d = xdirstat(&path, r->namecmp)) == nil){
r->respond(r, smbmkerror());
goto out;
}
mtime = d->mtime + tzoff;
if(!pack(r->rh, r->rp, r->re, "#0b{*2wll__________}#1w{}.",
dosfileattr(d), mtime, filesize32(d->length), &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
out:
free(name);
free(path);
free(d);
}
void
smbsetinformation(Req *r, uchar *h, uchar *p, uchar *e)
{
char *name, *path;
int err, attr, mtime;
Dir *d, nd;
d = nil;
name = path = nil;
if(!unpack(h, p, e, "#0b{*2wl__________}#1w{_f}", &attr, &mtime, r->o->nameunpack, &name)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(r->tid, name, nil, &err)) == nil){
r->respond(r, err);
goto out;
}
if((d = xdirstat(&path, r->namecmp)) == nil){
r->respond(r, smbmkerror());
goto out;
}
nulldir(&nd);
if(mtime)
nd.mtime = mtime-tzoff;
nd.mode = d->mode;
if(attr & ATTR_READONLY){
if(nd.mode & 0222)
nd.mode &= ~0222;
}else{
if((nd.mode & 0222) == 0)
nd.mode |= 0222;
}
if(attr & ATTR_ARCHIVE)
nd.mode &= ~DMTMP;
else
nd.mode |= DMTMP;
if(nd.mode == d->mode)
nd.mode = ~0;
if(dirwstat(path, &nd) < 0){
r->respond(r, smbmkerror());
goto out;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}.", &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
xdirflush(path, r->namecmp);
out:
free(name);
free(path);
free(d);
}
void
smbcheckdirectory(Req *r, uchar *h, uchar *p, uchar *e)
{
char *name, *path;
int err;
Dir *d;
d = nil;
name = path = nil;
if(!unpack(h, p, e, "#0b{*2}#1w{_f}", r->o->nameunpack, &name)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(r->tid, name, nil, &err)) == nil){
r->respond(r, err);
goto out;
}
if((d = xdirstat(&path, r->namecmp)) == nil){
r->respond(r, smbmkerror());
goto out;
}
if((d->qid.type & QTDIR) == 0){
r->respond(r, STATUS_OBJECT_PATH_NOT_FOUND);
goto out;
}
if(!pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}.", &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
out:
free(name);
free(path);
free(d);
}
void
smbqueryinformation2(Req *r, uchar *h, uchar *p, uchar *e)
{
int err, fid, adate, atime, mdate, mtime;
Tree *t;
File *f;
Dir *d;
f = nil;
t = nil;
d = nil;
if(!unpack(h, p, e, "#0b{*2w}#1w{}", &fid)){
r->respond(r, STATUS_NOT_SUPPORTED);
goto out;
}
if((f = getfile(r->tid, fid, &t, &err)) == nil){
r->respond(r, err);
goto out;
}
if((d = statfile(f)) == nil){
r->respond(r, smbmkerror());
goto out;
}
todatetime(d->atime+tzoff, &adate, &atime);
todatetime(d->mtime+tzoff, &mdate, &mtime);
if(!pack(r->rh, r->rp, r->re, "#0b{*2wwwwwwllw}#1w{}.",
mdate, mtime, adate, atime, mdate, mtime,
filesize32(d->length), filesize32(allocsize(d->length, t->share->blocksize)),
dosfileattr(d), &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
out:
putfile(f);
free(d);
}
void
smbqueryinformationdisk(Req *r, uchar *h, uchar *p, uchar *e)
{
Tree *t;
Share *s;
if(!unpack(h, p, e, "#0b{*2}#1w{}")){
r->respond(r, STATUS_NOT_SUPPORTED);
return;
}
if((t = gettree(r->tid)) == nil){
r->respond(r, STATUS_SMB_BAD_TID);
return;
}
s = t->share;
if(!pack(r->rh, r->rp, r->re, "#0b{*2wwww__}#1w{}.",
(int)(allocsize(s->allocsize + s->freesize, s->blocksize) / s->blocksize),
s->blocksize / s->sectorsize, s->sectorsize,
(int)(allocsize(s->freesize, s->blocksize) / s->blocksize), &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, 0);
}
static int
fpackdir(Req *r, Dir *d, Tree *t, int i, int level, uchar *b, uchar *p, uchar *e, uchar **prevoff, uchar **nameoff)
{
vlong atime, mtime, alen, dlen;
uchar shortname[2*12];
uchar *namep;
Share *share;
int n;
share = t->share;
dlen = d->length;
alen = allocsize(dlen, share->blocksize);
atime = tofiletime(d->atime);
mtime = tofiletime(d->mtime);
memset(shortname, 0, sizeof(shortname));
switch(level){
case 0x0101: /* SMB_FIND_FILE_DIRECTORY_INFO */
n = pack(b, p, e, "llvvvvvvl#0l{.f}%4",
0, i, mtime, atime, mtime, mtime, dlen, alen, extfileattr(d),
&namep, r->o->untermnamepack, d->name);
break;
case 0x0102: /* SMB_FIND_FILE_FULL_DIRECTORY_INFO */
n = pack(b, p, e, "llvvvvvvl#0ll{.f}%4",
0, i, mtime, atime, mtime, mtime, dlen, alen, extfileattr(d), 0,
&namep, r->o->untermnamepack, d->name);
break;
case 0x0103: /* SMB_FIND_FILE_NAMES_INFO */
n = pack(b, p, e, "ll#0l{.f}%4",
0, i, &namep, r->o->untermnamepack, d->name);
break;
case 0x0104: /* SMB_FIND_FILE_BOTH_DIRECTORY_INFO */
n = pack(b, p, e, "llvvvvvvl#1l#2lb_[]{.f}{}____%4",
0, i, mtime, atime, mtime, mtime, dlen, alen, extfileattr(d),
0, shortname, shortname+sizeof(shortname),
&namep, r->o->untermnamepack, d->name);
break;
default:
logit("[%.4x] unknown FIND infolevel", level);
return -1;
}
if(n <= 0)
return 0;
if(nameoff)
*nameoff = namep;
if(prevoff && *prevoff)
pack(b, *prevoff, e, "l", (int)(p - *prevoff));
if(prevoff)
*prevoff = p;
return n;
}
static int
qpackdir(Req *, Dir *d, Tree *t, File *f, int level, uchar *b, uchar *p, uchar *e)
{
vlong atime, mtime, dlen, alen;
int link, delete, isdir;
Share *share;
if(debug)
fprint(2, "QYERY level %.4x\n", level);
share = t->share;
dlen = d->length;
alen = allocsize(dlen, share->blocksize);
atime = tofiletime(d->atime);
mtime = tofiletime(d->mtime);
isdir = (d->qid.type & QTDIR) != 0;
delete = f && deletedfile(f);
link = !delete;
switch(level){
case 0x0101: /* SMB_QUERY_FILE_BASIC_INFO */
return pack(b, p, e, "vvvvl____", mtime, atime, mtime, mtime, extfileattr(d));
case 0x0102: /* SMB_QUERY_FILE_STANDARD_INFO */
return pack(b, p, e, "vvlbb", alen, dlen, link, delete, isdir);
case 0x0103: /* SMB_QUERY_FILE_EA_INFO */
return pack(b, p, e, "l", 0);
case 0x0107: /* SMB_QUERY_FILE_ALL_INFO */
return pack(b, p, e, "vvvvl____vvlbb__#1l#0l{f}{}",
mtime, atime, mtime, mtime, extfileattr(d), alen, dlen, link, delete, isdir,
smbuntermnamepack16, d->name);
case 0x0109: /* SMB_QUERY_FILE_STREAM_INFO */
if(isdir)
return 0;
return pack(b, p, e, "l#0lvv{f}", 0, dlen, alen, smbuntermstrpack16, "::$DATA");
default:
logit("[%.4x] unknown QUERY infolevel", level);
return -1;
}
}
void
trans2querypathinformation(Trans *t)
{
char *name, *path;
Tree *tree;
int n, level;
Dir *d;
d = nil;
path = name = nil;
if(!unpack(t->in.param.b, t->in.param.p, t->in.param.e, "w____f",
&level, t->o->nameunpack, &name)){
t->respond(t, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(t->r->tid, name, &tree, &n)) == nil){
t->respond(t, n);
goto out;
}
if((d = xdirstat(&path, t->namecmp)) == nil){
t->respond(t, smbmkerror());
goto out;
}
pack(t->out.param.b, t->out.param.p, t->out.param.e, "__.", &t->out.param.p);
if((n = qpackdir(t->r, d, tree, nil, level, t->out.data.b, t->out.data.p, t->out.data.e)) < 0)
t->respond(t, STATUS_OS2_INVALID_LEVEL);
else {
t->out.data.p += n;
t->respond(t, 0);
}
out:
free(name);
free(path);
free(d);
}
void
trans2queryfileinformation(Trans *t)
{
int n, fid, level;
Tree *tree;
File *f;
Dir *d;
f = nil;
d = nil;
if(!unpack(t->in.param.b, t->in.param.p, t->in.param.e, "ww", &fid, &level)){
t->respond(t, STATUS_NOT_SUPPORTED);
goto out;
}
if((f = getfile(t->r->tid, fid, &tree, &n)) == nil){
t->respond(t, n);
goto out;
}
if((d = statfile(f)) == nil){
t->respond(t, smbmkerror());
goto out;
}
pack(t->out.param.b, t->out.param.p, t->out.param.e, "__.", &t->out.param.p);
if((n = qpackdir(t->r, d, tree, f, level, t->out.data.b, t->out.data.p, t->out.data.e)) < 0)
t->respond(t, STATUS_OS2_INVALID_LEVEL);
else {
t->out.data.p += n;
t->respond(t, 0);
}
out:
putfile(f);
free(d);
}
static int
setfilepathinformation(Req *r, Dir *d, File *f, char *path, int level, uchar *b, uchar *p, uchar *e)
{
int attr, adt, atm, mdt, mtm, delete;
vlong len, atime, mtime;
Dir nd;
nulldir(&nd);
if(debug)
fprint(2, "SET level %.4x\n", level);
switch(level){
case 0x0001: /* SMB_INFO_STANDARD */
if(!unpack(b, p, e, "____wwww__________", &adt, &atm, &mdt, &mtm))
goto unsup;
nd.atime = fromdatetime(adt, atm)-tzoff;
nd.mtime = fromdatetime(mdt, mtm)-tzoff;
break;
case 0x0101: /* SMB_SET_FILE_BASIC_INFO */
if(f == nil || !unpack(b, p, e, "________vv________l____", &atime, &mtime, &attr))
goto unsup;
if(atime && atime != -1LL)
nd.atime = fromfiletime(atime);
if(mtime && mtime != -1LL)
nd.mtime = fromfiletime(mtime);
if(attr){
if(attr & ATTR_READONLY){
if(d->mode & 0222)
nd.mode = d->mode & ~0222;
} else {
if((d->mode & 0222) == 0)
nd.mode = d->mode | 0222;
}
}
break;
case 0x0102: /* SMB_SET_FILE_DISPOSITION_INFO */
if(f == nil || !unpack(b, p, e, "b", &delete))
goto unsup;
if((f->dacc & FILE_DELETE) == 0)
return STATUS_ACCESS_DENIED;
deletefile(f, delete);
break;
case 0x0103: /* SMB_SET_FILE_ALLOCATION_INFO */
case 0x0104: /* SMB_SET_FILE_END_OF_FILE_INFO */
if(f == nil || !unpack(b, p, e, "v", &len))
goto unsup;
if(d->qid.type & QTDIR)
return STATUS_OS2_INVALID_ACCESS;
if(len != -1LL)
nd.length = len;
break;
default:
logit("[%.4x] unknown SET infolevel", level);
return STATUS_OS2_INVALID_LEVEL;
unsup:
return STATUS_NOT_SUPPORTED;
}
if(debug)
fprint(2, "wstat\nmode %lo\natime %ld\nmtime %ld\nlength %llux\n",
nd.mode, nd.atime, nd.mtime, nd.length);
if(((f && f->fd >= 0) ? dirfwstat(f->fd, &nd) : dirwstat(path, &nd)) < 0)
return smbmkerror();
xdirflush(path, r->namecmp);
return 0;
}
void
trans2setpathinformation(Trans *t)
{
int err, level;
Tree *tree;
char *name, *path;
Dir *d;
d = nil;
name = path = nil;
if(!unpack(t->in.param.b, t->in.param.p, t->in.param.e, "w____f", &level,
t->o->nameunpack, &name)){
t->respond(t, STATUS_NOT_SUPPORTED);
goto out;
}
if((path = getpath(t->r->tid, name, &tree, &err)) == nil)
goto errout;
if((d = xdirstat(&path, t->namecmp)) == nil){
t->respond(t, smbmkerror());
goto out;
}
if(err = setfilepathinformation(t->r, d, nil, path, level, t->in.data.b, t->in.data.p, t->in.data.e)){
errout:
t->respond(t, err);
goto out;
}
pack(t->out.param.b, t->out.param.p, t->out.param.e, "__.", &t->out.param.p);
t->respond(t, 0);
out:
free(name);
free(path);
free(d);
}
void
trans2setfileinformation(Trans *t)
{
int err, fid, level;
Tree *tree;
File *f;
Dir *d;
f = nil;
d = nil;
if(!unpack(t->in.param.b, t->in.param.p, t->in.param.e, "ww__", &fid, &level)){
t->respond(t, STATUS_NOT_SUPPORTED);
goto out;
}
if((f = getfile(t->r->tid, fid, &tree, &err)) == nil)
goto errout;
if((d = statfile(f)) == nil){
t->respond(t, smbmkerror());
goto out;
}
if(err = setfilepathinformation(t->r, d, f, f->path, level, t->in.data.b, t->in.data.p, t->in.data.e)){
errout:
t->respond(t, err);
goto out;
}
pack(t->out.param.b, t->out.param.p, t->out.param.e, "__.", &t->out.param.p);
t->respond(t, 0);
out:
putfile(f);
free(d);
}
enum {
FILE_CASE_SENSITIVE_SEARCH = 1,
FILE_CASE_PRESERVED_NAMES = 2,
FILE_UNICODE_ON_DISK = 4,
};
void
trans2queryfsinformation(Trans *t)
{
int n, level;
Share *share;
Tree *tree;
char *s;
s = nil;
if(!unpack(t->in.param.b, t->in.param.p, t->in.param.e, "w", &level)){
t->respond(t, STATUS_NOT_SUPPORTED);
goto out;
}
if((tree = gettree(t->r->tid)) == nil){
t->respond(t, STATUS_SMB_BAD_TID);
goto out;
}
share = tree->share;
if(debug)
fprint(2, "FS level %.4x\n", level);
switch(level){
case 0x0001: /* SMB_INFO_ALLOCATION */
n = pack(t->out.data.b, t->out.data.p, t->out.data.e, "llllw",
0x00000000, share->blocksize/share->sectorsize,
filesize32(allocsize(share->allocsize+share->freesize, share->blocksize)/share->blocksize),
filesize32(allocsize(share->freesize, share->blocksize)/share->blocksize),
share->sectorsize);
break;
case 0x0002: /* SMB_INFO_VOLUME */
s = smprint("%.12s", share->name);
n = pack(t->out.data.b, t->out.data.p, t->out.data.e, "l#0b{f}",
(int)namehash(share->root), smbuntermstrpack8, s);
break;
case 0x0102: /* SMB_QUERY_FS_VOLUME_INFO */
s = smprint("%.12s", share->name);
n = pack(t->out.data.b, t->out.data.p, t->out.data.e, "vl#0l__{f}",
tofiletime(starttime), (int)namehash(share->root), smbuntermstrpack16, s);
break;
case 0x0103: /* SMB_QUERY_FS_SIZE_INFO */
n = pack(t->out.data.b, t->out.data.p, t->out.data.e, "vvll",
allocsize(share->allocsize+share->freesize, share->blocksize)/share->blocksize,
allocsize(share->freesize, share->blocksize)/share->blocksize,
share->blocksize/share->sectorsize,
share->sectorsize);
break;
case 0x0105: /* SMB_QUERY_FS_ATTRIBUTE_INFO */
n = pack(t->out.data.b, t->out.data.p, t->out.data.e, "ll#0l{f}",
FILE_CASE_SENSITIVE_SEARCH |
FILE_CASE_PRESERVED_NAMES |
FILE_UNICODE_ON_DISK,
share->namelen, smbuntermstrpack16, share->fsname);
break;
default:
logit("[%.4x] unknown FS infolevel", level);
t->respond(t, STATUS_OS2_INVALID_LEVEL);
goto out;
}
if(n <= 0)
t->respond(t, STATUS_INVALID_SMB);
else {
t->out.data.p += n;
t->respond(t, 0);
}
out:
free(s);
}
enum {
SMB_FIND_CLOSE_AFTER_REQUEST = 0x1,
SMB_FIND_CLOSE_AT_EOS = 0x2,
SMB_FIND_RETURN_RESUME_KEYS = 0x4,
SMB_FIND_CONTINUE_FROM_LAST = 0x8,
};
void
trans2findfirst2(Trans *t)
{
int i, nsid, eos, n, attr, count, flags, level;
uchar *prevoff, *nameoff;
char *name, *path;
Tree *tree;
Find *f;
Dir *d;
f = nil;
name = path = nil;
if(!unpack(t->in.param.b, t->in.param.p, t->in.param.e, "wwww____f",
&attr, &count, &flags, &level, t->o->nameunpack, &name)){
t->respond(t, STATUS_NOT_SUPPORTED);
goto out;
}
if(debug)
fprint(2, "FIND level %.4x\n", level);
if((path = getpath(t->r->tid, name, &tree, &n)) == nil){
t->respond(t, n);
goto out;
}
if((f = openfind(path, t->namecmp, attr, 1, &n)) == nil){
t->respond(t, n);
goto out;
}
n = eos = 0;
prevoff = nameoff = nil;
for(i = 0; i < count; i++){
if((eos = readfind(f, f->index, &d)) < 0)
break;
if((n = fpackdir(t->r, d, tree, 0, level,
t->out.data.b, t->out.data.p, t->out.data.e,
&prevoff, &nameoff)) <= 0)
break;
t->out.data.p += n;
f->index = eos + 1;
}
if((n < 0) || (flags & SMB_FIND_CLOSE_AFTER_REQUEST) ||
((flags & SMB_FIND_CLOSE_AT_EOS) && (eos < 0))){
if(n < 0){
t->respond(t, STATUS_OS2_INVALID_LEVEL);
goto out;
}
eos = -1;
nsid = 0;
} else {
nsid = newsid(tree, f);
}
if(!i && (eos < 0)){
t->respond(t, STATUS_NO_MORE_FILES);
goto out;
}
if(!pack(t->out.param.b, t->out.param.p, t->out.param.e, "wwwww.",
nsid, i, (eos < 0), 0, (int)(nameoff - t->out.data.b), &t->out.param.p)){
t->respond(t, STATUS_INVALID_SMB);
delsid(tree, nsid);
} else
t->respond(t, 0);
out:
free(name);
free(path);
putfind(f);
}
void
trans2findnext2(Trans *t)
{
int i, n, eos, sid, count, level, index, flags;
uchar *prevoff, *nameoff;
char *name;
Tree *tree;
Find *f;
Dir *d;
f = nil;
name = nil;
if(!unpack(t->in.param.b, t->in.param.p, t->in.param.e, "wwwlwf",
&sid, &count, &level, &index, &flags, t->o->nameunpack, &name)){
t->respond(t, STATUS_NOT_SUPPORTED);
goto out;
}
if(debug)
fprint(2, "FIND level %.4x\n", level);
if((f = getfind(t->r->tid, sid, &tree, &n)) == nil){
t->respond(t, n);
goto out;
}
n = eos = 0;
if((flags & SMB_FIND_CONTINUE_FROM_LAST) == 0){
f->index = 0;
while((eos = readfind(f, f->index, &d)) >= 0){
f->index = eos + 1;
if(strcmp(name, d->name) == 0)
break;
}
}
prevoff = nameoff = nil;
for(i = 0; i < count; i++){
if((eos = readfind(f, f->index, &d)) < 0)
break;
if((n = fpackdir(t->r, d, tree, 0, level,
t->out.data.b, t->out.data.p, t->out.data.e,
&prevoff, &nameoff)) <= 0)
break;
t->out.data.p += n;
f->index = eos + 1;
}
if((flags & SMB_FIND_CLOSE_AFTER_REQUEST) ||
((flags & SMB_FIND_CLOSE_AT_EOS) && (eos < 0))){
delsid(tree, sid);
eos = -1;
}
if(!i && (eos < 0)){
t->respond(t, STATUS_NO_MORE_FILES);
goto out;
}
if(!pack(t->out.param.b, t->out.param.p, t->out.param.e, "wwww.",
i, (eos < 0), 0, (int)(nameoff - t->out.data.b), &t->out.param.p))
t->respond(t, STATUS_INVALID_SMB);
else
t->respond(t, 0);
out:
free(name);
putfind(f);
}
static void
transrespond(Trans *t, int err)
{
Req *r;
r = t->r;
t->r = nil;
t->respond = nil;
if(!err && !pack(r->rh, r->rp, r->re,
"#0b{*2ww__#3w@3ww#4w@4ww#1b_[*2]}#2w{%4[]%4[]}.",
t->out.param.p - t->out.param.b,
t->out.data.p - t->out.data.b,
0, 0,
t->out.setup.b, t->out.setup.p,
t->out.param.b, t->out.param.p,
t->out.data.b, t->out.data.p, &r->rp))
r->respond(r, STATUS_INVALID_SMB);
else
r->respond(r, err);
free(t->out.param.b);
free(t->out.data.b);
free(t->out.setup.b);
}
struct {
char *name;
void (*fun)(Trans *t);
} transoptab[] = {
[0x0000] { "TRANS_RAP", transrap },
}, trans2optab[] = {
[0x0001] { "TRANS2_FIND_FIRST2", trans2findfirst2 },
[0x0002] { "TRANS2_FIND_NEXT2", trans2findnext2 },
[0x0003] { "TRANS2_QUERY_FS_INFORMATION", trans2queryfsinformation },
[0x0005] { "TRANS2_QUERY_PATH_INFORMATION", trans2querypathinformation },
[0x0007] { "TRANS2_QUERY_FILE_INFORMATION", trans2queryfileinformation },
[0x0006] { "TRANS2_SET_PATH_INFORMATION", trans2setpathinformation },
[0x0008] { "TRANS2_SET_FILE_INFORMATION", trans2setfileinformation },
};
void
smbtransaction(Req *r, uchar *h, uchar *p, uchar *e)
{
int tpc, tdc, rpc, rdc, rsc;
uchar *sa, *se, *da, *de, *pa, *pe;
void (*fun)(Trans *t);
Trans t;
t.r = r;
t.o = r->o;
t.namecmp = r->namecmp;
t.cmd = 0;
t.respond = transrespond;
if(!unpack(h, p, e, "#0b{*2wwwwb_w______#3w@3w#4w@4w#1b_[*2]}#2w{[?][?]}",
&tpc, &tdc, &rpc, &rdc, &rsc, &t.flags, &sa, &se, &pa, &pe, &da, &de)){
unsup:
r->respond(r, STATUS_NOT_SUPPORTED);
return;
}
unpack(sa, sa, se, "w", &t.cmd);
switch(r->cmd){
case 0x25: /* SMB_COM_TRANSACTION */
if((t.cmd >= nelem(transoptab)) || ((fun = transoptab[t.cmd].fun) == nil)){
logit("[%.4x] transaction subcommand not implemented", t.cmd);
goto unsup;
}
t.name = transoptab[t.cmd].name;
break;
case 0x32: /* SMB_COM_TRANSACTION2 */
if((t.cmd >= nelem(trans2optab)) || ((fun = trans2optab[t.cmd].fun) == nil)){
logit("[%.4x] transaction2 subcommand not implemented", t.cmd);
goto unsup;
}
t.name = trans2optab[t.cmd].name;
break;
default:
goto unsup;
}
if((tpc > (pe - pa)) || (tdc > (de - da))){
logit("[%.4x] %s request truncated", t.cmd, t.name);
goto unsup;
}
if(57+((rsc+1)&~1)+((rpc+3)&~3)+((rdc+3)&~3) > remotebuffersize){
rdc = remotebuffersize-(57+((rsc+1)&~1)+((rpc+3)&~3)) & ~3;
if(rdc <= 0){
logit("[%.4x] %s response doesnt fit in client buffer", t.cmd, t.name);
goto unsup;
}
}
t.in.param.b = t.in.param.p = pa; t.in.param.e = pe;
t.in.data.b = t.in.data.p = da; t.in.data.e = de;
t.in.setup.b = t.in.setup.p = sa; t.in.setup.e = se;
t.out.param.b = t.out.param.p = t.out.param.e = (rpc > 0) ? malloc(rpc) : nil;
t.out.param.e += rpc;
t.out.data.b = t.out.data.p = t.out.data.e = (rdc > 0) ? malloc(rdc) : nil;
t.out.data.e += rdc;
t.out.setup.b = t.out.setup.p = t.out.setup.e = (rsc > 0) ? malloc(rsc) : nil;
t.out.setup.e += rsc;
if(debug)
fprint(2, "[%.4x] %s\n", t.cmd, t.name);
(*fun)(&t);
}
void
smbnoandxcommand(Req *r, uchar *, uchar *, uchar *)
{
r->respond(r, (r->cmd == 0xFF) ? STATUS_INVALID_SMB : 0);
}
struct {
char *name;
void (*fun)(Req *, uchar *, uchar *, uchar *);
} optab[] = {
[0x00] { "SMB_COM_CREATE_DIRECTORY", smbcreatedirectory },
[0x01] { "SMB_COM_DELETE_DIRECTORY", smbdeletedirectory },
[0x04] { "SMB_COM_CLOSE", smbcloseflush },
[0x05] { "SMB_COM_FLUSH", smbcloseflush },
[0x06] { "SMB_COM_DELETE", smbdelete },
[0x07] { "SMB_COM_RENAME", smbrename },
[0x08] { "SMB_COM_QUERY_INFORMATION", smbqueryinformation },
[0x09] { "SMB_COM_SET_INFORMATION", smbsetinformation },
[0x10] { "SMB_CON_CHECK_DIRECTORY", smbcheckdirectory },
[0x0b] { "SMB_COM_WRITE", smbwrite },
[0x23] { "SMB_COM_QUERY_INFORMATION2", smbqueryinformation2 },
[0x24] { "SMB_COM_LOCKING_ANDX", smblockingandx },
[0x25] { "SMB_COM_TRANSACTION", smbtransaction },
[0x2b] { "SMB_COM_ECHO", smbecho },
[0x2d] { "SMB_COM_OPEN_ANDX", smbopenandx },
[0x2e] { "SMB_COM_READ_ANDX", smbreadandx },
[0x2f] { "SMB_COM_WRITE_ANDX", smbwriteandx },
[0x32] { "SMB_COM_TRANSACTION2", smbtransaction },
[0x34] { "SMB_COM_FIND_CLOSE2", smbcloseflush },
[0x71] { "SMB_COM_DISCONNECT_TREE", smbdisconnecttree },
[0x72] { "SMB_COM_NEGOTIATE", smbnegotiate },
[0x73] { "SMB_COM_SESSION_SETUP_ANX", smbsessionsetupandx },
[0x74] { "SMB_COM_LOGOFF_ANDX", smblogoffandx },
[0x75] { "SMB_COM_TREE_CONNECT_ANDX", smbtreeconnectandx },
[0x80] { "SMB_COM_QUERY_INFORMATION_DISK", smbqueryinformationdisk },
[0xa2] { "SMB_COM_NT_CREATE_ANDX", smbntcreatendx },
[0xFF] { "SMB_COM_NO_ANDX_COMMAND", smbnoandxcommand },
};
void
smbcmd(Req *r, int cmd, uchar *h, uchar *p, uchar *e)
{
if((cmd >= nelem(optab)) || (optab[cmd].fun == nil)){
logit("[%.2x] command not implemented", cmd);
r->respond(r, STATUS_NOT_SUPPORTED);
return;
}
r->name = optab[cmd].name;
if(debug)
fprint(2, "[%.2x] %s\n", cmd, r->name);
if((!negotiated && cmd != 0x72) || (negotiated && cmd == 0x72)){
r->respond(r, STATUS_INVALID_SMB);
return;
}
if(!remoteuser){
switch(cmd){
case 0x72: /* SMB_COM_NEGOTIATE */
case 0x73: /* SMB_COM_SESSION_SETUP_ANX */
case 0x74: /* SMB_COM_LOGOFF_ANDX */
case 0xFF: /* SMB_COM_NO_ANDX_COMMAND */
break;
default:
logit("auth not completed in %s request", r->name);
case 0x75: /* SMB_COM_TREE_CONNECT_ANDX */
r->respond(r, STATUS_LOGON_FAILURE);
return;
}
} else if(r->uid != sessionuid){
switch(cmd){
case 0x73: /* SMB_COM_SESSION_SETUP_ANX */
case 0x2b: /* SMB_COM_ECHO */
break;
default:
logit("bad uid %.4x in %s request", r->uid, r->name);
r->respond(r, STATUS_SMB_BAD_UID);
return;
}
}
(*optab[cmd].fun)(r, h, p, e);
}