plan9fox/sys/src/cmd/cifs/cifs.c

837 lines
18 KiB
C

#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "cifs.h"
static char magic[] = { 0xff, 'S', 'M', 'B' };
Session *
cifsdial(char *host, char *called, char *sysname)
{
int nbt, fd;
char *addr;
Session *s;
if(Debug)
fprint(2, "cifsdial: host=%s called=%s sysname=%s\n", host, called, sysname);
if((addr = netmkaddr(host, "tcp", "cifs")) == nil)
return nil;
nbt = 0;
if((fd = dial(addr, nil, nil, nil)) == -1){
nbt = 1;
if((fd = nbtdial(host, called, sysname)) == -1)
return nil;
}
s = emalloc9p(sizeof(Session));
memset(s, 0, sizeof(Session));
s->fd = fd;
s->nbt = nbt;
s->mtu = MTU;
s->pid = getpid();
s->mid = time(nil) ^ getpid();
s->uid = NO_UID;
s->seq = 0;
s->seqrun = 0;
s->secmode = SECMODE_SIGN_ENABLED; /* hope for the best */
s->flags2 = FL2_KNOWS_LONG_NAMES | FL2_HAS_LONG_NAMES | FL2_PAGEING_IO;
s->macidx = -1;
if(s->mtu > MTU)
s->mtu = MTU;
return s;
}
void
cifsclose(Session *s)
{
if(s->fd)
close(s->fd);
free(s);
}
Pkt *
cifshdr(Session *s, Share *sp, int cmd)
{
Pkt *p;
int sign, tid, dfs;
dfs = 0;
tid = NO_TID;
Active = IDLE_TIME;
werrstr("");
sign = s->secmode & SECMODE_SIGN_ENABLED? FL2_PACKET_SIGNATURES: 0;
if(sp){
tid = sp->tid;
// FIXME! if(sp->options & SMB_SHARE_IS_IN_DFS)
// FIXME! dfs = FL2_DFS;
}
p = emalloc9p(sizeof(Pkt) + MTU);
memset(p, 0, sizeof(Pkt) +MTU);
p->buf = (uchar *)p + sizeof(Pkt);
p->s = s;
p->request = cmd; /* for debug */
qlock(&s->seqlock);
if(s->seqrun){
p->seq = s->seq;
s->seq = (s->seq + 2) % 0x10000;
}
qunlock(&s->seqlock);
nbthdr(p);
pmem(p, magic, nelem(magic));
p8(p, cmd);
pl32(p, 0); /* status (error) */
p8(p, FL_CASELESS_NAMES | FL_CANNONICAL_NAMES); /* flags */
pl16(p, s->flags2 | dfs | sign); /* flags2 */
pl16(p, (s->pid >> 16) & 0xffff); /* PID MS bits */
pl32(p, p->seq); /* MAC / sequence number */
pl32(p, 0); /* MAC */
pl16(p, 0); /* padding */
pl16(p, tid);
pl16(p, s->pid & 0xffff);
pl16(p, s->uid);
pl16(p, s->mid);
p->wordbase = p8(p, 0); /* filled in by pbytes() */
return p;
}
void
pbytes(Pkt *p)
{
int n;
assert(p->wordbase != nil); /* cifshdr not called */
assert(p->bytebase == nil); /* called twice */
n = p->pos - p->wordbase;
assert(n % 2 != 0); /* even addr */
*p->wordbase = n / 2;
p->bytebase = pl16(p, 0); /* filled in by cifsrpc() */
}
static void
dmp(int seq, uchar *buf)
{
int i;
if(seq == 99)
print("\n ");
else
print("%+2d ", seq);
for(i = 0; i < 8; i++)
print("%02x ", buf[i] & 0xff);
print("\n");
}
int
cifsrpc(Pkt *p)
{
int reply, got, err;
uint tid, uid, seq;
uchar *pos;
char m[nelem(magic)];
pos = p->pos;
if(p->bytebase){
p->pos = p->bytebase;
pl16(p, pos - (p->bytebase + 2)); /* 2 = sizeof bytecount */
}
p->pos = pos;
if(p->s->secmode & SECMODE_SIGN_ENABLED)
macsign(p, p->seq);
qlock(&p->s->rpclock);
got = nbtrpc(p);
qunlock(&p->s->rpclock);
if(got < 32+NBHDRLEN){
werrstr("cifs packet too small (%d < %d)\n", got, 32+NBHDRLEN);
return -1;
}
gmem(p, m, nelem(magic));
if(memcmp(m, magic, nelem(magic)) != 0){
werrstr("cifsrpc: bad magic number in packet 0x%02ux%02ux%02ux%02ux",
m[0], m[1], m[2], m[3]);
return -1;
}
reply = g8(p); /* cmd */
err = gl32(p); /* errcode */
g8(p); /* flags */
p->flags2 = gl16(p); /* flags2 */
gl16(p); /* PID MS bits */
seq = gl32(p); /* reserved */
gl32(p); /* MAC (if in use) */
gl16(p); /* Padding */
tid = gl16(p); /* TID */
gl16(p); /* PID lsbs */
uid = gl16(p); /* UID */
gl16(p); /* mid */
g8(p); /* word count */
if(reply != p->request){
fprint(2, "unexpected reply (cmd=%x/%x seq=%d/%d)\n",
reply, p->request, seq, p->seq);
return -1;
}
if(p->s->secmode & SECMODE_SIGN_ENABLED){
if(macsign(p, p->seq+1) != 0 && p->s->seqrun){
werrstr("cifsrpc: invalid packet signature");
print("MAC signature bad\n");
// FIXME: for debug only return -1;
}
}else{
/*
* We allow the sequence number of zero as some old samba
* servers seem to fall back to this unexpectedly
* after reporting sequence numbers correctly for a while.
*
* Some other samba servers seem to always report a sequence
* number of zero if MAC signing is disabled, so we have to
* catch that too.
*/
if(p->s->seqrun && seq != p->seq && seq != 0){
werrstr("bad sequence number (%d != %d)\n", p->seq, seq);
return -1;
}
}
p->tid = tid;
if(p->s->uid == NO_UID)
p->s->uid = uid;
if(p->flags2 & FL2_NT_ERRCODES){
/* is it a real error rather than info/warning/chatter? */
if((err & 0xF0000000) == 0xC0000000){
werrstr("%s", nterrstr(err));
return -1;
}
}else{
if(err){
werrstr("%s", doserrstr(err));
return -1;
}
}
return got;
}
/*
* Some older servers (old samba) prefer to talk older
* dialects but if given no choice they will talk the
* more modern ones, so we don't give them the choice.
*/
int
CIFSnegotiate(Session *s, long *svrtime, char *domain, int domlen, char *cname,
int cnamlen)
{
int d, i;
char *ispeak = "NT LM 0.12";
static char *dialects[] = {
// { "PC NETWORK PROGRAM 1.0"},
// { "MICROSOFT NETWORKS 1.03"},
// { "MICROSOFT NETWORKS 3.0"},
// { "LANMAN1.0"},
// { "LM1.2X002"},
// { "NT LANMAN 1.0"},
{ "NT LM 0.12" },
};
Pkt *p;
/*
* This should not be necessary, however the XP seems to use
* Unicode strings in its Negoiate response, but not set the
* Flags2 UNICODE flag.
*
* It does however echo back the FL_UNICODE flag we set in the
* flags2 negoiate request.
*
* The bodge is to force FL_UNICODE for this single request,
* clearing it after. Later we set FL2_UNICODE if the server
* agrees to CAP_UNICODE as it "should" be done.
*/
s->flags2 |= FL2_UNICODE;
p = cifshdr(s, nil, SMB_COM_NEGOTIATE);
s->flags2 &= ~FL2_UNICODE;
pbytes(p);
for(i = 0; i < nelem(dialects); i++){
p8(p, STR_DIALECT);
pascii(p, dialects[i]);
}
if(cifsrpc(p) == -1){
free(p);
return -1;
}
d = gl16(p);
if(d < 0 || d > nelem(dialects)){
werrstr("no CIFS dialect in common");
free(p);
return -1;
}
if(strcmp(dialects[d], ispeak) != 0){
werrstr("%s dialect unsupported", dialects[d]);
free(p);
return -1;
}
s->secmode = g8(p); /* Security mode */
gl16(p); /* Max outstanding requests */
gl16(p); /* Max VCs */
s->mtu = gl32(p); /* Max buffer size */
gl32(p); /* Max raw buffer size (depricated) */
gl32(p); /* Session key */
s->caps = gl32(p); /* Server capabilities */
*svrtime = gvtime(p); /* fileserver time */
s->tz = (short)gl16(p) * 60; /* TZ in mins, is signed (SNIA doc is wrong) */
s->challen = g8(p); /* Encryption key length */
gl16(p);
gmem(p, s->chal, s->challen); /* Get the challenge */
gstr(p, domain, domlen); /* source domain */
{ /* NetApp Filer seem not to report its called name */
char *cn = emalloc9p(cnamlen);
gstr(p, cn, cnamlen); /* their name */
if(strlen(cn) > 0)
memcpy(cname, cn, cnamlen);
free(cn);
}
if(s->caps & CAP_UNICODE)
s->flags2 |= FL2_UNICODE;
free(p);
return 0;
}
int
CIFSsession(Session *s)
{
char os[64], *q;
Rune r;
Pkt *p;
enum {
mycaps = CAP_UNICODE | CAP_LARGE_FILES | CAP_NT_SMBS |
CAP_NT_FIND | CAP_STATUS32,
};
s->seqrun = 1; /* activate the sequence number generation/checking */
p = cifshdr(s, nil, SMB_COM_SESSION_SETUP_ANDX);
p8(p, 0xFF); /* No secondary command */
p8(p, 0); /* Reserved (must be zero) */
pl16(p, 0); /* Offset to next command */
pl16(p, MTU); /* my max buffer size */
pl16(p, 1); /* my max multiplexed pending requests */
pl16(p, 0); /* Virtual connection # */
pl32(p, 0); /* Session key (if vc != 0) */
if((s->secmode & SECMODE_PW_ENCRYPT) == 0) {
pl16(p, utflen(Sess->auth->resp[0])*2 + 2); /* passwd size */
pl16(p, utflen(Sess->auth->resp[0])*2 + 2); /* passwd size (UPPER CASE) */
pl32(p, 0); /* Reserved */
pl32(p, mycaps);
pbytes(p);
for(q = Sess->auth->resp[0]; *q; ){
q += chartorune(&r, q);
pl16(p, toupperrune(r));
}
pl16(p, 0);
for(q = Sess->auth->resp[0]; *q; ){
q += chartorune(&r, q);
pl16(p, r);
}
pl16(p, 0);
}else{
pl16(p, Sess->auth->len[0]); /* LM passwd size */
pl16(p, Sess->auth->len[1]); /* NTLM passwd size */
pl32(p, 0); /* Reserved */
pl32(p, mycaps);
pbytes(p);
pmem(p, Sess->auth->resp[0], Sess->auth->len[0]);
pmem(p, Sess->auth->resp[1], Sess->auth->len[1]);
}
pstr(p, Sess->auth->user); /* Account name */
pstr(p, Sess->auth->windom); /* Primary domain */
pstr(p, "plan9"); /* Client OS */
pstr(p, argv0); /* Client LAN Manager type */
if(cifsrpc(p) == -1){
free(p);
return -1;
}
g8(p); /* Reserved (0) */
gl16(p); /* Offset to next command wordcount */
Sess->isguest = gl16(p) & 1; /* logged in as guest */
gl16(p);
gl16(p);
/* no security blob here - we don't understand extended security anyway */
gstr(p, os, sizeof os);
s->remos = estrdup9p(os);
free(p);
return 0;
}
CIFStreeconnect(Session *s, char *cname, char *tree, Share *sp)
{
int len;
char *resp, *path;
char zeros[24];
Pkt *p;
resp = Sess->auth->resp[0];
len = Sess->auth->len[0];
if((s->secmode & SECMODE_USER) != SECMODE_USER){
memset(zeros, 0, sizeof zeros);
resp = zeros;
len = sizeof zeros;
}
p = cifshdr(s, nil, SMB_COM_TREE_CONNECT_ANDX);
p8(p, 0xFF); /* Secondary command */
p8(p, 0); /* Reserved */
pl16(p, 0); /* Offset to next Word Count */
pl16(p, 0); /* Flags */
if((s->secmode & SECMODE_PW_ENCRYPT) == 0){
pl16(p, len+1); /* password len, including null */
pbytes(p);
pascii(p, resp);
}else{
pl16(p, len);
pbytes(p);
pmem(p, resp, len);
}
path = smprint("//%s/%s", cname, tree);
ppath(p, path); /* path */
free(path);
pascii(p, "?????"); /* service type any (so we can do RAP calls) */
if(cifsrpc(p) == -1){
free(p);
return -1;
}
g8(p); /* Secondary command */
g8(p); /* Reserved */
gl16(p); /* Offset to next command */
sp->options = g8(p); /* options supported */
sp->tid = p->tid; /* get received TID from packet header */
free(p);
return 0;
}
int
CIFSlogoff(Session *s)
{
int rc;
Pkt *p;
p = cifshdr(s, nil, SMB_COM_LOGOFF_ANDX);
p8(p, 0xFF); /* No ANDX command */
p8(p, 0); /* Reserved (must be zero) */
pl16(p, 0); /* offset ot ANDX */
pbytes(p);
rc = cifsrpc(p);
free(p);
return rc;
}
int
CIFStreedisconnect(Session *s, Share *sp)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_TREE_DISCONNECT);
pbytes(p);
rc = cifsrpc(p);
free(p);
return rc;
}
int
CIFSdeletefile(Session *s, Share *sp, char *name)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_DELETE);
pl16(p, ATTR_HIDDEN|ATTR_SYSTEM); /* search attributes */
pbytes(p);
p8(p, STR_ASCII); /* buffer format */
ppath(p, name);
rc = cifsrpc(p);
free(p);
return rc;
}
int
CIFSdeletedirectory(Session *s, Share *sp, char *name)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_DELETE_DIRECTORY);
pbytes(p);
p8(p, STR_ASCII); /* buffer format */
ppath(p, name);
rc = cifsrpc(p);
free(p);
return rc;
}
int
CIFScreatedirectory(Session *s, Share *sp, char *name)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_CREATE_DIRECTORY);
pbytes(p);
p8(p, STR_ASCII);
ppath(p, name);
rc = cifsrpc(p);
free(p);
return rc;
}
int
CIFSrename(Session *s, Share *sp, char *old, char *new)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_RENAME);
pl16(p, ATTR_HIDDEN|ATTR_SYSTEM|ATTR_DIRECTORY); /* search attributes */
pbytes(p);
p8(p, STR_ASCII);
ppath(p, old);
p8(p, STR_ASCII);
ppath(p, new);
rc = cifsrpc(p);
free(p);
return rc;
}
/* for NT4/Win2k/XP */
int
CIFS_NT_opencreate(Session *s, Share *sp, char *name, int flags, int options,
int attrs, int access, int share, int action, int *result, FInfo *fi)
{
Pkt *p;
int fh;
p = cifshdr(s, sp, SMB_COM_NT_CREATE_ANDX);
p8(p, 0xFF); /* Secondary command */
p8(p, 0); /* Reserved */
pl16(p, 0); /* Offset to next command */
p8(p, 0); /* Reserved */
pl16(p, utflen(name) *2); /* file name len */
pl32(p, flags); /* Flags */
pl32(p, 0); /* fid of cwd, if relative path */
pl32(p, access); /* access desired */
pl64(p, 0); /* initial allocation size */
pl32(p, attrs); /* Extended attributes */
pl32(p, share); /* Share Access */
pl32(p, action); /* What to do on success/failure */
pl32(p, options); /* Options */
pl32(p, SECURITY_IMPERSONATION); /* Impersonation level */
p8(p, SECURITY_CONTEXT_TRACKING | SECURITY_EFFECTIVE_ONLY); /* security flags */
pbytes(p);
p8(p, 0); /* FIXME: padding? */
ppath(p, name); /* filename */
if(cifsrpc(p) == -1){
free(p);
return -1;
}
memset(fi, 0, sizeof(FInfo));
g8(p); /* Secondary command */
g8(p); /* Reserved */
gl16(p); /* Offset to next command */
g8(p); /* oplock granted */
fh = gl16(p); /* FID for opened object */
*result = gl32(p); /* create action taken */
gl64(p); /* creation time */
fi->accessed = gvtime(p); /* last access time */
fi->written = gvtime(p); /* last written time */
fi->changed = gvtime(p); /* change time */
fi->attribs = gl32(p); /* extended attributes */
gl64(p); /* bytes allocated */
fi->size = gl64(p); /* file size */
free(p);
return fh;
}
/* for Win95/98/ME */
CIFS_SMB_opencreate(Session *s, Share *sp, char *name, int access,
int attrs, int action, int *result)
{
Pkt *p;
int fh;
p = cifshdr(s, sp, SMB_COM_OPEN_ANDX);
p8(p, 0xFF); /* Secondary command */
p8(p, 0); /* Reserved */
pl16(p, 0); /* Offset to next command */
pl16(p, 0); /* Flags (0 == no stat(2) info) */
pl16(p, access); /* desired access */
pl16(p, ATTR_HIDDEN|ATTR_SYSTEM);/* search attributes */
pl16(p, attrs); /* file attribytes */
pdatetime(p, 0); /* creation time (0 == now) */
pl16(p, action); /* What to do on success/failure */
pl32(p, 0); /* allocation size */
pl32(p, 0); /* reserved */
pl32(p, 0); /* reserved */
pbytes(p);
ppath(p, name); /* filename */
if(cifsrpc(p) == -1){
free(p);
return -1;
}
g8(p); /* Secondary command */
g8(p); /* Reserved */
gl16(p); /* Offset to next command */
fh = gl16(p); /* FID for opened object */
gl16(p); /* extended attributes */
gvtime(p); /* last written time */
gl32(p); /* file size */
gl16(p); /* file type (disk/fifo/printer etc) */
gl16(p); /* device status (for fifos) */
*result = gl16(p); /* access granted */
free(p);
return fh;
}
vlong
CIFSwrite(Session *s, Share *sp, int fh, uvlong off, void *buf, vlong n)
{
Pkt *p;
vlong got;
/* FIXME: Payload should be padded to long boundary */
assert((n & 0xffffffff00000000LL) == 0 || s->caps & CAP_LARGE_FILES);
assert((off & 0xffffffff00000000LL) == 0 || s->caps & CAP_LARGE_FILES);
assert(n < s->mtu - T2HDRLEN || s->caps & CAP_LARGE_WRITEX);
p = cifshdr(s, sp, SMB_COM_WRITE_ANDX);
p8(p, 0xFF); /* Secondary command */
p8(p, 0); /* Reserved */
pl16(p, 0); /* Offset to next command */
pl16(p, fh); /* File handle */
pl32(p, off & 0xffffffff); /* LSBs of Offset */
pl32(p, 0); /* Reserved (0) */
pl16(p, s->nocache); /* Write mode (0 - write through) */
pl16(p, 0); /* Bytes remaining */
pl16(p, n >> 16); /* MSBs of length */
pl16(p, n & 0xffffffff); /* LSBs of length */
pl16(p, T2HDRLEN); /* Offset to data, in bytes */
pl32(p, off >> 32); /* MSBs of offset */
pbytes(p);
p->pos = p->buf +T2HDRLEN +NBHDRLEN;
pmem(p, buf, n); /* Data */
if(cifsrpc(p) == -1){
free(p);
return -1;
}
g8(p); /* Secondary command */
g8(p); /* Reserved */
gl16(p); /* Offset to next command */
got = gl16(p); /* LSWs of bytes written */
gl16(p); /* remaining (space ?) */
got |= (gl16(p) << 16); /* MSWs of bytes written */
free(p);
return got;
}
vlong
CIFSread(Session *s, Share *sp, int fh, uvlong off, void *buf, vlong n,
vlong minlen)
{
int doff;
vlong got;
Pkt *p;
assert((n & 0xffffffff00000000LL) == 0 || s->caps & CAP_LARGE_FILES);
assert((off & 0xffffffff00000000LL) == 0 || s->caps & CAP_LARGE_FILES);
assert(n < s->mtu - T2HDRLEN || s->caps & CAP_LARGE_READX);
p = cifshdr(s, sp, SMB_COM_READ_ANDX);
p8(p, 0xFF); /* Secondary command */
p8(p, 0); /* Reserved */
pl16(p, 0); /* Offset to next command */
pl16(p, fh); /* File handle */
pl32(p, off & 0xffffffff); /* Offset to beginning of write */
pl16(p, n); /* Maximum number of bytes to return */
pl16(p, minlen); /* Minimum number of bytes to return */
pl32(p, (uint)n >> 16); /* MSBs of maxlen */
pl16(p, 0); /* Bytes remaining to satisfy request */
pl32(p, off >> 32); /* MS 32 bits of offset */
pbytes(p);
if(cifsrpc(p) == -1){
free(p);
return -1;
}
g8(p); /* Secondary command */
g8(p); /* Reserved */
gl16(p); /* Offset to next command */
gl16(p); /* Remaining */
gl16(p); /* Compression mode */
gl16(p); /* Reserved */
got = gl16(p); /* length */
doff = gl16(p); /* Offset from header to data */
got |= gl16(p) << 16;
p->pos = p->buf + doff + NBHDRLEN;
gmem(p, buf, got); /* data */
free(p);
return got;
}
int
CIFSflush(Session *s, Share *sp, int fh)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_FLUSH);
pl16(p, fh); /* fid */
pbytes(p);
rc = cifsrpc(p);
free(p);
return rc;
}
/*
* Setting the time of last write to -1 gives "now" if the file
* was written and leaves it the same if the file wasn't written.
*/
int
CIFSclose(Session *s, Share *sp, int fh)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_CLOSE);
pl16(p, fh); /* fid */
pl32(p, ~0L); /* Time of last write (none) */
pbytes(p);
rc = cifsrpc(p);
free(p);
return rc;
}
int
CIFSfindclose2(Session *s, Share *sp, int sh)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_FIND_CLOSE2);
pl16(p, sh); /* sid */
pbytes(p);
rc = cifsrpc(p);
free(p);
return rc;
}
int
CIFSecho(Session *s)
{
Pkt *p;
int rc;
p = cifshdr(s, nil, SMB_COM_ECHO);
pl16(p, 1); /* number of replies */
pbytes(p);
pascii(p, "abcdefghijklmnopqrstuvwxyz"); /* data */
rc = cifsrpc(p);
free(p);
return rc;
}
int
CIFSsetinfo(Session *s, Share *sp, char *path, FInfo *fip)
{
int rc;
Pkt *p;
p = cifshdr(s, sp, SMB_COM_SET_INFORMATION);
pl16(p, fip->attribs);
pl32(p, time(nil) - s->tz); /* modified time */
pl64(p, 0); /* reserved */
pl16(p, 0); /* reserved */
pbytes(p);
p8(p, STR_ASCII); /* buffer format */
ppath(p, path);
rc = cifsrpc(p);
free(p);
return rc;
}