shithub: riscv

Download patch

ref: 4b550911d969d0c5930d1eac5a9c8dc15c0595a6
parent: 7dfb8b6ec5832d48da20a91c41540c9dd0eeeda1
author: cinap_lenrek <cinap_lenrek@centraldogma>
date: Mon May 9 06:42:20 EDT 2011

add cifsd

--- /dev/null
+++ b/sys/man/8/cifsd
@@ -1,0 +1,105 @@
+.TH cifsd 8
+.SH NAME
+cifsd \- CIFS/SMB network daemon
+.SH SYNOPSIS
+.B ip/cifsd
+[
+.B -t
+] [
+.B -d
+] [
+.B -f
+.I debuglog
+] [
+.B -w
+.I name
+] [
+.B -o
+.I option
+] ... [
+.I conndir
+]
+.SH DESCRIPTION
+.I Cifsd
+exports filesystems to
+.SM CIFS or
+.SM SMB
+clients like Microsoft \(tm Windows.
+.PP
+It is normally started by the network listen process via the
+.B /rc/bin/service/tcp445
+service script (see
+.IR listen (8)),
+wich passes
+.I conndir
+and filedescriptors 0 and 1 to the incoming connection.
+.PP
+Users are authenticated by ther Inferno/pop secret held by the auth server.
+When successfull,
+.I cifsd
+changes its user id and namespace to the authenticated user.
+Informative log messages are appended to
+.B /sys/log/cifsd
+if it exists.
+.PP
+By default the share 
+.B local
+is offered, which represents the root of the namespace
+described by
+.B /lib/namespace.
+If a different name is explicitly requested by the client then
+.B /bin/9fs
+(see
+.IR srv (4))
+is invoked to attach that filesystem, which is then exported instead.
+.PP
+The flags are:
+.TP
+.B t
+Run the server in trusted mode, so it will not require
+authentication from the client and keep running in the callers
+namespace.
+.TP
+.B d
+Enable or increases debug verbosity for the
+.I debuglog
+file. Debug messages are never written to the system logfile.
+.TP
+.B f
+Specify the filename for the
+.I debuglog
+file. If not specified no debug messages are generated.
+.TP
+.B w
+Set the workgroup (or primary domain) to
+.I name. The default is
+.SM WORKGROUP
+.TP
+.B o
+Enables the following
+.I option
+string.
+This flag can appear multiple times when more than one option has to
+be enabled.  Valid options are:
+.RS
+.TP
+.B trspaces
+transforms whitespaces in filenames to non breaking whitespaces. This is usefull
+when exporting filesystems other than fossil.
+.TP
+.B casesensitive
+By default, filename lookups are done case insensitive to match
+windows filesystem sematics.  This option disables case insensitive
+lookups wich can result in a performance improvement, but might break
+some applications.
+.SH FILES
+.B /rc/bin/service/tcp445
+.br
+.B /sys/log/cifsd
+.br
+.B /lib/namespace
+.SH SOURCE
+.B /sys/src/cmd/ip/cifsd
+.SH "SEE ALSO"
+.IR listen (8),
+.IR srv (4)
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/README
@@ -1,0 +1,20 @@
+look at the cifsd.man manpage for instrctions. 
+
+check for new releases on the web:
+http://9hal.ath.cx/usr/cinap_lenrek/cifsd.tgz
+http://9hal.ath.cx/usr/cinap_lenrek/
+
+or on sources:
+/n/sources/contrib/cinap_lenrek/cifsd.tgz
+/n/sources/contrib/cinap_lenrek
+
+you can email me for requests/bugreports/contrib at:
+cinap_lenrek AT gmx DOT de
+
+special thanks goes to:
+
+capso (http://nanosouffle.net/)
+for testing and bug reporting
+
+steve simon (http://quintile.net/)
+factotum/authsrv support for NTLM auth and cifsd testing and bug reporting
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/dat.h
@@ -1,0 +1,232 @@
+typedef struct Rop Rop;
+typedef struct Req Req;
+typedef struct Trans Trans;
+
+typedef struct Share Share;
+typedef struct File File;
+typedef struct Find Find;
+typedef struct Tree Tree;
+
+struct Rop
+{
+	int (*strpack)(uchar *, uchar *, uchar *, void *);
+	int (*strunpack)(uchar *, uchar *, uchar *, void *);
+	int (*namepack)(uchar *, uchar *, uchar *, void *);
+	int (*nameunpack)(uchar *, uchar *, uchar *, void *);
+	int (*untermstrpack)(uchar *, uchar *, uchar *, void *);
+	int (*untermnamepack)(uchar *, uchar *, uchar *, void *);
+};
+
+struct Req
+{
+	int cmd;
+	int tid;
+	int pid;
+	int uid;
+	int mid;
+	int flags;
+	int flags2;
+
+	uchar sig[8];
+
+	uchar *lh, *rh, *rp, *re;
+
+	Rop *o;
+	char *name;
+	void (*respond)(Req *r, int err);
+	int (*namecmp)(char *, char *);
+};
+
+struct Trans
+{
+	int cmd;
+	int flags;
+
+	struct {
+		struct {
+			uchar *b, *p, *e;
+		} param, data, setup;
+	} in, out;
+
+	Req *r;
+	Rop *o;
+	char *name;
+	void (*respond)(Trans *t, int err);
+	int (*namecmp)(char *, char *);
+};
+
+struct File
+{
+	int ref;
+	int fd;
+	int rtype;
+	int dacc;
+	char *path;
+	void *aux;
+};
+
+struct Find
+{
+	int ref;
+	int attr;
+	char *base;
+	char *pattern;
+	int casesensitive;
+	int index;
+	Dir *dotdot;
+	Dir *dot;
+	Dir *dir;
+	int ndir;
+};
+
+struct Share
+{
+	Share *next;
+
+	char *service;
+	int stype;
+
+	char *name;
+	char *root;
+	char *remark;
+
+	char *fsname;
+	int namelen;
+	vlong allocsize;
+	vlong freesize;
+	int sectorsize;
+	int blocksize;
+};
+
+struct Tree
+{
+	int tid;
+
+	void **file;
+	int nfile;
+
+	void **find;
+	int nfind;
+
+	Share *share;
+};
+
+int debug;
+int trspaces;
+int needauth;
+char *domain;
+char *progname;
+char *osname;
+
+char *remotesys;
+char *remoteuser;
+int remotebuffersize;
+
+long starttime;
+int tzoff;
+
+enum
+{
+	BUFFERSIZE = 0x8000,
+
+	STATUS_INVALID_SMB				= 0x00010002,
+	STATUS_SMB_BAD_TID				= 0x00050002,
+	STATUS_SMB_BAD_FID				= 0x00060001,
+	STATUS_OS2_INVALID_ACCESS		= 0x000C0001,
+	STATUS_SMB_BAD_UID				= 0x005B0002,
+	STATUS_OS2_INVALID_LEVEL  		= 0x007C0001,
+	STATUS_NO_MORE_FILES			= 0x80000006,
+	STATUS_INVALID_HANDLE			= 0xC0000008,
+	STATUS_NO_SUCH_FILE				= 0xC000000F,
+	STATUS_ACCESS_DENIED			= 0xC0000022,
+	STATUS_OBJECT_NAME_NOT_FOUND	= 0xC0000034,
+	STATUS_OBJECT_NAME_COLLISION	= 0xC0000035,
+	STATUS_OBJECT_PATH_INVALID		= 0xC0000039,
+	STATUS_OBJECT_PATH_NOT_FOUND	= 0xC000003A,
+	STATUS_OBJECT_PATH_SYNTAX_BAD	= 0xC000003B,
+	STATUS_SHARING_VIOLATION		= 0xC0000043,
+	STATUS_LOGON_FAILURE			= 0xC000006D,
+	STATUS_FILE_IS_A_DIRECTORY		= 0xC00000BA,
+	STATUS_NOT_SUPPORTED			= 0xC00000BB,
+	STATUS_BAD_DEVICE_TYPE			= 0xC00000CB,
+	STATUS_BAD_NETWORK_NAME 		= 0xC00000CC,
+	STATUS_NOT_SAME_DEVICE			= 0xC00000D4,
+	STATUS_DIRECTORY_NOT_EMPTY		= 0xC0000101,
+
+	/* resource type */
+	FileTypeDisk = 0,
+
+	/* stype */
+	STYPE_DISKTREE = 0,
+	STYPE_PRINTQ = 1,
+	STYPE_DEVICE = 2,
+	STYPE_IPC = 3,
+
+	/* capabilities */
+	CAP_UNICODE = 0x4,
+	CAP_LARGEFILES = 0x8,
+	CAP_NT_SMBS = 0x10,
+	CAP_NT_STATUS = 0x40,
+	CAP_NT_FIND = 0x200,
+
+	/* extended file attributes */
+	ATTR_READONLY = 0x1,
+	ATTR_HIDDEN = 0x2,
+	ATTR_SYSTEM = 0x4,
+	ATTR_DIRECTORY = 0x10,
+	ATTR_ARCHIVE = 0x20,
+	ATTR_NORMAL = 0x80,
+
+	DOSMASK	 = 0x37,
+
+	/* access */
+	FILE_READ_DATA = 0x1,
+	FILE_WRITE_DATA = 0x2,
+	FILE_APPEND_DATA = 0x4,
+	FILE_EXECUTE = 0x20,
+	FILE_DELETE = 0x10000,
+	GENERIC_ALL = 0x10000000,
+	GENERIC_EXECUTE = 0x20000000,
+	GENERIC_WRITE = 0x40000000,
+	GENERIC_READ = 0x80000000,
+
+	READMASK =
+		FILE_READ_DATA |
+		FILE_EXECUTE |
+		GENERIC_ALL |
+		GENERIC_EXECUTE |
+		GENERIC_READ,
+
+	WRITEMASK = 
+		FILE_WRITE_DATA |
+		FILE_APPEND_DATA |
+		GENERIC_ALL |
+		GENERIC_WRITE,
+
+	/* share access */
+	FILE_SHARE_NONE = 0,
+	FILE_SHARE_READ = 1,
+	FILE_SHARE_WRITE = 2,
+	FILE_SHARE_DELETE = 4,
+	FILE_SHARE_COMPAT = -1,
+
+	/* createdisposition */
+	FILE_SUPERSEDE = 0,
+	FILE_OPEN,
+	FILE_CREATE,
+	FILE_OPEN_IF,
+	FILE_OVERWRITE,
+	FILE_OVERWRITE_IF,
+
+	/* createaction */
+	FILE_SUPERSEDED = 0,
+	FILE_OPEND,
+	FILE_CREATED,
+	FILE_OVERWRITTEN,
+
+	/* createoptions */
+	FILE_DIRECTORY_FILE = 0x1,
+	FILE_NON_DIRECTORY_FILE = 0x40,
+	FILE_DELETE_ON_CLOSE = 0x1000,
+	FILE_OPEN_BY_FILE_ID = 0x2000,
+};
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/dir.c
@@ -1,0 +1,218 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+static char*
+append(char **p, char *s)
+{
+	int n;
+	char *o;
+
+	if(s == nil)
+		return nil;
+	n = strlen(s)+1;
+	memmove(o = *p, s, n);
+	*p += n;
+	return o;
+}
+
+static Dir*
+xdirdup(Dir *d, int n)
+{
+	char *p;
+	Dir *o;
+	int i;
+
+	p = nil;
+	for(i=0; i<n; i++){
+		p += strlen(d[i].name)+1;
+		if(d[i].uid) p += strlen(d[i].uid)+1;
+		if(d[i].gid) p += strlen(d[i].gid)+1;
+		if(d[i].muid) p += strlen(d[i].muid)+1;
+	}
+	o = malloc(n*sizeof(*d) + (int)p);
+	memmove(o, d, n*sizeof(*d));
+	p = (char*)&o[n];
+	for(i=0; i<n; i++){
+		o[i].name = append(&p, d[i].name);
+		o[i].uid = append(&p, d[i].uid);
+		o[i].gid = append(&p, d[i].gid);
+		o[i].muid = append(&p, d[i].muid);
+	}
+	return o;
+}
+
+static int xdirread0(char **path, int (*namecmp)(char *, char *), Dir **d);
+
+int
+xdirread(char **path, int (*namecmp)(char *, char *), Dir **d)
+{
+	Dir *t;
+	int n;
+
+	if((n = xdirread0(path, namecmp, &t)) > 0)
+		*d =  xdirdup(t, n);
+	else
+		*d = nil;
+	return n;
+}
+
+static Dir*
+xdirstat0(char **path, int (*namecmp)(char *, char *), char *err)
+{
+	char *base, *name;
+	Dir *d, *t;
+	int n, i;
+
+	if(d = dirstat(*path))
+		return d;
+	if(!splitpath(*path, &base, &name))
+		return nil;
+	if((n = xdirread0(&base, namecmp, &t)) < 0)
+		goto out;
+	for(i=0; i<n; i++){
+		if(namecmp(t[i].name, name))
+			continue;
+		free(*path); *path = conspath(base, t[i].name);
+		d = xdirdup(&t[i], 1);
+		goto out;
+	}
+	werrstr("%s", err);
+out:
+	free(base);
+	free(name);
+	return d;
+}
+
+Dir*
+xdirstat(char **path, int (*namecmp)(char *, char *))
+{
+	return xdirstat0(path, namecmp, "name not found");
+}
+
+typedef struct XDir XDir;
+struct XDir
+{
+	Qid	qid;
+	char	*path;
+	int	ndir;
+	Dir	*dir;
+	XDir	*next;
+};
+
+static void
+freexdir(XDir *x)
+{
+	free(x->path);
+	free(x->dir);
+	free(x);
+}
+
+static int
+qidcmp(Qid *q1, Qid *q2)
+{
+	return (q1->type != q2->type) || (q1->path != q2->path) || (q1->vers != q2->vers);
+}
+
+static XDir *xdirlist;
+static int xdircount;
+
+static int
+xdirread0(char **path, int (*namecmp)(char *, char *), Dir **d)
+{
+	XDir *x, *p;
+	int fd, n;
+	Dir *t;
+
+	t = nil;
+	for(p = nil, x = xdirlist; x; p=x, x=x->next){
+		if(namecmp(x->path, *path))
+			continue;
+		if(x == xdirlist)
+			xdirlist = x->next;
+		else
+			p->next = x->next;
+		while(t = dirstat(x->path)){
+			if(qidcmp(&t->qid, &x->qid))
+				break;
+			free(t);
+			x->next = xdirlist;
+			xdirlist = x;
+			if(strcmp(x->path, *path)){
+				free(*path);
+				*path = strdup(x->path);
+			}
+			if(d) *d = x->dir;
+			return x->ndir;
+		}
+		xdircount--;
+		freexdir(x);
+		break;
+	}
+	if((fd = open(*path, OREAD)) < 0){
+		free(t);
+		if(t = xdirstat0(path, namecmp, "directory entry not found"))
+			fd = open(*path, OREAD);
+	} else if(t == nil)
+		t = dirfstat(fd);
+
+	n = -1;
+	if(fd < 0 || t == nil)
+		goto out;
+	if(t->qid.type != QTDIR){
+		werrstr("not a directory");
+		goto out;
+	}
+	if((n = dirreadall(fd, d)) < 0)
+		goto out;
+
+	if(xdircount >= 8){
+		xdircount--;
+		for(p = xdirlist, x = xdirlist->next; x->next; p = x, x = x->next)
+			;
+		p->next = nil;
+		freexdir(x);
+	}
+
+	x = mallocz(sizeof(*x), 1);
+	x->qid = t->qid;
+	x->path = strdup(*path);
+	x->ndir = n;
+	x->dir = *d;
+
+	x->next = xdirlist;
+	xdirlist = x;
+	xdircount++;
+
+out:
+	if(fd >= 0)
+		close(fd);
+	free(t);
+	return n;
+}
+
+void
+xdirflush(char *path, int (*namecmp)(char *, char *))
+{
+	XDir **pp, **xx, *x;
+	char *d, *s;
+	int n;
+
+	n = strlen(path);
+	if(s = strrchr(path, '/'))
+		n = s - path;
+	d = smprint("%.*s", n, path);
+	s = malloc(++n);
+	for(pp = &xdirlist; x = *pp; pp = xx){
+		xx = &x->next;
+		snprint(s, n, "%s", x->path);
+		if(namecmp(d, s) == 0){
+			*pp = *xx; xx = pp;
+			xdircount--;
+			freexdir(x);
+		}
+	}
+	free(s);
+	free(d);
+}
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/error.c
@@ -1,0 +1,129 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+enum {
+	/* error class */
+	ERRDOS = 1,
+	ERRSRV = 2,
+	ERRHRD = 3,
+	ERRCMD = 0xFF,
+
+	/* error codes */
+	ERRbadfunc = 0x1,
+	ERRbadfile = 0x2,
+	ERRbadpath = 0x3,
+	ERRnofids = 0x4,
+	ERRnoaccess = 0x5,
+	ERRbadfid = 0x6,
+	ERRbadmcp = 0x7,
+	ERRnomem = 0x8,
+	ERRbadmem = 0x9,
+	ERRbadenv = 0xA,
+	ERRbadformat = 0xB,
+	ERRbadaccess = 0xC,
+	ERRbaddata = 0xD,
+	ERRbaddrive = 0xF,
+	ERRremcd = 0x10,
+	ERRdiffdevice = 0x11,
+	ERRnofiles = 0x12,
+	ERRgeneral = 0x1F,
+	ERRbadshare = 0x20,
+	ERRlock = 0x21,
+	ERReof = 0x26,
+	ERRunsup = 0x32,
+	ERRfilexists = 0x50,
+	ERRinvalidparam = 0x57,
+	ERRunknownlevel = 0x7C,
+	ERRbadpipe = 0xE6,
+	ERRinvnetname = 0x06,
+	ERRreqnotaccep = 0x47,
+	ERRnosuchshare = 0x43,
+	ERRerror = 0x1,
+	ERRbadpw = 0x2,
+	ERRaccess = 0x4,
+	ERRinvtid = 0x5,
+	ERRinvdevice = 0x7,
+	ERRbaduid = 0x5b,
+};
+
+int
+doserror(int err)
+{
+#define SE(c,e)	e<<16 | c
+	static struct Ent {
+		int error;
+		int status;
+	} tab[] = {
+		SE(ERRSRV, ERRerror), STATUS_INVALID_SMB,
+		SE(ERRSRV, ERRinvtid), STATUS_SMB_BAD_TID,
+		SE(ERRDOS, ERRbadfid), STATUS_SMB_BAD_FID,
+		SE(ERRDOS, ERRbadaccess), STATUS_OS2_INVALID_ACCESS,
+		SE(ERRSRV, ERRbaduid), STATUS_SMB_BAD_UID,
+		SE(ERRDOS, ERRunknownlevel), STATUS_OS2_INVALID_LEVEL,
+		SE(ERRDOS, ERRnofiles), STATUS_NO_MORE_FILES,
+		SE(ERRDOS, ERRbadfid), STATUS_INVALID_HANDLE,
+		SE(ERRDOS, ERRnoaccess), STATUS_ACCESS_DENIED,
+		SE(ERRDOS, ERRbadfile), STATUS_OBJECT_NAME_NOT_FOUND,
+		SE(ERRDOS, ERRfilexists), STATUS_OBJECT_NAME_COLLISION,
+		SE(ERRDOS, ERRbadpath), STATUS_OBJECT_PATH_INVALID,
+		SE(ERRDOS, ERRbadpath), STATUS_OBJECT_PATH_NOT_FOUND,
+		SE(ERRDOS, ERRbadpath), STATUS_OBJECT_PATH_SYNTAX_BAD,
+		SE(ERRDOS, ERRbadshare), STATUS_SHARING_VIOLATION,
+		SE(ERRSRV, ERRbadpw), STATUS_LOGON_FAILURE,
+		SE(ERRDOS, ERRnoaccess), STATUS_FILE_IS_A_DIRECTORY,
+		SE(ERRDOS, ERRunsup), STATUS_NOT_SUPPORTED,
+		SE(ERRSRV, ERRinvdevice), STATUS_BAD_DEVICE_TYPE,
+		SE(ERRSRV, ERRinvnetname), STATUS_BAD_NETWORK_NAME,
+		SE(ERRDOS, ERRdiffdevice), STATUS_NOT_SAME_DEVICE,
+		SE(ERRDOS, ERRremcd), STATUS_DIRECTORY_NOT_EMPTY,
+		SE(ERRSRV, ERRerror), 0,
+	};
+	struct Ent *p;
+
+	for(p=tab; p->status; p++)
+		if(p->status == err)
+			break;
+	return p->error;
+}
+
+int
+smbmkerror(void)
+{
+	static struct Ent {
+		int status;
+		char *str;
+	}  tab[] = {
+		STATUS_ACCESS_DENIED, "permission denied",
+		STATUS_ACCESS_DENIED, "access permission denied",
+		STATUS_ACCESS_DENIED, "create prohibited",
+		STATUS_ACCESS_DENIED, "mounted directory forbids creation",
+		STATUS_DIRECTORY_NOT_EMPTY, "directory not empty",
+		STATUS_NO_SUCH_FILE, "no such file",
+		STATUS_OBJECT_NAME_NOT_FOUND, "name not found",
+		STATUS_OBJECT_PATH_NOT_FOUND, "directory entry not found",
+		STATUS_OBJECT_PATH_NOT_FOUND, "not a directory",
+		STATUS_OBJECT_PATH_NOT_FOUND, "does not exist",
+		STATUS_OBJECT_PATH_SYNTAX_BAD, "bad character",
+		STATUS_OBJECT_PATH_SYNTAX_BAD, "file name syntax",
+		STATUS_OBJECT_NAME_COLLISION, "file already exists",
+		STATUS_FILE_IS_A_DIRECTORY, "is a directory",
+		/* kenfs */
+		STATUS_OBJECT_NAME_COLLISION, "create/wstat -- file exists",
+		STATUS_ACCESS_DENIED, "wstat -- not owner",
+		STATUS_ACCESS_DENIED, "wstat -- not in group",
+		/* unknown error */
+		STATUS_INVALID_SMB, nil,
+	};
+	char buf[ERRMAX];
+	struct Ent *p;
+
+	rerrstr(buf, sizeof(buf));
+	for(p = tab; p->str; p++)
+		if(strstr(buf, p->str))
+			break;
+	if(debug)
+		fprint(2, "smbmkerror: %s -> %lux\n", buf, (ulong)p->status);
+	return p->status;
+}
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/file.c
@@ -1,0 +1,319 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Opl Opl;
+struct Opl
+{
+	int ref;
+	ulong hash;
+	char *path;
+	Opl *next;
+	File *locked;
+	int dacc;
+	int sacc;
+	int delete;
+};
+
+static Opl *locktab[64];
+
+static Opl*
+getopl(char **path, int (*namecmp)(char *, char *), int dacc, int sacc)
+{
+	Opl *opl, **pp;
+	ulong h;
+
+	h = namehash(*path);
+	for(pp = &locktab[h % nelem(locktab)]; *pp; pp=&((*pp)->next)){
+		opl = *pp;
+		if(namecmp(opl->path, *path))
+			continue;
+		if(sacc == FILE_SHARE_COMPAT){
+			if(sacc != opl->sacc)
+				return nil;
+			if((opl->dacc | dacc) & WRITEMASK)
+				return nil;
+		} else {
+			if(opl->sacc == FILE_SHARE_COMPAT)
+				return nil;
+			if((dacc & READMASK) && (opl->sacc & FILE_SHARE_READ)==0)
+				return nil;
+			if((dacc & WRITEMASK) && (opl->sacc & FILE_SHARE_WRITE)==0)
+				return nil;
+			if((dacc & FILE_DELETE) && (opl->sacc & FILE_SHARE_DELETE)==0)
+				return nil;
+		}
+		opl->ref++;
+		if(strcmp(opl->path, *path)){
+			free(*path);
+			*path = strdup(opl->path);
+		}
+		return opl;
+	}
+
+	opl = mallocz(sizeof(*opl), 1);
+	opl->ref = 1;
+	opl->hash = h;
+	opl->dacc = dacc;
+	opl->sacc = sacc;
+	*pp = opl;
+	return opl;
+}
+
+static void
+putopl(Opl *opl)
+{
+	Opl **pp;
+
+	if(opl==nil || --opl->ref)
+		return;
+	for(pp = &locktab[opl->hash % nelem(locktab)]; *pp; pp=&((*pp)->next)){
+		if(*pp == opl){
+			*pp = opl->next;
+			opl->next = nil;
+			break;
+		}
+	}
+	if(opl->path && opl->delete){
+		if(debug)
+			fprint(2, "remove on close: %s\n", opl->path);
+		if(remove(opl->path) < 0)
+			logit("remove %s: %r", opl->path);
+	}
+	free(opl->path);
+	free(opl);
+}
+
+File*
+createfile(char *path, int (*namecmp)(char *, char *),
+	int dacc, int sacc, int cdisp, int copt, vlong csize, int fattr, int *pact, Dir **pdir, int *perr)
+{
+	int err, act, fd, mode, perm, isdir, delete;
+	Opl *o;
+	File *f;
+	Dir *d;
+
+	o = nil;
+	f = nil;
+	d = nil;
+	fd = -1;
+	path = strdup(path);
+
+	if(copt & FILE_OPEN_BY_FILE_ID){
+unsup:
+		err = STATUS_NOT_SUPPORTED;
+		goto out;
+	}
+	if((o = getopl(&path, namecmp, dacc, sacc)) == nil){
+		err = STATUS_SHARING_VIOLATION;
+		goto out;
+	}
+	mode = -1;
+	if(dacc & READMASK)
+		mode += 1;
+	if(dacc & WRITEMASK)
+		mode += 2;
+	delete = isdir = 0;
+	if(d = xdirstat(&path, namecmp)){
+		if(mode >= 0 && d->type != '/' && d->type != 'M'){
+noaccess:
+			err = STATUS_ACCESS_DENIED;
+			goto out;
+		}
+
+		isdir = d->qid.type == QTDIR;
+		switch(cdisp){
+		case FILE_SUPERSEDE:
+			act = FILE_SUPERSEDED;
+			if(remove(path) < 0){
+				logit("remove: %r");
+oserror:
+				err = smbmkerror();
+				goto out;
+			}
+			goto docreate;
+		case FILE_OVERWRITE:
+		case FILE_OVERWRITE_IF:
+			act = FILE_OVERWRITTEN;
+			if(isdir || (mode != OWRITE && mode != ORDWR))
+				goto noaccess;
+			d->length = 0;
+			mode |= OTRUNC;
+			break;
+		case FILE_OPEN:
+		case FILE_OPEN_IF:
+			act = FILE_OPEND;
+			break;
+		case FILE_CREATE:
+			err = STATUS_OBJECT_NAME_COLLISION;
+			goto out;
+		default:
+			goto unsup;
+		}
+		if((copt & FILE_DIRECTORY_FILE) && !isdir)
+			goto noaccess;
+		if((copt & FILE_NON_DIRECTORY_FILE) && isdir){
+			err = STATUS_FILE_IS_A_DIRECTORY;
+			goto out;
+		}
+		if(copt & FILE_DELETE_ON_CLOSE){
+			if(isdir || (dacc & FILE_DELETE)==0)
+				goto noaccess;
+			delete = 1;
+		}
+		if(mode >= 0 && !isdir)
+			if((fd = open(path, mode)) < 0)
+				goto oserror;
+	} else {
+		switch(cdisp){
+		case FILE_SUPERSEDE:
+		case FILE_CREATE:
+		case FILE_OPEN_IF:
+		case FILE_OVERWRITE_IF:
+			act = FILE_CREATED;
+			break;
+		case FILE_OVERWRITE:
+		case FILE_OPEN:
+			err = smbmkerror();
+			goto out;
+		default:
+			goto unsup;
+		}
+
+docreate:
+		perm = 0666;
+		if(fattr & ATTR_READONLY)
+			perm &= ~0222;
+		if(copt & FILE_DIRECTORY_FILE){
+			perm |= DMDIR | 0111;
+			mode = OREAD;
+			isdir = 1;
+		}
+		if(mode < 0)
+			mode = OREAD;
+		if(copt & FILE_DELETE_ON_CLOSE){
+			if(isdir || (dacc & FILE_DELETE)==0)
+				goto noaccess;
+			delete = 1;
+		}
+		if((fd = create(path, mode, perm)) < 0){
+			char *name, *base;
+			Dir *t;
+
+			err = smbmkerror();
+			if(!splitpath(path, &base, &name))
+				goto out;
+			if((t = xdirstat(&base, namecmp)) == nil){
+				free(base); free(name);
+				goto out;
+			}
+			free(t);
+			free(path); path = conspath(base, name);
+			free(base); free(name);
+			if((fd = create(path, mode, perm)) < 0)
+				goto oserror;
+		}
+		if(csize > 0 && !isdir){
+			Dir nd;
+
+			nulldir(&nd);
+			nd.length = csize;
+			if(dirfwstat(fd, &nd) < 0)
+				goto oserror;
+		}
+		if(pdir)
+			if((d = dirfstat(fd)) == nil)
+				goto oserror;
+		if(isdir){
+			close(fd);
+			fd = -1;
+		}
+	}
+
+	f = mallocz(sizeof(*f), 1);
+	f->ref = 1;
+	f->fd = fd; fd = -1;
+	f->rtype = FileTypeDisk;
+	f->dacc = dacc;
+	o->delete |= delete;
+	if(o->path == nil){
+		o->path = path;
+		path = nil;
+	}
+	f->path = o->path;
+	f->aux = o; o = nil;
+	if(pact)
+		*pact = act;
+	if(pdir){
+		*pdir = d;
+		d = nil;
+	}
+	err = 0;
+
+out:
+	if(perr)
+		*perr = err;
+	if(fd >= 0)
+		close(fd);
+	free(path);
+	putopl(o);
+	free(d);
+
+	return f;
+}
+
+Dir*
+statfile(File *f)
+{
+	if(f == nil)
+		return nil;
+	if(f->fd >= 0)
+		return dirfstat(f->fd);
+	else
+		return dirstat(f->path);
+}
+
+int
+lockfile(File *f)
+{
+	Opl *opl = f->aux;
+	if(opl->locked && opl->locked != f)
+		return 0;
+	opl->locked = f;
+	return 1;
+}
+
+void
+deletefile(File *f, int delete)
+{
+	Opl *opl = f->aux;
+	if(opl->delete == delete)
+		return;
+	opl->delete = delete;
+}
+
+int
+deletedfile(File *f)
+{
+	Opl *opl = f->aux;
+	return opl->delete;
+}
+
+
+void
+putfile(File *f)
+{
+	Opl *opl;
+
+	if(f == nil || --f->ref)
+		return;
+	if(f->fd >= 0)
+		close(f->fd);
+	opl = f->aux;
+	if(opl->locked == f)
+		opl->locked = nil;
+	putopl(opl);
+	free(f);
+}
+
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/find.c
@@ -1,0 +1,191 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+static int
+iswild(char *pattern)
+{
+	return strchrs(pattern, "*?<>\"") != nil;
+}
+
+static int
+matchpattern(char *name, char *pattern, int casesensitive)
+{
+	Rune p, r;
+	int n;
+
+	while(*pattern){
+		pattern += chartorune(&p, pattern);
+		n = chartorune(&r, name);
+		switch(p){
+		case '?':
+			if(r == 0)
+				return 0;
+			name += n;
+			break;
+		case '>':
+			switch(r){
+			case '.':
+				if(!name[1] && matchpattern(name+1, pattern, casesensitive))
+					return 1;
+			case 0:
+				return matchpattern(name, pattern, casesensitive);
+			}
+			name += n;
+			break;
+		case '*':
+		case '<':
+			while(r){
+				if(matchpattern(name, pattern, casesensitive))
+					return 1;
+				if(p == '<' && r == '.' && !strchrs(name+1, ".")){
+					name++;
+					break;
+				}
+				n = chartorune(&r, name += n);
+			}
+			break;
+		case '"':
+			if(r == 0 && matchpattern(name, pattern, casesensitive))
+				return 1;
+			if(r != '.')
+				return 0;
+			name += n;
+			break;
+		default:
+			if(p != r && casesensitive || toupperrune(p) != toupperrune(r))
+				return 0;
+			name += n;
+		}
+	}
+	return *name == 0;
+}
+
+int
+matchattr(Dir *d, int s)
+{
+	int a, m;
+
+	m = ATTR_HIDDEN | ATTR_SYSTEM | ATTR_DIRECTORY;
+	a = dosfileattr(d);
+	if((a & ~s) & m)
+		return 0;
+	m = (s >> 8) & m;
+	if(m && ((m & a) != m))
+		return 0;
+	return 1;
+}
+
+
+Find*
+openfind(char *path, int (*namecmp)(char *, char *), int attr, int withdot, int *perr)
+{
+	char *base, *pattern, *parent;
+	Dir *dir;
+	int ndir, err;
+	Find *f;
+
+	f = nil;
+	path = strdup(path);
+	base = pattern = parent = nil;
+	if(!splitpath(path, &base, &pattern)){
+		err = STATUS_OBJECT_PATH_SYNTAX_BAD;
+		goto out;
+	}
+	if(debug)
+		fprint(2, "base %s\npattern %s\nattr %x\nwithdot %d\n", base, pattern, attr, withdot);
+
+	if(iswild(pattern)){
+		if((ndir = xdirread(&base, namecmp, &dir)) < 0){
+			err = smbmkerror();
+			goto out;
+		}
+	} else {
+		ndir = 0;
+		withdot = 0;
+		if(dir = xdirstat(&path, namecmp)){	
+			free(base);
+			free(pattern);
+			splitpath(path, &base, &pattern);
+			ndir++;
+		}
+	}
+
+	f = mallocz(sizeof(*f), 1);
+	f->ref = 1;
+	f->base = base;
+	f->pattern = pattern;
+	f->attr = attr;
+	f->dir = dir;
+	f->ndir = ndir;
+	f->index = 0;
+	f->casesensitive = (namecmp == strcmp);
+
+	if(withdot){
+		if(f->dot = dirstat(base))
+			f->dot->name = ".";
+		if(splitpath(base, &parent, nil))
+			if(f->dotdot = dirstat(parent))
+				f->dotdot->name = "..";
+	} 
+
+	base = nil;
+	pattern = nil;
+	err = 0;
+
+out:
+	if(perr)
+		*perr = err;
+
+	free(base);
+	free(pattern);
+	free(parent);
+	free(path);
+
+	return f;
+}
+
+int
+readfind(Find *f, int i, Dir **dp)
+{
+	Dir *d;
+	int x;
+
+	x = i;
+	if(f->dot && f->dotdot)
+		x -= 2;
+	for(;;){
+		if(x == -2){
+			d = f->dot;
+		} else if(x == -1){
+			d = f->dotdot;
+		} else if(x < f->ndir){
+			d = f->dir + x;
+		} else {
+			d = nil;
+			i = -1;
+			break;
+		}
+		if(matchattr(d, f->attr) && matchpattern(d->name, f->pattern, f->casesensitive))
+			break;
+		i++; x++;
+	}
+	if(debug && d)
+		fprint(2, "readfile [%d] attr=%x name=%s\n", i, extfileattr(d), d->name);
+	*dp = d;
+	return i;
+}
+
+void
+putfind(Find *f)
+{
+	if(f == nil || --f->ref)
+		return;
+	free(f->pattern);
+	free(f->base);
+	free(f->dot);
+	free(f->dotdot);
+	free(f->dir);
+	free(f);
+}
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/fns.h
@@ -1,0 +1,85 @@
+/* pack */
+int unpack(uchar *b, uchar *p, uchar *e, char *f, ...);
+int vunpack(uchar *b, uchar *p, uchar *e, char *f, va_list a);
+int pack(uchar *b, uchar *p, uchar *e, char *f, ...);
+int vpack(uchar *b, uchar *p, uchar *e, char *f, va_list a);
+
+/* error */
+int smbmkerror(void);
+int doserror(int err);
+
+/* util */
+void logit(char *fmt, ...);
+#pragma varargck argpos logit 1
+char *getremote(char *dir);
+char *conspath(char *base, char *name);
+int splitpath(char *path, char **base, char **name);
+void dumphex(char *s, uchar *h, uchar *e);
+void todatetime(long time, int *pdate, int *ptime);
+long fromdatetime(int date, int time);
+vlong tofiletime(long time);
+long fromfiletime(vlong filetime);
+int filesize32(vlong);
+vlong allocsize(vlong size, int blocksize);
+int extfileattr(Dir *d);
+int dosfileattr(Dir *d);
+ulong namehash(char *s);
+char *strtr(char *s, Rune (*tr)(Rune));
+char *strchrs(char *s, char *c);
+int smbstrpack8(uchar *, uchar *p, uchar *e, void *arg);
+int smbstrpack16(uchar *b, uchar *p, uchar *e, void *arg);
+int smbstrunpack8(uchar *, uchar *p, uchar *e, void *arg);
+int smbstrunpack16(uchar *b, uchar *p, uchar *e, void *arg);
+int smbnamepack8(uchar *b, uchar *p, uchar *e, void *arg);
+int smbnamepack16(uchar *b, uchar *p, uchar *e, void *arg);
+int smbnameunpack8(uchar *b, uchar *p, uchar *e, void *arg);
+int smbnameunpack16(uchar *b, uchar *p, uchar *e, void *arg);
+int smbuntermstrpack8(uchar *b, uchar *p, uchar *e, void *arg);
+int smbuntermstrpack16(uchar *b, uchar *p, uchar *e, void *arg);
+int smbuntermnamepack8(uchar *b, uchar *p, uchar *e, void *arg);
+int smbuntermnamepack16(uchar *b, uchar *p, uchar *e, void *arg);
+
+/* smb */
+void smbcmd(Req *r, int cmd, uchar *h, uchar *p, uchar *e);
+
+/* share */
+Share *mapshare(char *path);
+
+/* rap */
+void transrap(Trans *t);
+
+/* tree */
+Tree *connecttree(char *service, char *path, int *perr);
+int disconnecttree(int tid);
+void logoff(void);
+
+Tree *gettree(int tid);
+int newfid(Tree *t, File *f);
+void delfid(Tree *t, int fid);
+File *getfile(int tid, int fid, Tree **ptree, int *perr);
+char *getpath(int tid, char *name, Tree **ptree, int *perr);
+
+int newsid(Tree *t, Find *f);
+void delsid(Tree *t, int sid);
+Find *getfind(int tid, int sid, Tree **ptree, int *perr);
+
+/* file */
+File* createfile(char *path, int (*namecmp)(char *, char *),
+	int dacc, int sacc, int cdisp, int copt, vlong csize, int fattr, int *pact, Dir **pdir, int *perr);
+Dir* statfile(File *f);
+void putfile(File *f);
+int lockfile(File *f);
+void deletefile(File *f, int delete);
+int deletedfile(File *f);
+
+/* find */
+Find *openfind(char *path, int (*namecmp)(char *, char *),
+	int attr, int withdot, int *perr);
+int matchattr(Dir *d, int s);
+int readfind(Find *f, int i, Dir **dp);
+void putfind(Find *f);
+
+/* dir */
+int xdirread(char **path, int (*namecmp)(char *, char *), Dir **d);
+Dir *xdirstat(char **path, int (*namecmp)(char *, char *));
+void xdirflush(char *path, int (*namecmp)(char *, char *));
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/main.c
@@ -1,0 +1,214 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+enum {
+	LENHDR = 4,
+
+	MAGIC = 0xFF | ('S'<<8) | ('M'<<16) | ('B'<<24),
+
+	SMB_FLAGS_CASE_INSENSITIVE = 0x08,
+	SMB_FLAGS_CANONICALIZED_PATHS = 0x10,
+	SMB_FLAGS_REPLY = 0x80,
+
+	NOCASEMASK = SMB_FLAGS_CASE_INSENSITIVE | SMB_FLAGS_CANONICALIZED_PATHS,
+
+	SMB_FLAGS2_LONG_NAMES = 0x0001,
+	SMB_FLAGS2_EAS = 0x0002,
+	SMB_FLAGS2_IS_LONG_NAME = 0x0040,
+	SMB_FLAGS2_NT_STATUS = 0x4000,
+	SMB_FLAGS2_UNICODE = 0x8000,
+};
+
+static int casesensitive = 0;
+
+static void
+respond(Req *r, int err)
+{
+	int n, flags, flags2;
+
+	if(err && !(r->flags2 & SMB_FLAGS2_NT_STATUS))
+		err = doserror(err);
+	flags = (r->flags & (r->namecmp != strcmp ? NOCASEMASK : 0)) |
+		SMB_FLAGS_REPLY;
+	flags2 = (r->flags2 & (SMB_FLAGS2_NT_STATUS | 
+		SMB_FLAGS2_LONG_NAMES | SMB_FLAGS2_UNICODE)) | 
+		SMB_FLAGS2_IS_LONG_NAME;
+	if(r->cmd != 0x73) /* SMB_COM_SESSION_SETUP_ANDX */
+		memset(r->sig, 0, sizeof(r->sig));
+	n = pack(r->rh, r->rh, r->rh+32, "lblbww[]__wwww",
+		MAGIC, r->cmd, err, flags,  flags2, r->pid>>16, r->sig, r->sig+sizeof(r->sig),
+		r->tid, r->pid & 0xFFFF, r->uid, r->mid);
+	if(err){
+		r->rp = r->rh+n;
+		r->rp += pack(r->rh, r->rp, r->re, "#0b{*2}#1w{}");
+	}
+	if(debug > 1)
+		dumphex("respond", r->rh, r->rp);
+	if(debug)
+		fprint(2, "respond: err=%x\n\n", err);
+	n = r->rp - r->rh;
+	r->lh[0] = 0;
+	r->lh[1] = 0;
+	r->lh[2] = n>>8 & 0xFF;
+	r->lh[3] = n & 0xFF;
+	write(1, r->lh, LENHDR+n);
+}
+
+static void
+receive(uchar *h, uchar *e)
+{
+	static uchar buffer[LENHDR + BUFFERSIZE];
+	static Rop rop8 = {
+		.strpack = smbstrpack8,
+		.strunpack = smbstrunpack8,
+		.namepack = smbnamepack8,
+		.nameunpack = smbnameunpack8,
+		.untermstrpack = smbuntermstrpack8,
+		.untermnamepack = smbuntermnamepack8,
+	}, rop16 = {
+		.strpack = smbstrpack16,
+		.strunpack = smbstrunpack16,
+		.namepack = smbnamepack16,
+		.nameunpack = smbnameunpack16,
+		.untermstrpack = smbuntermstrpack16,
+		.untermnamepack = smbuntermnamepack16,
+	};
+
+	uchar *sig;
+	int n, hpid, magic;
+	Req r;
+
+	if(debug > 1)
+		dumphex("receive", h, e);
+	if((n = unpack(h, h, e, "lb____bww{.________}__wwww", &magic,
+		&r.cmd, &r.flags, &r.flags2, &hpid, &sig, &r.tid, &r.pid, &r.uid, &r.mid)) == 0){
+		logit("bad smb header");
+		return;
+	}
+	if(magic != MAGIC){
+		logit("bad smb magic");
+		return;
+	}
+	r.pid |= hpid<<16;
+	r.lh = buffer;
+	r.rh = r.lh + LENHDR;
+	r.rp = r.rh + n;
+	r.re = r.rh + remotebuffersize;
+	r.o = (r.flags2 & SMB_FLAGS2_UNICODE) ? &rop16 : &rop8;
+	memmove(r.sig, sig, sizeof(r.sig));
+	r.name = nil;
+	r.respond = respond;
+	r.namecmp = ((r.flags & NOCASEMASK) && !casesensitive) ? cistrcmp : strcmp;
+	smbcmd(&r, r.cmd, h, h+n, e);
+}
+
+static void
+serve(void)
+{
+	static uchar buffer[LENHDR + BUFFERSIZE];
+	uchar *m, *me;
+	uchar *p, *pe;
+	uchar *hl;
+	int n;
+
+	p = hl = buffer;
+	pe = p + LENHDR+BUFFERSIZE;
+
+	for(;;){
+		n = read(0, p, pe - p);
+		if(n <= 0)
+			break;
+		p += n;
+Next:
+		if(p - hl < LENHDR)
+			continue;
+		n = hl[2]<<8 | hl[3];
+		m = hl + LENHDR;
+		me = m + n;
+		if(me > pe){
+			logit("message too big");
+			break;
+		}
+		if(me > p)
+			continue;
+		receive(m, me);
+		n = p - me;
+		p = hl + n;
+		if(n > 0){
+			memmove(hl, me, n);
+			goto Next;
+		}
+	}
+}
+
+void
+main(int argc, char *argv[])
+{
+	static struct {
+		char *s;
+		int *v;
+	} opts[] = {
+		{ "trspaces", &trspaces },
+		{ "casesensitive", &casesensitive },
+		{ nil, nil }
+	}, *o;
+
+	char *log, *opt;
+	Tm *tm;
+	int pid;
+
+	debug = 0;
+	trspaces = 0;
+	needauth = 1;
+	domain = "WORKGROUP";
+	progname = "cifsd";
+	osname = "Plan 9";
+	log = nil;
+
+	ARGBEGIN {
+	case 't':
+		needauth = 0;
+		break;
+	case 'd':
+		debug++;
+		break;
+	case 'f':
+		log = EARGF(exits("bad arg"));
+		break;
+	case 'w':
+		domain = EARGF(exits("bad arg"));
+		break;
+	case 'o':
+		opt = EARGF(exits("bad arg"));
+		for(o=opts; o->s; o++)
+			if(strcmp(opt, o->s) == 0){
+				*o->v = 1;
+				break;
+			}
+		if(o->s == nil)
+			exits("bad arg");
+		break;
+	} ARGEND
+
+	close(2);
+	if(!log || open(log, OWRITE) < 0){
+		open("/dev/null", OWRITE);
+		debug = 0;
+	}
+
+	remotesys = argc ? getremote(argv[argc-1]) : nil;
+	remoteuser = nil;
+	remotebuffersize = BUFFERSIZE;
+	starttime = time(nil);
+	pid = getpid();
+	srand(starttime ^ pid);
+	tm = localtime(starttime);
+	tzoff = tm->tzoff;
+
+	logit("started [%d]", pid);
+	serve();
+	logoff();
+	logit("exited [%d]", pid);
+}
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/mkfile
@@ -1,0 +1,21 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/ip
+TARG=cifsd
+
+HFILES=dat.h fns.h
+
+OFILES=\
+	pack.$O \
+	util.$O \
+	error.$O \
+	smb.$O \
+	rap.$O \
+	share.$O \
+	tree.$O \
+	dir.$O \
+	file.$O \
+	find.$O \
+	main.$O \
+	
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/pack.c
@@ -1,0 +1,305 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+int
+unpack(uchar *b, uchar *p, uchar *e, char *f, ...)
+{
+	va_list a;
+	int r;
+
+	va_start(a, f);
+	r = vunpack(b, p, e, f, a);
+	va_end(a);
+	return r;
+}
+
+int
+pack(uchar *b, uchar *p, uchar *e, char *f, ...)
+{
+	va_list a;
+	int r;
+
+	va_start(a, f);
+	r = vpack(b, p, e, f, a);
+	va_end(a);
+	return r;
+}
+
+int
+vunpack(uchar *b, uchar *p, uchar *e, char *f, va_list a)
+{
+	struct {
+		void *prev;
+		int o, c, i;
+		uchar *e;
+		uchar **ap, **ae;
+	} sub[8], *sp, *sa;
+	int (*funpack)(uchar *, uchar *, uchar *, void *);
+	char c, ff[2];
+	int i, x, n;
+	uchar *t;
+
+	memset(sub, 0, sizeof(sub));
+	for(sp = sub; sp < sub+nelem(sub); sp++){
+		sp->o = -1;
+		sp->c = -1;
+		sp->i = 1;
+	}
+
+	t = p;
+	sp = nil;
+	sa = sub;
+	while(c = *f++){
+		switch(c){
+		case '_':
+		case 'b':
+			if(p >= e)
+				return 0;
+			if(c == 'b')
+				*va_arg(a, int*) = *p;
+			p++;
+			break;
+		case 'w':
+			if(p+1 >= e)
+				return 0;
+			*va_arg(a, int*) = (int)p[1]<<8 | (int)p[0];
+			p+=2;
+			break;
+		case 'l':
+			if(p+3 >= e)
+				return 0;
+			*va_arg(a, int*) = (int)p[3]<<24 | (int)p[2]<<16 | (int)p[1]<<8 | (int)p[0];
+			p+=4;
+			break;
+		case 'v':
+			if(p+7 >= e)
+				return 0;
+			*va_arg(a, vlong*) =
+					(vlong)p[7]<<56 |
+					(vlong)p[6]<<48 |
+					(vlong)p[5]<<40 |
+					(vlong)p[4]<<32 |
+					(vlong)p[3]<<24 |
+					(vlong)p[2]<<16 |
+					(vlong)p[1]<<8 |
+					(vlong)p[0];
+			p += 8;
+			break;
+		case '%':
+			x = *f++ - '0';
+			while((p - b) % x)
+				p++;
+			break;
+		case 'f':
+			funpack = va_arg(a, void*);
+			if((n = funpack(b, p, e, va_arg(a, void*))) == 0)
+				return 0;
+			p += n;
+			break;
+		case '#':
+		case '@':
+			x = *f++ - '0';
+			ff[0] = *f++;
+			ff[1] = 0;
+			if((n = unpack(b, p, e, ff, &i)) == 0)
+				return 0;
+			p += n;
+			if(c == '#'){
+				sub[x].c = i;
+			} else {
+				sub[x].o = i;
+			}
+			break;
+		case '{':
+		case '[':
+			sa->prev = sp;
+			sp = sa++;
+			if(*f == '*'){
+				sp->i = f[1]-'0';
+				f += 2;
+			}
+			if(sp->o >= 0 && b + sp->o > p)
+				if(b + sp->o <= e || *f != '?')
+					p = b + sp->o;
+			if(*f == '?')
+				f++;
+			sp->o = p - b;
+			sp->e = e;
+			if(sp->c >= 0){
+				e = p + sp->c * sp->i;
+				if(e > sp->e)
+					return 0;
+			}
+			if(c == '['){
+				sp->ap = va_arg(a, uchar**);
+				sp->ae = va_arg(a, uchar**);
+			}
+			break;
+		case '}':
+		case ']':
+			e = sp->e;
+			if(sp->c < 0)
+				sp->c = ((p - (b + sp->o))+sp->i-1)/sp->i;
+			p = b + sp->o + sp->c * sp->i;
+			if(p > e)
+				return 0;
+			if(sp->ap)
+				*sp->ap = b + sp->o;
+			if(sp->ae)
+				*sp->ae = p;
+			sp = sp->prev;
+			break;
+		case '.':
+			*va_arg(a, uchar**) = p;
+			break;
+		}
+		if(p > e)
+			return 0;
+	}
+	return p - t;
+}
+
+vpack(uchar *b, uchar *p, uchar *e, char *f, va_list a)
+{
+	struct {
+		void *prev;
+		int o, i;
+		uchar *wc, *wo, wcf, wof;
+	} sub[8], *sp, *sa;
+	int (*fpack)(uchar *, uchar *, uchar *, void *);
+	char c, ff[2];
+	int i, x, n;
+	vlong v;
+	uchar *t;
+
+	memset(sub, 0, sizeof(sub));
+	for(sp = sub; sp < sub+nelem(sub); sp++){
+		sp->o = -1;
+		sp->i = 1;
+	}
+
+	t = p;
+	sp = nil;
+	sa = sub;
+	while(c = *f++){
+		switch(c){	
+		case '_':
+		case 'b':
+			if(p >= e)
+				return 0;
+			if(c == 'b')
+				*p++ = va_arg(a, int);
+			else
+				*p++ = 0;
+			break;
+		case 'w':
+			if(p+1 >= e)
+				return 0;
+			i = va_arg(a, int);
+			*p++ = i & 0xFF;
+			*p++ = i>>8 & 0xFF;
+			break;
+		case 'l':
+			if(p+3 >= e)
+				return 0;
+			i = va_arg(a, int);
+			*p++ = i & 0xFF;
+			*p++ = i>>8 & 0xFF;
+			*p++ = i>>16 & 0xFF;
+			*p++ = i>>24 & 0xFF;
+			break;
+		case 'v':
+			if(p+7 >= e)
+				return 0;
+			v = va_arg(a, vlong);
+			*p++ = v & 0xFF;
+			*p++ = v>>8 & 0xFF;
+			*p++ = v>>16 & 0xFF;
+			*p++ = v>>24 & 0xFF;
+			*p++ = v>>32 & 0xFF;
+			*p++ = v>>40 & 0xFF;
+			*p++ = v>>48 & 0xFF;
+			*p++ = v>>56 & 0xFF;
+			break;
+		case '%':
+			x = *f++ - '0';
+			while((p - b) % x){
+				if(p >= e)
+					return 0;
+				*p++ = 0;
+			}
+			break;
+		case 'f':
+			fpack = va_arg(a, void*);
+			if((n = fpack(b, p, e, va_arg(a, void*))) == 0)
+				return 0;
+			p += n;
+			break;
+		case '#':
+		case '@':
+			x = *f++ - '0';
+			ff[0] = *f++;
+			ff[1] = 0;
+			if((n = pack(b, p, e, ff, 0)) == 0)
+				return 0;
+			if(c == '#'){
+				sub[x].wc = p;
+				sub[x].wcf = ff[0];
+			} else {
+				sub[x].wo = p;
+				sub[x].wof = ff[0];
+			}
+			p += n;
+			break;
+		case '{':
+		case '[':
+			sa->prev = sp;
+			sp = sa++;
+			if(*f == '*'){
+				sp->i = f[1]-'0';
+				f += 2;
+			}
+			if(*f == '?')
+				f++;
+			sp->o = p - b;
+			if(c == '['){
+				uchar *s, *se;
+
+				s = va_arg(a, uchar*);
+				se = va_arg(a, uchar*);
+				n = se - s;
+				if(n < 0 || p + n > e)
+					return 0;
+				if(p != s)
+					memmove(p, s, n);
+				p += n;
+			}
+			break;
+		case '}':
+		case ']':
+			n = ((p - (b + sp->o))+sp->i-1)/sp->i;
+			p = b + sp->o + n * sp->i;
+			if(sp->wc){
+				ff[0] = sp->wcf;
+				ff[1] = 0;
+				pack(b, sp->wc, e, ff, n);
+			}
+			if(sp->wo){
+				ff[0] = sp->wof;
+				ff[1] = 0;
+				pack(b, sp->wo, e, ff, sp->o);
+			}
+			sp = sp->prev;
+			break;
+		case '.':
+			*va_arg(a, uchar**) = p;
+			break;
+		}
+		if(p > e)
+			return 0;
+	}
+	return p - t;
+}
+
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/rap.c
@@ -1,0 +1,143 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include "dat.h"
+#include "fns.h"
+
+static void
+padname(uchar *buf, int len, char *name)
+{
+	int n;
+	n = strlen(name);
+	if(n >= len)
+		n = len-1;
+	memset(buf, 0, len);
+	memmove(buf, name, n);
+}
+
+static int
+packshareinfo(Trans *t, int level, char *name, int *pstatus)
+{
+	Share *share;
+	uchar buf[13];
+
+	if((share = mapshare(name)) == nil){
+		if(pstatus)
+			*pstatus = 0x906;	/* NERR_NetNameNotFound */
+		return 0;
+	}
+	padname(buf, sizeof(buf), share->name);
+	switch(level){
+	case 0:
+		return pack(t->out.data.b, t->out.data.p, t->out.data.e, "[]",
+			buf, buf+sizeof(buf));
+	case 1:
+		return pack(t->out.data.b, t->out.data.p, t->out.data.e, "[]_w@1l{f}",
+			buf, buf+sizeof(buf), share->stype, smbstrpack8, share->remark);
+	case 2:
+		return pack(t->out.data.b, t->out.data.p, t->out.data.e, "[]_w@1l__ww@2l__________{f}{f}",
+			buf, buf+sizeof(buf), share->stype, 100, 1, smbstrpack8, share->remark,
+			smbnamepack8, share->root);
+	default:
+		return -1;
+	}
+}
+
+void
+transrap(Trans *t)
+{
+	char *pd, *dd, *name;
+	int n, code, status, level, rbs;
+	uchar *ip, *ipe, *op, *opb, *ope;
+	uchar buf[16];
+
+	code = 0;
+	name = nil;
+	pd = dd = nil;
+	ip = ipe = t->in.param.e;
+	if(!unpack(t->in.param.b, t->in.param.p, t->in.param.e, "wff[]", &code, 
+		smbstrunpack8, &pd, smbstrunpack8, &dd, &ip, nil)){
+		t->respond(t, STATUS_NOT_SUPPORTED);
+		goto out;
+	}
+
+	ope = t->out.param.e;
+	opb = op = t->out.param.b+2+2;
+
+	n = status = level = 0;
+	switch(code){
+	case 0x0000:	/* NetShareEnum */
+		op += pack(opb, op, ope, "ww", 0, 0);
+		if(!unpack(ip, ip, ipe, "ww", &level, &rbs))
+			break;
+		if((n = packshareinfo(t, level, "local", nil)) > 0){
+			t->out.data.p += n;
+			pack(opb, opb, ope, "ww", 1, 1);
+		}
+		break;
+
+	case 0x0001:	/* NetShareGetInfo */
+		op += pack(opb, op, ope, "w", 0);
+		if(!unpack(ip, ip, ipe, "fww", smbstrunpack8, &name, &level, &rbs))
+			break;
+		if((n = packshareinfo(t, level, name, &status)) > 0){
+outlen:
+			t->out.data.p += n;
+			pack(opb, opb, ope, "w", n);
+		}
+		break;
+
+	case 0x000d:	/* NetServerGetInfo */
+		op += pack(opb, op, ope, "w", 0);
+		if(!unpack(ip, ip, ipe, "ww", &level, &rbs))
+			break;
+		padname(buf, sizeof(buf), "");
+		switch(level){
+		case 0:
+			if((n = pack(t->out.data.b, t->out.data.p, t->out.data.e, "[]", 
+				buf, buf+sizeof(buf))) > 0)
+				goto outlen;
+			break;
+		case 1:
+			if((n = pack(t->out.data.b, t->out.data.p, t->out.data.e, "[]bbl@1l{f}", 
+				buf, buf+sizeof(buf), 0x05, 0x00, 2, smbstrpack8, osname)) > 0)
+				goto outlen;
+		default:
+			n = -1;
+		}
+		break;
+
+	case 0x003f:	/* NetWrkstaGetInfo */
+		op += pack(opb, op, ope, "w", 0);
+		if(!unpack(ip, ip, ipe, "ww", &level, &rbs))
+			break;
+		if(level != 10){
+			n = -1;
+			break;
+		}
+		if((n = pack(t->out.data.b, t->out.data.p, t->out.data.e, 
+			"@0l____@1lbb________{f}{f}", 0x05, 0x00, 
+			smbstrpack8, sysname(), smbstrpack8, domain)) > 0)
+			goto outlen;
+		break;
+
+	default:
+		logit("[%.4x] unknown rap command pd=%s dd=%s", code, pd, dd);
+	}
+	if(n < 0){
+		logit("[%.4x] unknown rap level [%.4x]", code, level);
+		status = 0x7C;
+	}
+	if((n = pack(t->out.param.b, t->out.param.p, t->out.param.e, "w__[]", status, opb, op)) == 0){
+		t->respond(t, STATUS_INVALID_SMB);
+		goto out;
+	}
+	t->out.param.p += n;
+	t->respond(t, 0);
+
+out:
+	free(name);
+	free(pd);
+	free(dd);
+	return;
+}
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/share.c
@@ -1,0 +1,124 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+static int
+run9fs(char *arg)
+{
+	char buf[1024], *argv[3], *s;
+	Waitmsg *w;
+	int fd, pid;
+
+	switch(pid = rfork(RFCFDG|RFREND|RFPROC)){
+	case -1:
+		return -1;
+	case 0:
+		open("/dev/null", ORDWR);
+		snprint(buf, sizeof(buf), "/sys/log/%s", progname);
+		if((fd = open(buf, OWRITE)) >= 0)
+			seek(fd, 0, 2);
+		else
+			fd = 0;
+		dup(fd, 1);
+		dup(fd, 2);
+		argv[0] = "/bin/9fs";
+		argv[1] = arg;
+		argv[2] = 0;
+		exec(argv[0], argv);
+		exits("failed to exec 9fs");
+	}
+	for (;;) {
+		if((w = wait()) == nil)
+			return -1;
+		if (w->pid == pid)
+			break;
+		free(w);
+	}
+	if(w->msg[0]){
+		if(s = strchr(w->msg, ':'))
+			s = s+1;
+		else
+			s = w->msg;
+		werrstr("%s", s);
+		free(w);
+		return -1;
+	} else {
+		free(w);
+		return 0;
+	}
+}
+
+static Share *shares;
+
+Share*
+mapshare(char *path)
+{
+	char *tmp, *tmp2, *name, *root, *service, *fsname, *remark;
+	int stype;
+	Share *s;
+
+	if(name = strrchr(path, '/'))
+		name++;
+	else if(name = strrchr(path, '\\'))
+		name++;
+	else
+		name = path;
+	if(name==nil || *name==0 || *name=='.' || strchrs(name, "\\* ") || strstr(name, ".."))
+		return nil;
+	root = tmp = smprint("/n/%s", name);
+	name = strtr(strrchr(root, '/')+1, tolowerrune);
+	service = "A:";
+	stype = STYPE_DISKTREE;
+	fsname = "9fs";
+	remark = tmp2 = smprint("9fs %s; cd %s", name, root);
+	if(!strcmp(name, "local")){
+		root = "/";
+		fsname = "local";
+		remark = "The standard namespace";
+	}
+	if(!strcmp(name, "ipc$")){
+		root = "/dev/null";
+		name = "IPC$";
+		fsname = "";
+		service = "IPC";
+		stype = STYPE_IPC;
+		remark = "The IPC service";
+	}
+
+	for(s = shares; s; s=s->next)
+		if(!strcmp(s->name, name))
+			goto out;
+
+	logit("mapshare %s -> %s %s %s", path, service, name, root);
+
+	if(!strcmp(service, "A:") && (stype == STYPE_DISKTREE)){
+		if(!strcmp(fsname, "9fs") && (run9fs(name) < 0)){
+			logit("9fs %s: %r", name);
+			goto out;
+		}
+	}
+
+	s = malloc(sizeof(*s));
+	s->service = strdup(service);
+	s->stype = stype;
+
+	s->name = strdup(name);
+	s->root = strdup(root);
+
+	s->remark = strdup(remark);
+	s->fsname = strdup(fsname);
+	s->namelen = 255;
+	s->sectorsize = 0x200;
+	s->blocksize = 0x2000;
+	s->allocsize = 0;
+	s->freesize = s->blocksize;
+
+	s->next = shares;
+	shares = s;
+
+out:
+	free(tmp);
+	free(tmp2);
+	return s;
+}
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/smb.c
@@ -1,0 +1,1691 @@
+#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;
+	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)
+			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");
+	}
+	if(!pack(r->rh, r->rp, r->re, "#0b{*2wbwwllllvw#2b}#1w{[]f}.",
+		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, r->o->strpack, domain, &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;
+			memset(&mcr, 0, sizeof(mcr));
+			if((lme - lm) == sizeof(mcr.LMresp))
+				memmove(mcr.LMresp, lm, lme - lm);
+			if((nte - nt) == sizeof(mcr.NTresp))
+				memmove(mcr.NTresp, nt, nte - nt);
+			smbcs->user = user;
+			smbcs->resp = &mcr;
+			smbcs->nresp = sizeof(mcr);
+			if((ai = auth_response(smbcs)) == nil)
+				logit("auth_response: %r");
+			auth_freechal(smbcs);
+			smbcs = nil;
+			if(ai == nil)
+				break;
+			if(auth_chuid(ai, nil) < 0)
+				logit("auth_chuid: %r");
+			auth_freeAI(ai);
+		}
+		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, 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, &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, 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;
+	}
+	off = (vlong)hoff<<32 | loff;
+	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 + maxcount;
+	if(re > r->re)
+		re = r->re;
+	if((rb + mincount) > re)
+		goto badsmb;
+	n = 0;
+	rp = rb;
+	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, loff, hoff, bufoff, buflen;
+	uchar *d, *de, *xp;
+	vlong off;
+	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;
+	}
+	off = (vlong)hoff<<32 | loff;
+	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, off)) < 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, 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) || (dosfileattr(d) & ATTR_READONLY)){
+		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;
+	if(attr & ATTR_READONLY){
+		if(d->mode & 0222)
+			nd.mode = d->mode & ~0222;
+	} else {
+		if((d->mode & 0222) == 0)
+			nd.mode = d->mode | 0222;
+	}
+	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){
+		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;
+	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){
+		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);
+}
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/tree.c
@@ -1,0 +1,233 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+static void*
+getid(void **a, int n, int i)
+{
+	void *p;
+	if(i <= 0 || i > n || (p = a[i-1]) == nil)
+		return nil;
+	return p;
+}
+
+static void
+setid(void **a, int n, int i, void *p)
+{
+	assert(i > 0 || i <= n);
+	a[i-1] = p;
+}
+
+static int
+newid(void ***pa, int *pn, void *p)
+{
+	int i;
+	for(i=0; i < *pn; i++)
+		if((*pa)[i] == nil)
+			break;
+	if(i == *pn){
+		(*pn)++;
+		if((i % 8) == 0)
+			*pa = realloc(*pa, (i + 8) * sizeof(p));
+	}
+	(*pa)[i] = p;
+	return i+1;
+}
+
+static void **tree;
+static int ntree;
+
+static void
+freetree(Tree *t)
+{
+	int i;
+
+	if(t == nil)
+		return;
+	for(i = 0; i < t->nfile; i++)
+		putfile(t->file[i]);
+	for(i = 0; i < t->nfind; i++)
+		putfile(t->find[i]);
+	free(t->file);
+	free(t->find);
+	free(t);
+}
+
+Tree*
+connecttree(char *service, char *path, int *perr)
+{
+	Share *s;
+	Tree *t;
+	int err;
+
+	t = nil;
+	if((s = mapshare(path)) == nil){
+		err = STATUS_BAD_NETWORK_NAME;
+		goto out;
+	}
+	if(strcmp(service, "?????") && cistrcmp(service, s->service)){
+		err = STATUS_BAD_DEVICE_TYPE;
+		goto out;
+	}
+	t = mallocz(sizeof(*t), 1);
+	t->share = s;
+	t->tid = newid(&tree, &ntree, t);
+	err = 0;
+out:
+	if(perr)
+		*perr = err;
+	return t;
+}
+
+int
+disconnecttree(int tid)
+{
+	Tree *t;
+
+	if((t = gettree(tid)) == nil)
+		return STATUS_SMB_BAD_TID;
+	setid(tree, ntree, tid, nil);
+	freetree(t);
+	return 0;
+}
+
+void
+logoff(void)
+{
+	int i;
+
+	for(i=0; i<ntree; i++)
+		freetree(tree[i]);
+	free(tree);
+	tree = nil;
+	ntree = 0;
+}
+
+Tree*
+gettree(int tid)
+{
+	Tree *t;
+
+	if(t = getid(tree, ntree, tid))
+		if(debug)
+			fprint(2, "tree [%.4x] %s\n", tid, t->share->root);
+	return t;
+}
+
+int
+newfid(Tree *t, File *f)
+{
+	f->ref++;
+	return newid(&t->file, &t->nfile, f);
+}
+
+void
+delfid(Tree *t, int fid)
+{
+	File *f;
+
+	if(f = getid(t->file, t->nfile, fid)){
+		setid(t->file, t->nfile, fid, nil);
+		putfile(f);
+	}
+}
+
+File*
+getfile(int tid, int fid, Tree **ptree, int *perr)
+{
+	Tree *t;
+	File *f;
+	int err;
+
+	f = nil;
+	if((t = gettree(tid)) == nil){
+		err = STATUS_SMB_BAD_TID;
+		goto out;
+	}
+	if((f = getid(t->file, t->nfile, fid)) == nil){
+		err = STATUS_SMB_BAD_FID;
+		goto out;
+	}
+	f->ref++;
+	err = 0;
+	if(debug)
+		fprint(2, "file [%x] %s\n", fid, f->path);
+out:
+	if(perr)
+		*perr = err;
+	if(ptree)
+		*ptree = t;
+	return f;
+}
+
+char*
+getpath(int tid, char *name, Tree **ptree, int *perr)
+{
+	Tree *t;
+	char *p;
+	int err;
+
+	if(t = gettree(tid)){
+		err = 0;
+		p = conspath(t->share->root, name);
+		if(debug)
+			fprint(2, "path %s\n", p);
+	} else {
+		err = STATUS_SMB_BAD_TID;
+		p = nil;
+	}
+	if(perr)
+		*perr = err;
+	if(ptree)
+		*ptree = t;
+	return p;
+}
+
+int
+newsid(Tree *t, Find *f)
+{
+	f->ref++;
+	return newid(&t->find, &t->nfind, f);
+}
+
+void
+delsid(Tree *t, int sid)
+{
+	Find *f;
+
+	if(f = getid(t->find, t->nfind, sid)){
+		setid(t->find, t->nfind, sid, nil);
+		putfind(f);
+	}
+}
+
+Find*
+getfind(int tid, int sid, Tree **ptree, int *perr)
+{
+	Tree *t;
+	Find *f;
+	int err;
+
+	f = nil;
+	if((t = gettree(tid)) == nil){
+		err = STATUS_SMB_BAD_TID;
+		goto out;
+	}
+	if((f = getid(t->find, t->nfind, sid)) == nil){
+		err = STATUS_SMB_BAD_FID;
+		goto out;
+	}
+	f->ref++;
+	err = 0;
+	if(debug)
+		fprint(2, "find [%x] %s %s\n", sid, f->base, f->pattern);
+out:
+	if(perr)
+		*perr = err;
+	if(ptree)
+		*ptree = t;
+	return f;
+}
+
+
--- /dev/null
+++ b/sys/src/cmd/ip/cifsd/util.c
@@ -1,0 +1,409 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+logit(char *fmt, ...)
+{
+	char buf[8192];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(buf, buf + sizeof(buf), fmt, arg);
+	va_end(arg);
+	if(debug)
+		fprint(2, "%s\n", buf);
+	syslog(0, progname, "(%s\\%s) %s", remotesys, remoteuser, buf);
+}
+
+char*
+getremote(char *dir)
+{
+	int fd, n;
+	char remfile[256];
+	static char buf[256];
+
+	sprint(remfile, "%s/remote", dir);
+	fd = open(remfile, OREAD);
+	if(fd < 0)
+		return nil;
+	if((n = read(fd, buf, sizeof(buf)-1))>0)
+		buf[n-1] = 0;
+	else
+		strcpy(buf, remfile);
+	close(fd);
+	return buf;
+}
+
+char*
+conspath(char *base, char *name)
+{
+	return cleanname(smprint("%s/%s", base, name ? name : ""));
+}
+
+int
+splitpath(char *path, char **base, char **name)
+{
+	char *p, *b;
+
+	b = strdup(path);
+	if((p = strrchr(b, '/')) == nil){
+		free(b);
+		if(name)
+			*name = nil;
+		if(base)
+			*base = nil;
+		return 0;
+	}
+	if(p == b){
+		if(name) *name = strdup(p+1);
+		p[1] = 0;
+	} else {
+		*p++ = 0;
+		if(name) *name = strdup(p);
+	}
+	if(base)
+		*base = b;
+	else
+		free(b);
+	return 1;
+}
+
+void
+dumphex(char *s, uchar *h, uchar *e)
+{
+	int i, n;
+
+	n = e - h;
+	for(i=0; i<n; i++){
+		if((i % 16) == 0)
+			fprint(2, "%s%s: [%.4x] ", i ? "\n" : "", s, i);
+		fprint(2, "%.2x ", (int)h[i]);
+	}
+	fprint(2, "\n");
+}
+
+void
+todatetime(long time, int *pdate, int *ptime)
+{
+	Tm *tm;
+
+	tm = gmtime(time);
+	if(pdate)
+		*pdate = (tm->mday) | ((tm->mon + 1) << 5) | ((tm->year - 80) << 9);
+	if(ptime)
+		*ptime = (tm->sec >> 1) | (tm->min << 5) | (tm->hour << 11);
+}
+
+long
+fromdatetime(int date, int time)
+{
+	Tm tm;
+
+	strcpy(tm.zone, "GMT");
+	tm.mday = date & 0x1f;
+	tm.mon = ((date >> 5) & 0xf) - 1;
+	tm.year = (date >> 9) + 80;
+	tm.yday = 0;
+	tm.sec = (time & 0x1f) << 1;
+	tm.min = (time >> 5) & 0x3f;
+	tm.hour = time >> 11;
+	return tm2sec(&tm);
+}
+
+vlong
+tofiletime(long time)
+{
+	return ((vlong)time + 11644473600LL) * 10000000;
+}
+
+long
+fromfiletime(vlong filetime)
+{
+	return filetime / 10000000 - 11644473600LL;
+}
+
+
+int
+filesize32(vlong size)
+{
+	if(size > 0xFFFFFFFFUL)
+		return 0xFFFFFFFF;
+	return size;
+}
+
+vlong
+allocsize(vlong size, int blocksize)
+{
+	return ((size + blocksize-1)/blocksize)*blocksize;
+}
+
+int
+extfileattr(Dir *d)
+{
+	int a;
+
+	a = (d->qid.type == QTDIR) ? ATTR_DIRECTORY : ATTR_NORMAL;
+	if((d->mode & 0222) == 0)
+		a |= ATTR_READONLY;
+	if(d->name[0] == '.' && d->name[1] && d->name[1] != '.')
+		a |= ATTR_HIDDEN;
+	return a;
+}
+
+int
+dosfileattr(Dir *d)
+{
+	return extfileattr(d) & DOSMASK;
+}
+
+ulong
+namehash(char *s)
+{
+	ulong h, t;
+	Rune r;
+
+	h = 0;
+	while(*s){
+		s += chartorune(&r, s);
+		r = toupperrune(r);
+		t = h & 0xf8000000;
+		h <<= 5;
+		h ^= t>>27;
+		h ^= (ulong)r;
+	}
+	return h;
+}
+
+char*
+strtr(char *s, Rune (*tr)(Rune))
+{
+	char buf[UTFmax], *p, *w;
+	Rune r;
+	int n;
+
+	p = s;
+	w = s;
+	while(*p){
+		p += chartorune(&r, p);
+		r = (*tr)(r);
+		n = runetochar(buf, &r);
+		if(w + n <= p){
+			memmove(w, buf, n);
+			w += n;
+		}
+	}
+	*w = 0;
+	return s;
+}
+
+char*
+strchrs(char *s, char *c)
+{
+	Rune r;
+	int n;
+
+	while(*s){
+		n = chartorune(&r, s);
+		if(strchr(c, r))
+			return s;
+		s += n;
+	}
+	return nil;
+}
+
+static int
+strpack8(uchar *, uchar *p, uchar *e, char *s, int term, Rune (*tr)(Rune))
+{
+	uchar *t;
+	Rune r;
+
+	t = p;
+	while((p < e) && *s){
+		s += chartorune(&r, s);
+		r = tr(r);
+		*p++ = r & 0x7F;
+	}
+	if(p >= e)
+		return 0;
+	if(term)
+		*p++ = 0;
+	return p - t;
+}
+
+static int
+strpack16(uchar *b, uchar *p, uchar *e, char *s, int term, Rune (*tr)(Rune))
+{
+	unsigned int rr;
+	uchar *t;
+	Rune r;
+
+	t = p;
+	if((p - b) % 2){
+		if(p >= e)
+			return 0;
+		*p++ = 0;
+	}
+	while((p+1 < e) && *s){
+		s += chartorune(&r, s);
+		rr = tr(r);
+		if(rr > 0xFFFF){
+			if(p+3 >= e)
+				break;
+			rr -= 0x10000;
+			*p++ = (rr>>10) & 0xFF;
+			*p++ = ((rr>>18)&3) + 0xD8;
+			*p++ = rr & 0xFF;
+			*p++ = ((rr>>8)&3) + 0xDC;
+		} else {
+			*p++ = rr & 0xFF;
+			*p++ = rr>>8;
+		}
+	}
+	if(p+1 >= e)
+		return 0;
+	if(term){
+		*p++ = 0;
+		*p++ = 0;
+	}
+	return p - t;
+}
+
+static int
+strunpack8(uchar *, uchar *p, uchar *e, char **dp, int term, Rune (*tr)(Rune))
+{
+	uchar *t;
+	char *d;
+	Rune r;
+	int n;
+
+	t = p;
+	n = 0;
+	while((p < e) && (!term || *p)){
+		p++;
+		n++;
+	}
+	if(term && ((p >= e) || *p))
+		return 0;
+	p -= n;
+	*dp = d = malloc(n*UTFmax+1);
+	while(n--){
+		r = *p & 0x7F;
+		r = tr(r);
+		d += runetochar(d, &r);
+		p++;
+	}
+	*d = 0;
+	if(term)
+		p++;
+	return p - t;
+}
+
+static int
+strunpack16(uchar *b, uchar *p, uchar *e, char **dp, int term, Rune (*tr)(Rune))
+{
+	unsigned int rr;
+	uchar *t;
+	char *d;
+	Rune r;
+	int n;
+
+	t = p;
+	if((p - b) % 2)
+		p++;
+	n = 0;
+	while((p+1 < e) && (!term || (p[0] || p[1]))){
+		p += 2;
+		n++;
+	}
+	if(term && ((p+1 >= e) || p[0] || p[1]))
+		return 0;
+	p -= 2*n;
+	*dp = d = malloc(n*UTFmax+1);
+	while(n--){
+		if(p[1] >= 0xD8 && p[1] <= 0xDB){
+			if(!n--)
+				break;
+			rr = ((p[0]<<10) | ((p[1]-0xD8)<<18) | p[2] | ((p[3]-0xDC)<<8))+0x10000;
+			p += 2;
+		} else
+			rr = p[0] | (p[1]<<8);
+		r = tr(rr);
+		d += runetochar(d, &r);
+		p += 2;
+	}
+	*d = 0;
+	if(term)
+		p += 2;
+	return p - t;
+}
+
+static Rune
+notr(Rune r)
+{
+	return r;
+}
+
+static Rune
+fromnametr(Rune r)
+{
+	switch(r){
+	case '\\':
+		return '/';
+	case ' ':
+		if(trspaces)
+			return 0xa0;
+	}
+	return r;
+}
+
+static Rune
+tonametr(Rune r)
+{
+	switch(r){
+	case '/':
+		return '\\';
+	case 0xa0:
+		if(trspaces)
+			return ' ';
+	}
+	return r;
+}
+
+int smbstrpack8(uchar *b, uchar *p, uchar *e, void *arg){
+	return strpack8(b, p, e, (char*)arg, 1, notr);
+}
+int smbstrpack16(uchar *b, uchar *p, uchar *e, void *arg){
+	return strpack16(b, p, e, (char*)arg, 1, notr);
+}
+int smbstrunpack8(uchar *b, uchar *p, uchar *e, void *arg){
+	return strunpack8(b, p, e, (char**)arg, 1, notr);
+}
+int smbstrunpack16(uchar *b, uchar *p, uchar *e, void *arg){
+	return strunpack16(b, p, e, (char**)arg, 1, notr);
+}
+int smbnamepack8(uchar *b, uchar *p, uchar *e, void *arg){
+	return strpack8(b, p, e, (char*)arg, 1, tonametr);
+}
+int smbnamepack16(uchar *b, uchar *p, uchar *e, void *arg){
+	return strpack16(b, p, e, (char*)arg, 1, tonametr);
+}
+int smbnameunpack8(uchar *b, uchar *p, uchar *e, void *arg){
+	return strunpack8(b, p, e, (char**)arg, 1, fromnametr);
+}
+int smbnameunpack16(uchar *b, uchar *p, uchar *e, void *arg){
+	return strunpack16(b, p, e, (char**)arg, 1, fromnametr);
+}
+int smbuntermstrpack8(uchar *b, uchar *p, uchar *e, void *arg){
+	return strpack8(b, p, e, (char*)arg, 0, notr);
+}
+int smbuntermstrpack16(uchar *b, uchar *p, uchar *e, void *arg){
+	return strpack16(b, p, e, (char*)arg, 0, notr);
+}
+int smbuntermnamepack8(uchar *b, uchar *p, uchar *e, void *arg){
+	return strpack8(b, p, e, (char*)arg, 0, tonametr);
+}
+int smbuntermnamepack16(uchar *b, uchar *p, uchar *e, void *arg){
+	return strpack16(b, p, e, (char*)arg, 0, tonametr);
+}