Browse Source

Plan 9 from Bell Labs 2010-01-14

David du Colombier 14 years ago
parent
commit
d8fb251925

+ 8 - 2
acme/mail/src/mesg.c

@@ -949,7 +949,7 @@ ext(char *type)
 void
 mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
 {
-	char *dest;
+	char *dest, *maildest;
 
 	if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){
 		if(strlen(m->filename) == 0){
@@ -957,8 +957,14 @@ mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
 			dest[strlen(dest)-1] = '\0';
 		}else
 			dest = estrdup(m->filename);
-		if(m->filename[0] != '/')
+		if(maildest = getenv("maildest")){
+			maildest = eappend(maildest, "/", dest);
+			Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), maildest);
+			free(maildest);
+		}
+		if(m->filename[0] != '/'){
 			dest = egrow(estrdup(home), "/", dest);
+		}
 		Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest);
 		free(dest);
 	}else if(!fileonly)

+ 41 - 0
rc/bin/usbeject

@@ -0,0 +1,41 @@
+#!/bin/rc
+# usbeject - unmount usb disks given as arguments
+#	unmount all of them if no arguments given
+rfork e
+disk = ()
+mtpt = /n/usb
+
+test -e /dev/fs/ctl || bind -b '#k' /dev >[2]/dev/null
+
+test -e /dev/usb || bind -a '#u' /dev || {
+	echo $0: no '#u/usb' >[1=2]
+	exit nousb
+}
+test -e /dev/usbdctl || mount -a /srv/usb /dev || {
+	echo $0: cannot mount /srv/usb >[1=2]
+	exit nousbd
+}
+
+disks=()
+mtpt=()
+switch ($#*) {
+case 0
+	disks=`{ls -pd /n/sdU*}
+case *
+	disks=()
+	for (a) {
+		if(~ $a sd??)
+			disk=`{ls -pd /n/^$*^*}
+		if not
+			disk=$a
+		disks=($disks $disk)
+	}
+}
+if (~ $disks '''sdU*''')
+	exit ''
+for (disk in $disks) {
+	unmount /n/$disk >[2]/dev/null && echo $disk unmounted
+	if (test -e /dev/fs/ctl)
+		echo del $disk^parts/^'*' >/dev/fs/ctl >[2]/dev/null
+}
+exit ''

+ 1 - 0
sys/games/lib/fortunes

@@ -4265,3 +4265,4 @@ Sharing your DNA hasn't been this exciting since the good old days. - Genealogy.
 -rwxr-xr-x  1 rae rae 141307906 Nov 10 16:55 arm-2009q3-67-arm-none-linux-gnueabi.bin  # shell script
 Beam to 1.04TeV... Lost due to tune drifting [sh*t!]... Tunes for beam 1 were different when we returned to injection energy (no precycle)... Trimmed and incorporated... Ramped 2 beams, all the way to 1.18TeV !!
 anguish up -1 days, 00:00:01
+People admire complexity. - Rob Pike

+ 188 - 0
sys/man/4/cifs

@@ -0,0 +1,188 @@
+.TH CIFS 4
+.SH NAME
+cifs - Microsoft™ Windows network filesystem client
+.SH SYNOPSIS
+.B aux/cifs
+[
+.B -dDb
+] [
+.B -a
+.I auth-method
+] [
+.B -s
+.I srvname
+] [
+.B -n
+.I called-name
+] [
+.B -k
+.I keyparam
+] [
+.B -m
+.I mntpnt
+]
+.I host
+[
+.I share...
+]
+.SH DESCRIPTION
+.I Cifs
+translates between Microsoft's file-sharing protocol
+(a.k.a. CIFS or SMB) and 9P, allowing Plan9 clients to mount file systems
+(shares or trees in MS terminology) published by such servers.
+.PP
+The root of the mounted directory contains one subdirectory per share,
+always named in lower case, and a few virtual files of mixed case which
+give additional server, session, share, and user information.
+The arguments are:
+.TF "-a\fI auth-method"
+.PD
+.TP
+.BI -a " auth-method"
+.I Cifs
+authenticates using
+.L BNTLM
+by default, but alternative strategies may be
+selected using this option.
+.I Cifs
+eschews cleartext authentication, however
+it may be enabled with the
+.L plain
+auth method.
+The list of currently-supported methods is printed
+if no method name is supplied.
+.IP
+.I "Windows server 2003"
+requires the
+.B BNTLMv2
+method by default, though it can be configured to be more flexible.
+.TP
+.B -b
+Enable file ownership resolution in
+.IR stat (2)
+calls.
+This requires an open and close per file and thus will slow
+.I cifs
+considerably; its use is not recommended.
+.TP
+.B -d
+CIFS packet debug.
+.TP
+.B -D
+9P request debug.
+.TP
+.BI -k " keyparam"
+lists extra parameters which will be passed to
+.IR factotum (4)
+to select a specific key.
+The remote servers's domain is always included in the keyspec,
+under the assumption
+that all servers in a Windows domain share an authentication domain;
+thus
+.I cifs
+expects keys in
+.I factotum
+of the form:
+.RS
+.IP
+.EX
+key proto=pass dom=THEIR-DOMAIN service=cifs
+	user=MY-USERNAME !password=XYZZY
+.EE
+.RE
+.TP
+.BI -m " mntpnt"
+set the mount point for the remote filesystem;
+the default is
+.BI /n/ host.
+.TP
+.BI -n " called-name"
+The CIFS protocol requires clients to know the NetBios name of the
+server they are attaching to, the
+.IR Icalled-name .
+If this is not specified on the command line,
+.I cifs
+attempts to discover this name from the remote server.
+If this fails it will then try
+.IR host ,
+and finally it will try the name
+.LR *SMBSERVER .
+.TP
+.BI -s " srvname"
+post the service as
+.BI /srv/ srvname.
+.TP
+.I host
+The address of the remote server to connect to.
+.TP
+.I share
+A list of share names to attach on the remote server; if none is given,
+.I cifs
+will attempt to attach all shares published by the remote host.
+.SS "Synthetic Files"
+Several synthetic files appear in the root of the mounted filesystem:
+.TF Workstations
+.PD
+.TP
+.B Shares
+Contains a list of the currently attached shares,
+with fields giving the share name,  disk free space / capacity, the share type,
+and a descriptive comment from the server.
+.TP
+.B Connection
+Contains the username used for authentication,
+server's called name, server's domain,
+server's OS, the time slip between the local host and the server,
+the Maximum Transfer Unit (MTU) the server requested, and optionally a flag
+indicating only guest access has been granted.
+The second line contains a list of capabilities offered by the server which is
+mainly of use for debugging
+.IR cifs .
+.TP
+.B Users
+Each line contains a user's name, the user's full name,
+and a descriptive comment.
+.TP
+.B Groups
+Each line gives a group's name, and a list of the names of the users who
+are members of that group.
+.TP
+.B Sessions
+Lists the users authenticated, the client machine's NetBios name or IP address,
+the time since the connection was established,
+and the time for which the connection has been idle.
+.TP
+.B Domains
+One line per domain giving the domain name and a descriptive comment.
+.TP
+.B Workstations
+One line per domain giving the domain name and a descriptive comment,
+the version number of the OS it is running, and comma-separated list of flags
+giving the features of that OS.
+.TP
+.B Dfsroot
+Top level DFS routing giving the DFS link type, time to live of the data,
+proximity of the server, the Netbios or DNS name and
+a physical path or a machine that this maps to.
+.IP
+DNS paths are usually assigned dynamicially as a form of load balancing.
+.SH SOURCE
+.B /sys/src/cmd/cifs
+.SH SEE ALSO
+.IR factotum (4),
+.IR aquarela (8)
+.SH BUGS
+NetApp Filer compatibility has not yet been tested; there may not be any.
+.PP
+DFS support is unfinished.
+.PP
+Kerbros authentication is unfinished.
+.PP
+NetBios name resolution is not supported, though it is now rarely used.
+.PP
+.I Cifs
+has only been tested against
+.IR aquarela (8),
+Windows 95, NT4.0sp6,
+Windows server 2003, WinXP pro, Samba 3.0, and Samba 2.0 (Pluto VideoSpace).
+No support is attempted for servers predating NT 4.0.

+ 25 - 22
sys/man/4/usb

@@ -298,9 +298,9 @@ configures and manages a USB audio device.
 It implements a file system,
 normally mounted on
 .BI /dev ,
-but this can be changed with the
-.B \-m
-option, with files
+but this can be changed with
+.BR \-m ,
+containing files
 .BR volume ,
 .BR audioctl ,
 .BR audio ,
@@ -315,7 +315,9 @@ maintain backward compatibility with the Soundblaster driver.
 The
 .B \-V
 option (verbose)
-causes usbaudio to print information about the device on startup.
+causes
+.I audio
+to print information about the device on startup.
 The
 .B \-s
 option specifies a name for a file descriptor to be posted in
@@ -392,10 +394,11 @@ samples ordered primarily by time and
 secondarily by channel.
 Samples occupy the minimum integral number of bytes.
 Read and write operations of arbitrary size are allowed.
+.
 .SS Ccid
 .I Ccid
 discovers and configures SIM or SAM cards using the CCID standard.
-It provides a file system (usually seen at
+It provides a file system (usually mounted at
 .BR /dev )
 that includes three files,
 .BI ctl ,
@@ -430,16 +433,16 @@ a new ATR is written to it.
 .SS Ibuddy
 .PP
 Ibuddy supports a USB I-buddy toy, a little winged-demon.
-The driver provides one directory per attached toy with a 
-single 
-.BR ctl 
-file to control the device. Directories are named
+The driver provides one directory per attached toy with a single
+.BR ctl
+file to control the device.
+Directories are named
 .BR ibuddyN ,
-being 
+being
 .I N
 the corresponding usb device number.
-When read, the 
-.BR ctl 
+When read, the
+.BR ctl
 file provides the state of the device in this form:
 .IP
 .EX
@@ -451,25 +454,25 @@ blue on|off
 heart on|off
 .EE
 .PP
-Each line describes the status of one feature. 
-.IR  Red , 
+Each line describes the status of one feature.
+.IR  Red ,
 .IR  blue ,
-and 
+and
 .IR  green
 are the different leds in the head of
-the toy. 
-.IR  Heart 
+the toy.
+.IR  Heart
 represents the red led in the chest of
-the toy. 
-.IR  Wings 
+the toy.
+.IR  Wings
 represents the status of the wings, which
 can be closed or open.
-.IR  Hips 
+.IR  Hips
 represents the orientation
 of the toy (left or right, from the figure's point of view).
 .PP
-Lines can be written to the 
-.BR ctl 
+Lines can be written to the
+.BR ctl
 file to command the device.
 Multiple lines (six at most) can be written
 at once, with one action per line.

+ 43 - 3
sys/src/cmd/acme/exec.c

@@ -320,12 +320,35 @@ delcol(Text *et, Text*, Text*, int, int, Rune*, int)
 }
 
 void
-del(Text *et, Text*, Text*, int flag1, int, Rune*, int)
+del(Text *et, Text*, Text *argt, int flag1, int, Rune *arg, int narg)
 {
+	Window *w;
+	char *name, *p;
+	Plumbmsg *pm;
+
 	if(et->col==nil || et->w == nil)
 		return;
-	if(flag1 || et->w->body.file->ntext>1 || winclean(et->w, FALSE))
+	if(flag1 || et->w->body.file->ntext>1 || winclean(et->w, FALSE)){
+		w = et->w;
+		name = getname(&w->body, argt, arg, narg, TRUE);
+		if(name && plumbsendfd >= 0){
+			pm = emalloc(sizeof(Plumbmsg));
+			pm->src = estrdup("acme");
+			pm->dst = estrdup("close");
+			pm->wdir = estrdup(name);
+			if(p = strrchr(pm->wdir, '/'))
+				*p = '\0';
+			pm->type = estrdup("text");
+			pm->attr = nil;
+			pm->data = estrdup(name);
+			pm->ndata = strlen(pm->data);
+			if(pm->ndata < messagesize-1024)
+				plumbsend(plumbsendfd, pm);
+			else
+				plumbfree(pm);
+		}
 		colclose(et->col, et->w, TRUE);
+	}
 }
 
 void
@@ -539,10 +562,11 @@ putfile(File *f, int q0, int q1, Rune *namer, int nname)
 {
 	uint n, m;
 	Rune *r;
-	char *s, *name;
+	char *s, *name, *p;
 	int i, fd, q;
 	Dir *d, *d1;
 	Window *w;
+	Plumbmsg *pm;
 	int isapp;
 
 	w = f->curtext->w;
@@ -610,6 +634,22 @@ putfile(File *f, int q0, int q1, Rune *namer, int nname)
 			f->text[i]->w->dirty = w->dirty;
 		}
 	}
+	if(plumbsendfd >= 0){
+		pm = emalloc(sizeof(Plumbmsg));
+		pm->src = estrdup("acme");
+		pm->dst = estrdup("put");
+		pm->wdir = estrdup(name);
+		if(p = strrchr(pm->wdir, '/'))
+			*p = '\0';
+		pm->type = estrdup("text");
+		pm->attr = nil;
+		pm->data = estrdup(name);
+		pm->ndata = strlen(pm->data);
+		if(pm->ndata < messagesize-1024)
+			plumbsend(plumbsendfd, pm);
+		else
+			plumbfree(pm);
+	}
 	fbuffree(s);
 	fbuffree(r);
 	free(d);

+ 1 - 0
sys/src/cmd/acme/fns.h

@@ -16,6 +16,7 @@ char*	getarg(Text*, int, int, Rune**, int*);
 char*	getbytearg(Text*, int, int, char**);
 void	new(Text*, Text*, Text*, int, int, Rune*, int);
 void	undo(Text*, Text*, Text*, int, int, Rune*, int);
+char*	getname(Text*, Text*, Rune*, int, int);
 void	scrsleep(uint);
 void	savemouse(Window*);
 void	restoremouse(Window*);

+ 222 - 0
sys/src/cmd/cifs/apinums.h

@@ -0,0 +1,222 @@
+/********************************************************************/
+/**                     Microsoft LAN Manager                      **/
+/**            Copyright(c) Microsoft Corp., 1987-1991             **/
+/********************************************************************/
+
+#define API_WShareEnum			0
+#define API_WShareGetInfo		1
+#define API_WShareSetInfo		2
+#define API_WShareAdd			3
+#define API_WShareDel			4
+#define API_NetShareCheck		5
+#define API_WSessionEnum		6
+#define API_WSessionGetInfo		7
+#define API_WSessionDel			8
+#define API_WConnectionEnum		9
+#define API_WFileEnum			10
+#define API_WFileGetInfo		11
+#define API_WFileClose			12
+#define API_WServerGetInfo		13
+#define API_WServerSetInfo		14
+#define API_WServerDiskEnum		15
+#define API_WServerAdminCommand		16
+#define API_NetAuditOpen		17
+#define API_WAuditClear			18
+#define API_NetErrorLogOpen		19
+#define API_WErrorLogClear		20
+#define API_NetCharDevEnum		21
+#define API_NetCharDevGetInfo		22
+#define API_WCharDevControl		23
+#define API_NetCharDevQEnum		24
+#define API_NetCharDevQGetInfo		25
+#define API_WCharDevQSetInfo		26
+#define API_WCharDevQPurge		27
+#define API_WCharDevQPurgeSelf		28
+#define API_WMessageNameEnum		29
+#define API_WMessageNameGetInfo		30
+#define API_WMessageNameAdd		31
+#define API_WMessageNameDel		32
+#define API_WMessageNameFwd		33
+#define API_WMessageNameUnFwd		34
+#define API_WMessageBufferSend		35
+#define API_WMessageFileSend		36
+#define API_WMessageLogFileSet		37
+#define API_WMessageLogFileGet		38
+#define API_WServiceEnum		39
+#define API_WServiceInstall		40
+#define API_WServiceControl		41
+#define API_WAccessEnum			42
+#define API_WAccessGetInfo		43
+#define API_WAccessSetInfo		44
+#define API_WAccessAdd			45
+#define API_WAccessDel			46
+#define API_WGroupEnum			47
+#define API_WGroupAdd			48
+#define API_WGroupDel			49
+#define API_WGroupAddUser		50
+#define API_WGroupDelUser		51
+#define API_WGroupGetUsers		52
+#define API_WUserEnum			53
+#define API_WUserAdd			54
+#define API_WUserDel			55
+#define API_WUserGetInfo		56
+#define API_WUserSetInfo		57
+#define API_WUserPasswordSet		58
+#define API_WUserGetGroups		59
+#define API_DeadTableEntry		60
+/* This line and number replaced a Dead Entry */
+#define API_WWkstaSetUID		62
+#define API_WWkstaGetInfo		63
+#define API_WWkstaSetInfo		64
+#define API_WUseEnum			65
+#define API_WUseAdd			66
+#define API_WUseDel			67
+#define API_WUseGetInfo			68
+#define API_WPrintQEnum			69
+#define API_WPrintQGetInfo		70
+#define API_WPrintQSetInfo		71
+#define API_WPrintQAdd			72
+#define API_WPrintQDel			73
+#define API_WPrintQPause		74
+#define API_WPrintQContinue		75
+#define API_WPrintJobEnum		76
+#define API_WPrintJobGetInfo		77
+#define API_WPrintJobSetInfo_OLD	78
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+#define API_WPrintJobDel		81
+#define API_WPrintJobPause		82
+#define API_WPrintJobContinue		83
+#define API_WPrintDestEnum		84
+#define API_WPrintDestGetInfo		85
+#define API_WPrintDestControl		86
+#define API_WProfileSave		87
+#define API_WProfileLoad		88
+#define API_WStatisticsGet		89
+#define API_WStatisticsClear		90
+#define API_NetRemoteTOD		91
+#define API_WNetBiosEnum		92
+#define API_WNetBiosGetInfo		93
+#define API_NetServerEnum		94
+#define API_I_NetServerEnum		95
+#define API_WServiceGetInfo		96
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+#define API_WPrintQPurge		103
+#define API_NetServerEnum2		104
+#define API_WAccessGetUserPerms		105
+#define API_WGroupGetInfo		106
+#define API_WGroupSetInfo		107
+#define API_WGroupSetUsers		108
+#define API_WUserSetGroups		109
+#define API_WUserModalsGet		110
+#define API_WUserModalsSet		111
+#define API_WFileEnum2			112
+#define API_WUserAdd2			113
+#define API_WUserSetInfo2		114
+#define API_WUserPasswordSet2		115
+#define API_I_NetServerEnum2		116
+#define API_WConfigGet2			117
+#define API_WConfigGetAll2		118
+#define API_WGetDCName			119
+#define API_NetHandleGetInfo		120
+#define API_NetHandleSetInfo		121
+#define API_WStatisticsGet2		122
+#define API_WBuildGetInfo		123
+#define API_WFileGetInfo2		124
+#define API_WFileClose2			125
+#define API_WNetServerReqChallenge	126
+#define API_WNetServerAuthenticate	127
+#define API_WNetServerPasswordSet	128
+#define API_WNetAccountDeltas		129
+#define API_WNetAccountSync		130
+#define API_WUserEnum2			131
+#define API_WWkstaUserLogon		132
+#define API_WWkstaUserLogoff		133
+#define API_WLogonEnum			134
+#define API_WErrorLogRead		135
+#define API_WI_NetPathType		136
+#define API_WI_NetPathCanonicalize	137
+#define API_WI_NetPathCompare		138
+#define API_WI_NetNameValidate		139
+#define API_WI_NetNameCanonicalize	140
+#define API_WI_NetNameCompare		141
+#define API_WAuditRead			142
+#define API_WPrintDestAdd		143
+#define API_WPrintDestSetInfo		144
+#define API_WPrintDestDel		145
+#define API_WUserValidate2		146
+#define API_WPrintJobSetInfo		147
+#define API_TI_NetServerDiskEnum	148
+#define API_TI_NetServerDiskGetInfo	149
+#define API_TI_FTVerifyMirror		150
+#define API_TI_FTAbortVerify		151
+#define API_TI_FTGetInfo		152
+#define API_TI_FTSetInfo		153
+#define API_TI_FTLockDisk		154
+#define API_TI_FTFixError		155
+#define API_TI_FTAbortFix		156
+#define API_TI_FTDiagnoseError		157
+#define API_TI_FTGetDriveStats		158
+/* This line and number replaced a Dead Entry */
+#define API_TI_FTErrorGetInfo		160
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+#define API_NetAccessCheck		163
+#define API_NetAlertRaise		164
+#define API_NetAlertStart		165
+#define API_NetAlertStop		166
+#define API_NetAuditWrite		167
+#define API_NetIRemoteAPI		168
+#define API_NetServiceStatus		169
+#define API_I_NetServerRegister		170
+#define API_I_NetServerDeregister	171
+#define API_I_NetSessionEntryMake	172
+#define API_I_NetSessionEntryClear	173
+#define API_I_NetSessionEntryGetInfo	174
+#define API_I_NetSessionEntrySetInfo	175
+#define API_I_NetConnectionEntryMake	176
+#define API_I_NetConnectionEntryClear	177
+#define API_I_NetConnectionEntrySetInfo	178
+#define API_I_NetConnectionEntryGetInfo	179
+#define API_I_NetFileEntryMake		180
+#define API_I_NetFileEntryClear		181
+#define API_I_NetFileEntrySetInfo	182
+#define API_I_NetFileEntryGetInfo	183
+#define API_AltSrvMessageBufferSend	184
+#define API_AltSrvMessageFileSend	185
+#define API_wI_NetRplWkstaEnum		186
+#define API_wI_NetRplWkstaGetInfo	187
+#define API_wI_NetRplWkstaSetInfo	188
+#define API_wI_NetRplWkstaAdd		189
+#define API_wI_NetRplWkstaDel		190
+#define API_wI_NetRplProfileEnum	191
+#define API_wI_NetRplProfileGetInfo	192
+#define API_wI_NetRplProfileSetInfo	193
+#define API_wI_NetRplProfileAdd		194
+#define API_wI_NetRplProfileDel		195
+#define API_wI_NetRplProfileClone	196
+#define API_wI_NetRplBaseProfileEnum	197
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+#define API_WIServerSetInfo		201
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+/* This line and number replaced a Dead Entry */
+#define API_WPrintDriverEnum		205
+#define API_WPrintQProcessorEnum	206
+#define API_WPrintPortEnum		207
+#define API_WNetWriteUpdateLog		208
+#define API_WNetAccountUpdate		209
+#define API_WNetAccountConfirmUpdate	210
+#define API_WConfigSet			211
+#define API_WAccountsReplicate		212
+/*   213 is used by WfW  */
+#define API_SamOEMChgPasswordUser2_P	214
+#define API_NetServerEnum3		215
+#define MAX_API				215

+ 477 - 0
sys/src/cmd/cifs/auth-testcase.c

@@ -0,0 +1,477 @@
+/*
+ * Beware the LM hash is easy to crack (google for l0phtCrack)
+ * and though NTLM is more secure it is still breakable.
+ * Ntlmv2 is better and seen as good enough by the Windows community.
+ * For real security use Kerberos.
+ */
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <auth.h>
+#include <libsec.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+
+#define NTLMV2_TEST	1
+#define DEF_AUTH 	"ntlmv2"
+
+static enum {
+	MACkeylen	= 40,	/* MAC key len */
+	MAClen		= 8,	/* signature length */
+	MACoff		= 14,	/* sign. offset from start of SMB (not netbios) pkt */
+	Bliplen		= 8,	/* size of LMv2 client nonce */
+};
+
+static void
+dmp(char *s, int seq, void *buf, int n)
+{
+	int i;
+	char *p = buf;
+
+	print("%s %3d      ", s, seq);
+	while(n > 0){
+		for(i = 0; i < 16 && n > 0; i++, n--)
+			print("%02x ", *p++ & 0xff);
+		if(n > 0)
+			print("\n");
+	}
+	print("\n");
+}
+
+static Auth *
+auth_plain(char *windom, char *keyp, uchar *chal, int len)
+{
+	UserPasswd *up;
+	static Auth *ap;
+
+	USED(chal, len);
+
+	up = auth_getuserpasswd(auth_getkey, "windom=%s proto=pass service=cifs %s",
+		windom, keyp);
+	if(! up)
+		sysfatal("cannot get key - %r");
+
+	ap = emalloc9p(sizeof(Auth));
+	memset(ap, 0, sizeof(ap));
+	ap->user = estrdup9p(up->user);
+	ap->windom = estrdup9p(windom);
+
+	ap->resp[0] = estrdup9p(up->passwd);
+	ap->len[0] = strlen(up->passwd);
+	memset(up->passwd, 0, strlen(up->passwd));
+	free(up);
+
+	return ap;
+}
+
+static Auth *
+auth_lm_and_ntlm(char *windom, char *keyp, uchar *chal, int len)
+{
+	int err;
+	Auth *ap;
+	char user[64];
+	MSchapreply mcr;
+
+	err = auth_respond(chal, len, user, sizeof user, &mcr, sizeof mcr,
+		auth_getkey, "windom=%s proto=mschap role=client service=cifs %s",
+		windom, keyp);
+	if(err == -1)
+		sysfatal("cannot get key - %r");
+
+	ap = emalloc9p(sizeof(Auth));
+	memset(ap, 0, sizeof(ap));
+	ap->user = estrdup9p(user);
+	ap->windom = estrdup9p(windom);
+
+	/* LM response */
+	ap->len[0] = sizeof(mcr.LMresp);
+	ap->resp[0] = emalloc9p(ap->len[0]);
+	memcpy(ap->resp[0], mcr.LMresp, ap->len[0]);
+
+	/* NTLM response */
+	ap->len[1] = sizeof(mcr.NTresp);
+	ap->resp[1] = emalloc9p(ap->len[1]);
+	memcpy(ap->resp[1], mcr.NTresp, ap->len[1]);
+
+	return ap;
+}
+
+/*
+ * NTLM response only, the LM response is a just
+ * copy of the NTLM one.  We do this because the lm
+ * response is easily reversed - Google for l0pht for more info.
+ */
+static Auth *
+auth_ntlm(char *windom, char *keyp, uchar *chal, int len)
+{
+	Auth *ap;
+
+	if((ap = auth_lm_and_ntlm(windom, keyp, chal, len)) == nil)
+		return nil;
+
+	free(ap->resp[0]);
+	ap->len[0] = ap->len[1];
+	ap->resp[0] = emalloc9p(ap->len[0]);
+	memcpy(ap->resp[0], ap->resp[1], ap->len[0]);
+	return ap;
+}
+
+/*
+ * This is not really nescessary as all fields hmac_md5'ed
+ * in the ntlmv2 protocol are less than 64 bytes long, however
+ * I still do this for completeness.
+ */
+static DigestState *
+hmac_t64(uchar *data, ulong dlen, uchar *key, ulong klen, uchar *digest,
+	DigestState *state)
+{
+	if(klen > 64)
+		klen = 64;
+	return hmac_md5(data, dlen, key, klen, digest, state);
+}
+
+
+static int
+ntv2_blob(uchar *blob, int len, char *windom)
+{
+	int n;
+	uvlong nttime;
+	Rune r;
+	char *d;
+	uchar *p;
+	enum {			/* name types */
+		Beof,		/* end of name list */
+		Bnetbios,	/* Netbios machine name */
+		Bdomain,	/* Windows Domain name (NT) */
+		Bdnsfqdn,	/* DNS Fully Qualified Domain Name */
+		Bdnsname,	/* DNS machine name (win2k) */
+	};
+
+	p = blob;
+	*p++ = 1;		/* response type */
+	*p++ = 1;		/* max response type understood by client */
+
+	*p++ = 0;
+	*p++ = 0;		/* 2 bytes reserved */
+
+	*p++ = 0;
+	*p++ = 0;
+	*p++ = 0;
+	*p++ = 0;		/* 4 bytes unknown */
+
+#ifdef NTLMV2_TEST
+	*p++ = 0xf0;
+	*p++ = 0x20;
+	*p++ = 0xd0;
+	*p++ = 0xb6;
+	*p++ = 0xc2;
+	*p++ = 0x92;
+	*p++ = 0xbe;
+	*p++ = 0x01;
+#else
+	nttime = time(nil);	/* nt time now */
+	nttime = nttime + 11644473600LL;
+	nttime = nttime * 10000000LL;
+	*p++ = nttime & 0xff;
+	*p++ = (nttime >> 8) & 0xff;
+	*p++ = (nttime >> 16) & 0xff;
+	*p++ = (nttime >> 24) & 0xff;
+	*p++ = (nttime >> 32) & 0xff;
+	*p++ = (nttime >> 40) & 0xff;
+	*p++ = (nttime >> 48) & 0xff;
+	*p++ = (nttime >> 56) & 0xff;
+#endif
+#ifdef NTLMV2_TEST
+	*p++ = 0x05;
+	*p++ = 0x83;
+	*p++ = 0x32;
+	*p++ = 0xec;
+	*p++ = 0xfa;
+	*p++ = 0xe4;
+	*p++ = 0xf3;
+	*p++ = 0x6d;
+#else
+	genrandom(p, 8);
+	p += 8;			/* client nonce */
+#endif
+	*p++ = 0x6f;
+	*p++ = 0;
+	*p++ = 0x6e;
+	*p++ = 0;		/* unknown data */
+
+	*p++ = Bdomain;
+	*p++ = 0;		/* name type */
+
+	n = utflen(windom) * 2;
+	*p++ = n;
+	*p++ = n >> 8;		/* name length */
+
+	d = windom;
+	while(*d && p - blob < len - 8){
+		d += chartorune(&r, d);
+		r = toupperrune(r);
+		*p++ = r;
+		*p++ = r >> 8;
+	}
+
+	*p++ = 0;
+	*p++ = Beof;		/* name type */
+
+	*p++ = 0;
+	*p++ = 0;		/* name length */
+
+	*p++ = 0x65;
+	*p++ = 0;
+	*p++ = 0;
+	*p++ = 0;		/* unknown data */
+	return p - blob;
+}
+
+static Auth *
+auth_ntlmv2(char *windom, char *keyp, uchar *chal, int len)
+{
+	int i, n;
+	Rune r;
+	char *p, *u;
+	uchar c, lm_hmac[MD5dlen], nt_hmac[MD5dlen], nt_sesskey[MD5dlen];
+	uchar lm_sesskey[MD5dlen];
+	uchar v1hash[MD5dlen], blip[Bliplen], blob[1024], v2hash[MD5dlen];
+	DigestState *ds;
+	UserPasswd *up;
+	static Auth *ap;
+
+	up = auth_getuserpasswd(auth_getkey, "windom=%s proto=pass service=cifs-ntlmv2 %s",
+		windom, keyp);
+	if(! up)
+		sysfatal("cannot get key - %r");
+
+#ifdef NTLMV2_TEST
+{
+	static uchar srvchal[] = { 0x52, 0xaa, 0xc8, 0xe8, 0x2c, 0x06, 0x7f, 0xa1 };
+	up->user = "ADMINISTRATOR";
+	windom = "rocknroll";
+	chal = srvchal;
+}
+#endif
+	ap = emalloc9p(sizeof(Auth));
+	memset(ap, 0, sizeof(ap));
+
+	/* Standard says unlimited length, experience says 128 max */
+	if((n = strlen(up->passwd)) > 128)
+		n = 128;
+
+	ds = md4(nil, 0, nil, nil);
+	for(i = 0, p = up->passwd; i < n; i++) {
+		p += chartorune(&r, p);
+		c = r;
+		md4(&c, 1, nil, ds);
+		c = r >> 8;
+		md4(&c, 1, nil, ds);
+	}
+	md4(nil, 0, v1hash, ds);
+
+#ifdef NTLMV2_TEST
+{
+	uchar v1[] = {
+		0x0c, 0xb6, 0x94, 0x88, 0x05, 0xf7, 0x97, 0xbf,
+		0x2a, 0x82, 0x80, 0x79, 0x73, 0xb8, 0x95, 0x37
+	;
+	memcpy(v1hash, v1, sizeof(v1));
+}
+#endif
+	/*
+	 * Some documentation insists that the username must be forced to
+	 * uppercase, but the domain name should not be. Other shows both
+	 * being forced to uppercase.  I am pretty sure this is irrevevant as
+	 * the domain name passed from the remote server always seems to be in
+	 * uppercase already.
+	 */
+        ds = hmac_t64(nil, 0, v1hash, MD5dlen, nil, nil);
+	u = up->user;
+	while(*u){
+		u += chartorune(&r, u);
+		r = toupperrune(r);
+		c = r & 0xff;
+        	hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
+		c = r >> 8;
+        	hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
+	}
+	u = windom;
+
+	while(*u){
+		u += chartorune(&r, u);
+		c = r;
+        	hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
+		c = r >> 8;
+        	hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
+	}
+        hmac_t64(nil, 0, v1hash, MD5dlen, v2hash, ds);
+#ifdef NTLMV2_TEST
+	print("want:               40 e1 b3 24...\n");
+	dmp("v2hash==kr", 0, v2hash, MD5dlen);
+#endif
+	ap->user = estrdup9p(up->user);
+	ap->windom = estrdup9p(windom);
+
+	/* LM v2 */
+
+	genrandom(blip, Bliplen);
+#ifdef NTLMV2_TEST
+{
+	uchar t[] = { 0x05, 0x83, 0x32, 0xec, 0xfa, 0xe4, 0xf3, 0x6d };
+	memcpy(blip, t, 8);
+}
+#endif
+        ds = hmac_t64(chal, len, v2hash, MD5dlen, nil, nil);
+	hmac_t64(blip, Bliplen, v2hash, MD5dlen, lm_hmac, ds);
+	ap->len[0] = MD5dlen+Bliplen;
+	ap->resp[0] = emalloc9p(ap->len[0]);
+	memcpy(ap->resp[0], lm_hmac, MD5dlen);
+	memcpy(ap->resp[0]+MD5dlen, blip, Bliplen);
+#ifdef NTLMV2_TEST
+	print("want:               38 6b ae...\n");
+	dmp("lmv2 resp ", 0, lm_hmac, MD5dlen);
+#endif
+
+	/* LM v2 session key */
+	hmac_t64(lm_hmac, MD5dlen, v2hash, MD5dlen, lm_sesskey, nil);
+
+	/* LM v2 MAC key */
+	ap->mackey[0] = emalloc9p(MACkeylen);
+	memcpy(ap->mackey[0], lm_sesskey, MD5dlen);
+	memcpy(ap->mackey[0]+MD5dlen, ap->resp[0], MACkeylen-MD5dlen);
+
+	/* NTLM v2 */
+	n = ntv2_blob(blob, sizeof(blob), windom);
+        ds = hmac_t64(chal, len, v2hash, MD5dlen, nil, nil);
+	hmac_t64(blob, n, v2hash, MD5dlen, nt_hmac, ds);
+	ap->len[1] = MD5dlen+n;
+	ap->resp[1] = emalloc9p(ap->len[1]);
+	memcpy(ap->resp[1], nt_hmac, MD5dlen);
+	memcpy(ap->resp[1]+MD5dlen, blob, n);
+#ifdef NTLMV2_TEST
+	print("want:               1a ad 55...\n");
+	dmp("ntv2 resp ", 0, nt_hmac, MD5dlen);
+#endif
+
+	/* NTLM v2 session key */
+	hmac_t64(nt_hmac, MD5dlen, v2hash, MD5dlen, nt_sesskey, nil);
+
+	/* NTLM v2 MAC key */
+	ap->mackey[1] = emalloc9p(MACkeylen);
+	memcpy(ap->mackey[1], nt_sesskey, MD5dlen);
+	memcpy(ap->mackey[1]+MD5dlen, ap->resp[1], MACkeylen-MD5dlen);
+	free(up);
+
+	return ap;
+}
+
+struct {
+	char	*name;
+	Auth	*(*func)(char *, char *, uchar *, int);
+} methods[] = {
+	{ "plain",	auth_plain },
+	{ "lm+ntlm",	auth_lm_and_ntlm },
+	{ "ntlm",	auth_ntlm },
+	{ "ntlmv2",	auth_ntlmv2 },
+//	{ "kerberos",	auth_kerberos },
+};
+
+void
+autherr(void)
+{
+	int i;
+
+	fprint(2, "supported auth methods:\t");
+	for(i = 0; i < nelem(methods); i++)
+		fprint(2, "%s ", methods[i].name);
+	fprint(2, "\n");
+	exits("usage");
+}
+
+Auth *
+getauth(char *name, char *windom, char *keyp, int secmode, uchar *chal, int len)
+{
+	int i;
+	Auth *ap;
+
+	if(name == nil){
+		name = DEF_AUTH;
+		if((secmode & SECMODE_PW_ENCRYPT) == 0)
+			sysfatal("plaintext authentication required, use '-a plain'");
+	}
+
+	ap = nil;
+	for(i = 0; i < nelem(methods); i++)
+		if(strcmp(methods[i].name, name) == 0){
+			ap = methods[i].func(windom, keyp, chal, len);
+			break;
+		}
+
+	if(! ap){
+		fprint(2, "%s: %s - unknown auth method\n", argv0, name);
+		autherr();		/* never returns */
+	}
+	return ap;
+}
+
+static int
+genmac(uchar *buf, int len, int seq, uchar key[MACkeylen], uchar mine[MAClen])
+{
+	DigestState *ds;
+	uchar *sig, digest[MD5dlen], their[MAClen];
+
+	sig = buf+MACoff;
+	memcpy(their, sig, MAClen);
+	memset(sig, 0, MAClen);
+	sig[0] = seq;
+	sig[1] = seq >> 8;
+	sig[2] = seq >> 16;
+	sig[3] = seq >> 24;
+
+	ds = md5(key, MACkeylen, nil, nil);
+	md5(buf, len, nil, ds);
+	md5(nil, 0, digest, ds);
+	memcpy(mine, digest, MAClen);
+
+	return memcmp(their, mine, MAClen);
+}
+
+int
+macsign(Pkt *p)
+{
+	int i, len;
+	uchar *sig, *buf, mac[MAClen], zeros[MACkeylen];
+
+	sig = p->buf + NBHDRLEN + MACoff;
+	buf = p->buf + NBHDRLEN;
+	len = (p->pos - p->buf) - NBHDRLEN;
+
+	for(i = -3; i < 4; i++){
+		memset(zeros, 0, sizeof(zeros));
+		if(genmac(buf, len, p->seq+i, zeros, mac) == 0){
+			dmp("got", 0, buf, len);
+			dmp("Zero OK", p->seq, mac, MAClen);
+			return 0;
+		}
+
+		if(genmac(buf, len, p->seq+i, p->s->auth->mackey[0], mac) == 0){
+			dmp("got", 0, buf, len);
+			dmp("LM-hash OK", p->seq, mac, MAClen);
+			return 0;
+		}
+
+		if(genmac(buf, len, p->seq+i, p->s->auth->mackey[1], mac) == 0){
+			dmp("got", 0, buf, len);
+			dmp("NT-hash OK", p->seq, mac, MAClen);
+			return 0;
+		}
+	}
+	genmac(buf, len, p->seq, p->s->auth->mackey[0], mac);
+
+	memcpy(sig, mac, MAClen);
+	return -1;
+}

+ 416 - 0
sys/src/cmd/cifs/auth.c

@@ -0,0 +1,416 @@
+/*
+ * Beware the LM hash is easy to crack (google for l0phtCrack)
+ * and though NTLM is more secure it is still breakable.
+ * Ntlmv2 is better and seen as good enough by the windows community.
+ * For real security use kerberos.
+ */
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <auth.h>
+#include <libsec.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+
+#define DEF_AUTH 	"ntlmv2"
+
+static enum {
+	MACkeylen	= 40,	/* MAC key len */
+	MAClen		= 8,	/* signature length */
+	MACoff		= 14,	/* sign. offset from start of SMB (not netbios) pkt */
+	Bliplen		= 8,	/* size of LMv2 client nonce */
+};
+
+static void
+dmp(char *s, int seq, void *buf, int n)
+{
+	int i;
+	char *p = buf;
+
+	print("%s %3d      ", s, seq);
+	while(n > 0){
+		for(i = 0; i < 16 && n > 0; i++, n--)
+			print("%02x ", *p++ & 0xff);
+		if(n > 0)
+			print("\n");
+	}
+	print("\n");
+}
+
+static Auth *
+auth_plain(char *windom, char *keyp, uchar *chal, int len)
+{
+	UserPasswd *up;
+	static Auth *ap;
+
+	USED(chal, len);
+
+	up = auth_getuserpasswd(auth_getkey, "windom=%s proto=pass service=cifs %s",
+		windom, keyp);
+	if(! up)
+		sysfatal("cannot get key - %r");
+
+	ap = emalloc9p(sizeof(Auth));
+	memset(ap, 0, sizeof(ap));
+	ap->user = estrdup9p(up->user);
+	ap->windom = estrdup9p(windom);
+
+	ap->resp[0] = estrdup9p(up->passwd);
+	ap->len[0] = strlen(up->passwd);
+	memset(up->passwd, 0, strlen(up->passwd));
+	free(up);
+
+	return ap;
+}
+
+static Auth *
+auth_lm_and_ntlm(char *windom, char *keyp, uchar *chal, int len)
+{
+	int err;
+	char user[64];
+	Auth *ap;
+	MSchapreply mcr;
+
+	err = auth_respond(chal, len, user, sizeof user, &mcr, sizeof mcr,
+		auth_getkey, "windom=%s proto=mschap role=client service=cifs %s",
+		windom, keyp);
+	if(err == -1)
+		sysfatal("cannot get key - %r");
+
+	ap = emalloc9p(sizeof(Auth));
+	memset(ap, 0, sizeof(ap));
+	ap->user = estrdup9p(user);
+	ap->windom = estrdup9p(windom);
+
+	/* LM response */
+	ap->len[0] = sizeof(mcr.LMresp);
+	ap->resp[0] = emalloc9p(ap->len[0]);
+	memcpy(ap->resp[0], mcr.LMresp, ap->len[0]);
+
+	/* NTLM response */
+	ap->len[1] = sizeof(mcr.NTresp);
+	ap->resp[1] = emalloc9p(ap->len[1]);
+	memcpy(ap->resp[1], mcr.NTresp, ap->len[1]);
+
+	return ap;
+}
+
+/*
+ * NTLM response only, the LM response is a just
+ * copy of the NTLM one. we do this because the lm
+ * response is easily reversed - Google for l0pht
+ * for more info.
+ */
+static Auth *
+auth_ntlm(char *windom, char *keyp, uchar *chal, int len)
+{
+	Auth *ap;
+
+	if((ap = auth_lm_and_ntlm(windom, keyp, chal, len)) == nil)
+		return nil;
+
+	free(ap->resp[0]);
+	ap->len[0] = ap->len[1];
+	ap->resp[0] = emalloc9p(ap->len[0]);
+	memcpy(ap->resp[0], ap->resp[1], ap->len[0]);
+	return ap;
+}
+
+/*
+ * This is not really nescessary as all fields hmac_md5'ed
+ * in the ntlmv2 protocol are less than 64 bytes long, however
+ * I still do this for completeness
+ */
+static DigestState *
+hmac_t64(uchar *data, ulong dlen, uchar *key, ulong klen, uchar *digest,
+	DigestState *state)
+{
+	if(klen > 64)
+		klen = 64;
+	return hmac_md5(data, dlen, key, klen, digest, state);
+}
+
+
+static int
+ntv2_blob(uchar *blob, int len, char *windom)
+{
+	int n;
+	uvlong nttime;
+	Rune r;
+	char *d;
+	uchar *p;
+	enum {			/* name types */
+		Beof,		/* end of name list */
+		Bnetbios,	/* Netbios machine name */
+		Bdomain,	/* Windows Domain name (NT) */
+		Bdnsfqdn,	/* DNS Fully Qualified Domain Name */
+		Bdnsname,	/* DNS machine name (win2k) */
+	};
+
+	p = blob;
+	*p++ = 1;		/* response type */
+	*p++ = 1;		/* max response type understood by client */
+
+	*p++ = 0;
+	*p++ = 0;		/* 2 bytes reserved */
+
+	*p++ = 0;
+	*p++ = 0;
+	*p++ = 0;
+	*p++ = 0;		/* 4 bytes unknown */
+
+	nttime = time(nil);	/* nt time now */
+	nttime += 11644473600LL;
+	nttime *= 10000000LL;
+	*p++ = nttime;
+	*p++ = nttime >> 8;
+	*p++ = nttime >> 16;
+	*p++ = nttime >> 24;
+	*p++ = nttime >> 32;
+	*p++ = nttime >> 40;
+	*p++ = nttime >> 48;
+	*p++ = nttime >> 56;
+
+	genrandom(p, 8);
+	p += 8;			/* client nonce */
+	*p++ = 0x6f;
+	*p++ = 0;
+	*p++ = 0x6e;
+	*p++ = 0;		/* unknown data */
+
+	*p++ = Bdomain;
+	*p++ = 0;		/* name type */
+
+	n = utflen(windom) * 2;
+	*p++ = n;
+	*p++ = n >> 8;		/* name length */
+
+	d = windom;
+	while(*d && p-blob < (len-8)){
+		d += chartorune(&r, d);
+		r = toupperrune(r);
+		*p++ = r;
+		*p++ = r >> 8;
+	}
+
+	*p++ = 0;
+	*p++ = Beof;		/* name type */
+
+	*p++ = 0;
+	*p++ = 0;		/* name length */
+
+	*p++ = 0x65;
+	*p++ = 0;
+	*p++ = 0;
+	*p++ = 0;		/* unknown data */
+	return p - blob;
+}
+
+static Auth *
+auth_ntlmv2(char *windom, char *keyp, uchar *chal, int len)
+{
+	int i, n;
+	Rune r;
+	char *p, *u;
+	uchar v1hash[MD5dlen], blip[Bliplen], blob[1024], v2hash[MD5dlen];
+	uchar c, lm_hmac[MD5dlen], nt_hmac[MD5dlen], nt_sesskey[MD5dlen],
+		lm_sesskey[MD5dlen];
+	DigestState *ds;
+	UserPasswd *up;
+	static Auth *ap;
+
+	up = auth_getuserpasswd(auth_getkey, "windom=%s proto=pass  service=cifs-ntlmv2 %s",
+		windom, keyp);
+	if(!up)
+		sysfatal("cannot get key - %r");
+
+	ap = emalloc9p(sizeof(Auth));
+	memset(ap, 0, sizeof(ap));
+
+	/* Standard says unlimited length, experience says 128 max */
+	if((n = strlen(up->passwd)) > 128)
+		n = 128;
+
+	ds = md4(nil, 0, nil, nil);
+	for(i=0, p=up->passwd; i < n; i++) {
+		p += chartorune(&r, p);
+		c = r;
+		md4(&c, 1, nil, ds);
+		c = r >> 8;
+		md4(&c, 1, nil, ds);
+	}
+	md4(nil, 0, v1hash, ds);
+
+	/*
+	 * Some documentation insists that the username must be forced to
+	 * uppercase, but the domain name should not be. Other shows both
+	 * being forced to uppercase. I am pretty sure this is irrevevant as the
+	 * domain name passed from the remote server always seems to be in
+	 * uppercase already.
+	 */
+        ds = hmac_t64(nil, 0, v1hash, MD5dlen, nil, nil);
+	u = up->user;
+	while(*u){
+		u += chartorune(&r, u);
+		r = toupperrune(r);
+		c = r;
+        	hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
+		c = r >> 8;
+        	hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
+	}
+	u = windom;
+
+	while(*u){
+		u += chartorune(&r, u);
+		c = r;
+        	hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
+		c = r >> 8;
+        	hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
+	}
+        hmac_t64(nil, 0, v1hash, MD5dlen, v2hash, ds);
+	ap->user = estrdup9p(up->user);
+	ap->windom = estrdup9p(windom);
+
+	/* LM v2 */
+
+	genrandom(blip, Bliplen);
+        ds = hmac_t64(chal, len, v2hash, MD5dlen, nil, nil);
+	hmac_t64(blip, Bliplen, v2hash, MD5dlen, lm_hmac, ds);
+	ap->len[0] = MD5dlen+Bliplen;
+	ap->resp[0] = emalloc9p(ap->len[0]);
+	memcpy(ap->resp[0], lm_hmac, MD5dlen);
+	memcpy(ap->resp[0]+MD5dlen, blip, Bliplen);
+
+	/* LM v2 session key */
+	hmac_t64(lm_hmac, MD5dlen, v2hash, MD5dlen, lm_sesskey, nil);
+
+	/* LM v2 MAC key */
+	ap->mackey[0] = emalloc9p(MACkeylen);
+	memcpy(ap->mackey[0], lm_sesskey, MD5dlen);
+	memcpy(ap->mackey[0]+MD5dlen, ap->resp[0], MACkeylen-MD5dlen);
+
+	/* NTLM v2 */
+	n = ntv2_blob(blob, sizeof(blob), windom);
+        ds = hmac_t64(chal, len, v2hash, MD5dlen, nil, nil);
+	hmac_t64(blob, n, v2hash, MD5dlen, nt_hmac, ds);
+	ap->len[1] = MD5dlen+n;
+	ap->resp[1] = emalloc9p(ap->len[1]);
+	memcpy(ap->resp[1], nt_hmac, MD5dlen);
+	memcpy(ap->resp[1]+MD5dlen, blob, n);
+
+	/*
+	 * v2hash definitely OK by
+	 * the time we get here.
+	 */
+	/* NTLM v2 session key */
+	hmac_t64(nt_hmac, MD5dlen, v2hash, MD5dlen, nt_sesskey, nil);
+
+	/* NTLM v2 MAC key */
+	ap->mackey[1] = emalloc9p(MACkeylen);
+	memcpy(ap->mackey[1], nt_sesskey, MD5dlen);
+	memcpy(ap->mackey[1]+MD5dlen, ap->resp[1], MACkeylen-MD5dlen);
+	free(up);
+
+	return ap;
+}
+
+struct {
+	char	*name;
+	Auth	*(*func)(char *, char *, uchar *, int);
+} methods[] = {
+	{ "plain",	auth_plain },
+	{ "lm+ntlm",	auth_lm_and_ntlm },
+	{ "ntlm",	auth_ntlm },
+	{ "ntlmv2",	auth_ntlmv2 },
+//	{ "kerberos",	auth_kerberos },
+};
+
+void
+autherr(void)
+{
+	int i;
+
+	fprint(2, "supported auth methods:\t");
+	for(i = 0; i < nelem(methods); i++)
+		fprint(2, "%s ", methods[i].name);
+	fprint(2, "\n");
+	exits("usage");
+}
+
+Auth *
+getauth(char *name, char *windom, char *keyp, int secmode, uchar *chal, int len)
+{
+	int i;
+	Auth *ap;
+
+	if(name == nil){
+		name = DEF_AUTH;
+		if((secmode & SECMODE_PW_ENCRYPT) == 0)
+			sysfatal("plaintext authentication required, use '-a plain'");
+	}
+
+	ap = nil;
+	for(i = 0; i < nelem(methods); i++)
+		if(strcmp(methods[i].name, name) == 0){
+			ap = methods[i].func(windom, keyp, chal, len);
+			break;
+		}
+
+	if(! ap){
+		fprint(2, "%s: %s - unknown auth method\n", argv0, name);
+		autherr();	/* never returns */
+	}
+	return ap;
+}
+
+static int
+genmac(uchar *buf, int len, int seq, uchar key[MACkeylen], uchar ours[MAClen])
+{
+	DigestState *ds;
+	uchar *sig, digest[MD5dlen], theirs[MAClen];
+
+	sig = buf+MACoff;
+	memcpy(theirs, sig, MAClen);
+
+	memset(sig, 0, MAClen);
+	sig[0] = seq;
+	sig[1] = seq >> 8;
+	sig[2] = seq >> 16;
+	sig[3] = seq >> 24;
+
+	ds = md5(key, MACkeylen, nil, nil);
+	md5(buf, len, digest, ds);
+	memcpy(ours, digest, MAClen);
+
+	return memcmp(theirs, ours, MAClen);
+}
+
+int
+macsign(Pkt *p, int seq)
+{
+	int rc, len;
+	uchar *sig, *buf, mac[MAClen];
+
+	sig = p->buf + NBHDRLEN + MACoff;
+	buf = p->buf + NBHDRLEN;
+	len = (p->pos - p->buf) - NBHDRLEN;
+
+#ifdef DEBUG_MAC
+	if(seq & 1)
+		dmp("rx", seq, sig, MAClen);
+#endif
+	rc = 0;
+	if(! p->s->seqrun)
+		memcpy(mac, "BSRSPYL ", 8);	/* no idea, ask MS */
+	else
+		rc = genmac(buf, len, seq, p->s->auth->mackey[0], mac);
+#ifdef DEBUG_MAC
+	if(!(seq & 1))
+		dmp("tx", seq, mac, MAClen);
+#endif
+	memcpy(sig, mac, MAClen);
+	return rc;
+}

+ 804 - 0
sys/src/cmd/cifs/cifs.c

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

+ 635 - 0
sys/src/cmd/cifs/cifs.h

@@ -0,0 +1,635 @@
+/* cifs.h */
+
+enum {
+	Proot		= 1,		/* LSBs of qid.path for root dir */
+	Pinfo		= 2,		/* LSBs of qid.path for info files */
+	Pshare		= 4,		/* LSBs of qid.path for share dirs */
+	NBHDRLEN	= 4,		/* length of a netbios header */
+	T2HDRLEN	= 64,		/* Transaction2 header length */
+	NO_UID		= 0xffff,	/* initial UID on connect */
+	NO_TID		= 0xffff,	/* initial TID on connect */
+	MTU		= 0xefff,	/* our MTU */
+	CACHETIME	= 2,		/* seconds read-ahead is valid for */
+	CIFS_FNAME_MAX	= 0xff,		/* max file path component len */
+	OVERHEAD	= 80,		/* max packet overhead when reading & writing */
+	IDLE_TIME	= 10,		/* keepalive send rate in mins */
+	NBNSTOUT	= 300,		/* Netbios Name Service Timeout (300ms x 3retrys) */
+	NBRPCTOUT	= 45*60*1000,	/* Netbios RPC Timeout (45sec) */
+	MAX_SHARES	= 4096,		/* static table of shares attached */
+	RAP_ERR_MOREINFO= 234,		/* non-error code, more info to be fetched */
+	MAX_DFS_PATH	= 512,		/* MS says never more than 250 chars... */
+};
+
+enum {
+	SMB_COM_CREATE_DIRECTORY	= 0x00,
+	SMB_COM_DELETE_DIRECTORY	= 0x01,
+	SMB_COM_OPEN			= 0x02,
+	SMB_COM_CREATE			= 0x03,
+	SMB_COM_CLOSE			= 0x04,
+	SMB_COM_FLUSH			= 0x05,
+	SMB_COM_DELETE			= 0x06,
+	SMB_COM_RENAME			= 0x07,
+	SMB_COM_QUERY_INFORMATION	= 0x08,
+	SMB_COM_SET_INFORMATION		= 0x09,
+	SMB_COM_READ			= 0x0A,
+	SMB_COM_WRITE			= 0x0B,
+	SMB_COM_LOCK_BYTE_RANGE		= 0x0C,
+	SMB_COM_UNLOCK_BYTE_RANGE	= 0x0D,
+	SMB_COM_CREATE_TEMPORARY	= 0x0E,
+	SMB_COM_CREATE_NEW		= 0x0F,
+	SMB_COM_CHECK_DIRECTORY		= 0x10,
+	SMB_COM_PROCESS_EXIT		= 0x11,
+	SMB_COM_SEEK			= 0x12,
+	SMB_COM_LOCK_AND_READ		= 0x13,
+	SMB_COM_WRITE_AND_UNLOCK	= 0x14,
+	SMB_COM_READ_RAW		= 0x1A,
+	SMB_COM_READ_MPX		= 0x1B,
+	SMB_COM_READ_MPX_SECONDARY	= 0x1C,
+	SMB_COM_WRITE_RAW		= 0x1D,
+	SMB_COM_WRITE_MPX		= 0x1E,
+	SMB_COM_WRITE_MPX_SECONDARY	= 0x1F,
+	SMB_COM_WRITE_COMPLETE		= 0x20,
+	SMB_COM_QUERY_SERVER		= 0x21,
+	SMB_COM_SET_INFORMATION2	= 0x22,
+	SMB_COM_QUERY_INFORMATION2	= 0x23,
+	SMB_COM_LOCKING_ANDX		= 0x24,
+	SMB_COM_TRANSACTION		= 0x25,
+	SMB_COM_TRANSACTION_SECONDARY	= 0x26,
+	SMB_COM_IOCTL			= 0x27,
+	SMB_COM_IOCTL_SECONDARY		= 0x28,
+	SMB_COM_COPY			= 0x29,
+	SMB_COM_MOVE			= 0x2A,
+	SMB_COM_ECHO			= 0x2B,
+	SMB_COM_WRITE_AND_CLOSE		= 0x2C,
+	SMB_COM_OPEN_ANDX		= 0x2D,
+	SMB_COM_READ_ANDX		= 0x2E,
+	SMB_COM_WRITE_ANDX		= 0x2F,
+	SMB_COM_NEW_FILE_SIZE		= 0x30,
+	SMB_COM_CLOSE_AND_TREE_DISC	= 0x31,
+	SMB_COM_TRANSACTION2		= 0x32,
+	SMB_COM_TRANSACTION2_SECONDARY	= 0x33,
+	SMB_COM_FIND_CLOSE2		= 0x34,
+	SMB_COM_FIND_NOTIFY_CLOSE	= 0x35,
+	SMB_COM_TREE_CONNECT		= 0x70,
+	SMB_COM_TREE_DISCONNECT		= 0x71,
+	SMB_COM_NEGOTIATE		= 0x72,
+	SMB_COM_SESSION_SETUP_ANDX	= 0x73,
+	SMB_COM_LOGOFF_ANDX		= 0x74,
+	SMB_COM_TREE_CONNECT_ANDX	= 0x75,
+	SMB_COM_QUERY_INFORMATION_DISK	= 0x80,
+	SMB_COM_SEARCH			= 0x81,
+	SMB_COM_FIND			= 0x82,
+	SMB_COM_FIND_UNIQUE		= 0x83,
+	SMB_COM_FIND_CLOSE		= 0x84,
+	SMB_COM_NT_TRANSACT		= 0xA0,
+	SMB_COM_NT_TRANSACT_SECONDARY	= 0xA1,
+	SMB_COM_NT_CREATE_ANDX		= 0xA2,
+	SMB_COM_NT_CANCEL		= 0xA4,
+	SMB_COM_NT_RENAME		= 0xA5,
+	SMB_COM_OPEN_PRINT_FILE		= 0xC0,
+	SMB_COM_WRITE_PRINT_FILE	= 0xC1,
+	SMB_COM_CLOSE_PRINT_FILE	= 0xC2,
+	SMB_COM_GET_PRINT_QUEUE		= 0xC3,
+	SMB_COM_READ_BULK		= 0xD8,
+	SMB_COM_WRITE_BULK		= 0xD9,
+	SMB_COM_WRITE_BULK_DATA		= 0xDA,
+
+	TRANS2_OPEN2			= 0x00,
+	TRANS2_FIND_FIRST2		= 0x01,
+	TRANS2_FIND_NEXT2		= 0x02,
+	TRANS2_QUERY_FS_INFORMATION	= 0x03,
+	TRANS2_QUERY_PATH_INFORMATION	= 0x05,
+	TRANS2_SET_PATH_INFORMATION	= 0x06,
+	TRANS2_QUERY_FILE_INFORMATION	= 0x07,
+	TRANS2_SET_FILE_INFORMATION	= 0x08,
+	TRANS2_CREATE_DIRECTORY 	= 0x0D,
+	TRANS2_SESSION_SETUP		= 0x0E,
+	TRANS2_GET_DFS_REFERRAL		= 0x10,
+
+	NT_TRANSACT_CREATE 		= 0x01,
+	NT_TRANSACT_IOCTL 		= 0x02,
+	NT_TRANSACT_SET_SECURITY_DESC 	= 0x03,
+	NT_TRANSACT_NOTIFY_CHANGE 	= 0x04,
+	NT_TRANSACT_RENAME 		= 0x05,
+	NT_TRANSACT_QUERY_SECURITY_DESC = 0x06
+};
+
+enum {						/* CIFS flags */
+	FL_CASELESS_NAMES	= 1<<3,
+	FL_CANNONICAL_NAMES	= 1<<4,
+
+	FL2_KNOWS_LONG_NAMES	= 1<<0,
+	FL2_PACKET_SIGNATURES	= 1<<2,
+	FL2_HAS_LONG_NAMES	= 1<<6,
+	FL2_EXTENDED_SECURITY	= 1<<11,
+	FL2_DFS			= 1<<12,
+	FL2_PAGEING_IO		= 1<<13,	/* allow read of exec only files */
+	FL2_NT_ERRCODES		= 1<<14,
+	FL2_UNICODE		= 1<<15,
+};
+
+enum {						/* Capabilities Negoiated */
+	CAP_RAW_MODE		= 1,
+	CAP_MPX_MODE		= 1<<1,
+	CAP_UNICODE		= 1<<2,
+	CAP_LARGE_FILES		= 1<<3,		/* 64 bit files */
+	CAP_NT_SMBS		= 1<<4,
+	CAP_RPC_REMOTE_APIS	= 1<<5,
+	CAP_STATUS32		= 1<<6,
+	CAP_L2_OPLOCKS		= 1<<7,
+	CAP_LOCK_READ		= 1<<8,
+	CAP_NT_FIND		= 1<<9,
+	CAP_DFS			= 1<<12,
+	CAP_INFO_PASSTHRU	= 1<<13,
+	CAP_LARGE_READX		= 1<<14,
+	CAP_LARGE_WRITEX	= 1<<15,
+	CAP_UNIX		= 1<<23,
+	CAP_BULK_TRANSFER	= 1<<29,
+	CAP_COMPRESSED		= 1<<30,
+	CAP_EX_SECURE		= 1<<31
+};
+
+enum {	/* string prefixes */
+	STR_DIALECT 		= 2,
+	STR_PATH 		= 3,
+	STR_ASCII 		= 4,
+};
+
+enum {	/* optional support bits in treeconnect */
+	SMB_SUPPROT_SEARCH_BITS = 1,
+	SMB_SHARE_IS_IN_DFS 	= 2,
+};
+
+enum {	/* DFS referal header flags */
+	DFS_HEADER_ROOT	 	= 1,	/* Server type, returns root targets */
+	DFS_HEADER_STORAGE 	= 2,	/* server has storage, no more referals */
+	DFS_HEADER_FAILBACK 	= 4,	/* target failback enabled */
+};
+
+enum {	/* DFS referal entry flags */
+	DFS_SERVER_ROOT	 	= 1,	/* Server type, returns root targets */
+	DFS_REFERAL_LIST 	= 0x200,	/* reply is a list (v3 only) */
+	DFS_REFERAL_SET 	= 0x400,	/* target is a member of a set */
+};
+
+enum {	/* share types */
+	STYPE_DISKTREE		= 0,
+	STYPE_PRINTQ		= 1,
+	STYPE_DEVICE		= 2,
+	STYPE_IPC		= 3,
+	STYPE_SPECIAL		= 4,
+	STYPE_TEMP		= 5,
+};
+
+enum {	/* Security */
+	SECMODE_USER		= 0x01,	/* i.e. not share level security */
+	SECMODE_PW_ENCRYPT	= 0x02,
+	SECMODE_SIGN_ENABLED	= 0x04,
+	SECMODE_SIGN_REQUIRED	= 0x08,
+};
+
+enum {	/* file access rights */
+	DELETE			= 0x00010000,
+	SYNCHRONIZE		= 0x00100000,
+
+	READ_CONTROL		= 0x00020000,
+	GENERIC_ALL		= 0x10000000,
+	GENERIC_EXECUTE		= 0x20000000,
+	GENERIC_WRITE		= 0x40000000,
+	GENERIC_READ		= 0x80000000,
+
+	ATTR_READONLY 		= 0x0001,
+	ATTR_HIDDEN   		= 0x0002,
+	ATTR_SYSTEM   		= 0x0004,
+	ATTR_VOLUME   		= 0x0008,
+	ATTR_DIRECTORY		= 0x0010,
+	ATTR_ARCHIVE  		= 0x0020,
+	ATTR_DEVICE   		= 0x0040,
+	ATTR_NORMAL   		= 0x0080,
+	ATTR_TEMPORARY		= 0x0100,
+	ATTR_SPARSE   		= 0x0200,
+	ATTR_REPARSE  		= 0x0400,
+	ATTR_COMPRESSED		= 0x0800,
+	ATTR_OFFLINE   		= 0x100,	/* offline storage */
+	ATTR_NOT_CONTENT_INDEXED= 0x2000,
+	ATTR_ENCRYPTED 		= 0x4000,
+	ATTR_POSIX_SEMANTICS	= 0x01000000,
+	ATTR_BACKUP_SEMANTICS	= 0x02000000,
+	ATTR_DELETE_ON_CLOSE	= 0x04000000,
+	ATTR_SEQUENTIAL_SCAN	= 0x08000000,
+	ATTR_RANDOM_ACCESS  	= 0x10000000,
+	ATTR_NO_BUFFERING   	= 0x20000000,
+	ATTR_WRITE_THROUGH  	= 0x80000000,
+
+	/* ShareAccess flags */
+	FILE_NO_SHARE    	= 0,
+	FILE_SHARE_READ  	= 1,
+	FILE_SHARE_WRITE 	= 2,
+	FILE_SHARE_DELETE	= 4,
+	FILE_SHARE_ALL   	= 7,
+
+	/* CreateDisposition flags */
+	FILE_SUPERSEDE   	= 0,
+	FILE_OPEN		= 1,
+	FILE_CREATE		= 2,
+	FILE_OPEN_IF		= 3,
+	FILE_OVERWRITE		= 4,
+	FILE_OVERWRITE_IF	= 5,
+
+	/* CreateOptions */
+	FILE_DIRECTORY_FILE		= 0x00000001,
+	FILE_WRITE_THROUGH		= 0x00000002,
+	FILE_SEQUENTIAL_ONLY		= 0x00000004,
+	FILE_NO_INTERMEDIATE_BUFFERING	= 0x00000008,
+	FILE_SYNCHRONOUS_IO_ALERT	= 0x00000010,
+	FILE_SYNCHRONOUS_IO_NONALERT	= 0x00000020,
+	FILE_NON_DIRECTORY_FILE		= 0x00000040,
+	FILE_CREATE_TREE_CONNECTION	= 0x00000080,
+	FILE_COMPLETE_IF_OPLOCKED	= 0x00000100,
+	FILE_NO_EA_KNOWLEDGE		= 0x00000200,
+	FILE_OPEN_FOR_RECOVERY		= 0x00000400,
+	FILE_EIGHT_DOT_THREE_ONLY	= 0x00000400,	/* samba source says so... */
+	FILE_RANDOM_ACCESS		= 0x00000800,
+	FILE_DELETE_ON_CLOSE		= 0x00001000,
+	FILE_OPEN_BY_FILE_ID		= 0x00002000,
+	FILE_OPEN_FOR_BACKUP_INTENT	= 0x00004000,
+	FILE_NO_COMPRESSION		= 0x00008000,
+
+	/* open/create result codes */
+	FILE_WAS_OPENED			= 1,
+	FILE_WAS_CREATED		= 2,
+	FILE_WAS_OVERWRITTEN		= 3,
+
+	/* ImpersonationLevel flags */
+	SECURITY_ANONYMOUS     		= 0,
+	SECURITY_IDENTIFICATION		= 1,
+	SECURITY_IMPERSONATION		= 2,
+	SECURITY_DELEGATION		= 3,
+
+	/* SecurityFlags */
+	SECURITY_CONTEXT_TRACKING 	= 1,
+	SECURITY_EFFECTIVE_ONLY		= 2,
+
+	/* security descriptor bitmask */
+	QUERY_OWNER_SECURITY_INFORMATION = 1,
+	QUERY_GROUP_SECURITY_INFORMATION = 2,
+	QUERY_DACL_SECURITY_INFORMATION = 4,
+	QUERY_SACL_SECURITY_INFORMATION = 8,
+
+};
+
+enum {	/* PathInfo/FileInfo infolevels */
+	SMB_INFO_STANDARD              	= 0x1,
+	SMB_INFO_IS_NAME_VALID         	= 0x6,
+	SMB_QUERY_FILE_BASIC_INFO      	= 0x101,
+	SMB_QUERY_FILE_STANDARD_INFO   	= 0x102,
+	SMB_QUERY_FILE_NAME_INFO       	= 0x104,
+	SMB_QUERY_FILE_ALLOCATION_INFO 	= 0x105,
+	SMB_QUERY_FILE_END_OF_FILE_INFO = 0x106,
+	SMB_QUERY_FILE_ALL_INFO        	= 0x107,
+	SMB_QUERY_ALT_NAME_INFO        	= 0x108,
+	SMB_QUERY_FILE_STREAM_INFO     	= 0x109,
+	SMB_QUERY_FILE_COMPRESSION_INFO	= 0x10b,
+	SMB_QUERY_FILE_UNIX_BASIC      	= 0x200,
+	SMB_QUERY_FILE_UNIX_LINK       	= 0x201,
+
+	SMB_SET_FILE_BASIC_INFO	       	= 0x101,
+	SMB_SET_FILE_DISPOSITION_INFO  	= 0x102,
+	SMB_SET_FILE_ALLOCATION_INFO   	= 0x103,
+	SMB_SET_FILE_END_OF_FILE_INFO  	= 0x104,
+	SMB_SET_FILE_UNIX_BASIC        	= 0x200,
+	SMB_SET_FILE_UNIX_LINK         	= 0x201,
+	SMB_SET_FILE_UNIX_HLINK        	= 0x203,
+	SMB_SET_FILE_BASIC_INFO2       	= 0x3ec,
+	SMB_SET_FILE_RENAME_INFORMATION	= 0x3f2,
+	SMB_SET_FILE_ALLOCATION_INFO2  	= 0x3fb,
+	SMB_SET_FILE_END_OF_FILE_INFO2 	= 0x3fc,
+
+	/* Find File infolevels */
+	SMB_FIND_FILE_DIRECTORY_INFO	= 0x101,
+	SMB_FIND_FILE_FULL_DIRECTORY_INFO= 0x102,
+	SMB_FIND_FILE_NAMES_INFO	= 0x103,
+	SMB_FIND_FILE_BOTH_DIRECTORY_INFO= 0x104,
+	SMB_FIND_FILE_UNIX		= 0x202,
+
+	/* Trans2 FindFirst & FindNext */
+	CIFS_SEARCH_CLOSE_ALWAYS	= 0x0001,
+	CIFS_SEARCH_CLOSE_AT_END	= 0x0002,
+	CIFS_SEARCH_RETURN_RESUME	= 0x0004,
+	CIFS_SEARCH_CONTINUE_FROM_LAST	= 0x0008,
+	CIFS_SEARCH_BACKUP_SEARCH	= 0x0010,
+
+	/* Trans2 FsInfo */
+	SMB_INFO_ALLOCATION		= 0x1,
+	SMB_INFO_VOLUME			= 0x2,
+	SMB_QUERY_FS_VOLUME_INFO	= 0x102,
+	SMB_QUERY_FS_SIZE_INFO		= 0x103,
+	SMB_QUERY_FS_DEVICE_INFO	= 0x104,
+	SMB_QUERY_FS_ATTRIBUTE_INFO	= 0x105,
+	SMB_QUERY_CIFS_UNIX_INFO	= 0x200,
+};
+
+enum {	/* things to search for in server lookups */
+	LOCAL_AUTHORATIVE_ONLY	= 0x40000000,
+	LIST_DOMAINS_ONLY	= 0x80000000,
+	ALL_LEARNT_IN_DOMAIN	= 0xFFFFFFFF
+};
+
+typedef struct {
+	char	*user;		/* username */
+	char	*windom;	/* remote server's domain name */
+	char	*resp[2];	/* ASCII/Unicode or LM/NTLM keys */
+	int	len[2];		/* length of above */
+	uchar	*mackey[2];	/* Message Authentication key */
+} Auth;
+
+typedef struct {
+	int	fd;		/* File descriptor for I/O  */
+	int	nbt;		/* am using cifs over netbios */
+	int	trn;		/* TRN (unique RPC) ID  */
+	int	uid;		/* user (authentication) ID  */
+	int	seq;		/* sequence number */
+	int	seqrun;		/* sequence numbering active */
+	int	caps;		/* server capabilities */
+	int	support;	/* support bits */
+	int	flags;		/* SMB flags  */
+	int	flags2;		/* SMB flags 2  */
+	int	nocache;	/* disable write behind caching in server */
+	int	pid;		/* process ID  */
+	int	mid;		/* multiplex ID */
+	int	mtu;		/* max size of packet  */
+	int	tz;		/* Timezone, mins from UTC  */
+	int	isguest;	/* logged in as guest */
+	int	secmode;	/* security mode  */
+	int	macidx;		/* which MAC is in use, -1 is none */
+	uchar	chal[0xff +1];	/* server's challange for authentication  */
+	int	challen;	/* length of challange */
+	long	slip;		/* time difference between the server and us */
+	uvlong	lastfind;	/* nsec when last find peformed */
+	QLock	seqlock;	/* sequence number increment */
+	QLock	rpclock;	/* actual remote procedure call */
+	char	*cname;		/* remote hosts called name (for info) */
+	char	*remos;		/* remote hosts OS (for info) */
+	Auth	*auth;		/* authentication info */
+} Session;
+
+typedef struct {
+	Session *s;
+
+	int tid;		/* tree ID received from server */
+	int seq;		/* sequence number expected in reply */
+
+	uchar *seqbase; 	/* cifs: pos of sequence number in packet */
+	uchar *wordbase; 	/* cifs: base of words section of data  */
+	uchar *bytebase; 	/* cifs: base of bytes section of data  */
+	uchar *tbase;		/* transactions: start of trans packet */
+	uchar *tsetup;		/* transactions: start of setup section */
+	uchar *tparam; 		/* transactions: start of params section */
+	uchar *tdata; 		/* transactions: start of data section */
+
+	uchar *eop;		/* received end of packet */
+	uchar *pos;		/* current pos in packet  */
+	uchar *buf;		/* packet buffer, must be last entry in struct  */
+} Pkt;
+
+typedef struct {
+	char	*name;
+	int	tid;		/* not part of the protocol, housekeeping */
+	int	options;	/* not part of the protocol, housekeeping */
+} Share;
+
+typedef struct {
+	long	created;	/* last access time */
+	long	accessed;	/* last access time */
+	long	written;	/* last written time */
+	long	changed;	/* change time */
+	uvlong	size;		/* file size */
+	long	attribs;	/* attributes */
+	char	name[CIFS_FNAME_MAX +1]; /* name */
+} FInfo;
+
+typedef struct {
+	char	*wrkstn;
+	char	*user;
+	long	sesstime;
+	long	idletime;
+} Sessinfo;
+
+typedef struct {
+	char	*name;
+} Namelist;
+
+typedef struct {
+	char	*user;
+	char	*comment;
+	char	*user_comment;
+	char	*fullname;
+} Userinfo;
+
+typedef struct {
+	char	*name;
+	int	type;
+	char	*comment;
+	int	perms;
+	int	maxusrs;
+	int	activeusrs;
+	char	*path;
+	char	*passwd;
+} Shareinfo2;
+
+typedef struct {
+	char	*name;
+	int	major;
+	int	minor;
+	int	type;
+	char	*comment;
+} Serverinfo;
+
+typedef struct {
+	int	type;	/* o=unknown, 1=CIFS, 2=netware 3=domain */
+	int	flags;	/* 1 == strip off consumed chars before resubmitting */
+	int	ttl;	/* time to live of this info in secs */
+	int	prox;	/* lower value is preferred */
+	char	*path;	/* new path */
+	char	*addr;	/* new server */
+} Refer;
+
+typedef struct {
+	char	*node;
+	char	*user;
+	char	*langroup;
+	int	major;
+	int	minor;
+	char	*pridom;
+	char	*otherdoms;
+} Wrkstainfo;
+
+typedef struct {
+	int	ident;
+	int	perms;
+	int	locks;
+	char	*path;
+	char	*user;
+} Fileinfo;
+
+extern int Dfstout;
+extern char *Debug;
+extern Share Ipc;
+extern Session *Sess;
+extern int Active;
+
+Share Shares[MAX_SHARES];
+int Nshares;
+
+/* main.c */
+Qid	mkqid(char *, int, long, int, long);
+
+/* auth.c */
+Auth	*getauth(char *, char *, char *, int, uchar *, int);
+void	autherr(void);
+int	macsign(Pkt *, int);
+
+/* cifs.c */
+Session	*cifsdial(char *, char *, char *);
+void	cifsclose(Session *);
+Pkt	*cifshdr(Session *, Share *, int);
+void	pbytes(Pkt *);
+int	cifsrpc(Pkt *);
+int	CIFSnegotiate(Session *, long *, char *, int, char *, int);
+int	CIFSsession(Session *);
+int	CIFStreeconnect(Session *, char *, char *, Share *);
+int	CIFSlogoff(Session *);
+int	CIFStreedisconnect(Session *, Share *);
+int	CIFSdeletefile(Session *, Share *, char *);
+int	CIFSdeletedirectory(Session *, Share *, char *);
+int	CIFScreatedirectory(Session *, Share *, char *);
+int	CIFSrename(Session *, Share *, char *, char *);
+int	CIFS_NT_opencreate(Session *, Share *, char *, int, int, int, int, int, int, int *, FInfo *);
+int	CIFS_SMB_opencreate(Session *, Share *, char *, int, int, int, int *);
+vlong	CIFSwrite(Session *, Share *, int, uvlong, void *, vlong);
+vlong	CIFSread(Session *, Share *, int, uvlong, void *, vlong, vlong);
+int	CIFSflush(Session *, Share *, int);
+int	CIFSclose(Session *, Share *, int);
+int	CIFSfindclose2(Session *, Share *, int);
+int	CIFSecho(Session *);
+int	CIFSsetinfo(Session *, Share *, char *, FInfo *);
+void	goff(Pkt *, uchar *, char *, int);
+
+/* dfs.c */
+char	*mapfile(char *);
+int	mapshare(char *, Share **);
+int	redirect(Session *, Share *s, char *);
+int	dfscacheinfo(Fmt *);
+
+/* doserrstr.c */
+char	*doserrstr(uint);
+
+/* fs.c */
+int	shareinfo(Fmt *);
+int	conninfo(Fmt *);
+int	sessioninfo(Fmt *);
+int	userinfo(Fmt *);
+int	groupinfo(Fmt *);
+int	domaininfo(Fmt *);
+int	workstationinfo(Fmt *);
+int	dfsrootinfo(Fmt *);
+int	openfileinfo(Fmt *);
+int	dfsrootinfo(Fmt *);
+int	filetableinfo(Fmt *); 	/* is in main.c due to C scope */
+
+/* info.c */
+int	walkinfo(char *);
+int	numinfo(void);
+int	dirgeninfo(int, Dir *);
+int	makeinfo(int);
+int	readinfo(int, char *, int, int);
+void	freeinfo(int);
+
+/* main.c */
+void	usage(void);
+void	dmpkey(char *, void *, int);
+void	main(int, char **);
+
+/* misc.c */
+char	*strupr(char *);
+char	*strlwr(char *);
+
+/* netbios.c */
+void	Gmem(uchar **, void *, int);
+int	calledname(char *, char *);
+int	nbtdial(char *, char *, char *);
+void	nbthdr(Pkt *);
+int	nbtrpc(Pkt *);
+void	xd(char *, void *, int);
+
+/* nterrstr.c */
+char	*nterrstr(uint);
+
+/* pack.c */
+void	*pmem(Pkt *, void *, int);
+void	*ppath(Pkt *, char *);
+void	*pstr(Pkt *, char *);
+void	*pascii(Pkt *, char *);
+void	*pl64(Pkt *, uvlong);
+void	*pb32(Pkt *, uint);
+void	*pl32(Pkt *, uint);
+void	*pb16(Pkt *, uint);
+void	*pl16(Pkt *, uint);
+void	*p8(Pkt *, uint);
+void	*pname(Pkt *, char *, char);
+void	*pvtime(Pkt *, uvlong);
+void	*pdatetime(Pkt *, long);
+void	gmem(Pkt *, void *, int);
+void	gstr(Pkt *, char *, int);
+void	gascii(Pkt *, char *, int);
+uvlong	gl64(Pkt *);
+uvlong	gb48(Pkt *);
+uint	gb32(Pkt *);
+uint	gl32(Pkt *);
+uint	gb16(Pkt *);
+uint	gl16(Pkt *);
+uint	g8(Pkt *);
+long	gdatetime(Pkt *);
+long	gvtime(Pkt *);
+void	gconv(Pkt *, int, char *, int);
+
+/* raperrstr.c */
+char	*raperrstr(uint);
+
+/* sid2name.c */
+void	upd_names(Session *, Share *, char *, Dir *);
+
+/* trans.c */
+int	RAPshareenum(Session *, Share *, Share **);
+int	RAPshareinfo(Session *, Share *, char *, Shareinfo2 *);
+
+int	RAPsessionenum(Session *, Share *, Sessinfo **);
+
+int	RAPgroupenum(Session *, Share *, Namelist **);
+int	RAPgroupusers(Session *, Share *, char *, Namelist **);
+
+int	RAPuserenum(Session *, Share *, Namelist **);
+int	RAPuserenum2(Session *, Share *, Namelist **);
+int	RAPuserinfo(Session *, Share *, char *, Userinfo *);
+
+int	RAPServerenum2(Session *, Share *, char *, int, int *, Serverinfo **);
+int	RAPServerenum3(Session *, Share *, char *, int, int, Serverinfo *);
+
+int	RAPFileenum2(Session *, Share *, char *, char *, Fileinfo **);
+
+/* trans2.c */
+int	T2findfirst(Session *, Share *, int, char *, int *, long *, FInfo *);
+int	T2findnext(Session *, Share *, int, char *, int *, long *, FInfo *, int);
+int	T2queryall(Session *, Share *, char *, FInfo *);
+int	T2querystandard(Session *, Share *, char *, FInfo *);
+int	T2setpathinfo(Session *, Share *, char *, FInfo *);
+int	T2setfilelength(Session *, Share *, int, FInfo *);
+int	T2fsvolumeinfo(Session *, Share *, long *, long *, char *, int);
+int	T2fssizeinfo(Session *, Share *, uvlong *, uvlong *);
+int	T2getdfsreferral(Session *, Share *, char *, int *, int *, Refer *, int);
+
+/* transnt.c */
+int	TNTquerysecurity(Session *, Share *, int, char **, char **);
+
+/* ping.c */
+int	ping(char *, int);

+ 387 - 0
sys/src/cmd/cifs/dfs.c

@@ -0,0 +1,387 @@
+/*
+ * DNS referrals give two main fields: the path to connect to in
+ * /Netbios-host-name/share-name/path... form and a network
+ * address of how to find this path of the form domain.dom.
+ *
+ * The domain.dom is resolved in XP/Win2k etc using AD to do
+ * a lookup (this is a consensus view, I don't think anyone
+ * has proved it).  I cannot do this as AD needs Kerberos and
+ * LDAP which I don't have.
+ *
+ * Instead I just use the NetBios names passed in the paths
+ * and assume that the servers are in the same DNS domain as me
+ * and have their DNS hostname set the same as their netbios
+ * called-name; thankfully this always seems to be the case (so far).
+ *
+ * I have not added support for starting another instance of
+ * cifs to connect to other servers referenced in DFS links,
+ * this is not a problem for me and I think it hides a load
+ * of problems of its own wrt plan9's private namespaces.
+ *
+ * The proximity of my test server (AD enabled) is always 0 but some
+ * systems may report more meaningful values.  The expiry time is
+ * similarly zero, so I guess at 5 mins.
+ *
+ * If the redirection points to a "hidden" share (i.e., its name
+ * ends in a $) then the type of the redirection is 0 (unknown) even
+ * though it is a CIFS share.
+ *
+ * It would be nice to add a check for which subnet a server is on
+ * so our first choice is always the the server on the same subnet
+ * as us which replies to a ping (i.e., is up).  This could short-
+ * circuit the tests as the a server on the same subnet will always
+ * be the fastest to get to.
+ *
+ * If I set Flags2_DFS then I don't see DFS links, I just get
+ * path not found (?!).
+ *
+ * If I do a QueryFileInfo of a DFS link point (IE when I'am doing a walk)
+ * Then I just see a directory, its not until I try to walk another level
+ * That I get  "IO reparse tag not handled" error rather than
+ * "Path not covered".
+ *
+ * If I check the extended attributes of the QueryFileInfo in walk() then I can
+ * see this is a reparse point and so I can get the referral.  The only
+ * problem here is that samba and the like may not support this.
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <libsec.h>
+#include <ctype.h>
+#include <9p.h>
+#include "cifs.h"
+
+#define SINT_MAX	0x7fffffff
+
+typedef struct Dfscache Dfscache;
+struct Dfscache {
+	Dfscache*next;		/* next entry */
+	char	*src;
+	char	*host;
+	char	*share;
+	char	*path;
+	long	expiry;		/* expiry time in sec */
+	long	rtt;		/* round trip time, nsec */
+	int	prox;		/* proximity, lower = closer */
+};
+
+Dfscache *Cache;
+
+int
+dfscacheinfo(Fmt *f)
+{
+	long ex;
+	Dfscache *cp;
+
+	for(cp = Cache; cp; cp = cp->next){
+		ex = cp->expiry - time(nil);
+		if(ex < 0)
+			ex = -1;
+		fmtprint(f, "%-42s %6ld %8.1f %4d %-16s %-24s %s\n",
+			cp->src, ex, (double)cp->rtt/1000.0L, cp->prox,
+			cp->host, cp->share, cp->path);
+	}
+	return 0;
+}
+
+static char *
+trimshare(char *s)
+{
+	char *p;
+	static char name[128];
+
+	strncpy(name, s, sizeof(name));
+	name[sizeof(name)-1] = 0;
+	if((p = strrchr(name, '$')) != nil && p[1] == 0)
+		*p = 0;
+	return name;
+}
+
+static Dfscache *
+lookup(char *opath, int *exact)
+{
+	char *path;
+	int len, n, m;
+	Dfscache *cp, *best;
+
+	*exact = 0;
+
+	len = 0;
+	best = nil;
+	path = opath;
+	m = strlen(opath);
+	for(cp = Cache; cp; cp = cp->next){
+		n = strlen(cp->src);
+		if(n < len || cistrncmp(path, cp->src, n) != 0 ||
+		    path[n] != 0 && path[n] != '/')
+			continue;
+		best = cp;
+		len = n;
+		if(n == m){
+			*exact = 1;
+			break;
+		}
+	}
+	return best;
+}
+
+char *
+mapfile(char *opath)
+{
+	int exact;
+	char *p, *path;
+	Dfscache *cp;
+	static char npath[MAX_DFS_PATH];
+
+	path = opath;
+	if((cp = lookup(path, &exact)) != nil){
+		snprint(npath, sizeof npath, "/%s%s%s%s", cp->share,
+			*cp->path? "/": "", cp->path, path + strlen(cp->src));
+		path = npath;
+	}
+
+	if((p = strchr(path+1, '/')) == nil)
+		p = "/";
+	if(Debug && strstr(Debug, "dfs") != nil)
+		print("mapfile src=%q => dst=%q\n", opath, p);
+	return p;
+}
+
+int
+mapshare(char *path, Share **osp)
+{
+	int i, exact;
+	char *try, *tail[] = { "", "$" };
+	Dfscache *cp;
+	Share *sp;
+
+	if((cp = lookup(path, &exact)) == nil)
+		return 0;
+
+	for(sp = Shares; sp < Shares+Nshares; sp++)
+		if(cistrcmp(cp->share, trimshare(sp->name)) == 0){
+			if(Debug && strstr(Debug, "dfs") != nil)
+				print("mapshare, already connected, src=%q => dst=%q\n",
+					path, sp->name);
+			*osp = sp;
+			return 0;
+		}
+	/*
+	 * Try to autoconnect to share if it is not known.  Note even if you
+	 * didn't specify any shares and let the system autoconnect you may
+	 * not already have the share you need as RAP (which we use) throws
+	 * away names > 12 chars long.  If we where to use RPC then this block
+	 * of code would be less important, though it would still be useful
+	 * to catch Shares added since cifs(1) was started.
+	 */
+	sp = Shares + Nshares;
+	for(i = 0; i < 2; i++){
+		try = smprint("%s%s", cp->share, tail[i]);
+		if(CIFStreeconnect(Sess, Sess->cname, try, sp) == 0){
+			sp->name = try;
+			*osp = sp;
+			Nshares++;
+			if(Debug && strstr(Debug, "dfs") != nil)
+				print("mapshare connected, src=%q dst=%q\n",
+					path, cp->share);
+			return 0;
+		}
+		free(try);
+	}
+
+	if(Debug && strstr(Debug, "dfs") != nil)
+		print("mapshare failed src=%s\n", path);
+	return -1;
+}
+
+/*
+ * Rtt_tol is the fractional tollerance for RTT comparisons.
+ * If a later (further down the list) host's RTT is less than
+ * 1/Rtt_tol better than my current best then I don't bother
+ * with it.  This biases me towards entries at the top of the list
+ * which Active Directory has already chosen for me and prevents
+ * noise in RTTs from pushing me to more distant machines.
+ */
+static int
+remap(Dfscache *cp, Refer *re)
+{
+	int n;
+	long rtt;
+	char *p, *a[4];
+	enum {
+		Host = 1,
+		Shre = 2,
+		Path = 3,
+		Rtt_tol = 10,
+	};
+
+	if(Debug && strstr(Debug, "dfs") != nil)
+		print("	remap %s\n", re->addr);
+
+	for(p = re->addr; *p; p++)
+		if(*p == '\\')
+			*p = '/';
+
+	if(cp->prox < re->prox){
+		if(Debug && strstr(Debug, "dfs") != nil)
+			print("	remap %d < %d\n", cp->prox, re->prox);
+		return -1;
+	}
+	if((n = getfields(re->addr, a, sizeof(a), 0, "/")) < 3){
+		if(Debug && strstr(Debug, "dfs") != nil)
+			print("	remap nfields=%d\n", n);
+		return -1;
+	}
+	if((rtt = ping(a[Host], Dfstout)) == -1){
+		if(Debug && strstr(Debug, "dfs") != nil)
+			print("	remap ping failed\n");
+		return -1;
+	}
+	if(cp->rtt < rtt && (rtt/labs(rtt-cp->rtt)) < Rtt_tol){
+		if(Debug && strstr(Debug, "dfs") != nil)
+			print("	remap bad ping %ld < %ld && %ld < %d\n",
+				cp->rtt, rtt, (rtt/labs(rtt-cp->rtt)), Rtt_tol);
+		return -1;
+	}
+
+	if(n < 4)
+		a[Path] = "";
+	if(re->ttl == 0)
+		re->ttl = 60*5;
+
+	free(cp->host);
+	free(cp->share);
+	free(cp->path);
+	cp->rtt = rtt;
+	cp->prox = re->prox;
+	cp->expiry = time(nil)+re->ttl;
+	cp->host = estrdup9p(a[Host]);
+	cp->share = estrdup9p(trimshare(a[Shre]));
+	cp->path = estrdup9p(a[Path]);
+	if(Debug && strstr(Debug, "dfs") != nil)
+		print("	remap ping OK prox=%d host=%s share=%s path=%s\n",
+			cp->prox, cp->host, cp->share, cp->path);
+	return 0;
+}
+
+static int
+redir1(Session *s, char *path, Dfscache *cp, int level)
+{
+	Refer retab[16], *re;
+	int n, gflags, used, found;
+
+	if(level > 8)
+		return -1;
+
+	if((n = T2getdfsreferral(s, &Ipc, path, &gflags, &used, retab,
+	    nelem(retab))) == -1)
+		return -1;
+
+	if(! (gflags & DFS_HEADER_ROOT))
+		used = SINT_MAX;
+
+	found = 0;
+	for(re = retab; re < retab+n; re++){
+		if(Debug && strstr(Debug, "dfs") != nil)
+			print("referal level=%d prox=%d path=%q addr=%q\n",
+				level, re->prox, re->path, re->addr);
+
+		if(gflags & DFS_HEADER_STORAGE){
+			if(remap(cp, re) == 0)
+				found = 1;
+		} else{
+			if(redir1(s, re->addr, cp, level+1) != -1)  /* ???? */
+				found = 1;
+		}
+		free(re->addr);
+		free(re->path);
+	}
+
+	if(Debug && strstr(Debug, "dfs") != nil)
+		print("referal level=%d path=%q found=%d used=%d\n",
+			level, path, found, used);
+	if(!found)
+		return -1;
+	return used;
+}
+
+/*
+ * We can afford to ignore the used count returned by redir
+ * because of the semantics of 9p - we always walk to directories
+ * ome and we a time and we always walk before any other file operations
+ */
+int
+redirect(Session *s, Share *sp, char *path)
+{
+	int exact;
+	char *unc;
+	Dfscache *cp;
+
+	if(Debug && strstr(Debug, "dfs") != nil)
+		print("redirect name=%q path=%q\n", sp->name, path);
+
+	cp = lookup(path, &exact);
+	if(cp && exact){
+		if(cp->expiry >= time(nil)){	/* cache hit */
+			if(Debug && strstr(Debug, "dfs") != nil)
+				print("redirect cache=hit src=%q => share=%q path=%q\n",
+					cp->src, cp->share, cp->path);
+			return 0;
+
+		} else{				/* cache hit, but entry stale */
+			cp->rtt = SINT_MAX;
+			cp->prox = SINT_MAX;
+
+			unc = smprint("//%s/%s/%s%s%s", s->auth->windom,
+				cp->share, cp->path, *cp->path? "/": "",
+				path + strlen(cp->src) + 1);
+			if(unc == nil)
+				sysfatal("no memory: %r");
+			if(redir1(s, unc, cp, 1) == -1){
+				if(Debug && strstr(Debug, "dfs") != nil)
+					print("redirect refresh failed unc=%q\n",
+						unc);
+				free(unc);
+				return -1;
+			}
+			free(unc);
+			if(Debug && strstr(Debug, "dfs") != nil)
+				print("redirect refresh cache=stale src=%q => share=%q path=%q\n",
+					cp->src, cp->share, cp->path);
+			return 0;
+		}
+	}
+
+	/* in-exact match or complete miss */
+	if(cp)
+		unc = smprint("//%s/%s/%s%s%s", s->auth->windom, cp->share,
+			cp->path, *cp->path? "/": "", path + strlen(cp->src) + 1);
+	else
+		unc = smprint("//%s%s", s->auth->windom, path);
+	if(unc == nil)
+		sysfatal("no memory: %r");
+
+	cp = emalloc9p(sizeof(Dfscache));
+	memset(cp, 0, sizeof(Dfscache));
+	cp->rtt = SINT_MAX;
+	cp->prox = SINT_MAX;
+
+	if(redir1(s, unc, cp, 1) == -1){
+		if(Debug && strstr(Debug, "dfs") != nil)
+			print("redirect new failed unc=%q\n", unc);
+		free(unc);
+		free(cp);
+		return -1;
+	}
+	free(unc);
+
+	cp->src = estrdup9p(path);
+	cp->next = Cache;
+	Cache = cp;
+	if(Debug && strstr(Debug, "dfs") != nil)
+		print("redirect cache=miss src=%q => share=%q path=%q\n",
+			cp->src, cp->share, cp->path);
+	return 0;
+}

+ 187 - 0
sys/src/cmd/cifs/doserrstr.c

@@ -0,0 +1,187 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * This file is derrived from nterr.h in the samba distribution
+ */
+
+/*
+   Unix SMB/CIFS implementation.
+   DOS error code constants
+   Copyright (C) Andrew Tridgell              1992-2000
+   Copyright (C) John H Terpstra              1996-2000
+   Copyright (C) Luke Kenneth Casson Leighton 1996-2000
+   Copyright (C) Paul Ashton                  1998-2000
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+static struct {
+	int	err;
+	char	*msg;
+} DOSerrs[] = {
+	/* smb x/open error codes for the errdos error class */
+	{ (0<<16)|1,	"no error" },
+	{ (1<<16)|1,	"invalid function" },
+	{ (2<<16)|1,	"file not found" },
+	{ (3<<16)|1,	"directory not found" },
+	{ (4<<16)|1,	"too many open files" },
+	{ (5<<16)|1,	"access denied" },
+	{ (6<<16)|1,	"invalid fid" },
+	{ (7<<16)|1,	"memory control blocks destroyed." },
+	{ (8<<16)|1,	"out of memory" },
+	{ (9<<16)|1,	"invalid memory block address" },
+	{ (10<<16)|1,	"invalid environment" },
+	{ (12<<16)|1,	"invalid open mode" },
+	{ (13<<16)|1,	"invalid data (only from ioctl call)" },
+	{ (14<<16)|1,	"reserved" },
+	{ (15<<16)|1,	"invalid drive" },
+	{ (16<<16)|1,	"attempt to delete current directory" },
+	{ (17<<16)|1,	"rename/move across filesystems" },
+	{ (18<<16)|1,	"no more files found" },
+	{ (31<<16)|1,	"general failure" },
+	{ (32<<16)|1,	"share mode conflict with open mode" },
+	{ (33<<16)|1,	"lock conflicts" },
+	{ (50<<16)|1,	"request unsupported" },
+	{ (64<<16)|1,	"network name not available" },
+	{ (66<<16)|1,	"ipc unsupported (guess)" },
+	{ (67<<16)|1,	"invalid share name" },
+	{ (80<<16)|1,	"file already exists" },
+	{ (87<<16)|1,	"invalid paramater" },
+	{ (110<<16)|1,	"cannot open" },
+	{ (122<<16)|1,  "insufficent buffer" },
+	{ (123<<16)|1,	"invalid name" },
+	{ (124<<16)|1,	"unknown level" },
+	{ (158<<16)|1,	"this region already locked" },
+
+	{ (183<<16)|1,	"rename failed" },
+
+	{ (230<<16)|1,	"named pipe invalid" },
+	{ (231<<16)|1,	"pipe busy" },
+	{ (232<<16)|1,	"close in progress" },
+	{ (233<<16)|1,	"no reader of named pipe" },
+	{ (234<<16)|1,	"more data to be returned" },
+	{ (259<<16)|1,	"no more items" },
+	{ (267<<16)|1,	"invalid directory name in a path" },
+	{ (282<<16)|1,	"extended attributes" },
+	{ (1326<<16)|1,	"authentication failed" },
+	{ (2123<<16)|1,	"buffer too small" },
+	{ (2142<<16)|1,	"unknown ipc" },
+	{ (2151<<16)|1,	"no such print job" },
+	{ (2455<<16)|1,	"invalid group" },
+
+	/* Error codes for the ERRSRV class */
+	{ (1<<16)|2,	"non specific error" },
+	{ (2<<16)|2,	"bad password" },
+	{ (3<<16)|2,	"reserved" },
+	{ (4<<16)|2,	"permission denied" },
+	{ (5<<16)|2,	"tid invalid" },
+	{ (6<<16)|2,	"invalid server name" },
+	{ (7<<16)|2,	"invalid device" },
+	{ (22<<16)|2,	"unknown smb" },
+	{ (49<<16)|2,	"print queue full" },
+	{ (50<<16)|2,	"queued item too big" },
+	{ (52<<16)|2,	"fid invalid in print file" },
+	{ (64<<16)|2,	"unrecognised command" },
+	{ (65<<16)|2,	"smb server internal error" },
+	{ (67<<16)|2,	"fid/pathname invalid" },
+	{ (68<<16)|2,	"reserved 68" },
+	{ (69<<16)|2,	"access is invalid" },
+	{ (70<<16)|2,	"reserved 70" },
+	{ (71<<16)|2,	"attribute mode invalid" },
+	{ (81<<16)|2,	"message server paused" },
+	{ (82<<16)|2,	"not receiving messages" },
+	{ (83<<16)|2,	"no room for message" },
+	{ (87<<16)|2,	"too many remote usernames" },
+	{ (88<<16)|2,	"operation timed out" },
+	{ (89<<16)|2,	"no resources" },
+	{ (90<<16)|2,	"too many userids" },
+	{ (91<<16)|2,	"bad userid" },
+	{ (250<<16)|2,	"retry with mpx mode" },
+	{ (251<<16)|2,	"retry with standard mode" },
+	{ (252<<16)|2,	"resume mpx mode" },
+	{ (0xffff<<16)|2, "function not supported" },
+
+	/* Error codes for the ERRHRD class */
+	{ (19<<16)|3,	"read only media" },
+	{ (20<<16)|3,	"unknown device" },
+	{ (21<<16)|3,	"drive not ready" },
+	{ (22<<16)|3,	"unknown command" },
+	{ (23<<16)|3,	"data (CRC) error" },
+	{ (24<<16)|3,	"bad request length" },
+	{ (25<<16)|3,	"seek failed" },
+	{ (26<<16)|3,	"bad media" },
+	{ (27<<16)|3,	"bad sector" },
+	{ (28<<16)|3,	"no paper" },
+	{ (29<<16)|3,	"write fault" },
+	{ (30<<16)|3,	"read fault" },
+	{ (31<<16)|3,	"general hardware failure" },
+	{ (34<<16)|3,	"wrong disk" },
+	{ (35<<16)|3,	"FCB unavailable" },
+	{ (36<<16)|3,	"share buffer exceeded" },
+	{ (39<<16)|3,	"disk full" },
+
+};
+
+char *
+doserrstr(uint err)
+{
+	int i, match;
+	char *class;
+	static char buf[0xff];
+
+	switch(err & 0xff){
+ 	case 1:
+		class = "dos";
+		break;
+	case 2:
+		class = "network";
+		break;
+	case 3:
+		class = "hardware";
+		break;
+	case 4:
+		class = "Xos";
+		break;
+	case 0xe1:
+		class = "mx1";
+		break;
+	case 0xe2:
+		class = "mx2";
+		break;
+	case 0xe3:
+		class = "mx3";
+		break;
+	case 0xff:
+		class = "packet";
+		break;
+	default:
+		class = "unknown";
+		break;
+	}
+
+	match = -1;
+	for(i = 0; i < nelem(DOSerrs); i++)
+		if(DOSerrs[i].err == err)
+			match = i;
+
+	if(match != -1)
+		snprint(buf, sizeof(buf), "%s, %s", class, DOSerrs[match].msg);
+	else
+		snprint(buf, sizeof(buf), "%s, %ud/0x%ux - unknown error",
+			class, err >> 16, err >> 16);
+	return buf;
+}

+ 359 - 0
sys/src/cmd/cifs/fs.c

@@ -0,0 +1,359 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+
+static char *period(long sec);
+
+int
+shareinfo(Fmt *f)
+{
+	int i, j, n;
+	char *type;
+	Shareinfo2 si2;
+	Share *sp, *sip;
+
+	if((n = RAPshareenum(Sess, &Ipc, &sip)) < 1){
+		fmtprint(f, "can't enumerate shares: %r\n");
+		return 0;
+	}
+
+	for(i = 0; i < n; i++){
+		fmtprint(f, "%-13q ", sip[i].name);
+
+		sp = &sip[i];
+		for(j = 0; j < Nshares; j++)
+			if(strcmp(Shares[j].name, sip[i].name) == 0){
+				sp = &Shares[j];
+				break;
+			}
+		sp->tid = Ipc.tid;
+
+		if(RAPshareinfo(Sess, sp, sp->name, &si2) != -1){
+			switch(si2.type){
+			case STYPE_DISKTREE:	type = "disk"; break;
+			case STYPE_PRINTQ:	type = "printq"; break;
+			case STYPE_DEVICE:	type = "device"; break;
+			case STYPE_IPC:		type = "ipc"; break;
+			case STYPE_SPECIAL:	type = "special"; break;
+			case STYPE_TEMP:	type = "temp"; break;
+			default:		type = "unknown"; break;
+			}
+
+			fmtprint(f, "%-8s %s", type, si2.comment);
+			free(si2.name);
+			free(si2.comment);
+			free(si2.path);
+			free(si2.passwd);
+		}
+		fmtprint(f, "\n");
+
+	}
+	free(sip);
+	return 0;
+}
+
+int
+openfileinfo(Fmt *f)
+{
+	int got,  i;
+	Fileinfo *fi;
+
+	fi = nil;
+	if((got = RAPFileenum2(Sess, &Ipc, "", "", &fi)) == -1){
+		fmtprint(f, "RAPfileenum: %r (Only Administrator has permission)\n");
+		return 0;
+	}
+
+	for(i = 0; i < got; i++){
+		fmtprint(f, "0x%02x %-4d %-24q %q ", fi[i].perms,
+			fi[i].locks, fi[i].user, fi[i].path);
+		free(fi[i].path);
+		free(fi[i].user);
+	}
+	free(fi);
+	return 0;
+}
+
+int
+conninfo(Fmt *f)
+{
+	int i;
+	typedef struct {
+		int	val;
+		char	*name;
+	} Tab;
+	static Tab captab[] = {
+		{ 1,		"raw-mode" },
+		{ 2,		"mpx-mode" },
+		{ 4,		"unicode" },
+		{ 8,		"large-files" },
+		{ 0x10,		"NT-smbs" },
+		{ 0x20,		"rpc-remote-APIs" },
+		{ 0x40,		"status32" },
+		{ 0x80,		"l2-oplocks" },
+		{ 0x100,	"lock-read" },
+		{ 0x200,	"NT-find" },
+		{ 0x1000,	"Dfs" },
+		{ 0x2000,	"info-passthru" },
+		{ 0x4000,	"large-readx" },
+		{ 0x8000,	"large-writex" },
+		{ 0x800000,	"Unix" },
+		{ 0x20000000,	"bulk-transfer" },
+		{ 0x40000000,	"compressed" },
+		{ 0x80000000,	"extended-security" },
+	};
+	static Tab sectab[] = {
+		{ 1,		"user-auth" },
+		{ 2,		"challange-response" },
+		{ 4,		"signing-available" },
+		{ 8,		"signing-required" },
+	};
+
+	fmtprint(f, "%q %q %q %q %+ldsec %dmtu %s\n",
+		Sess->auth->user, Sess->cname,
+		Sess->auth->windom, Sess->remos,
+		Sess->slip, Sess->mtu, Sess->isguest? "as guest": "");
+
+	fmtprint(f, "caps: ");
+	for(i = 0; i < nelem(captab); i++)
+		if(Sess->caps & captab[i].val)
+			fmtprint(f, "%s ", captab[i].name);
+	fmtprint(f, "\n");
+
+	fmtprint(f, "security: ");
+	for(i = 0; i < nelem(sectab); i++)
+		if(Sess->secmode & sectab[i].val)
+			fmtprint(f, "%s ", sectab[i].name);
+	fmtprint(f, "\n");
+
+	if(Sess->nbt)
+		fmtprint(f, "transport: cifs over netbios\n");
+	else
+		fmtprint(f, "transport: cifs\n");
+	return 0;
+}
+
+int
+sessioninfo(Fmt *f)
+{
+	int got,  i;
+	Sessinfo *si;
+
+	si = nil;
+	if((got = RAPsessionenum(Sess, &Ipc, &si)) == -1){
+		fmtprint(f, "RAPsessionenum: %r\n");
+		return 0;
+	}
+
+	for(i = 0; i < got; i++){
+		fmtprint(f, "%-24q %-24q ", si[i].user, si[i].wrkstn);
+		fmtprint(f, "%12s ", period(si[i].sesstime));
+		fmtprint(f, "%12s\n", period(si[i].idletime));
+		free(si[i].wrkstn);
+		free(si[i].user);
+	}
+	free(si);
+	return 0;
+}
+
+/*
+ * We request the domain referral for "" which gives the
+ * list of all the trusted domains in the clients forest, and
+ * other trusted forests.
+ *
+ * We then sumbit each of these names in turn which gives the
+ * names of the domain controllers for that domain.
+ *
+ * We get a DNS domain name for each domain controller as well as a
+ * netbios name.  I THINK I am correct in saying that a name
+ * containing a dot ('.') must be a DNS name, as the NetBios
+ * name munging cannot encode one.  Thus names which contain no
+ * dots must be netbios names.
+ */
+static void
+dfsredir(Fmt *f, char *path, int depth)
+{
+	Refer *re, retab[128];
+	int n, used, flags;
+
+	n = T2getdfsreferral(Sess, &Ipc, path, &flags, &used, retab, nelem(retab));
+	if(n == -1)
+		return;
+	for(re = retab; re < retab+n; re++){
+		if(strcmp(path, re->path) != 0)
+			dfsredir(f, re->path, depth+1);
+		else
+			fmtprint(f, "%-32q %q\n", re->path, re->addr);
+		free(re->addr);
+		free(re->path);
+	}
+}
+
+int
+dfsrootinfo(Fmt *f)
+{
+	dfsredir(f, "", 0);
+	return 0;
+}
+
+
+int
+userinfo(Fmt *f)
+{
+	int got, i;
+	Namelist *nl;
+	Userinfo ui;
+
+	nl = nil;
+	if((got = RAPuserenum2(Sess, &Ipc, &nl)) == -1)
+		if((got = RAPuserenum(Sess, &Ipc, &nl)) == -1){
+			fmtprint(f, "RAPuserenum: %r\n");
+			return 0;
+		}
+
+	for(i = 0; i < got; i++){
+		fmtprint(f, "%-24q ", nl[i].name);
+
+		if(RAPuserinfo(Sess, &Ipc, nl[i].name, &ui) != -1){
+			fmtprint(f, "%-48q %q", ui.fullname, ui.comment);
+			free(ui.user);
+			free(ui.comment);
+			free(ui.fullname);
+			free(ui.user_comment);
+		}
+		free(nl[i].name);
+		fmtprint(f, "\n");
+	}
+	free(nl);
+	return 0;
+}
+
+int
+groupinfo(Fmt *f)
+{
+	int got1, got2, i, j;
+	Namelist *grps, *usrs;
+
+	grps = nil;
+	if((got1 = RAPgroupenum(Sess, &Ipc, &grps)) == -1){
+		fmtprint(f, "RAPgroupenum: %r\n");
+		return 0;
+	}
+
+	for(i = 0; i < got1; i++){
+		fmtprint(f, "%q ", grps[i].name);
+		usrs = nil;
+		if((got2 = RAPgroupusers(Sess, &Ipc, grps[i].name, &usrs)) != -1){
+			for(j = 0; j < got2; j++){
+				fmtprint(f, "%q ", usrs[j].name);
+				free(usrs[j].name);
+			}
+			free(usrs);
+		}
+		free(grps[i].name);
+		fmtprint(f, "\n");
+	}
+	free(grps);
+	return 0;
+}
+
+static int
+nodelist(Fmt *f, int type)
+{
+	int more, got, i, j;
+	Serverinfo *si;
+	static char *types[] = {
+		[0]	"workstation",
+		[1]	"server",
+		[2]	"SQL server",
+		[3]	"DC",
+		[4]	"backup DC",
+		[5]	"time source",
+		[6]	"Apple server",
+		[7]	"Novell server",
+		[8]	"domain member",
+		[9]	"printer server",
+		[10]	"dial-up server",
+		[11]	"Unix",
+		[12]	"NT",
+		[13]	"WFW",
+		[14]	"MFPN (?)",
+		[15]	"NT server",
+		[16]	"potential browser",
+		[17]	"backup browser",
+		[18]	"LMB",
+		[19]	"DMB",
+		[20]	"OSF Unix",
+		[21]	"VMS",
+		[22]	"Win95",
+		[23]	"DFS",
+		[24]	"NT cluster",
+		[25]	"Terminal server",
+		[26]	"[26]",
+		[27]	"[27]",
+		[28]	"IBM DSS",
+	};
+
+	si = nil;
+	if((got = RAPServerenum2(Sess, &Ipc, Sess->auth->windom, type, &more,
+	    &si)) == -1){
+		fmtprint(f, "RAPServerenum2: %r\n");
+		return 0;
+	}
+	if(more)
+		if((got = RAPServerenum3(Sess, &Ipc, Sess->auth->windom, type,
+		    got-1, si)) == -1){
+			fmtprint(f, "RAPServerenum3: %r\n");
+			return 0;
+		}
+
+	for(i = 0; i < got; i++){
+		fmtprint(f, "%-16q %-16q ", si[i].name, si[i].comment);
+		if(type != LIST_DOMAINS_ONLY){
+			fmtprint(f, "v%d.%d ", si[i].major, si[i].minor);
+			for(j = 0; j < nelem(types); j++)
+				if(si[i].type & (1 << j) && types[j])
+					fmtprint(f, "%s,", types[j]);
+		}
+		fmtprint(f, "\n");
+		free(si[i].name);
+		free(si[i].comment);
+	}
+	free(si);
+	return 0;
+}
+
+int
+domaininfo(Fmt *f)
+{
+	return nodelist(f, LIST_DOMAINS_ONLY);
+}
+
+int
+workstationinfo(Fmt *f)
+{
+	return nodelist(f, ALL_LEARNT_IN_DOMAIN);
+}
+
+static char *
+period(long sec)
+{
+	int days, hrs, min;
+	static char when[32];
+
+	days = sec  / (60L * 60L * 24L);
+	sec -= days * (60L * 60L * 24L);
+	hrs  = sec / (60L * 60L);
+	sec -= hrs * (60L * 60L);
+	min  = sec / 60L;
+	sec -= min * 60L;
+	if(days)
+		snprint(when, sizeof(when), "%d %d:%d:%ld ", days, hrs, min, sec);
+	else
+		snprint(when, sizeof(when), "%d:%d:%ld ", hrs, min, sec);
+	return when;
+}

+ 106 - 0
sys/src/cmd/cifs/info.c

@@ -0,0 +1,106 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+
+
+struct {
+	char	*name;
+	int	(*func)(Fmt *f);
+	char	*buf;
+	int	len;
+} Infdir[] = {
+	{ "Users",	userinfo },	
+	{ "Groups",	groupinfo },	
+	{ "Shares",	shareinfo },	
+	{ "Connection",	conninfo },	
+	{ "Sessions",	sessioninfo },	
+	{ "Dfsroot",	dfsrootinfo },	
+	{ "Dfscache",	dfscacheinfo },	
+	{ "Domains",	domaininfo },	
+	{ "Openfiles",	openfileinfo },	
+	{ "Workstations", workstationinfo },	
+	{ "Filetable",	filetableinfo },	
+};
+
+int
+walkinfo(char *name)
+{
+	int i;
+
+	for(i = 0; i < nelem(Infdir); i++)
+		if(strcmp(Infdir[i].name, name) == 0)
+			return(i);
+	return -1;
+}
+
+int
+numinfo(void)
+{
+	return nelem(Infdir);
+}
+
+int
+dirgeninfo(int slot, Dir *d)
+{
+	if(slot < 0 || slot > nelem(Infdir))
+		return -1;
+
+	memset(d, 0, sizeof(Dir));
+	d->type = 'N';
+	d->dev = 99;
+	d->name = estrdup9p(Infdir[slot].name);
+	d->uid = estrdup9p("other");
+	d->muid = estrdup9p("other");
+	d->gid = estrdup9p("other");
+	d->mode = 0666;
+	d->atime = time(0);
+	d->mtime = d->atime;
+	d->qid = mkqid(Infdir[slot].name, 0, 1, Pinfo, slot);
+	d->qid.vers = 1;
+	d->qid.path = slot;
+	d->qid.type = 0;
+	return 0;
+}
+
+int
+makeinfo(int path)
+{
+	Fmt f;
+
+	if(path < 0 || path > nelem(Infdir))
+		return -1;
+	if(Infdir[path].buf != nil)
+		return 0;
+	fmtstrinit(&f);
+	if((*Infdir[path].func)(&f) == -1l)
+		return -1;
+	Infdir[path].buf = fmtstrflush(&f);
+	Infdir[path].len = strlen(Infdir[path].buf);
+	return 0;
+}
+
+int
+readinfo(int path, char *buf, int len, int off)
+{
+	if(path < 0 || path > nelem(Infdir))
+		return -1;
+	if(off > Infdir[path].len)
+		return 0;
+	if(len + off > Infdir[path].len)
+		len = Infdir[path].len - off;
+	memmove(buf, Infdir[path].buf + off, len);
+	return len;
+}
+
+void
+freeinfo(int path)
+{
+	if(path < 0 || path > nelem(Infdir))
+		return;
+	free(Infdir[path].buf);
+	Infdir[path].buf = nil;
+}

+ 1192 - 0
sys/src/cmd/cifs/main.c

@@ -0,0 +1,1192 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <libsec.h>
+#include <9p.h>
+#include "cifs.h"
+
+#define max(a,b)	(((a) > (b))? (a): (b))
+#define min(a,b)	(((a) < (b))? (a): (b))
+
+typedef struct Aux Aux;
+struct Aux {
+	Aux	*next;
+	Aux	*prev;
+	char	*path;		/* full path fo file */
+	Share	*sp;		/* this share's info */
+	long	expire;		/* expiration time of cache */
+	long	off;		/* file pos of start of cache */
+	long	end;		/* file pos of end of cache */
+	char	*cache;
+	int	fh;		/* file handle */
+	int	sh;		/* search handle */
+	long	srch;		/* find first's internal state */
+};
+
+extern int chatty9p;
+
+int Dfstout = 100; /* timeout (in ms) for ping of dfs servers (assume they are local) */
+int Billtrog = 1;		/* enable file owner/group resolution */
+int Attachpid;			/* pid of proc that attaches (ugh !) */
+char *Debug = nil;		/* messages */
+Qid Root;			/* root of remote system */
+Share Ipc;			/* Share info of IPC$ share */
+Session *Sess;			/* current session */
+int Active = IDLE_TIME;		/* secs until next keepalive is sent */
+static int Keeppid;		/* process ID of keepalive thread */
+Share Shares[MAX_SHARES]; 	/* table of connected shares */
+int Nshares = 0;		/* number of Shares connected */
+Aux *Auxroot = nil;		/* linked list of Aux structs */
+char *Host = nil;		/* host we are connected to */
+
+#define ptype(x)	(((x) & 0xf))
+#define pindex(x)	(((x) & 0xff0) >> 4)
+
+void
+setup(void)
+{
+	int fd;
+	char buf[32];
+
+	/*
+	 * This is revolting but I cannot see any other way to get
+	 * the pid of the server.  We need this as Windows doesn't
+	 * drop the TCP connection when it closes a connection.
+	 * Thus we keepalive() to detect when/if we are thrown off.
+	 */
+	Attachpid = getpid();
+
+	snprint(buf, sizeof buf, "#p/%d/args", getpid());
+	if((fd = open(buf, OWRITE)) >= 0){
+		fprint(fd, "%s network", Host);
+		close(fd);
+	}
+}
+
+int
+filetableinfo(Fmt *f)
+{
+	Aux *ap;
+	char *type;
+
+	if((ap = Auxroot) != nil)
+		do{
+			type = "walked";
+			if(ap->sh != -1)
+				type = "opendir";
+			if(ap->fh != -1)
+				type = "openfile";
+			fmtprint(f, "%-9s %s\n", type, ap->path);
+			ap = ap->next;
+		}while(ap != Auxroot);
+	return 0;
+}
+
+Qid
+mkqid(char *s, int is_dir, long vers, int subtype, long path)
+{
+	Qid q;
+	union {				/* align digest suitably */
+		uchar	digest[SHA1dlen];
+		uvlong	uvl;
+	} u;
+
+	sha1((uchar *)s, strlen(s), u.digest, nil);
+	q.type = is_dir? QTDIR: 0;
+	q.vers = vers;
+	if(subtype){
+		q.path = *((uvlong *)u.digest) & ~0xfffL;
+		q.path |= (path & 0xff) << 4 | (subtype & 0xf);
+	}else
+		q.path = *((uvlong *)u.digest) & ~0xfL;
+	return q;
+}
+
+/*
+ * used only for root dir and shares
+ */
+static void
+V2D(Dir *d, Qid qid, char *name)
+{
+	memset(d, 0, sizeof(Dir));
+	d->type = 'C';
+	d->dev = 1;
+	d->name = strlwr(estrdup9p(name));
+	d->uid = estrdup9p("bill");
+	d->muid = estrdup9p("boyd");
+	d->gid = estrdup9p("trog");
+	d->mode = 0755 | DMDIR;
+	d->atime = time(nil);
+	d->mtime = d->atime;
+	d->length = 0;
+	d->qid = qid;
+}
+
+static void
+I2D(Dir *d, Share *sp, char *path, FInfo *fi)
+{
+	char *name;
+
+	if((name = strrchr(fi->name, '\\')) != nil)
+		name++;
+	else
+		name = fi->name;
+	d->name = estrdup9p(name);
+	d->type = 'C';
+	d->dev = sp->tid;
+	d->uid = estrdup9p("bill");
+	d->gid = estrdup9p("trog");
+	d->muid = estrdup9p("boyd");
+	d->atime = fi->accessed;
+	d->mtime = fi->written;
+
+	if(fi->attribs & ATTR_READONLY)
+		d->mode = 0444;
+	else
+		d->mode = 0666;
+
+	d->length = fi->size;
+	d->qid = mkqid(path, fi->attribs & ATTR_DIRECTORY, fi->changed, 0, 0);
+
+	if(fi->attribs & ATTR_DIRECTORY){
+		d->length = 0;
+		d->mode |= DMDIR|0111;
+	}
+}
+
+static void
+responderrstr(Req *r)
+{
+	char e[ERRMAX];
+
+	*e = 0;
+	rerrstr(e, sizeof e);
+	respond(r, e);
+}
+
+static char *
+newpath(char *path, char *name)
+{
+	char *p, *q;
+
+	assert((p = strrchr(path, '/')) != nil);
+
+	if(strcmp(name, "..") == 0){
+		if(p == path)
+			return estrdup9p("/");
+		q = emalloc9p((p-path)+1);
+		strecpy(q, q+(p-path)+1, path);
+		return q;
+	}
+	if(strcmp(path, "/") == 0)
+		return smprint("/%s", name);
+	return smprint("%s/%s", path, name);
+}
+
+static int
+dirgen(int slot, Dir *d, void *aux)
+{
+	int numinf = numinfo(), rc, got;
+	int slots = min(Sess->mtu, MTU) / sizeof(FInfo);
+	long off;
+	char *npath;
+	Aux *a = aux;
+	FInfo *fi;
+
+	if(strcmp(a->path, "/") == 0){
+		if(slot < numinf){
+			dirgeninfo(slot, d);
+			return 0;
+		} else
+			slot -= numinf;
+
+		if(slot >= Nshares)
+			return -1;
+		V2D(d, mkqid(Shares[slot].name, 1, 1, Pshare, slot),
+			Shares[slot].name);
+		return 0;
+	}
+
+	off = slot * sizeof(FInfo);
+	if(off >= a->off && off < a->end && time(nil) < a->expire)
+		goto from_cache;
+
+	if(off == 0){
+		fi = (FInfo *)a->cache;
+		npath = smprint("%s/*", mapfile(a->path));
+		a->sh = T2findfirst(Sess, a->sp, slots, npath, &got, &a->srch,
+			(FInfo *)a->cache);
+		free(npath);
+		if(a->sh == -1)
+			return -1;
+
+		a->off = 0;
+		a->end = got * sizeof(FInfo);
+
+		if(got >= 2 && strcmp(fi[0].name, ".") == 0 &&
+		    strcmp(fi[1].name, "..") == 0){
+			a->end = (got - 2) * sizeof(FInfo);
+			memmove(a->cache, a->cache + sizeof(FInfo)*2,
+				a->end - a->off);
+		}
+	}
+
+	while(off >= a->end && a->sh != -1){
+		fi = (FInfo *)(a->cache + (a->end - a->off) - sizeof(FInfo));
+		a->off = a->end;
+		npath = smprint("%s/%s", mapfile(a->path), fi->name);
+		rc = T2findnext(Sess, a->sp, slots, npath,
+			&got, &a->srch, (FInfo *)a->cache, a->sh);
+		free(npath);
+		if(rc == -1 || got == 0)
+			break;
+		a->end = a->off + got * sizeof(FInfo);
+	}
+	a->expire = time(nil) + CACHETIME;
+
+	if(got < slots){
+		if(a->sh != -1)
+			CIFSfindclose2(Sess, a->sp, a->sh);
+		a->sh = -1;
+	}
+
+	if(off >= a->end)
+		return -1;
+
+from_cache:
+	fi = (FInfo *)(a->cache + (off - a->off));
+	npath = smprint("%s/%s", mapfile(a->path), fi->name);
+	I2D(d, a->sp, npath, fi);
+	if(Billtrog == 0)
+		upd_names(Sess, a->sp, npath, d);
+	free(npath);
+	return 0;
+}
+
+static void
+fsattach(Req *r)
+{
+	char *spec = r->ifcall.aname;
+	Aux *a;
+	static int first = 1;
+
+	if(first)
+		setup();
+
+	if(spec && *spec){
+		respond(r, "invalid attach specifier");
+		return;
+	}
+
+	r->ofcall.qid = mkqid("/", 1, 1, Proot, 0);
+	r->fid->qid = r->ofcall.qid;
+
+	a = r->fid->aux = emalloc9p(sizeof(Aux));
+	memset(a, 0, sizeof(Aux));
+	a->path = estrdup9p("/");
+	a->sp = nil;
+	a->fh = -1;
+	a->sh = -1;
+
+	if(Auxroot){
+		a->prev = Auxroot;
+		a->next = Auxroot->next;
+		Auxroot->next->prev = a;
+		Auxroot->next = a;
+	} else {
+		Auxroot = a;
+		a->next = a;
+		a->prev = a;
+	}
+	respond(r, nil);
+}
+
+static char*
+fsclone(Fid *ofid, Fid *fid)
+{
+	Aux *oa = ofid->aux;
+	Aux *a = emalloc9p(sizeof(Aux));
+
+	fid->aux = a;
+
+	memset(a, 0, sizeof(Aux));
+	a->sh = -1;
+	a->fh = -1;
+	a->sp = oa->sp;
+	a->path = estrdup9p(oa->path);
+
+	if(Auxroot){
+		a->prev = Auxroot;
+		a->next = Auxroot->next;
+		Auxroot->next->prev = a;
+		Auxroot->next = a;
+	} else {
+		Auxroot = a;
+		a->next = a;
+		a->prev = a;
+	}
+	return nil;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+	int rc, n, i;
+	char *npath;
+	Aux *a = fid->aux;
+	FInfo fi;
+	static char e[ERRMAX];
+
+	*e = 0;
+	npath = newpath(a->path, name);
+	if(strcmp(npath, "/") == 0)
+		*qid = mkqid("/", 1, 1, Proot, 0);
+	else if(strrchr(npath, '/') == npath){
+		if((n = walkinfo(name)) != -1)
+			*qid = mkqid(npath, 0, 1, Pinfo, n);
+		else {
+			for(i = 0; i < Nshares; i++){
+				n = strlen(Shares[i].name);
+				if(cistrncmp(npath+1, Shares[i].name, n) != 0 ||
+				    npath[n+1] != 0 && npath[n+1] != '/')
+					continue;
+				break;
+			}
+			if(i < Nshares){
+				a->sp = Shares+i;
+				*qid = mkqid(npath, 1, 1, Pshare, i);
+			} else {
+				free(npath);
+				return "not found";
+			}
+		}
+	} else {
+again:
+		if(mapshare(npath, &a->sp) == -1){
+			free(npath);
+			return "not found";
+		}
+
+		memset(&fi, 0, sizeof fi);
+
+		if(Sess->caps & CAP_NT_SMBS)
+			rc = T2queryall(Sess, a->sp, mapfile(npath), &fi);
+		else
+			rc = T2querystandard(Sess, a->sp, mapfile(npath), &fi);
+
+		if((a->sp->options & SMB_SHARE_IS_IN_DFS) != 0 &&
+		    (fi.attribs & ATTR_REPARSE) != 0 &&
+		    redirect(Sess, a->sp, npath) != -1)
+			goto again;
+		if(rc == -1){
+			rerrstr(e, sizeof(e));
+			free(npath);
+			return e;
+		}
+		*qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0);
+	}
+
+	free(a->path);
+	a->path = npath;
+	fid->qid = *qid;
+	return nil;
+}
+
+static void
+fsstat(Req *r)
+{
+	int rc;
+	FInfo fi;
+	Aux *a = r->fid->aux;
+
+	if(ptype(r->fid->qid.path) == Proot)
+		V2D(&r->d, r->fid->qid, "");
+	else if(ptype(r->fid->qid.path) == Pinfo)
+		dirgeninfo(pindex(r->fid->qid.path), &r->d);
+	else if(ptype(r->fid->qid.path) == Pshare)
+		V2D(&r->d, r->fid->qid, a->path +1);
+	else{
+		memset(&fi, 0, sizeof fi);
+		if(Sess->caps & CAP_NT_SMBS)
+			rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi);
+		else
+			rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi);
+		if(rc == -1){
+			responderrstr(r);
+			return;
+		}
+		I2D(&r->d, a->sp, a->path, &fi);
+		if(Billtrog == 0)
+			upd_names(Sess, a->sp, mapfile(a->path), &r->d);
+	}
+	respond(r, nil);
+}
+
+static int
+smbcreateopen(Aux *a, char *path, int mode, int perm, int is_create,
+	int is_dir, FInfo *fip)
+{
+	int rc, action, attrs, access, result;
+
+	if(is_create && is_dir){
+		if(CIFScreatedirectory(Sess, a->sp, path) == -1)
+			return -1;
+		return 0;
+	}
+
+	if(mode & DMAPPEND) {
+		werrstr("filesystem does not support DMAPPEND");
+		return -1;
+	}
+
+	if(is_create)
+		action = 0x12;
+	else if(mode & OTRUNC)
+		action = 0x02;
+	else
+		action = 0x01;
+
+	if(perm & 0222)
+		attrs = ATTR_NORMAL;
+	else
+		attrs = ATTR_NORMAL|ATTR_READONLY;
+
+	switch (mode & OMASK){
+	case OREAD:
+		access = 0;
+		break;
+	case OWRITE:
+		access = 1;
+		break;
+	case ORDWR:
+		access = 2;
+		break;
+	case OEXEC:
+		access = 3;
+		break;
+	default:
+		werrstr("%d bad open mode", mode & OMASK);
+		return -1;
+		break;
+	}
+
+	if(mode & DMEXCL == 0)
+		access |= 0x10;
+	else
+		access |= 0x40;
+
+	if((a->fh = CIFS_SMB_opencreate(Sess, a->sp, path, access, attrs,
+	    action, &result)) == -1)
+		return -1;
+
+	if(Sess->caps & CAP_NT_SMBS)
+		rc = T2queryall(Sess, a->sp, mapfile(a->path), fip);
+	else
+		rc = T2querystandard(Sess, a->sp, mapfile(a->path), fip);
+	if(rc == -1){
+		fprint(2, "internal error: stat of newly open/created file failed\n");
+		return -1;
+	}
+
+	if((mode & OEXCL) && (result & 0x8000) == 0){
+		werrstr("%d bad open mode", mode & OMASK);
+		return -1;
+	}
+	return 0;
+}
+
+/* Uncle Bill, you have a lot to answer for... */
+static int
+ntcreateopen(Aux *a, char *path, int mode, int perm, int is_create,
+	int is_dir, FInfo *fip)
+{
+	int options, result, attrs, flags, access, action, share;
+
+	if(mode & DMAPPEND){
+		werrstr("CIFSopen, DMAPPEND not supported");
+		return -1;
+	}
+
+	if(is_create){
+		if(mode & OEXCL)
+			action = FILE_OPEN;
+		else if(mode & OTRUNC)
+			action = FILE_CREATE;
+		else
+			action = FILE_OVERWRITE_IF;
+	} else {
+		if(mode & OTRUNC)
+			action = FILE_OVERWRITE_IF;
+		else
+			action = FILE_OPEN_IF;
+	}
+
+	flags = 0;		/* FIXME: really not sure */
+
+	if(mode & OEXCL)
+		share = FILE_NO_SHARE;
+	else
+		share = FILE_SHARE_ALL;
+
+	switch (mode & OMASK){
+	case OREAD:
+		access = GENERIC_READ;
+		break;
+	case OWRITE:
+		access = GENERIC_WRITE;
+		break;
+	case ORDWR:
+		access = GENERIC_ALL;
+		break;
+	case OEXEC:
+		access = GENERIC_EXECUTE;
+		break;
+	default:
+		werrstr("%d bad open mode", mode & OMASK);
+		return -1;
+		break;
+	}
+
+	if(is_dir){
+		action = FILE_CREATE;
+		options = FILE_DIRECTORY_FILE;
+		if(perm & 0222)
+			attrs = ATTR_DIRECTORY;
+		else
+			attrs = ATTR_DIRECTORY|ATTR_READONLY;
+	} else {
+		options = FILE_NON_DIRECTORY_FILE;
+		if(perm & 0222)
+			attrs = ATTR_NORMAL;
+		else
+			attrs = ATTR_NORMAL|ATTR_READONLY;
+	}
+
+	if(mode & ORCLOSE){
+		options |= FILE_DELETE_ON_CLOSE;
+		attrs |= ATTR_DELETE_ON_CLOSE;
+	}
+
+	if((a->fh = CIFS_NT_opencreate(Sess, a->sp, path, flags, options,
+	    attrs, access, share, action, &result, fip)) == -1)
+		return -1;
+
+	if((mode & OEXCL) && (result & 0x8000) == 0){
+		werrstr("%d bad open mode", mode & OMASK);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void
+fscreate(Req *r)
+{
+	int rc, is_dir;
+	char *npath;
+	Aux *a = r->fid->aux;
+	FInfo fi;
+
+	a->end = a->off = 0;
+	a->cache = emalloc9p(max(Sess->mtu, MTU));
+
+	is_dir = (r->ifcall.perm & DMDIR) == DMDIR;
+	npath = smprint("%s/%s", a->path, r->ifcall.name);
+
+	if(Sess->caps & CAP_NT_SMBS)
+		rc = ntcreateopen(a, mapfile(npath), r->ifcall.mode,
+			r->ifcall.perm, 1, is_dir, &fi);
+	else
+		rc = smbcreateopen(a, mapfile(npath), r->ifcall.mode,
+			r->ifcall.perm, 1, is_dir, &fi);
+	if(rc == -1){
+		free(npath);
+		responderrstr(r);
+		return;
+	}
+
+	r->fid->qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0);
+
+	r->ofcall.qid = r->fid->qid;
+	free(a->path);
+	a->path = npath;
+
+	respond(r, nil);
+}
+
+static void
+fsopen(Req *r)
+{
+	int rc;
+	FInfo fi;
+	Aux *a = r->fid->aux;
+
+	a->end = a->off = 0;
+	a->cache = emalloc9p(max(Sess->mtu, MTU));
+
+	if(ptype(r->fid->qid.path) == Pinfo){
+		if(makeinfo(pindex(r->fid->qid.path)) != -1)
+			respond(r, nil);
+		else
+			respond(r, "cannot generate info");
+		return;
+	}
+
+	if(r->fid->qid.type & QTDIR){
+		respond(r, nil);
+		return;
+	}
+
+	if(Sess->caps & CAP_NT_SMBS)
+		rc = ntcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777,
+			0, 0, &fi);
+	else
+		rc = smbcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777,
+			0, 0, &fi);
+	if(rc == -1){
+		responderrstr(r);
+		return;
+	}
+	respond(r, nil);
+}
+
+static void
+fswrite(Req *r)
+{
+	vlong n, m, got, len = r->ifcall.count, off = r->ifcall.offset;
+	char *buf = r->ifcall.data;
+	Aux *a = r->fid->aux;
+
+	got = 0;
+	n = Sess->mtu -OVERHEAD;
+	do{
+		if(len - got < n)
+			n = len - got;
+		m = CIFSwrite(Sess, a->sp, a->fh, off + got, buf + got, n);
+		if(m != -1)
+			got += m;
+	} while(got < len && m >= n);
+
+	r->ofcall.count = got;
+	if(m == -1)
+		responderrstr(r);
+	else
+		respond(r, nil);
+}
+
+static void
+fsread(Req *r)
+{
+	vlong n, m, got, len = r->ifcall.count, off = r->ifcall.offset;
+	char *buf = r->ofcall.data;
+	Aux *a = r->fid->aux;
+
+	if(ptype(r->fid->qid.path) == Pinfo){
+		r->ofcall.count = readinfo(pindex(r->fid->qid.path), buf, len,
+			off);
+		respond(r, nil);
+		return;
+	}
+
+	if(r->fid->qid.type & QTDIR){
+		dirread9p(r, dirgen, a);
+		respond(r, nil);
+		return;
+	}
+
+	got = 0;
+	n = Sess->mtu -OVERHEAD;
+	do{
+		if(len - got < n)
+			n = len - got;
+		m = CIFSread(Sess, a->sp, a->fh, off + got, buf + got, n, len);
+		if(m != -1)
+			got += m;
+	} while(got < len && m >= n);
+
+	r->ofcall.count = got;
+	if(m == -1)
+		responderrstr(r);
+	else
+		respond(r, nil);
+}
+
+static void
+fsdestroyfid(Fid *f)
+{
+	Aux *a = f->aux;
+
+	if(ptype(f->qid.path) == Pinfo)
+		freeinfo(pindex(f->qid.path));
+	f->omode = -1;
+	if(! a)
+		return;
+	if(a->fh != -1)
+		if(CIFSclose(Sess, a->sp, a->fh) == -1)
+			fprint(2, "%s: close failed fh=%d %r\n", argv0, a->fh);
+	if(a->sh != -1)
+		if(CIFSfindclose2(Sess, a->sp, a->sh) == -1)
+			fprint(2, "%s: findclose failed sh=%d %r\n",
+				argv0, a->sh);
+	if(a->path)
+		free(a->path);
+	if(a->cache)
+		free(a->cache);
+
+	if(a == Auxroot)
+		Auxroot = a->next;
+	a->prev->next = a->next;
+	a->next->prev = a->prev;
+	if(a->next == a->prev)
+		Auxroot = nil;
+	if(a)
+		free(a);
+}
+
+int
+rdonly(Session *s, Share *sp, char *path, int rdonly)
+{
+	int rc;
+	FInfo fi;
+
+	if(Sess->caps & CAP_NT_SMBS)
+		rc = T2queryall(s, sp, path, &fi);
+	else
+		rc = T2querystandard(s, sp, path, &fi);
+	if(rc == -1)
+		return -1;
+
+	if((rdonly && !(fi.attribs & ATTR_READONLY)) ||
+	    (!rdonly && (fi.attribs & ATTR_READONLY))){
+		fi.attribs &= ~ATTR_READONLY;
+		fi.attribs |= rdonly? ATTR_READONLY: 0;
+		rc = CIFSsetinfo(s, sp, path, &fi);
+	}
+	return rc;
+}
+
+static void
+fsremove(Req *r)
+{
+	int try, rc;
+	char e[ERRMAX];
+	Aux *ap, *a = r->fid->aux;
+
+	*e = 0;
+	if(ptype(r->fid->qid.path) == Proot ||
+	   ptype(r->fid->qid.path) == Pshare){
+		respond(r, "illegal operation");
+		return;
+	}
+
+	/* close all instences of this file/dir */
+	if((ap = Auxroot) != nil)
+		do{
+			if(strcmp(ap->path, a->path) == 0){
+				if(ap->sh != -1)
+					CIFSfindclose2(Sess, ap->sp, ap->sh);
+				ap->sh = -1;
+				if(ap->fh != -1)
+					CIFSclose(Sess, ap->sp, ap->fh);
+				ap->fh = -1;
+			}
+			ap = ap->next;
+		}while(ap != Auxroot);
+	try = 0;
+again:
+	if(r->fid->qid.type & QTDIR)
+		rc = CIFSdeletedirectory(Sess, a->sp, mapfile(a->path));
+	else
+		rc = CIFSdeletefile(Sess, a->sp, mapfile(a->path));
+
+	rerrstr(e, sizeof(e));
+	if(rc == -1 && try++ == 0 && strcmp(e, "permission denied") == 0 &&
+	    rdonly(Sess, a->sp, mapfile(a->path), 0) == 0)
+		goto again;
+	if(rc == -1)
+		responderrstr(r);
+	else
+		respond(r, nil);
+}
+
+static void
+fswstat(Req *r)
+{
+	int fh, result, rc;
+	char *p, *from, *npath;
+	Aux *a = r->fid->aux;
+	FInfo fi, tmpfi;
+
+	if(ptype(r->fid->qid.path) == Proot ||
+	   ptype(r->fid->qid.path) == Pshare){
+		respond(r, "illegal operation");
+		return;
+	}
+
+	if((r->d.uid && r->d.uid[0]) || (r->d.gid && r->d.gid[0])){
+		respond(r, "cannot change ownership");
+		return;
+	}
+
+	/*
+	 * get current info
+	 */
+	if(Sess->caps & CAP_NT_SMBS)
+		rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi);
+	else
+		rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi);
+	if(rc == -1){
+		werrstr("(query) - %r");
+		responderrstr(r);
+		return;
+	}
+
+	/*
+	 * always clear the readonly attribute if set,
+	 * before trying to set any other fields.
+	 * wstat() fails if the file/dir is readonly
+	 * and this function is so full of races - who cares about one more?
+	 */
+	rdonly(Sess, a->sp, mapfile(a->path), 0);
+
+	/*
+	 * rename - one piece of joy, renaming open files
+	 * is legal (sharing permitting).
+	 */
+	if(r->d.name && r->d.name[0]){
+		if((p = strrchr(a->path, '/')) == nil){
+			respond(r, "illegal path");
+			return;
+		}
+		npath = emalloc9p((p-a->path)+strlen(r->d.name)+2);
+		strecpy(npath, npath+(p- a->path)+2, a->path);
+		strcat(npath, r->d.name);
+
+		from = estrdup9p(mapfile(a->path));
+		if(CIFSrename(Sess, a->sp, from, mapfile(npath)) == -1){
+			werrstr("(rename) - %r");
+			responderrstr(r);
+			free(npath);
+			free(from);
+			return;
+		}
+		free(from);
+		free(a->path);
+		a->path = npath;
+	}
+
+	/*
+	 * set the files length, do this before setting
+	 * the file times as open() will alter them
+	 */
+	if(~r->d.length){
+		fi.size = r->d.length;
+
+		if(Sess->caps & CAP_NT_SMBS){
+			if((fh = CIFS_NT_opencreate(Sess, a->sp, mapfile(a->path),
+			    0, FILE_NON_DIRECTORY_FILE,
+	    		    ATTR_NORMAL, GENERIC_WRITE, FILE_SHARE_ALL,
+			    FILE_OPEN_IF, &result, &tmpfi)) == -1){
+				werrstr("(set length, open) - %r");
+				responderrstr(r);
+				return;
+			}
+			rc = T2setfilelength(Sess, a->sp, fh, &fi);
+			CIFSclose(Sess, a->sp, fh);
+			if(rc == -1){
+				werrstr("(set length), set) - %r");
+				responderrstr(r);
+				return;
+			}
+		} else {
+			if((fh = CIFS_SMB_opencreate(Sess, a->sp, mapfile(a->path),
+			    1, ATTR_NORMAL, 1, &result)) == -1){
+				werrstr("(set length, open) failed - %r");
+				responderrstr(r);
+				return;
+			}
+			rc = CIFSwrite(Sess, a->sp, fh, fi.size, 0, 0);
+			CIFSclose(Sess, a->sp, fh);
+			if(rc == -1){
+				werrstr("(set length, write) - %r");
+				responderrstr(r);
+				return;
+			}
+		}
+	}
+
+	/*
+	 * This doesn't appear to set length or
+	 * attributes, no idea why, so I do those seperately
+	 */
+	if(~r->d.mtime || ~r->d.atime){
+		if(~r->d.mtime)
+			fi.written = r->d.mtime;
+		if(~r->d.atime)
+			fi.accessed = r->d.atime;
+		if(T2setpathinfo(Sess, a->sp, mapfile(a->path), &fi) == -1){
+			werrstr("(set path info) - %r");
+			responderrstr(r);
+			return;
+		}
+	}
+
+	/*
+	 * always update the readonly flag as
+	 * we may have cleared it above.
+	 */
+	if(~r->d.mode)
+		if(r->d.mode & 0222)
+			fi.attribs &= ~ATTR_READONLY;
+		else
+			fi.attribs |= ATTR_READONLY;
+	if(rdonly(Sess, a->sp, mapfile(a->path), fi.attribs & ATTR_READONLY) == -1){
+		werrstr("(set info) - %r");
+		responderrstr(r);
+		return;
+	}
+
+	/*
+	 * Win95 has a broken write-behind cache for metadata
+	 * on open files (writes go to the cache, reads bypass
+	 * the cache), so we must flush the file.
+	 */
+	if(r->fid->omode != -1 && CIFSflush(Sess, a->sp, a->fh) == -1){
+		werrstr("(flush) %r");
+		responderrstr(r);
+		return;
+	}
+	respond(r, nil);
+}
+
+static void
+fsend(Srv *srv)
+{
+	int i;
+	USED(srv);
+
+	for(i = 0; i < Nshares; i++)
+		CIFStreedisconnect(Sess, Shares+i);
+	CIFSlogoff(Sess);
+	postnote(PNPROC, Keeppid, "die");
+}
+
+Srv fs = {
+	.destroyfid =	fsdestroyfid,
+	.attach=	fsattach,
+	.open=		fsopen,
+	.create=	fscreate,
+	.read=		fsread,
+	.write=		fswrite,
+	.remove=	fsremove,
+	.stat=		fsstat,
+	.wstat=		fswstat,
+	.clone= 	fsclone,
+	.walk1= 	fswalk1,
+	.end=		fsend,
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-d name] [-Dvb] [-a auth-method] [-s srvname] "
+		"[-n called-name] [-k factotum-params] [-m mntpnt] "
+		"host [share...]\n", argv0);
+	exits("usage");
+}
+
+/*
+ * SMBecho looks like the function to use for keepalives,
+ * sadly the echo packet does not seem to reload the
+ * idle timer in Microsoft's servers.  Instead we use
+ * "get file system size" on each share until we get one that succeeds.
+ */
+static void
+keepalive(void)
+{
+	int fd, i, rc = 0;
+	uvlong tot, fre;
+	char buf[32];
+
+	snprint(buf, sizeof buf, "#p/%d/args", getpid());
+	if((fd = open(buf, OWRITE)) >= 0){
+		fprint(fd, "%s keepalive", Host);
+		close(fd);
+	}
+
+	do{
+		sleep(6000);
+		if(Active-- != 0)
+			continue;
+		for(i = 0; i < Nshares; i++)
+			if((rc = T2fssizeinfo(Sess, Shares+i, &tot, &fre)) != -1)
+				break;
+	}while(rc != -1);
+	postnote(PNPROC, Attachpid, "die");
+}
+
+
+static void
+ding(void *u, char *msg)
+{
+	USED(u);
+	if(strstr(msg, "alarm") != nil)
+		noted(NCONT);
+	noted(NDFLT);
+}
+
+void
+dmpkey(char *s, void *v, int n)
+{
+	int i;
+	uchar *p = (uchar *)v;
+
+	print("%s", s);
+	for(i = 0; i < n; i++)
+		print("%02ux ", *p++);
+	print("\n");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, n;
+	long svrtime;
+	char windom[64], cname[64];
+	char *method, *sysname, *keyp, *mtpt, *svs;
+	static char *sh[1024];
+
+	*cname = 0;
+	keyp = "";
+	method = nil;
+	strcpy(windom, "unknown");
+	mtpt = svs = nil;
+
+	notify(ding);
+
+	ARGBEGIN{
+	case 'a':
+		method = EARGF(autherr());
+		break;
+	case 'b':
+		Billtrog ^= 1;
+		break;
+	case 'D':
+		chatty9p++;
+		break;
+	case 'd':
+		Debug = EARGF(usage());
+		break;
+	case 'k':
+		keyp = EARGF(usage());
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 'n':
+		strncpy(cname, EARGF(usage()), sizeof(cname));
+		cname[sizeof(cname) -1] = 0;
+		break;
+	case 's':
+		svs = EARGF(usage());
+		break;
+	case 't':
+		Dfstout = atoi(EARGF(usage()));
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND
+
+	if(argc < 1)
+		usage();
+
+	Host = argv[0];
+
+	if(mtpt == nil && svs == nil)
+		mtpt = smprint("/n/%s", Host);
+
+	if((sysname = getenv("sysname")) == nil)
+		sysname = "unknown";
+
+	if(*cname && (Sess = cifsdial(Host, cname, sysname)) != nil)
+		goto connected;
+
+	if(calledname(Host, cname) == 0 &&
+	    (Sess = cifsdial(Host, cname, sysname)) != nil)
+		goto connected;
+
+	strcpy(cname, Host);
+	if((Sess = cifsdial(Host, Host, sysname)) != nil ||
+	   (Sess = cifsdial(Host, "*SMBSERVER", sysname)) != nil)
+		goto connected;
+	sysfatal("%s - cannot dial, %r\n", Host);
+connected:
+	if(CIFSnegotiate(Sess, &svrtime, windom, sizeof windom, cname, sizeof cname) == -1)
+		sysfatal("%s - cannot negioate common protocol, %r\n", Host);
+
+#ifndef DEBUG_MAC
+	Sess->secmode &= ~SECMODE_SIGN_ENABLED;
+#endif
+
+	Sess->auth = getauth(method, windom, keyp, Sess->secmode, Sess->chal,
+		Sess->challen);
+
+	if(CIFSsession(Sess) < 0)
+		sysfatal("session authentication failed, %r\n");
+
+	Sess->slip = svrtime - time(nil);
+	Sess->cname = strlwr(estrdup9p(cname));
+
+	if(CIFStreeconnect(Sess, cname, "IPC$", &Ipc) == -1)
+		fprint(2, "IPC$, %r - can't connect\n");
+
+	Nshares = 0;
+	if(argc == 1){
+		Share *sip;
+
+		if((n = RAPshareenum(Sess, &Ipc, &sip)) < 1)
+			sysfatal("can't enumerate shares: %r - specify share "
+				"names on command line\n");
+
+		for(i = 0; i < n; i++){
+#ifdef NO_HIDDEN_SHARES
+			int l = strlen(sip[i].name);
+
+			if(l > 1 && sip[i].name[l-1] == '$'){
+				free(sip[i].name);
+				continue;
+			}
+#endif
+			memcpy(Shares+Nshares, sip+i, sizeof(Share));
+			if(CIFStreeconnect(Sess, Sess->cname,
+			    Shares[Nshares].name, Shares+Nshares) == -1){
+				free(Shares[Nshares].name);
+				continue;
+			}
+			Nshares++;
+		}
+		free(sip);
+	} else
+		for(i = 1; i < argc; i++){
+			if(CIFStreeconnect(Sess, Sess->cname, argv[i],
+			    Shares+Nshares) == -1){
+				fprint(2, "%s: %s  %q - can't connect to share"
+					", %r\n", argv0, Host, argv[i]);
+				continue;
+			}
+			Shares[Nshares].name = strlwr(estrdup9p(argv[i]));
+			Nshares++;
+		}
+
+	if(Nshares == 0)
+		fprint(2, "no available shares\n");
+
+	if((Keeppid = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG|RFNAMEG)) == 0){
+		keepalive();
+		exits(nil);
+	}
+	postmountsrv(&fs, svs, mtpt, MREPL|MCREATE);
+	exits(nil);
+}

+ 25 - 0
sys/src/cmd/cifs/misc.c

@@ -0,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+char*
+strupr(char *s)
+{
+	char *p;
+
+	for(p = s; *p; p++)
+		if(*p >= 0 && islower(*p))
+			*p = toupper(*p);
+	return s;
+}
+
+char*
+strlwr(char *s)
+{
+	char *p;
+
+	for(p = s; *p; p++)
+		if(*p >= 0 && isupper(*p))
+			*p = tolower(*p);
+	return s;
+}

+ 20 - 0
sys/src/cmd/cifs/mkfile

@@ -0,0 +1,20 @@
+</$objtype/mkfile
+
+# CFLAGS=$CFLAGS -DDEBUG_MAC
+
+TARG=cifs
+HFILES=cifs.h
+BIN=/$objtype/bin/aux
+OFILES= main.$O transnt.$O trans2.$O trans.$O cifs.$O 	\
+	netbios.$O pack.$O info.$O fs.$O sid2name.$O	\
+	misc.$O nterrstr.$O doserrstr.$O raperrstr.$O 	\
+	auth.$O dfs.$O ping.$O
+
+</sys/src/cmd/mkone
+
+install:V:
+	# cp $TARG.man /sys/man/4/$TARG
+
+
+test:V: 8.out
+	8.out se-00 root && ls /n/se-00/root/eng/design/conversion/* && p /n/se-00/Shares

+ 475 - 0
sys/src/cmd/cifs/netbios.c

@@ -0,0 +1,475 @@
+/*
+ * netbios dial, read, write
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+
+enum {
+	MAXNBPKT		= 8096,		/* max netbios packet size */
+	NBquery			= 0,		/* packet type - query */
+
+	NBAdapterStatus		= 0x21,		/* get host interface info */
+	NBInternet		= 1,		/* scope for info */
+
+	NBmessage 		= 0x00,		/* Netbios packet types */
+	NBrequest 		= 0x81,
+	NBpositive,
+	NBnegative,
+	NBretarget,
+	NBkeepalive,
+
+	ISgroup			= 0x8000,
+};
+
+
+static char *NBerr[] = {
+	[0]	"not listening on called name",
+	[1]	"not listening for calling name",
+	[2]	"called name not present",
+	[3]	"insufficient resources",
+	[15]	"unspecified error"
+};
+
+
+static ulong
+GL32(uchar **p)
+{
+	ulong n;
+
+	n  = *(*p)++;
+	n |= *(*p)++ << 8;
+	n |= *(*p)++ << 16;
+	n |= *(*p)++ << 24;
+	return n;
+}
+
+static ushort
+GL16(uchar **p)
+{
+	ushort n;
+
+	n  = *(*p)++;
+	n |= *(*p)++ << 8;
+	return n;
+}
+
+void
+Gmem(uchar **p, void *v, int n)
+{
+	uchar *str = v;
+
+	while(n--)
+		*str++ = *(*p)++;
+}
+
+
+static ulong
+GB32(uchar **p)
+{
+	ulong n;
+
+	n  = *(*p)++ << 24;
+	n |= *(*p)++ << 16;
+	n |= *(*p)++ << 8;
+	n |= *(*p)++;
+	return n;
+}
+
+static ushort
+GB16(uchar **p)
+{
+	ushort n;
+
+	n  = *(*p)++ << 8;
+	n |= *(*p)++;
+	return n;
+}
+
+static uchar
+G8(uchar **p)
+{
+	return *(*p)++;
+}
+
+static void
+PB16(uchar **p, uint n)
+{
+	*(*p)++ = n >> 8;
+	*(*p)++ = n;
+}
+
+static void
+P8(uchar **p, uint n)
+{
+	*(*p)++ = n;
+}
+
+
+static void
+nbname(uchar **p, char *name, char pad)
+{
+	char c;
+	int i;
+	int done = 0;
+
+	*(*p)++ = 0x20;
+	for(i = 0; i < 16; i++) {
+		c = pad;
+		if(!done && name[i] == '\0')
+			done = 1;
+		if(!done)
+			c = toupper(name[i]);
+		*(*p)++ = ((uchar)c >> 4) + 'A';
+		*(*p)++ = (c & 0xf) + 'A';
+	}
+	*(*p)++ = 0;
+}
+
+int
+calledname(char *host, char *name)
+{
+	char *addr;
+	uchar buf[1024], *p;
+	static char tmp[20];
+	int num, flg, svs, j, i, fd, trn;
+
+	trn = (getpid() ^ time(0)) & 0xffff;
+	if((addr = netmkaddr(host, "udp", "137")) == nil)
+		return -1;
+
+	if((fd = dial(addr, "137", 0, 0)) < 0)
+		return -1;
+	p = buf;
+
+	PB16(&p, trn);			/* TRNid */
+	P8(&p, 0);			/* flags */
+	P8(&p, 0x10);			/* type */
+	PB16(&p, 1);			/* # questions */
+	PB16(&p, 0);			/* # answers */
+	PB16(&p, 0);			/* # authority RRs */
+	PB16(&p, 0);			/* # Aditional RRs */
+	nbname(&p, "*", 0);
+	PB16(&p, NBAdapterStatus);
+	PB16(&p, NBInternet);
+
+	if(Debug && strstr(Debug, "dump"))
+		xd(nil, buf, p-buf);
+
+	if(write(fd, buf, p-buf) != p-buf)
+		return -1;
+
+	p = buf;
+	for(i = 0; i < 3; i++){
+		memset(buf, 0, sizeof(buf));
+		alarm(NBNSTOUT);
+		read(fd, buf, sizeof(buf));
+		alarm(0);
+		if(GB16(&p) == trn)
+			break;
+	}
+	close(fd);
+	if(i >= 3)
+		return -1;
+
+	p = buf +56;
+	num = G8(&p);			/* number of names */
+
+	for(i = 0; i < num; i++){
+		memset(tmp, 0, sizeof(tmp));
+		Gmem(&p, tmp, 15);
+		svs = G8(&p);
+		flg = GB16(&p);
+		for(j = 14; j >= 0 && tmp[j] == ' '; j--)
+			tmp[j] = 0;
+		if(svs == 0 && !(flg & ISgroup))
+			strcpy(name, tmp);
+	}
+	return 0;
+}
+
+
+int
+nbtdial(char *addr, char *called, char *sysname)
+{
+	char redir[20];
+	uchar *p, *lenp, buf[1024];
+	int type, len, err, fd, nkeepalive, nretarg;
+
+	nretarg = 0;
+	nkeepalive = 0;
+Redial:
+	if((addr = netmkaddr(addr, "tcp", "139")) == nil ||
+	    (fd = dial(addr, 0, 0, 0)) < 0)
+		return -1;
+
+	memset(buf, 0, sizeof(buf));
+
+	p = buf;
+	P8(&p, NBrequest);		/* type */
+	P8(&p, 0);			/* flags */
+	lenp = p; PB16(&p, 0);		/* length placeholder */
+	nbname(&p, called, ' ');	/* remote NetBios name */
+	nbname(&p, sysname, ' ');	/* our machine name */
+	PB16(&lenp, p-lenp -2);		/* length re-write */
+
+	if(Debug && strstr(Debug, "dump"))
+		xd(nil, buf, p-buf);
+	if(write(fd, buf, p-buf) != p-buf)
+		goto Error;
+Reread:
+	p = buf;
+	memset(buf, 0, sizeof(buf));
+	if(readn(fd, buf, 4) < 4)
+		goto Error;
+
+	type = G8(&p);
+	G8(&p);				/* flags */
+	len = GB16(&p);
+
+	if(readn(fd, buf +4, len -4) < len -4)
+		goto Error;
+
+	if(Debug && strstr(Debug, "dump"))
+		xd(nil, buf, len+4);
+
+	switch(type) {
+	case NBpositive:
+		return fd;
+	case NBnegative:
+		if(len < 1) {
+			werrstr("nbdial: bad error pkt");
+			goto Error;
+		}
+		err = G8(&p);
+		if(err < 0 || err > nelem(NBerr) || NBerr[err] == nil)
+			werrstr("NBT: %d - unknown error", err);
+		else
+			werrstr("NBT: %s", NBerr[err]);
+
+		goto Error;
+	case NBkeepalive:
+		if(++nkeepalive >= 16){
+			werrstr("nbdial: too many keepalives");
+			goto Error;
+		}
+		goto Reread;
+
+	case NBretarget:
+		if(++nretarg >= 16) {
+			werrstr("nbdial: too many redirects");
+			goto Error;
+		}
+		if(len < 4) {
+			werrstr("nbdial: bad redirect pkt");
+			goto Error;
+		}
+		sprint(redir, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+		addr = redir;
+		goto Redial;
+
+	default:
+		werrstr("nbdial: 0x%x - unknown packet in netbios handshake", type);
+		goto Error;
+	}
+Error:
+	close(fd);
+	return -1;
+}
+
+void
+nbthdr(Pkt *p)
+{
+	p->pos = p->buf;
+	memset(p->buf, 0xa5, MTU);
+
+	p8(p, NBmessage);		/* type */
+	p8(p, 0);			/* flags */
+	pb16(p, 0);			/* length (filled in later) */
+}
+
+int
+nbtrpc(Pkt *p)
+{
+	int len, got, type, nkeep;
+
+	len = p->pos - p->buf;
+
+	p->pos = p->buf +2;
+	pb16(p, len - NBHDRLEN);	/* length */
+
+	if(Debug && strstr(Debug, "dump"))
+		xd("tx", p->buf, len);
+
+	alarm(NBRPCTOUT);
+	if(write(p->s->fd, p->buf, len) != len){
+		werrstr("nbtrpc: write failed - %r");
+		alarm(0);
+		return -1;
+	}
+
+	nkeep = 0;
+retry:
+	p->pos = p->buf;
+	memset(p->buf, 0xa5, MTU);
+
+	got = readn(p->s->fd, p->buf, NBHDRLEN);
+
+	if(got < NBHDRLEN){
+		werrstr("nbtrpc: short read - %r");
+		alarm(0);
+		return -1;
+	}
+	p->eop = p->buf + got;
+
+	type = g8(p);			/* NBT type (session) */
+	if(type == NBkeepalive){
+		if(++nkeep > 16) {
+			werrstr("nbtrpc: too many keepalives (%d attempts)", nkeep);
+			alarm(0);
+			return -1;
+		}
+		goto retry;
+	}
+
+	g8(p);				/* NBT flags (none) */
+
+	len = gb16(p);			/* NBT payload length */
+	if((len +NBHDRLEN) > MTU){
+		werrstr("nbtrpc: packet bigger than MTU, (%d > %d)", len, MTU);
+		alarm(0);
+		return -1;
+	}
+
+	got = readn(p->s->fd, p->buf +NBHDRLEN, len);
+	alarm(0);
+
+	if(Debug && strstr(Debug, "dump"))
+		xd("rx", p->buf, got +NBHDRLEN);
+
+	if(got < 0)
+		return -1;
+	p->eop = p->buf + got +NBHDRLEN;
+	return got+NBHDRLEN;
+}
+
+
+void
+xd(char *str, void *buf, int n)
+{
+	int fd, flg, flags2, cmd;
+	uint sum;
+	long err;
+	uchar *p, *end;
+
+	if(n == 0)
+		return;
+
+	p = buf;
+	end = (uchar *)buf +n;
+
+	if(Debug && strstr(Debug, "log") != nil){
+		if((fd = open("pkt.log", ORDWR)) == -1)
+			return;
+		seek(fd, 0, 2);
+		fprint(fd, "%d	", 0);
+		while(p < end)
+			fprint(fd, "%02x ", *p++);
+		fprint(fd, "\n");
+		close(fd);
+		return;
+	}
+
+	if(!str)
+		goto Raw;
+
+	p = (uchar *)buf + 4;
+	if(GL32(&p) == 0x424d53ff){
+		buf = (uchar *)buf + 4;
+		n -= 4;
+	}
+	end = (uchar *)buf + n;
+
+	sum = 0;
+	p = buf;
+	while(p < end)
+		sum += *p++;
+	p = buf;
+
+	fprint(2, "%s : len=%ud sum=%d\n", str, n, sum);
+
+	fprint(2, "mag=0x%ulx ", GL32(&p));
+	fprint(2, "cmd=0x%ux ", cmd = G8(&p));
+	fprint(2, "err=0x%ulx ", err=GL32(&p));
+	fprint(2, "flg=0x%02ux ", flg = G8(&p));
+	fprint(2, "flg2=0x%04ux\n", flags2= GL16(&p));
+	fprint(2, "dfs=%s\n", (flags2 & FL2_DFS)? "y": "n");
+
+	fprint(2, "pidl=%ud ", GL16(&p));
+	fprint(2, "res=%uld ", GL32(&p));
+	fprint(2, "sid=%ud ", GL16(&p));
+	fprint(2, "seq=0x%ux ", GL16(&p));
+	fprint(2, "pad=%ud ", GL16(&p));
+
+	fprint(2, "tid=%ud ", GL16(&p));
+	fprint(2, "pid=%ud ", GL16(&p));
+	fprint(2, "uid=%ud ", GL16(&p));
+	fprint(2, "mid=%ud\n", GL16(&p));
+
+	if(cmd == 0x32 && (flg & 0x80) == 0){		/* TRANS 2, TX */
+		fprint(2, "words=%ud ", G8(&p));
+		fprint(2, "totparams=%ud ", GL16(&p));
+		fprint(2, "totdata=%ud ", GL16(&p));
+		fprint(2, "maxparam=%ud ", GL16(&p));
+		fprint(2, "maxdata=%ud\n", GL16(&p));
+		fprint(2, "maxsetup=%ud ", G8(&p));
+		fprint(2, "reserved=%ud ", G8(&p));
+		fprint(2, "flags=%ud ", GL16(&p));
+		fprint(2, "timeout=%uld\n", GL32(&p));
+		fprint(2, "reserved=%ud ", GL16(&p));
+		fprint(2, "paramcnt=%ud ", GL16(&p));
+		fprint(2, "paramoff=%ud ", GL16(&p));
+		fprint(2, "datacnt=%ud ", GL16(&p));
+		fprint(2, "dataoff=%ud ", GL16(&p));
+		fprint(2, "setupcnt=%ud ", G8(&p));
+		fprint(2, "reserved=%ud\n", G8(&p));
+		fprint(2, "trans2=0x%02x ", GL16(&p));
+		fprint(2, "data-words=%d ", G8(&p));
+		fprint(2, "padding=%d\n", G8(&p));
+	}
+	if(cmd == 0x32 && (flg & 0x80) == 0x80){	/* TRANS 2, RX */
+		fprint(2, "words=%ud ", G8(&p));
+		fprint(2, "totparams=%ud ", GL16(&p));
+		fprint(2, "totdata=%ud ", GL16(&p));
+		fprint(2, "reserved=%ud ", GL16(&p));
+		fprint(2, "paramcnt=%ud\n", GL16(&p));
+		fprint(2, "paramoff=%ud ", GL16(&p));
+		fprint(2, "paramdisp=%ud ", GL16(&p));
+		fprint(2, "datacnt=%ud\n", GL16(&p));
+		fprint(2, "dataoff=%ud ", GL16(&p));
+		fprint(2, "datadisp=%ud ", GL16(&p));
+		fprint(2, "setupcnt=%ud ", G8(&p));
+		fprint(2, "reserved=%ud\n", G8(&p));
+	}
+	if(err)
+		if(flags2 & FL2_NT_ERRCODES)
+			fprint(2, "err=%s\n", nterrstr(err));
+		else
+			fprint(2, "err=%s\n", doserrstr(err));
+Raw:
+	fprint(2, "\n");
+	for(; p < end; p++){
+		if((p - (uchar *)buf) % 16 == 0)
+			fprint(2, "\n%06lx\t", p - (uchar *)buf);
+		if(isprint((char)*p))
+			fprint(2, "%c  ", (char )*p);
+		else
+			fprint(2, "%02ux ", *p);
+	}
+	fprint(2, "\n");
+}

+ 1019 - 0
sys/src/cmd/cifs/nterrstr.c

@@ -0,0 +1,1019 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * some lines commented 4APE have been changed to
+ * make them the same as plan9 error messages.  This is not
+ * a problem for native programs but those built on APE
+ * will give unhelpful errors if this is not done
+ */
+
+static struct {
+	char	*msg;
+	int	err;
+} NTerrs[] = {
+	{ "success",					0x0 },
+	{ "wait 1",					0x1 },
+	{ "wait 2",					0x2 },
+	{ "wait 3",					0x3 },
+	{ "wait 63",					0x3f },
+	{ "abandoned",					0x80 },
+	{ "abandoned wait 63",				0xbf },
+	{ "user apc",					0xc0 },
+	{ "kernel apc",					0x100 },
+	{ "alerted",					0x101 },
+	{ "timeout",					0x102 },
+	{ "pending",					0x103 },
+	{ "reparse",					0x104 },
+	{ "more entries",				0x105 },
+	{ "not all assigned",				0x106 },
+	{ "some not mapped",				0x107 },
+	{ "oplock break in progress",			0x108 },
+	{ "volume mounted",				0x109 },
+	{ "rxact committed",				0x10a },
+	{ "notify cleanup",				0x10b },
+	{ "notify enum dir",				0x10c },
+	{ "no quotas for account",			0x10d },
+	{ "primary transport connect failed",		0x10e },
+	{ "page fault transition",			0x110 },
+	{ "page fault demand zero",			0x111 },
+	{ "page fault copy on write",			0x112 },
+	{ "page fault guard page",			0x113 },
+	{ "page fault paging file",			0x114 },
+	{ "cache page locked",				0x115 },
+	{ "crash dump",					0x116 },
+	{ "buffer all zeros",				0x117 },
+	{ "reparse object",				0x118 },
+	{ "resource requirements changed",		0x119 },
+	{ "translation complete",			0x120 },
+	{ "ds membership evaluated locally",		0x121 },
+	{ "nothing to terminate",			0x122 },
+	{ "process not in job",				0x123 },
+	{ "process in job",				0x124 },
+	{ "wait for oplock",				0x367 },
+	{ "object name exists",				0x40000000 },
+	{ "thread was suspended",			0x40000001 },
+	{ "working set limit range",			0x40000002 },
+	{ "image not at base",				0x40000003 },
+	{ "rxact state created",			0x40000004 },
+	{ "segment notification",			0x40000005 },
+	{ "local user session key",			0x40000006 },
+	{ "bad current directory",			0x40000007 },
+	{ "serial more writes",				0x40000008 },
+	{ "registry recovered",				0x40000009 },
+	{ "ft read recovery from backup",		0x4000000a },
+	{ "ft write recovery",				0x4000000b },
+	{ "serial counter timeout",			0x4000000c },
+	{ "null LM password",				0x4000000d },
+	{ "image machine type mismatch",		0x4000000e },
+	{ "receive partial",				0x4000000f },
+	{ "receive expedited",				0x40000010 },
+	{ "receive partial expedited",			0x40000011 },
+	{ "event done",					0x40000012 },
+	{ "event pending",				0x40000013 },
+	{ "checking file system",			0x40000014 },
+	{ "fatal app exit",				0x40000015 },
+	{ "predefined handle",				0x40000016 },
+	{ "was unlocked",				0x40000017 },
+	{ "service notification",			0x40000018 },
+	{ "was locked",					0x40000019 },
+	{ "log hard error",				0x4000001a },
+	{ "already win32",				0x4000001b },
+	{ "wx86 unsimulate",				0x4000001c },
+	{ "wx86 continue",				0x4000001d },
+	{ "wx86 single step",				0x4000001e },
+	{ "wx86 breakpoint",				0x4000001f },
+	{ "wx86 exception continue",			0x40000020 },
+	{ "wx86 exception lastchance",			0x40000021 },
+	{ "wx86 exception chain",			0x40000022 },
+	{ "image machine type mismatch exe",		0x40000023 },
+	{ "no yield performed",				0x40000024 },
+	{ "timer resume ignored",			0x40000025 },
+	{ "arbitration unhandled",			0x40000026 },
+	{ "cardbus not supported",			0x40000027 },
+	{ "wx86 createwx86tib",				0x40000028 },
+	{ "MP processor mismatch",			0x40000029 },
+	{ "hibernated",					0x4000002a },
+	{ "resume hibernation",				0x4000002b },
+	{ "wake system",				0x40000294 },
+	{ "ds shutting down",				0x40000370 },
+	{ "CTX cdm connect",				0x400a0004 },
+	{ "CTX cdm disconnect",				0x400a0005 },
+	{ "SXS release activation context",		0x4015000d },
+	{ "guard page violation",			0x80000001 },
+	{ "datatype misalignment",			0x80000002 },
+	{ "breakpoint",					0x80000003 },
+	{ "single step",				0x80000004 },
+	{ "buffer overflow",				0x80000005 },
+	{ "no more files",				0x80000006 },
+	{ "wake system debugger",			0x80000007 },
+	{ "handles closed",				0x8000000a },
+	{ "no inheritance",				0x8000000b },
+	{ "GUID substitution made",			0x8000000c },
+	{ "partial copy",				0x8000000d },
+	{ "device paper empty",				0x8000000e },
+	{ "device powered off",				0x8000000f },
+	{ "device off line",				0x80000010 },
+	{ "device busy",				0x80000011 },
+	{ "no more EAs",				0x80000012 },
+	{ "invalid EA name",				0x80000013 },
+	{ "ea list inconsistent",			0x80000014 },
+	{ "invalid ea flag",				0x80000015 },
+	{ "verify required",				0x80000016 },
+	{ "extraneous information",			0x80000017 },
+	{ "rxact commit necessary",			0x80000018 },
+	{ "no more entries",				0x8000001a },
+	{ "filemark detected",				0x8000001b },
+	{ "media changed",				0x8000001c },
+	{ "bus reset",					0x8000001d },
+	{ "end of media",				0x8000001e },
+	{ "beginning of media",				0x8000001f },
+	{ "media check",				0x80000020 },
+	{ "setmark detected",				0x80000021 },
+	{ "no data detected",				0x80000022 },
+	{ "redirector has open handles",		0x80000023 },
+	{ "server has open handles",			0x80000024 },
+	{ "already disconnected",			0x80000025 },
+	{ "longjump",					0x80000026 },
+	{ "cleaner cartridge installed",		0x80000027 },
+	{ "plugplay query vetoed",			0x80000028 },
+	{ "unwind consolidate",				0x80000029 },
+	{ "device requires cleaning",			0x80000288 },
+	{ "device door open",				0x80000289 },
+	{ "cluster node already up",			0x80130001 },
+	{ "cluster node already down",			0x80130002 },
+	{ "cluster network already online",		0x80130003 },
+	{ "cluster network already offline",		0x80130004 },
+	{ "cluster node already member",		0x80130005 },
+	{ "unsuccessful",				0xc0000001 },
+	{ "not implemented",				0xc0000002 },
+	{ "invalid info class",				0xc0000003 },
+	{ "info length mismatch",			0xc0000004 },
+	{ "access violation",				0xc0000005 },
+	{ "in page error",				0xc0000006 },
+	{ "pagefile quota",				0xc0000007 },
+	{ "invalid handle",				0xc0000008 },
+	{ "bad initial stack",				0xc0000009 },
+	{ "bad initial PC",				0xc000000a },
+	{ "invalid CID",				0xc000000b },
+	{ "timer not canceled",				0xc000000c },
+	{ "invalid parameter",				0xc000000d },
+	{ "no such device",				0xc000000e },
+	{ "no such file",				0xc000000f },
+	{ "invalid device request",			0xc0000010 },
+	{ "end of file",				0xc0000011 },
+	{ "wrong volume",				0xc0000012 },
+	{ "no media in device",				0xc0000013 },
+	{ "unrecognized media",				0xc0000014 },
+	{ "nonexistent sector",				0xc0000015 },
+	{ "more processing required",			0xc0000016 },
+	{ "no memory",					0xc0000017 },
+	{ "conflicting addresses",			0xc0000018 },
+	{ "not mapped view",				0xc0000019 },
+	{ "unable to free VM",				0xc000001a },
+	{ "unable to delete section",			0xc000001b },
+	{ "invalid system service",			0xc000001c },
+	{ "illegal instruction",			0xc000001d },
+	{ "invalid lock sequence",			0xc000001e },
+	{ "invalid view size",				0xc000001f },
+	{ "invalid file for section",			0xc0000020 },
+	{ "already committed",				0xc0000021 },
+	{ "permission denied",				0xc0000022 },
+//4APE	{ "access denied",				0xc0000022 },
+	{ "buffer too small",				0xc0000023 },
+	{ "object type mismatch",			0xc0000024 },
+	{ "noncontinuable exception",			0xc0000025 },
+	{ "invalid disposition",			0xc0000026 },
+	{ "unwind",					0xc0000027 },
+	{ "bad stack",					0xc0000028 },
+	{ "invalid unwind target",			0xc0000029 },
+	{ "not locked",					0xc000002a },
+	{ "parity error",				0xc000002b },
+	{ "unable to decommit VM",			0xc000002c },
+	{ "not committed",				0xc000002d },
+	{ "invalid port attributes",			0xc000002e },
+	{ "port message too long",			0xc000002f },
+	{ "invalid parameter mix",			0xc0000030 },
+	{ "invalid quota lower",			0xc0000031 },
+	{ "disk corrupt error",				0xc0000032 },
+	{ "file name syntax",				0xc0000033 },
+//4APE	{ "object name invalid",			0xc0000033 },
+	{ "does not exist",				0xc0000034 },
+//4APE	{ "object name not found",			0xc0000034 },
+	{ "create -- file exists",			0xc0000035 },
+//4APE	{ "object name collision",			0xc0000035 },
+	{ "port disconnected",				0xc0000037 },
+	{ "device already attached",			0xc0000038 },
+	{ "does not exist",				0xc0000039 },
+//4APE	{ "object path invalid",			0xc0000039 },
+	{ "does not exist",				0xc000003a },
+//4APE	{ "object path not found",			0xc000003a },
+	{ "file name syntax",				0xc000003b },
+//4APE	{ "object path syntax bad",			0xc000003b },
+	{ "data overrun",				0xc000003c },
+	{ "data late error",				0xc000003d },
+	{ "data error",					0xc000003e },
+	{ "crc error",					0xc000003f },
+	{ "section too big",				0xc0000040 },
+	{ "port connection refused",			0xc0000041 },
+	{ "invalid port handle",			0xc0000042 },
+	{ "sharing violation",				0xc0000043 },
+	{ "quota exceeded",				0xc0000044 },
+	{ "invalid page protection",			0xc0000045 },
+	{ "mutant not owned",				0xc0000046 },
+	{ "semaphore limit exceeded",			0xc0000047 },
+	{ "port already set",				0xc0000048 },
+	{ "section not image",				0xc0000049 },
+	{ "suspend count exceeded",			0xc000004a },
+	{ "thread is terminating",			0xc000004b },
+	{ "bad working set limit",			0xc000004c },
+	{ "incompatible file map",			0xc000004d },
+	{ "section protection",				0xc000004e },
+	{ "EAs not supported",				0xc000004f },
+	{ "EA too large",				0xc0000050 },
+	{ "nonexistent ea entry",			0xc0000051 },
+	{ "no EAs on file",				0xc0000052 },
+	{ "EA corrupt error",				0xc0000053 },
+	{ "file lock conflict",				0xc0000054 },
+	{ "lock not granted",				0xc0000055 },
+	{ "delete pending",				0xc0000056 },
+	{ "ctl file not supported",			0xc0000057 },
+	{ "unknown revision",				0xc0000058 },
+	{ "revision mismatch",				0xc0000059 },
+	{ "invalid owner",				0xc000005a },
+	{ "invalid primary group",			0xc000005b },
+	{ "no impersonation token",			0xc000005c },
+	{ "cant disable mandatory",			0xc000005d },
+	{ "no logon servers",				0xc000005e },
+	{ "no such logon session",			0xc000005f },
+	{ "no such privilege",				0xc0000060 },
+	{ "privilege not held",				0xc0000061 },
+	{ "invalid account name",			0xc0000062 },
+	{ "user exists",				0xc0000063 },
+	{ "no such user",				0xc0000064 },
+	{ "group exists",				0xc0000065 },
+	{ "no such group",				0xc0000066 },
+	{ "member in group",				0xc0000067 },
+	{ "member not in group",			0xc0000068 },
+	{ "last admin",					0xc0000069 },
+	{ "wrong password",				0xc000006a },
+	{ "ill-formed password",			0xc000006b },
+	{ "password restriction",			0xc000006c },
+	{ "logon failure",				0xc000006d },
+	{ "account restriction",			0xc000006e },
+	{ "invalid logon hours",			0xc000006f },
+	{ "invalid workstation",			0xc0000070 },
+	{ "password expired",				0xc0000071 },
+	{ "account disabled",				0xc0000072 },
+	{ "none mapped",				0xc0000073 },
+	{ "too many luids requested",			0xc0000074 },
+	{ "luids exhausted",				0xc0000075 },
+	{ "invalid sub authority",			0xc0000076 },
+	{ "invalid ACL",				0xc0000077 },
+	{ "invalid SID",				0xc0000078 },
+	{ "invalid security descr",			0xc0000079 },
+	{ "procedure not found",			0xc000007a },
+	{ "invalid image format",			0xc000007b },
+	{ "no token",					0xc000007c },
+	{ "bad inheritance ACL",			0xc000007d },
+	{ "range not locked",				0xc000007e },
+	{ "disk full",					0xc000007f },
+	{ "server disabled",				0xc0000080 },
+	{ "server not disabled",			0xc0000081 },
+	{ "too many guids requested",			0xc0000082 },
+	{ "guids exhausted",				0xc0000083 },
+	{ "invalid id authority",			0xc0000084 },
+	{ "agents exhausted",				0xc0000085 },
+	{ "invalid volume label",			0xc0000086 },
+	{ "section not extended",			0xc0000087 },
+	{ "not mapped data",				0xc0000088 },
+	{ "resource data not found",			0xc0000089 },
+	{ "resource type not found",			0xc000008a },
+	{ "resource name not found",			0xc000008b },
+	{ "array bounds exceeded",			0xc000008c },
+	{ "float denormal operand",			0xc000008d },
+	{ "float divide by zero",			0xc000008e },
+	{ "float inexact result",			0xc000008f },
+	{ "float invalid operation",			0xc0000090 },
+	{ "float overflow",				0xc0000091 },
+	{ "float stack check",				0xc0000092 },
+	{ "float underflow",				0xc0000093 },
+	{ "integer divide by zero",			0xc0000094 },
+	{ "integer overflow",				0xc0000095 },
+	{ "privileged instruction",			0xc0000096 },
+	{ "too many paging files",			0xc0000097 },
+	{ "file invalid",				0xc0000098 },
+	{ "allotted space exceeded",			0xc0000099 },
+	{ "insufficient resources",			0xc000009a },
+	{ "dfs exit path found",			0xc000009b },
+	{ "device data error",				0xc000009c },
+	{ "device not connected",			0xc000009d },
+	{ "device power failure",			0xc000009e },
+	{ "free VM not at base",			0xc000009f },
+	{ "memory not allocated",			0xc00000a0 },
+	{ "working set quota",				0xc00000a1 },
+	{ "media write protected",			0xc00000a2 },
+	{ "device not ready",				0xc00000a3 },
+	{ "invalid group attributes",			0xc00000a4 },
+	{ "bad impersonation level",			0xc00000a5 },
+	{ "cant open anonymous",			0xc00000a6 },
+	{ "bad validation class",			0xc00000a7 },
+	{ "bad token type",				0xc00000a8 },
+	{ "bad master boot record",			0xc00000a9 },
+	{ "instruction misalignment",			0xc00000aa },
+	{ "instance not available",			0xc00000ab },
+	{ "pipe not available",				0xc00000ac },
+	{ "invalid pipe state",				0xc00000ad },
+	{ "pipe busy",					0xc00000ae },
+	{ "illegal function",				0xc00000af },
+	{ "pipe disconnected",				0xc00000b0 },
+	{ "pipe closing",				0xc00000b1 },
+	{ "pipe connected",				0xc00000b2 },
+	{ "pipe listening",				0xc00000b3 },
+	{ "invalid read mode",				0xc00000b4 },
+	{ "IO timeout",					0xc00000b5 },
+	{ "file forced closed",				0xc00000b6 },
+	{ "profiling not started",			0xc00000b7 },
+	{ "profiling not stopped",			0xc00000b8 },
+	{ "could not interpret",			0xc00000b9 },
+	{ "file is a directory",			0xc00000ba },
+	{ "not supported",				0xc00000bb },
+	{ "remote not listening",			0xc00000bc },
+	{ "duplicate name",				0xc00000bd },
+	{ "bad network path",				0xc00000be },
+	{ "network busy",				0xc00000bf },
+	{ "device does not exist",			0xc00000c0 },
+	{ "too many commands",				0xc00000c1 },
+	{ "adapter hardware error",			0xc00000c2 },
+	{ "invalid network response",			0xc00000c3 },
+	{ "unexpected network error",			0xc00000c4 },
+	{ "bad remote adapter",				0xc00000c5 },
+	{ "print queue full",				0xc00000c6 },
+	{ "no spool space",				0xc00000c7 },
+	{ "print cancelled",				0xc00000c8 },
+	{ "network name deleted",			0xc00000c9 },
+	{ "network access denied",			0xc00000ca },
+	{ "bad device type",				0xc00000cb },
+	{ "bad network name",				0xc00000cc },
+	{ "too many names",				0xc00000cd },
+	{ "too many sessions",				0xc00000ce },
+	{ "sharing paused",				0xc00000cf },
+	{ "request not accepted",			0xc00000d0 },
+	{ "redirector paused",				0xc00000d1 },
+	{ "net write fault",				0xc00000d2 },
+	{ "profiling at limit",				0xc00000d3 },
+	{ "not same device",				0xc00000d4 },
+	{ "file renamed",				0xc00000d5 },
+	{ "virtual circuit closed",			0xc00000d6 },
+	{ "no security on object",			0xc00000d7 },
+	{ "cant wait",					0xc00000d8 },
+	{ "pipe empty",					0xc00000d9 },
+	{ "cant access domain info",			0xc00000da },
+	{ "cant terminate self",			0xc00000db },
+	{ "invalid server state",			0xc00000dc },
+	{ "invalid domain state",			0xc00000dd },
+	{ "invalid domain role",			0xc00000de },
+	{ "no such domain",				0xc00000df },
+	{ "domain exists",				0xc00000e0 },
+	{ "domain limit exceeded",			0xc00000e1 },
+	{ "oplock not granted",				0xc00000e2 },
+	{ "invalid oplock protocol",			0xc00000e3 },
+	{ "internal DB corruption",			0xc00000e4 },
+	{ "internal error",				0xc00000e5 },
+	{ "generic not mapped",				0xc00000e6 },
+	{ "bad descriptor format",			0xc00000e7 },
+	{ "invalid user buffer",			0xc00000e8 },
+	{ "unexpected io error",			0xc00000e9 },
+	{ "unexpected MM create err",			0xc00000ea },
+	{ "unexpected MM map error",			0xc00000eb },
+	{ "unexpected MM extend err",			0xc00000ec },
+	{ "not logon process",				0xc00000ed },
+	{ "logon session exists",			0xc00000ee },
+	{ "invalid parameter 1",			0xc00000ef },
+	{ "invalid parameter 2",			0xc00000f0 },
+	{ "invalid parameter 3",			0xc00000f1 },
+	{ "invalid parameter 4",			0xc00000f2 },
+	{ "invalid parameter 5",			0xc00000f3 },
+	{ "invalid parameter 6",			0xc00000f4 },
+	{ "invalid parameter 7",			0xc00000f5 },
+	{ "invalid parameter 8",			0xc00000f6 },
+	{ "invalid parameter 9",			0xc00000f7 },
+	{ "invalid parameter 10",			0xc00000f8 },
+	{ "invalid parameter 11",			0xc00000f9 },
+	{ "invalid parameter 12",			0xc00000fa },
+	{ "redirector not started",			0xc00000fb },
+	{ "redirector started",				0xc00000fc },
+	{ "stack overflow",				0xc00000fd },
+	{ "no such package",				0xc00000fe },
+	{ "bad function table",				0xc00000ff },
+	{ "variable not found",				0xc0000100 },
+	{ "directory not empty",			0xc0000101 },
+	{ "file corrupt error",				0xc0000102 },
+	{ "not a directory",				0xc0000103 },
+	{ "bad logon session state",			0xc0000104 },
+	{ "logon session collision",			0xc0000105 },
+	{ "name too long",				0xc0000106 },
+	{ "files open",					0xc0000107 },
+	{ "connection in use",				0xc0000108 },
+	{ "message not found",				0xc0000109 },
+	{ "process is terminating",			0xc000010a },
+	{ "invalid logon type",				0xc000010b },
+	{ "no guid translation",			0xc000010c },
+	{ "cannot impersonate",				0xc000010d },
+	{ "image already loaded",			0xc000010e },
+	{ "abios not present",				0xc000010f },
+	{ "abios lid not exist",			0xc0000110 },
+	{ "abios lid already owned",			0xc0000111 },
+	{ "abios not lid owner",			0xc0000112 },
+	{ "abios invalid command",			0xc0000113 },
+	{ "abios invalid lid",				0xc0000114 },
+	{ "abios selector not available",		0xc0000115 },
+	{ "abios invalid selector",			0xc0000116 },
+	{ "no LDT",					0xc0000117 },
+	{ "invalid LDT size",				0xc0000118 },
+	{ "invalid LDT offset",				0xc0000119 },
+	{ "invalid LDT descriptor",			0xc000011a },
+	{ "invalid image NE format",			0xc000011b },
+	{ "rxact invalid state",			0xc000011c },
+	{ "rxact commit failure",			0xc000011d },
+	{ "mapped file size zero",			0xc000011e },
+	{ "too many opened files",			0xc000011f },
+	{ "cancelled",					0xc0000120 },
+	{ "permission denied",				0xc0000121 },
+//	{ "cannot delete",				0xc0000121 },
+	{ "invalid computer name",			0xc0000122 },
+	{ "file deleted",				0xc0000123 },
+	{ "special account",				0xc0000124 },
+	{ "special group",				0xc0000125 },
+	{ "special user",				0xc0000126 },
+	{ "members primary group",			0xc0000127 },
+	{ "file closed",				0xc0000128 },
+	{ "too many threads",				0xc0000129 },
+	{ "thread not in process",			0xc000012a },
+	{ "token already in use",			0xc000012b },
+	{ "pagefile quota exceeded",			0xc000012c },
+	{ "commitment limit",				0xc000012d },
+	{ "invalid image le format",			0xc000012e },
+	{ "invalid image not MZ",			0xc000012f },
+	{ "invalid image protect",			0xc0000130 },
+	{ "invalid image win 16",			0xc0000131 },
+	{ "logon server conflict",			0xc0000132 },
+	{ "time difference at DC",			0xc0000133 },
+	{ "synchronization required",			0xc0000134 },
+	{ "DLL not found",				0xc0000135 },
+	{ "open failed",				0xc0000136 },
+	{ "IO privilege failed",			0xc0000137 },
+	{ "ordinal not found",				0xc0000138 },
+	{ "entrypoint not found",			0xc0000139 },
+	{ "control-C exit",				0xc000013a },
+	{ "local disconnect",				0xc000013b },
+	{ "remote disconnect",				0xc000013c },
+	{ "remote resources",				0xc000013d },
+	{ "link failed",				0xc000013e },
+	{ "link timeout",				0xc000013f },
+	{ "invalid connection",				0xc0000140 },
+	{ "invalid address",				0xc0000141 },
+	{ "DLL init failed",				0xc0000142 },
+	{ "missing systemfile",				0xc0000143 },
+	{ "unhandled exception",			0xc0000144 },
+	{ "application init failure",			0xc0000145 },
+	{ "pagefile create failed",			0xc0000146 },
+	{ "no pagefile",				0xc0000147 },
+	{ "invalid level",				0xc0000148 },
+	{ "wrong password core",			0xc0000149 },
+	{ "illegal float context",			0xc000014a },
+	{ "pipe broken",				0xc000014b },
+	{ "registry corrupt",				0xc000014c },
+	{ "registry io failed",				0xc000014d },
+	{ "no event pair",				0xc000014e },
+	{ "unrecognized volume",			0xc000014f },
+	{ "serial no device inited",			0xc0000150 },
+	{ "no such alias",				0xc0000151 },
+	{ "member not in alias",			0xc0000152 },
+	{ "member in alias",				0xc0000153 },
+	{ "alias exists",				0xc0000154 },
+	{ "logon not granted",				0xc0000155 },
+	{ "too many secrets",				0xc0000156 },
+	{ "secret too long",				0xc0000157 },
+	{ "internal db error",				0xc0000158 },
+	{ "fullscreen mode",				0xc0000159 },
+	{ "too many context IDs",			0xc000015a },
+	{ "logon type not granted",			0xc000015b },
+	{ "not registry file",				0xc000015c },
+	{ "NT cross encryption required",		0xc000015d },
+	{ "domain ctrlr config error",			0xc000015e },
+	{ "ft missing member",				0xc000015f },
+	{ "ill formed service entry",			0xc0000160 },
+	{ "illegal character",				0xc0000161 },
+	{ "unmappable character",			0xc0000162 },
+	{ "undefined character",			0xc0000163 },
+	{ "floppy volume",				0xc0000164 },
+	{ "floppy id mark not found",			0xc0000165 },
+	{ "floppy wrong cylinder",			0xc0000166 },
+	{ "floppy unknown error",			0xc0000167 },
+	{ "floppy bad registers",			0xc0000168 },
+	{ "disk recalibrate failed",			0xc0000169 },
+	{ "disk operation failed",			0xc000016a },
+	{ "disk reset failed",				0xc000016b },
+	{ "shared IRQ busy",				0xc000016c },
+	{ "FT orphaning",				0xc000016d },
+	{ "BIOS failed to connect interrupt",		0xc000016e },
+	{ "partition failure",				0xc0000172 },
+	{ "invalid block length",			0xc0000173 },
+	{ "device not partitioned",			0xc0000174 },
+	{ "unable to lock media",			0xc0000175 },
+	{ "unable to unload media",			0xc0000176 },
+	{ "eom overflow",				0xc0000177 },
+	{ "no media",					0xc0000178 },
+	{ "no such member",				0xc000017a },
+	{ "invalid member",				0xc000017b },
+	{ "key deleted",				0xc000017c },
+	{ "no log space",				0xc000017d },
+	{ "too many SIDs",				0xc000017e },
+	{ "LM cross encryption required",		0xc000017f },
+	{ "key has children",				0xc0000180 },
+	{ "child must be volatile",			0xc0000181 },
+	{ "device configuration error",			0xc0000182 },
+	{ "driver internal error",			0xc0000183 },
+	{ "invalid device state",			0xc0000184 },
+	{ "io device error",				0xc0000185 },
+	{ "device protocol error",			0xc0000186 },
+	{ "backup controller",				0xc0000187 },
+	{ "log file full",				0xc0000188 },
+	{ "too late",					0xc0000189 },
+	{ "no trust LSA secret",			0xc000018a },
+	{ "no trust SAM account",			0xc000018b },
+	{ "trusted domain failure",			0xc000018c },
+	{ "trusted relationship failure",		0xc000018d },
+	{ "eventlog file corrupt",			0xc000018e },
+	{ "eventlog cant start",			0xc000018f },
+	{ "trust failure",				0xc0000190 },
+	{ "mutant limit exceeded",			0xc0000191 },
+	{ "netlogon not started",			0xc0000192 },
+	{ "account expired",				0xc0000193 },
+	{ "possible deadlock",				0xc0000194 },
+	{ "network credential conflict",		0xc0000195 },
+	{ "remote session limit",			0xc0000196 },
+	{ "eventlog file changed",			0xc0000197 },
+	{ "nologon interdomain trust account",		0xc0000198 },
+	{ "nologon workstation trust account",		0xc0000199 },
+	{ "nologon server trust account",		0xc000019a },
+	{ "domain trust inconsistent",			0xc000019b },
+	{ "fs driver required",				0xc000019c },
+	{ "no user session key",			0xc0000202 },
+	{ "user session deleted",			0xc0000203 },
+	{ "resource lang not found",			0xc0000204 },
+	{ "insuff server resources",			0xc0000205 },
+	{ "invalid buffer size",			0xc0000206 },
+	{ "invalid address component",			0xc0000207 },
+	{ "invalid address wildcard",			0xc0000208 },
+	{ "too many addresses",				0xc0000209 },
+	{ "address already exists",			0xc000020a },
+	{ "address closed",				0xc000020b },
+	{ "connection disconnected",			0xc000020c },
+	{ "connection reset",				0xc000020d },
+	{ "too many nodes",				0xc000020e },
+	{ "transaction aborted",			0xc000020f },
+	{ "transaction timed out",			0xc0000210 },
+	{ "transaction no release",			0xc0000211 },
+	{ "transaction no match",			0xc0000212 },
+	{ "transaction responded",			0xc0000213 },
+	{ "transaction invalid id",			0xc0000214 },
+	{ "transaction invalid type",			0xc0000215 },
+	{ "not server session",				0xc0000216 },
+	{ "not client session",				0xc0000217 },
+	{ "cannot load registry file",			0xc0000218 },
+	{ "debug attach failed",			0xc0000219 },
+	{ "system process terminated",			0xc000021a },
+	{ "data not accepted",				0xc000021b },
+	{ "no browser servers found",			0xc000021c },
+	{ "VDM hard error",				0xc000021d },
+	{ "driver cancel timeout",			0xc000021e },
+	{ "reply message mismatch",			0xc000021f },
+	{ "mapped alignment",				0xc0000220 },
+	{ "image checksum mismatch",			0xc0000221 },
+	{ "lost writebehind data",			0xc0000222 },
+	{ "client server parameters invalid",		0xc0000223 },
+	{ "password must change",			0xc0000224 },
+	{ "not found",					0xc0000225 },
+	{ "not tiny stream",				0xc0000226 },
+	{ "recovery failure",				0xc0000227 },
+	{ "stack overflow read",			0xc0000228 },
+	{ "fail check",					0xc0000229 },
+	{ "duplicate objectid",				0xc000022a },
+	{ "objectid exists",				0xc000022b },
+	{ "convert to large",				0xc000022c },
+	{ "retry",					0xc000022d },
+	{ "found out of scope",				0xc000022e },
+	{ "allocate bucket",				0xc000022f },
+	{ "propset not found",				0xc0000230 },
+	{ "marshall overflow",				0xc0000231 },
+	{ "invalid variant",				0xc0000232 },
+	{ "domain controller not found",		0xc0000233 },
+	{ "account locked out",				0xc0000234 },
+	{ "handle not closable",			0xc0000235 },
+	{ "connection refused",				0xc0000236 },
+	{ "graceful disconnect",			0xc0000237 },
+	{ "address already associated",			0xc0000238 },
+	{ "address not associated",			0xc0000239 },
+	{ "connection invalid",				0xc000023a },
+	{ "connection active",				0xc000023b },
+	{ "network unreachable",			0xc000023c },
+	{ "host unreachable",				0xc000023d },
+	{ "protocol unreachable",			0xc000023e },
+	{ "port unreachable",				0xc000023f },
+	{ "request aborted",				0xc0000240 },
+	{ "connection aborted",				0xc0000241 },
+	{ "bad compression buffer",			0xc0000242 },
+	{ "user mapped file",				0xc0000243 },
+	{ "audit failed",				0xc0000244 },
+	{ "timer resolution not set",			0xc0000245 },
+	{ "connection count limit",			0xc0000246 },
+	{ "login time restriction",			0xc0000247 },
+	{ "login wkstation restriction",		0xc0000248 },
+	{ "image mp up mismatch",			0xc0000249 },
+	{ "insufficient logon info",			0xc0000250 },
+	{ "bad DLL entrypoint",				0xc0000251 },
+	{ "bad service entrypoint",			0xc0000252 },
+	{ "lpc reply lost",				0xc0000253 },
+	{ "IP address conflict1",			0xc0000254 },
+	{ "IP address conflict2",			0xc0000255 },
+	{ "registry quota limit",			0xc0000256 },
+	{ "path not covered",				0xc0000257 },
+	{ "no callback active",				0xc0000258 },
+	{ "license quota exceeded",			0xc0000259 },
+	{ "password too short",				0xc000025a },
+	{ "password too recent",			0xc000025b },
+	{ "password history conflict",			0xc000025c },
+	{ "plugplay no device",				0xc000025e },
+	{ "unsupported compression",			0xc000025f },
+	{ "invalid hw profile",				0xc0000260 },
+	{ "invalid plugplay device path",		0xc0000261 },
+	{ "driver ordinal not found",			0xc0000262 },
+	{ "driver entrypoint not found",		0xc0000263 },
+	{ "resource not owned",				0xc0000264 },
+	{ "too many links",				0xc0000265 },
+	{ "quota list inconsistent",			0xc0000266 },
+	{ "file is offline",				0xc0000267 },
+	{ "evaluation expiration",			0xc0000268 },
+	{ "illegal DLL relocation",			0xc0000269 },
+	{ "license violation",				0xc000026a },
+	{ "DLL init failed logoff",			0xc000026b },
+	{ "driver unable to load",			0xc000026c },
+	{ "dfs unavailable",				0xc000026d },
+	{ "volume dismounted",				0xc000026e },
+	{ "wx86 internal error",			0xc000026f },
+	{ "wx86 float stack check",			0xc0000270 },
+	{ "validate continue",				0xc0000271 },
+	{ "no match",					0xc0000272 },
+	{ "no more matches",				0xc0000273 },
+	{ "not a reparse point",			0xc0000275 },
+	{ "IO reparse tag invalid",			0xc0000276 },
+	{ "IO reparse tag mismatch",			0xc0000277 },
+	{ "IO reparse data invalid",			0xc0000278 },
+	{ "IO reparse tag not handled",			0xc0000279 },
+	{ "reparse point not resolved",			0xc0000280 },
+	{ "directory is a reparse point",		0xc0000281 },
+	{ "range list conflict",			0xc0000282 },
+	{ "source element empty",			0xc0000283 },
+	{ "destination element full",			0xc0000284 },
+	{ "illegal element address",			0xc0000285 },
+	{ "magazine not present",			0xc0000286 },
+	{ "reinitialization needed",			0xc0000287 },
+	{ "encryption failed",				0xc000028a },
+	{ "decryption failed",				0xc000028b },
+	{ "range not found",				0xc000028c },
+	{ "no recovery policy",				0xc000028d },
+	{ "no EFS",					0xc000028e },
+	{ "wrong EFS",					0xc000028f },
+	{ "no user keys",				0xc0000290 },
+	{ "file not encrypted",				0xc0000291 },
+	{ "not export format",				0xc0000292 },
+	{ "file encrypted",				0xc0000293 },
+	{ "WMI guid not found",				0xc0000295 },
+	{ "WMI instance not found",			0xc0000296 },
+	{ "WMI itemid not found",			0xc0000297 },
+	{ "WMI try again",				0xc0000298 },
+	{ "shared policy",				0xc0000299 },
+	{ "policy object not found",			0xc000029a },
+	{ "policy only in DS",				0xc000029b },
+	{ "volume not upgraded",			0xc000029c },
+	{ "remote storage not active",			0xc000029d },
+	{ "remote storage media error",			0xc000029e },
+	{ "no tracking service",			0xc000029f },
+	{ "server SID mismatch",			0xc00002a0 },
+	{ "DS no attribute or value",			0xc00002a1 },
+	{ "DS invalid attribute syntax",		0xc00002a2 },
+	{ "DS attribute type undefined",		0xc00002a3 },
+	{ "DS attribute or value exists",		0xc00002a4 },
+	{ "DS busy",					0xc00002a5 },
+	{ "DS unavailable",				0xc00002a6 },
+	{ "DS no RIDs allocated",			0xc00002a7 },
+	{ "DS no more RIDs",				0xc00002a8 },
+	{ "DS incorrect role owner",			0xc00002a9 },
+	{ "DS ridmgr init error",			0xc00002aa },
+	{ "DS obj class violation",			0xc00002ab },
+	{ "DS cant on non leaf",			0xc00002ac },
+	{ "DS cant on RDN",				0xc00002ad },
+	{ "DS cant mod obj class",			0xc00002ae },
+	{ "DS cross dom move failed",			0xc00002af },
+	{ "DS GC not available",			0xc00002b0 },
+	{ "directory service required",			0xc00002b1 },
+	{ "reparse attribute conflict",			0xc00002b2 },
+	{ "cant enable deny only",			0xc00002b3 },
+	{ "float multiple faults",			0xc00002b4 },
+	{ "float multiple traps",			0xc00002b5 },
+	{ "device removed",				0xc00002b6 },
+	{ "journal delete in progress",			0xc00002b7 },
+	{ "journal not active",				0xc00002b8 },
+	{ "nointerface",				0xc00002b9 },
+	{ "DS admin limit exceeded",			0xc00002c1 },
+	{ "driver failed sleep",			0xc00002c2 },
+	{ "mutual authentication failed",		0xc00002c3 },
+	{ "corrupt system file",			0xc00002c4 },
+	{ "datatype misalignment error",		0xc00002c5 },
+	{ "WMI read only",				0xc00002c6 },
+	{ "WMI set failure",				0xc00002c7 },
+	{ "commitment minimum",				0xc00002c8 },
+	{ "reg NAT consumption",			0xc00002c9 },
+	{ "transport full",				0xc00002ca },
+	{ "DS SAM init failure",			0xc00002cb },
+	{ "only if connected",				0xc00002cc },
+	{ "DS sensitive group violation",		0xc00002cd },
+	{ "PNP restart enumeration",			0xc00002ce },
+	{ "journal entry deleted",			0xc00002cf },
+	{ "DS cant mod primarygroupid",			0xc00002d0 },
+	{ "system image bad signature",			0xc00002d1 },
+	{ "PNP reboot required",			0xc00002d2 },
+	{ "power state invalid",			0xc00002d3 },
+	{ "DS invalid group type",			0xc00002d4 },
+	{ "DS no nest globalgroup in mixeddomain",	0xc00002d5 },
+	{ "DS no nest localgroup in mixeddomain",	0xc00002d6 },
+	{ "DS global can't have local member",		0xc00002d7 },
+	{ "DS global can't have universal member",	0xc00002d8 },
+	{ "DS universal can't have local member",	0xc00002d9 },
+	{ "DS global can't have crossdomain member",	0xc00002da },
+	{ "DS local can't have crossdomain local member",0xc00002db },
+	{ "DS have primary members",			0xc00002dc },
+	{ "WMI not supported",				0xc00002dd },
+	{ "insufficient power",				0xc00002de },
+	{ "SAM need bootkey password",			0xc00002df },
+	{ "SAM need bootkey floppy",			0xc00002e0 },
+	{ "DS cant start",				0xc00002e1 },
+	{ "DS init failure",				0xc00002e2 },
+	{ "SAM init failure",				0xc00002e3 },
+	{ "DS gc required",				0xc00002e4 },
+	{ "DS local member of local only",		0xc00002e5 },
+	{ "DS no FPO in universal groups",		0xc00002e6 },
+	{ "DS machine account quota exceeded",		0xc00002e7 },
+	{ "multiple fault violation",			0xc00002e8 },
+	{ "current domain not allowed",			0xc00002e9 },
+	{ "cannot make",				0xc00002ea },
+	{ "system shutdown",				0xc00002eb },
+	{ "DS init failure console",			0xc00002ec },
+	{ "DS sam init failure console",		0xc00002ed },
+	{ "unfinished context deleted",			0xc00002ee },
+	{ "no TGT reply",				0xc00002ef },
+	{ "objectid not found",				0xc00002f0 },
+	{ "no IP addresses",				0xc00002f1 },
+	{ "wrong credential handle",			0xc00002f2 },
+	{ "crypto system invalid",			0xc00002f3 },
+	{ "max referrals exceeded",			0xc00002f4 },
+	{ "must be kdc",				0xc00002f5 },
+	{ "strong crypto not supported",		0xc00002f6 },
+	{ "too many principals",			0xc00002f7 },
+	{ "no PA data",					0xc00002f8 },
+	{ "pkinit name mismatch",			0xc00002f9 },
+	{ "smartcard logon required",			0xc00002fa },
+	{ "KDC invalid request",			0xc00002fb },
+	{ "KDC unable to refer",			0xc00002fc },
+	{ "KDC unknown etype",				0xc00002fd },
+	{ "shutdown in progress",			0xc00002fe },
+	{ "server shutdown in progress",		0xc00002ff },
+	{ "not supported on sbs",			0xc0000300 },
+	{ "WMI GUID disconnected",			0xc0000301 },
+	{ "WMI already disabled",			0xc0000302 },
+	{ "WMI already enabled",			0xc0000303 },
+	{ "mft too fragmented",				0xc0000304 },
+	{ "copy protection failure",			0xc0000305 },
+	{ "CSS authentication failure",			0xc0000306 },
+	{ "CSS key not present",			0xc0000307 },
+	{ "CSS key not established",			0xc0000308 },
+	{ "CSS scrambled sector",			0xc0000309 },
+	{ "CSS region mismatch",			0xc000030a },
+	{ "CSS resets exhausted",			0xc000030b },
+	{ "pkinit failure",				0xc0000320 },
+	{ "smartcard subsystem failure",		0xc0000321 },
+	{ "no kerb key",				0xc0000322 },
+	{ "host down",					0xc0000350 },
+	{ "unsupported preauth",			0xc0000351 },
+	{ "EFS alg blob too big",			0xc0000352 },
+	{ "port not set",				0xc0000353 },
+	{ "debugger inactive",				0xc0000354 },
+	{ "ds version check failure",			0xc0000355 },
+	{ "auditing disabled",				0xc0000356 },
+	{ "prent4 machine account",			0xc0000357 },
+	{ "DS AG can't have universal member",		0xc0000358 },
+	{ "invalid image Win 32",			0xc0000359 },
+	{ "invalid image Win 64",			0xc000035a },
+	{ "bad bindings",				0xc000035b },
+	{ "network session expired",			0xc000035c },
+	{ "apphelp block",				0xc000035d },
+	{ "all SIDs filtered",				0xc000035e },
+	{ "not safe mode driver",			0xc000035f },
+	{ "access disabled by policy default",		0xc0000361 },
+	{ "access disabled by policy path",		0xc0000362 },
+	{ "access disabled by policy publisher",	0xc0000363 },
+	{ "access disabled by policy other",		0xc0000364 },
+	{ "failed driver entry",			0xc0000365 },
+	{ "device enumeration error",			0xc0000366 },
+	{ "mount point not resolved",			0xc0000368 },
+	{ "invalid device object parameter",		0xc0000369 },
+	{ "mca occured",				0xc000036a },
+	{ "driver blocked critical",			0xc000036b },
+	{ "driver blocked",				0xc000036c },
+	{ "driver database error",			0xc000036d },
+	{ "system hive too large",			0xc000036e },
+	{ "invalid import of non DLL",			0xc000036f },
+	{ "smartcard wrong pin",			0xc0000380 },
+	{ "smartcard card blocked",			0xc0000381 },
+	{ "smartcard card not authenticated",		0xc0000382 },
+	{ "smartcard no card",				0xc0000383 },
+	{ "smartcard no key container",			0xc0000384 },
+	{ "smartcard no certificate",			0xc0000385 },
+	{ "smartcard no keyset",			0xc0000386 },
+	{ "smartcard io error",				0xc0000387 },
+	{ "downgrade detected",				0xc0000388 },
+	{ "smartcard cert revoked",			0xc0000389 },
+	{ "issuing CA untrusted",			0xc000038a },
+	{ "revocation offline c",			0xc000038b },
+	{ "pkinit client failure",			0xc000038c },
+	{ "smartcard cert expired",			0xc000038d },
+	{ "driver failed prior unload",			0xc000038e },
+	{ "wow assertion",				0xc0009898 },
+	{ "PNP bad MPS table",				0xc0040035 },
+	{ "PNP translation failed",			0xc0040036 },
+	{ "PNP IRQ translation failed",			0xc0040037 },
+	{ "CTX winstation name invalid",		0xc00a0001 },
+	{ "CTX invalid PD",				0xc00a0002 },
+	{ "CTX PD not found",				0xc00a0003 },
+	{ "CTX close pending",				0xc00a0006 },
+	{ "CTX no outbuf",				0xc00a0007 },
+	{ "CTX modem inf not found",			0xc00a0008 },
+	{ "CTX invalid modemname",			0xc00a0009 },
+	{ "CTX response error",				0xc00a000a },
+	{ "CTX modem response timeout",			0xc00a000b },
+	{ "CTX modem response no carrier",		0xc00a000c },
+	{ "CTX modem response no dialtone",		0xc00a000d },
+	{ "CTX modem response busy",			0xc00a000e },
+	{ "CTX modem response voice",			0xc00a000f },
+	{ "CTX TD error",				0xc00a0010 },
+	{ "CTX license client invalid",			0xc00a0012 },
+	{ "CTX license not available",			0xc00a0013 },
+	{ "CTX license expired",			0xc00a0014 },
+	{ "CTX winstation not found",			0xc00a0015 },
+	{ "CTX winstation name collision",		0xc00a0016 },
+	{ "CTX winstation busy",			0xc00a0017 },
+	{ "CTX bad video mode",				0xc00a0018 },
+	{ "CTX graphics invalid",			0xc00a0022 },
+	{ "CTX not console",				0xc00a0024 },
+	{ "CTX client query timeout",			0xc00a0026 },
+	{ "CTX console disconnect",			0xc00a0027 },
+	{ "CTX console connect",			0xc00a0028 },
+	{ "CTX shadow denied",				0xc00a002a },
+	{ "CTX winstation access denied",		0xc00a002b },
+	{ "CTX invalid wd",				0xc00a002e },
+	{ "CTX WD not found",				0xc00a002f },
+	{ "CTX shadow invalid",				0xc00a0030 },
+	{ "CTX shadow disabled",			0xc00a0031 },
+	{ "RDP protocol error",				0xc00a0032 },
+	{ "CTX client license not set",			0xc00a0033 },
+	{ "CTX client license in use",			0xc00a0034 },
+	{ "CTX shadow ended by mode change",		0xc00a0035 },
+	{ "CTX shadow not running",			0xc00a0036 },
+	{ "cluster invalid node",			0xc0130001 },
+	{ "cluster node exists",			0xc0130002 },
+	{ "cluster join in progress",			0xc0130003 },
+	{ "cluster node not found",			0xc0130004 },
+	{ "cluster local node not found",		0xc0130005 },
+	{ "cluster network exists",			0xc0130006 },
+	{ "cluster network not found",			0xc0130007 },
+	{ "cluster netinterface exists",		0xc0130008 },
+	{ "cluster netinterface not found",		0xc0130009 },
+	{ "cluster invalid request",			0xc013000a },
+	{ "cluster invalid network provider",		0xc013000b },
+	{ "cluster node down",				0xc013000c },
+	{ "cluster node unreachable",			0xc013000d },
+	{ "cluster node not member",			0xc013000e },
+	{ "cluster join not in progress",		0xc013000f },
+	{ "cluster invalid network",			0xc0130010 },
+	{ "cluster no net adapters",			0xc0130011 },
+	{ "cluster node up",				0xc0130012 },
+	{ "cluster node paused",			0xc0130013 },
+	{ "cluster node not paused",			0xc0130014 },
+	{ "cluster no security context",		0xc0130015 },
+	{ "cluster network not internal",		0xc0130016 },
+	{ "cluster poisoned",				0xc0130017 },
+	{ "ACPI invalid opcode",			0xc0140001 },
+	{ "ACPI stack overflow",			0xc0140002 },
+	{ "ACPI assert failed",				0xc0140003 },
+	{ "ACPI invalid index",				0xc0140004 },
+	{ "ACPI invalid argument",			0xc0140005 },
+	{ "ACPI fatal",					0xc0140006 },
+	{ "ACPI invalid supername",			0xc0140007 },
+	{ "ACPI invalid argtype",			0xc0140008 },
+	{ "ACPI invalid objtype",			0xc0140009 },
+	{ "ACPI invalid targettype",			0xc014000a },
+	{ "ACPI incorrect argument count",		0xc014000b },
+	{ "ACPI address not mapped",			0xc014000c },
+	{ "ACPI invalid eventtype",			0xc014000d },
+	{ "ACPI handler collision",			0xc014000e },
+	{ "ACPI invalid data",				0xc014000f },
+	{ "ACPI invalid region",			0xc0140010 },
+	{ "ACPI invalid access size",			0xc0140011 },
+	{ "ACPI acquire global lock",			0xc0140012 },
+	{ "ACPI already initialized",			0xc0140013 },
+	{ "ACPI not initialized",			0xc0140014 },
+	{ "ACPI invalid mutex level",			0xc0140015 },
+	{ "ACPI mutex not owned",			0xc0140016 },
+	{ "ACPI mutex not owner",			0xc0140017 },
+	{ "ACPI rs access",				0xc0140018 },
+	{ "ACPI invalid table",				0xc0140019 },
+	{ "ACPI reg handler failed",			0xc0140020 },
+	{ "ACPI power request failed",			0xc0140021 },
+	{ "SXS section not found",			0xc0150001 },
+	{ "SXS cant gen actctx",			0xc0150002 },
+	{ "SXS invalid actctx data format",		0xc0150003 },
+	{ "SXS assembly not found",			0xc0150004 },
+	{ "SXS manifest format error",			0xc0150005 },
+	{ "SXS manifest parse error",			0xc0150006 },
+	{ "SXS activation context disabled",		0xc0150007 },
+	{ "SXS key not found",				0xc0150008 },
+	{ "SXS version conflict",			0xc0150009 },
+	{ "SXS wrong section type",			0xc015000a },
+	{ "SXS thread queries disabled",		0xc015000b },
+	{ "SXS assembly missing",			0xc015000c },
+	{ "SXS process default already set",		0xc015000e },
+	{ "SXS early deactivation",			0xc015000f },
+	{ "SXS invalid deactivation",			0xc0150010 },
+	{ "SXS multiple deactivation",			0xc0150011 },
+	{ "SXS system default activation context empty",0xc0150012 },
+	{ "SXS process termination requested",		0xc0150013 },
+};
+
+char *
+nterrstr(uint err)
+{
+	int i, f, match;
+	char *why, *facility, tmp[32];
+	static char buf[0xff];
+
+	f = (err >> 16) & 0x7ff;
+	switch(f){
+	case 0:
+		facility = "";
+		break;
+	case 1:
+		facility = " (hardware), ";
+		break;
+	case 2:
+		facility = " (dispatch), ";
+		break;
+	case 3:
+		facility = " (storage), ";
+		break;
+	case 4:
+		facility = " (itf), ";
+		break;
+	case 7:
+		facility = " (win32), ";
+		break;
+ 	case 8:
+		facility = " (windows), ";
+		break;
+	case 0x0a:
+		facility = " (control), ";
+		break;
+	default:
+		snprint(tmp, sizeof(tmp), " (facility=%d), ", f);
+		facility = tmp;
+		break;
+	}
+
+	match = -1;
+	for(i = 0; i < nelem(NTerrs); i++)
+		if(NTerrs[i].err == err)
+			match = i;
+
+	why = "";
+	if(!(err & 0x80000000))
+		why = "warning, ";
+
+	if(match != -1)
+		snprint(buf, sizeof buf, "%s%s%s", why, facility,
+			NTerrs[match].msg);
+	else
+		snprint(buf, sizeof buf, "%s%s%d/0x%ux - unknown NT error",
+			why, facility, err, err);
+	return buf;
+}

+ 463 - 0
sys/src/cmd/cifs/pack.c

@@ -0,0 +1,463 @@
+/* packet packing and unpacking */
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include "cifs.h"
+
+void *
+pmem(Pkt *p, void *v, int len)
+{
+	uchar *str = v;
+	void *s = p->pos;
+
+	if(!len || !v)
+		return s;
+	while(len--)
+		*p->pos++ = *str++;
+	return s;
+}
+
+void *
+ppath(Pkt *p, char *str)
+{
+	char c;
+	Rune r;
+	void *s = p->pos;
+
+	if(!str)
+		return s;
+
+	if(p->s->caps & CAP_UNICODE){
+		if(((p->pos - p->buf) % 2) != 0)	/* pad to even offset */
+			p8(p, 0);
+		while(*str){
+			str += chartorune(&r, str);
+			if(r == L'/')
+				r = L'\\';
+			pl16(p, r);
+		}
+		pl16(p, 0);
+	} else {
+		while((c = *str++) != 0){
+			if(c == '/')
+				c = '\\';
+			*p->pos++ = c;
+		}
+		*p->pos++ = 0;
+	}
+	return s;
+}
+
+void *
+pstr(Pkt *p, char *str)
+{
+	void *s = p->pos;
+	Rune r;
+
+	if(!str)
+		return s;
+
+	if(p->s->caps & CAP_UNICODE){
+		if(((p->pos - p->buf) % 2) != 0)
+			p8(p, 0);		/* pad to even offset */
+		while(*str){
+			str += chartorune(&r, str);
+			pl16(p, r);
+		}
+		pl16(p, 0);
+	} else {
+		while(*str)
+			*p->pos++ = *str++;
+		*p->pos++ = 0;
+	}
+	return s;
+}
+
+void *
+pascii(Pkt *p, char *str)
+{
+	void *s = p->pos;
+
+	while(*str)
+		*p->pos++ = *str++;
+	*p->pos++ = 0;
+	return s;
+}
+
+
+void *
+pl64(Pkt *p, uvlong n)
+{
+	void *s = p->pos;
+
+	*p->pos++ = n;
+	*p->pos++ = n >> 8;
+	*p->pos++ = n >> 16;
+	*p->pos++ = n >> 24;
+	*p->pos++ = n >> 32;
+	*p->pos++ = n >> 40;
+	*p->pos++ = n >> 48;
+	*p->pos++ = n >> 56;
+	return s;
+}
+
+void *
+pb32(Pkt *p, uint n)
+{
+	void *s = p->pos;
+
+	*p->pos++ = n >> 24;
+	*p->pos++ = n >> 16;
+	*p->pos++ = n >> 8;
+	*p->pos++ = n;
+	return s;
+}
+
+void *
+pl32(Pkt *p, uint n)
+{
+	void *s = p->pos;
+
+	*p->pos++ = n;
+	*p->pos++ = n >> 8;
+	*p->pos++ = n >> 16;
+	*p->pos++ = n >> 24;
+	return s;
+}
+
+void *
+pb16(Pkt *p, uint n)
+{
+	void *s = p->pos;
+
+	*p->pos++ = n >> 8;
+	*p->pos++ = n;
+	return s;
+}
+
+void *
+pl16(Pkt *p, uint n)
+{
+	void *s = p->pos;
+
+	*p->pos++ = n;
+	*p->pos++ = n >> 8;
+	return s;
+}
+
+void *
+p8(Pkt *p, uint n)
+{
+	void *s = p->pos;
+
+	*p->pos++ = n;
+	return s;
+}
+
+/*
+ * Encode a Netbios name
+ */
+void *
+pname(Pkt *p, char *name, char pad)
+{
+	int i, done = 0;
+	char c;
+	void *s = p->pos;
+
+	*p->pos++ = ' ';
+	for(i = 0; i < 16; i++) {
+		c = pad;
+		if(!done && name[i] == '\0')
+			done = 1;
+		if(!done)
+			c = islower(name[i])? toupper(name[i]): name[i];
+		*p->pos++ = ((uchar)c >> 4) + 'A';
+		*p->pos++ = (c & 0xf) + 'A';
+	}
+	*p->pos++ = '\0';
+	return s;
+}
+
+void *
+pvtime(Pkt *p, uvlong n)
+{
+	void *s = p->pos;
+
+	n += 11644473600LL;
+	n *= 10000000LL;
+
+	pl32(p, n);
+	pl32(p, n >> 32);
+	return s;
+}
+
+void *
+pdatetime(Pkt *p, long utc)
+{
+	void *s = p->pos;
+	Tm *tm = localtime(utc);
+	int t = tm->hour << 11 | tm->min << 5 | (tm->sec / 2);
+	int d = (tm->year - 80) << 9 | (tm->mon + 1) << 5 | tm->mday;
+
+	/*
+	 * bug in word swapping in Win95 requires this
+	 */
+	if(p->s->caps & CAP_NT_SMBS){
+		pl16(p, d);
+		pl16(p, t);
+	} else{
+		pl16(p, t);
+		pl16(p, d);
+	}
+	return s;
+}
+
+
+void
+gmem(Pkt *p, void *v, int n)
+{
+	uchar *str = v;
+
+	if(!n || !v)
+		return;
+	while(n-- && p->pos < p->eop)
+		*str++ = *p->pos++;
+}
+
+/*
+ * note len is the length of the source string in
+ * in runes or bytes, in ASCII mode this is also the size
+ * of the output buffer but this is not so in Unicode mode!
+ */
+void
+gstr(Pkt *p, char *str, int n)
+{
+	int i;
+	Rune r;
+
+	if(!n || !str)
+		return;
+
+	if(p->s->caps & CAP_UNICODE){
+		i = 0;
+		while(*p->pos && n && p->pos < p->eop){
+			r = gl16(p);
+			i += runetochar(str +i, &r);
+			n -= 2;
+		}
+		*(str + i) = 0;
+
+		while(*p->pos && p->pos < p->eop)
+			gl16(p);
+		/*
+		 * some versions of windows terminate a rune string
+		 * with a single nul so we do a dangerous hack...
+		 */
+		if(p->pos[1])
+			g8(p);
+		else
+			gl16(p);
+	} else {
+		while(*p->pos && n-- && p->pos < p->eop)
+			*str++ = *p->pos++;
+		*str = 0;
+		while(*p->pos++ && p->pos < p->eop)
+			continue;
+	}
+}
+
+void
+gascii(Pkt *p, char *str, int n)
+{
+	if(!n || !str)
+		return;
+
+	while(*p->pos && n-- && p->pos < p->eop)
+		*str++ = *p->pos++;
+	*str = 0;
+	while(*p->pos++ && p->pos < p->eop)
+		continue;
+}
+
+
+uvlong
+gl64(Pkt *p)
+{
+	uvlong n;
+
+	if(p->pos + 8 > p->eop)
+		return 0;
+
+	n  = (uvlong)*p->pos++;
+	n |= (uvlong)*p->pos++ << 8;
+	n |= (uvlong)*p->pos++ << 16;
+	n |= (uvlong)*p->pos++ << 24;
+	n |= (uvlong)*p->pos++ << 32;
+	n |= (uvlong)*p->pos++ << 40;
+	n |= (uvlong)*p->pos++ << 48;
+	n |= (uvlong)*p->pos++ << 56;
+	return n;
+}
+
+uvlong
+gb48(Pkt *p)
+{
+	uvlong n;
+
+	if(p->pos + 6 > p->eop)
+		return 0;
+
+	n  = (uvlong)*p->pos++ << 40;
+	n |= (uvlong)*p->pos++ << 24;
+	n |= (uvlong)*p->pos++ << 32;
+	n |= (uvlong)*p->pos++ << 16;
+	n |= (uvlong)*p->pos++ << 8;
+	n |= (uvlong)*p->pos++;
+	return n;
+}
+
+uint
+gb32(Pkt *p)
+{
+	uint n;
+
+	if(p->pos + 4 > p->eop)
+		return 0;
+
+	n  = (uint)*p->pos++ << 24;
+	n |= (uint)*p->pos++ << 16;
+	n |= (uint)*p->pos++ << 8;
+	n |= (uint)*p->pos++;
+	return n;
+}
+
+uint
+gl32(Pkt *p)
+{
+	uint n;
+
+	if(p->pos + 4 > p->eop)
+		return 0;
+
+	n  = (uint)*p->pos++;
+	n |= (uint)*p->pos++ << 8;
+	n |= (uint)*p->pos++ << 16;
+	n |= (uint)*p->pos++ << 24;
+	return n;
+}
+
+uint
+gb16(Pkt *p)
+{
+	uint n;
+
+	if(p->pos + 2 > p->eop)
+		return 0;
+	n  = (uint)*p->pos++ << 8;
+	n |= (uint)*p->pos++;
+	return n;
+}
+
+uint
+gl16(Pkt *p)
+{
+	uint n;
+
+	if(p->pos + 2 > p->eop)
+		return 0;
+	n  = (uint)*p->pos++;
+	n |= (uint)*p->pos++ << 8;
+	return n;
+}
+
+uint
+g8(Pkt *p)
+{
+	if(p->pos + 1 > p->eop)
+		return 0;
+	return (uint)*p->pos++;
+}
+
+long
+gdatetime(Pkt *p)
+{
+	Tm tm;
+	uint d, t;
+
+	if(p->pos + 4 > p->eop)
+		return 0;
+
+	/*
+	 * bug in word swapping in Win95 requires this
+	 */
+	if(p->s->caps & CAP_NT_SMBS){
+		d = gl16(p);
+		t = gl16(p);
+	}else{
+		t = gl16(p);
+		d = gl16(p);
+	}
+
+	tm.year = 80 + (d >> 9);
+	tm.mon = ((d >> 5) & 017) - 1;
+	tm.mday = d & 037;
+	tm.zone[0] = 0;
+	tm.tzoff = p->s->tz;
+
+	tm.hour = t >> 11;
+	tm.min = (t >> 5) & 63;
+	tm.sec = (t & 31) << 1;
+
+	return tm2sec(&tm);
+}
+
+long
+gvtime(Pkt *p)
+{
+	uvlong vl;
+
+	if(p->pos + 8 > p->eop)
+		return 0;
+
+	vl  = (uvlong)gl32(p);
+	vl |= (uvlong)gl32(p) << 32;
+
+	vl /= 10000000LL;
+	vl -= 11644473600LL;
+	return vl;
+}
+
+void
+gconv(Pkt *p, int conv, char *str, int n)
+{
+	int off;
+	uchar *pos;
+
+	off = gl32(p) & 0xffff;
+	if(off == 0 || p->tdata - conv + off > p->eop){
+		memset(str, 0, n);
+		return;
+	}
+
+	pos = p->pos;
+	p->pos = p->tdata - conv + off;
+	gascii(p, str, n);
+	p->pos = pos;
+}
+
+void
+goff(Pkt *p, uchar *base, char *str, int n)
+{
+	int off;
+	uchar *pos;
+
+	off = gl16(p);
+	if(off == 0 || base + off > p->eop){
+		memset(str, 0, n);
+		return;
+	}
+	pos = p->pos;
+	p->pos = base + off;
+	gstr(p, str, n);
+	p->pos = pos;
+}

+ 132 - 0
sys/src/cmd/cifs/ping.c

@@ -0,0 +1,132 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <libsec.h>
+#include <9p.h>
+
+extern char *Debug;
+
+typedef struct Pingcache Pingcache;
+struct Pingcache {
+	Pingcache*next;
+	long	rtt;
+	char	*host;
+	long	expire;
+};
+
+typedef struct {
+	uchar	vihl;		/* Version and header length */
+	uchar	tos;		/* Type of service */
+	uchar	length[2];	/* packet length */
+	uchar	id[2];		/* Identification */
+	uchar	frag[2];	/* Fragment information */
+	uchar	ttl;		/* Time to live */
+	uchar	proto;		/* Protocol */
+	uchar	ipcksum[2];	/* Header checksum */
+	uchar	src[4];		/* Ip source */
+	uchar	dst[4];		/* Ip destination */
+	uchar	type;
+	uchar	code;
+	uchar	cksum[2];
+	uchar	icmpid[2];
+	uchar	seq[2];
+	uchar	data[1];
+} Icmp;
+
+enum {			/* Packet Types */
+	EchoReply	= 0,
+	Unreachable	= 3,
+	SrcQuench	= 4,
+	EchoRequest	= 8,
+	TimeExceed	= 11,
+	Timestamp	= 13,
+	TimestampReply	= 14,
+	InfoRequest	= 15,
+	InfoReply	= 16,
+
+	ICMP_IPSIZE	= 20,
+	ICMP_HDRSIZE	= 8,
+
+	Npings		= 8,
+	Payload		= 32,
+
+	Cachetime	= 60,
+};
+
+static Pingcache *Cache;
+
+/*
+ * We ignore the first result as that is probably bigger
+ * than expected due to IP sorting out the routing to the host
+ */
+int
+ping(char *host, int timeout)
+{
+	int rtt, fd, i, seq;
+	long now;
+	vlong then;
+	uchar buf[128];
+	Icmp *ip;
+	Pingcache *c;
+
+	now = time(nil);
+	for(c = Cache; c; c = c->next)
+		if(strcmp(c->host, host) == 0 && now < c->expire){
+			if(Debug && strstr(Debug, "dfs") != nil)
+				print("\t\tping host=%s timeout=%d - cache hit\n",
+					host, timeout);
+			return c->rtt;
+		}
+
+	rtt = -1;
+	ip = (Icmp*)buf;
+
+	if((fd = dial(netmkaddr(host, "icmp", "1"), 0, 0, 0)) == -1)
+		goto fail;
+
+	for(seq = 0; seq < Npings; seq++){
+		then = nsec();
+		for(i = Payload; i < sizeof buf; i++)
+			buf[i] = i + seq;
+		ip->type = EchoRequest;
+		ip->code = 0;
+		ip->seq[0] = seq;
+		ip->seq[1] = seq;
+		alarm(timeout);
+		if(write(fd, ip, sizeof buf) != sizeof buf ||
+		    read(fd, ip, sizeof buf) != sizeof buf)
+			goto fail;
+		alarm(0);
+		if(ip->type != EchoReply || ip->code != 0 ||
+		    ip->seq[0] != seq || ip->seq[1] != seq)
+			goto fail;
+		for(i = Payload; i < sizeof buf; i++)
+			if((uchar)buf[i] != (uchar)(i + seq))
+				goto fail;
+		rtt = (rtt + nsec() - then) / 2;
+	}
+fail:
+	if(fd != -1)
+		close(fd);
+
+	if(Debug && strstr(Debug, "dfs") != nil)
+		print("\t\tping host=%s timeout=%d rtt=%d - failed\n",
+			host, timeout, rtt);
+
+	/*
+	 * failures get cached too
+	 */
+	for(c = Cache; c; c = c->next)
+		if(strcmp(c->host, host) == 0)
+			break;
+	if(c == nil){
+		c = emalloc9p(sizeof(Pingcache));
+		c->host = estrdup9p(host);
+		c->next = Cache;
+		Cache = c;
+	}
+	c->rtt = rtt;
+	c->expire = now+Cachetime;
+	return rtt;
+}

+ 346 - 0
sys/src/cmd/cifs/raperrstr.c

@@ -0,0 +1,346 @@
+#include <u.h>
+#include <libc.h>
+
+static struct {
+	int	err;
+	char	*msg;
+} RAPerrs[] = {
+	{ 0,	"ok" },
+	{ 5,	"permission denied" },
+	{ 50,	"request not supported" },
+	{ 65,	"access denied" },
+	{ 86,	"password invalid" },
+	{ 128,  "not listening on called name" },
+	{ 129,  "not listening for calling name" },
+	{ 130,  "called name not present" },
+	{ 131,  "called name present, but insufficient resources" },
+	{ 234,	"more data" },
+
+	{ 2102, "workstation driver is not installed" },
+	{ 2103, "server could not be located" },
+	{ 2104, "network cannot access a shared memory segment" },
+	{ 2105, "A network resource shortage occurred" },
+	{ 2106, "This operation is not supported on workstations" },
+	{ 2107, "device is not connected" },
+	{ 2114, "Server service is not started" },
+	{ 2115, "queue is empty" },
+	{ 2116, "device or directory does not exist" },
+	{ 2117, "operation is invalid on a redirected resource" },
+	{ 2118, "name has already been shared" },
+	{ 2119, "server is currently out of the requested resource" },
+	{ 2121, "Requested addition of items exceeds the maximum allowed" },
+	{ 2122, "Peer service supports only two simultaneous users" },
+	{ 2123, "API return buffer is too small" },
+	{ 2127, "remote API error occurred" },
+	{ 2131, "cannot open or read the configuration file" },
+	{ 2136, "general network error occurred" },
+	{ 2137, "Workstation service is corrupted" },
+	{ 2138, "Workstation service has not been started" },
+	{ 2139, "requested information is not available" },
+	{ 2140, "internal error" },
+	{ 2141, "server is not configured for transactions" },
+	{ 2142, "requested API is not supported on the remote server" },
+	{ 2143, "event name is invalid" },
+	{ 2144, "computer name already exists on the network" },
+	{ 2146, "specified component could not be found in the config info" },
+	{ 2147, "specified parameter could not be found in the config info" },
+	{ 2149, "A line in the configuration file is too long" },
+	{ 2150, "printer does not exist" },
+	{ 2151, "print job does not exist" },
+	{ 2152, "printer destination cannot be found" },
+	{ 2153, "printer destination already exists" },
+	{ 2154, "printer queue already exists" },
+	{ 2155, "no more printers can be added" },
+	{ 2156, "no more print jobs can be added" },
+	{ 2157, "no more printer destinations can be added" },
+	{ 2158, "printer idle and cannot accept control operations" },
+	{ 2159, "invalid control function in printer request" },
+	{ 2160, "print processor not responding" },
+	{ 2161, "spooler not running" },
+	{ 2162, "operation cannot be performed on the print destination in its current state" },
+	{ 2163, "operation cannot be performed on the printer queue in its current state" },
+	{ 2164, "operation cannot be performed on the print job in its current state" },
+	{ 2165, "spooler - no memory" },
+	{ 2166, "device driver does not exist" },
+	{ 2167, "data type not supported by the print processor" },
+	{ 2168, "print processor is not installed" },
+	{ 2180, "service database is locked" },
+	{ 2181, "service table full" },
+	{ 2182, "requested service already started" },
+	{ 2183, "service does not responding" },
+	{ 2184, "service not started" },
+	{ 2185, "service name invalid" },
+	{ 2186, "service is not responding to the control function" },
+	{ 2187, "service control is busy" },
+	{ 2188, "configuration file contains an invalid service program name" },
+	{ 2189, "service could not be controlled in its present state" },
+	{ 2190, "service ended abnormally" },
+	{ 2191, "requested pause or stop is not valid for this service" },
+	{ 2192, "could not find the service name in the dispatch table" },
+	{ 2193, "service control dispatcher pipe read failed" },
+	{ 2194, "thread create for the new service could not be created" },
+	{ 2200, "This workstation is already logged on to the local-area network" },
+	{ 2201, "workstation is not logged on to the local-area network" },
+	{ 2202, "user name or group name parameter is invalid" },
+	{ 2203, "password parameter is invalid" },
+	{ 2204, "logon processor did not add the message alias" },
+	{ 2205, "logon processor did not add the message alias" },
+	{ 2206, "logoff processor did not delete the message alias" },
+	{ 2207, "logoff processor did not delete the message alias" },
+	{ 2209, "Network logons are paused" },
+	{ 2210, "a centralized logon-server conflict occurred" },
+	{ 2211, "server is configured without a valid user path" },
+	{ 2212, "cannot run logon script" },
+	{ 2214, "logon server was not specified. computer will be logged on as STANDALONE" },
+	{ 2215, "logon server could not be found" },
+	{ 2216, "already a logon domain for this computer" },
+	{ 2217, "logon server could not validate the logon" },
+	{ 2219, "security database could not be found" },
+	{ 2220, "group name could not be found" },
+	{ 2221, "user name could not be found" },
+	{ 2222, "resource name could not be found" },
+	{ 2223, "group already exists" },
+	{ 2224, "user account already exists" },
+	{ 2225, "resource permission list already exists" },
+	{ 2226, "This operation is only allowed on the PDC of the domain" },
+	{ 2227, "security database has not been started" },
+	{ 2228, "There are too many names in the user accounts database" },
+	{ 2229, "disk I/O failure occurred" },
+	{ 2230, "limit of 64 entries per resource was exceeded" },
+	{ 2231, "Deleting a user with a session is not allowed" },
+	{ 2232, "parent directory could not be located" },
+	{ 2233, "Unable to add to the security database session cache segment" },
+	{ 2234, "This operation is not allowed on this special group" },
+	{ 2235, "This user is not cached in user accounts database session cache" },
+	{ 2236, "user already belongs to this group" },
+	{ 2237, "user does not belong to this group" },
+	{ 2238, "This user account is undefined" },
+	{ 2239, "This user account has expired" },
+	{ 2240, "user is not allowed to log on from this workstation" },
+	{ 2241, "user is not allowed to log on at this time" },
+	{ 2242, "password of this user has expired" },
+	{ 2243, "password of this user cannot change" },
+	{ 2244, "This password cannot be used now" },
+	{ 2245, "password does not meet the password policy requirements" },
+	{ 2246, "password of this user is too recent to change" },
+	{ 2247, "security database is corrupted" },
+	{ 2248, "No updates are necessary to this replicant network/local security database" },
+	{ 2249, "This replicant database is outdated; synchronization is required" },
+	{ 2250, "network connection could not be found" },
+	{ 2251, "This asg_type is invalid" },
+	{ 2252, "This device is currently being shared" },
+	{ 2270, "computer name cannot be added - name may already exist" },
+	{ 2271, "Messenger service is already started" },
+	{ 2272, "Messenger service failed to start" },
+	{ 2273, "message alias could not be found on the network" },
+	{ 2274, "This message alias has already been forwarded" },
+	{ 2275, "This message alias has been added but is still forwarded" },
+	{ 2276, "This message alias already exists locally" },
+	{ 2277, "maximum number of added message aliases has been exceeded" },
+	{ 2278, "computer name could not be deleted" },
+	{ 2279, "Messages cannot be forwarded back to the same workstation" },
+	{ 2280, "An error occurred in the domain message processor" },
+	{ 2281, "message was sent, but the recipient has paused the Messenger service" },
+	{ 2282, "message was sent but not received" },
+	{ 2283, "message alias is currently in use. Try again later" },
+	{ 2284, "Messenger service has not been started" },
+	{ 2285, "name is not on the local computer" },
+	{ 2286, "forwarded message alias could not be found on the network" },
+	{ 2287, "message alias table on the remote station is full" },
+	{ 2288, "Messages for this alias are not currently being forwarded" },
+	{ 2289, "broadcast message was truncated" },
+	{ 2294, "This is an invalid device name" },
+	{ 2295, "A write fault occurred" },
+	{ 2297, "A duplicate message alias exists on the network" },
+	{ 2298, "This message alias will be deleted later" },
+	{ 2299, "message alias was not successfully deleted from all networks" },
+	{ 2300, "This operation is not supported on computers with multiple networks" },
+	{ 2310, "This shared resource does not exist" },
+	{ 2311, "This device is not shared" },
+	{ 2312, "A session does not exist with that computer name" },
+	{ 2314, "There is not an open file with that identification number" },
+	{ 2315, "A failure occurred when executing a remote administration command" },
+	{ 2316, "A failure occurred when opening a remote temporary file" },
+	{ 2317, "Data returned from a RAP command has been truncated to 64K" },
+	{ 2318, "This device cannot be shared as both a spooled and a non-spooled resource" },
+	{ 2319, "information in the list of servers may be incorrect" },
+	{ 2320, "computer is not active in this domain" },
+	{ 2321, "share must be removed from DFS before it can be deleted" },
+	{ 2331, "operation is invalid for this device" },
+	{ 2332, "This device cannot be shared" },
+	{ 2333, "This device was not open" },
+	{ 2334, "This device name list is invalid" },
+	{ 2335, "queue priority is invalid" },
+	{ 2337, "There are no shared communication devices" },
+	{ 2338, "queue you specified does not exist" },
+	{ 2340, "This list of devices is invalid" },
+	{ 2341, "requested device is invalid" },
+	{ 2342, "This device is already in use by the spooler" },
+	{ 2343, "This device is already in use as a communication device" },
+	{ 2351, "This computer name is invalid" },
+	{ 2354, "string and prefix specified are too long" },
+	{ 2356, "This path component is invalid" },
+	{ 2357, "Could not determine the type of input" },
+	{ 2362, "buffer for types is not big enough" },
+	{ 2370, "Profile files cannot exceed 64K" },
+	{ 2371, "start offset is out of range" },
+	{ 2372, "system cannot delete current connections to network resources" },
+	{ 2373, "system was unable to parse the command line in this file" },
+	{ 2374, "An error occurred while loading the profile file" },
+	{ 2375, "Errors occurred while saving the profile file" },
+	{ 2377, "Log file %1 is full" },
+	{ 2378, "This log file has changed between reads" },
+	{ 2379, "Log file %1 is corrupt" },
+	{ 2380, "source path cannot be a directory" },
+	{ 2381, "source path is illegal" },
+	{ 2382, "destination path is illegal" },
+	{ 2383, "source and destination paths are on different servers" },
+	{ 2385, "Run server you requested is paused" },
+	{ 2389, "An error occurred when communicating with a Run server" },
+	{ 2391, "An error occurred when starting a background process" },
+	{ 2392, "shared resource you are connected to could not be found" },
+	{ 2400, "LAN adapter number is invalid" },
+	{ 2401, "There are open files on the connection" },
+	{ 2402, "Active connections still exist" },
+	{ 2403, "This share name or password is invalid" },
+	{ 2404, "device is being accessed by an active process" },
+	{ 2405, "drive letter is in use locally" },
+	{ 2430, "specified client is already registered for the specified event" },
+	{ 2431, "alert table is full" },
+	{ 2432, "An invalid or nonexistent alert name was raised" },
+	{ 2433, "alert recipient is invalid" },
+	{ 2434, "A user's session with this server has been deleted" },
+	{ 2440, "log file does not contain the requested record number" },
+	{ 2450, "user accounts database is not configured correctly" },
+	{ 2451, "This operation is not permitted when the Netlogon service is running" },
+	{ 2452, "This operation is not allowed on the last administrative account" },
+	{ 2453, "Could not find DC for this domain" },
+	{ 2454, "Could not set logon information for this user" },
+	{ 2455, "Netlogon service has not been started" },
+	{ 2456, "Unable to add to the user accounts database" },
+	{ 2457, "This server's clock is not synchronized with the PDC's clock" },
+	{ 2458, "A password mismatch has been detected" },
+	{ 2460, "server identification does not specify a valid server" },
+	{ 2461, "session identification does not specify a valid session" },
+	{ 2462, "connection identification does not specify a valid connection" },
+	{ 2463, "There is no space for another entry in the table of available servers" },
+	{ 2464, "server has reached the maximum number of sessions it supports" },
+	{ 2465, "server has reached the maximum number of connections it supports" },
+	{ 2466, "server cannot open more files because it has reached its maximum number" },
+	{ 2467, "There are no alternate servers registered on this server" },
+	{ 2470, "Try down-level (remote admin protocol) version of API instead" },
+	{ 2480, "UPS driver could not be accessed by the UPS service" },
+	{ 2481, "UPS service is not configured correctly" },
+	{ 2482, "UPS service could not access the specified Comm Port" },
+	{ 2483, "UPS indicated a line fail or low battery situation. Service not started" },
+	{ 2484, "UPS service failed to perform a system shut down" },
+	{ 2500, "program below returned an MS-DOS error code" },
+	{ 2501, "program below needs more memory" },
+	{ 2502, "program below called an unsupported MS-DOS function" },
+	{ 2503, "workstation failed to boot" },
+	{ 2504, "file below is corrupt" },
+	{ 2505, "No loader is specified in the boot-block definition file" },
+	{ 2506, "NetBIOS returned an error: The NCB and SMB are dumped above" },
+	{ 2507, "A disk I/O error occurred" },
+	{ 2508, "Image parameter substitution failed" },
+	{ 2509, "Too many image parameters cross disk sector boundaries" },
+	{ 2510, "image was not generated from an MS-DOS diskette formatted with /S" },
+	{ 2511, "Remote boot will be restarted later" },
+	{ 2512, "call to the Remoteboot server failed" },
+	{ 2513, "Cannot connect to the Remoteboot server" },
+	{ 2514, "Cannot open image file on the Remoteboot server" },
+	{ 2515, "Connecting to the Remoteboot server..." },
+	{ 2516, "Connecting to the Remoteboot server..." },
+	{ 2517, "Remote boot service was stopped" },
+	{ 2518, "Remote boot startup failed; check the error log" },
+	{ 2519, "A second connection to a Remoteboot resource is not allowed" },
+	{ 2550, "browser service was configured with MaintainServerList=No" },
+	{ 2610, "Service failed to startas no network adapters started with this service" },
+	{ 2611, "Service failed to start due to bad startup information in the registry" },
+	{ 2612, "Service failed to start because its database is absent or corrupt" },
+	{ 2613, "Service failed to start because RPLFILES share is absent" },
+	{ 2614, "Service failed to start because RPLUSER group is absent" },
+	{ 2615, "Cannot enumerate service records" },
+	{ 2616, "Workstation record information has been corrupted" },
+	{ 2617, "Workstation record was not found" },
+	{ 2618, "Workstation name is in use by some other workstation" },
+	{ 2619, "Profile record information has been corrupted" },
+	{ 2620, "Profile record was not found" },
+	{ 2621, "Profile name is in use by some other profile" },
+	{ 2622, "There are workstations using this profile" },
+	{ 2623, "Configuration record information has been corrupted" },
+	{ 2624, "Configuration record was not found" },
+	{ 2625, "Adapter ID record information has been corrupted" },
+	{ 2626, "An internal service error has occurred" },
+	{ 2627, "Vendor ID record information has been corrupted" },
+	{ 2628, "Boot block record information has been corrupted" },
+	{ 2629, "user account for this workstation record is missing" },
+	{ 2630, "RPLUSER local group could not be found" },
+	{ 2631, "Boot block record was not found" },
+	{ 2632, "Chosen profile is incompatible with this workstation" },
+	{ 2633, "Chosen network adapter ID is in use by some other workstation" },
+	{ 2634, "There are profiles using this configuration" },
+	{ 2635, "There are workstations, profiles, or configurations using this boot block" },
+	{ 2636, "Service failed to backup Remoteboot database" },
+	{ 2637, "Adapter record was not found" },
+	{ 2638, "Vendor record was not found" },
+	{ 2639, "Vendor name is in use by some other vendor record" },
+	{ 2640, "(boot name, vendor ID) is in use by some other boot block record" },
+	{ 2641, "Configuration name is in use by some other configuration" },
+	{ 2660, "internal database maintained by the Dfs service is corrupt" },
+	{ 2661, "One of the records in the internal Dfs database is corrupt" },
+	{ 2662, "There is no DFS name whose entry path matches the input Entry Path" },
+	{ 2663, "A root or link with the given name already exists" },
+	{ 2664, "server share specified is already shared in the Dfs" },
+	{ 2665, "indicated server share does not support the indicated DFS namespace" },
+	{ 2666, "operation is not valid on this portion of the namespace" },
+	{ 2667, "operation is not valid on this portion of the namespace" },
+	{ 2668, "operation is ambiguous because the link has multiple servers" },
+	{ 2669, "Unable to create a link" },
+	{ 2670, "server is not Dfs Aware" },
+	{ 2671, "specified rename target path is invalid" },
+	{ 2672, "specified DFS link is offline" },
+	{ 2673, "specified server is not a server for this link" },
+	{ 2674, "A cycle in the Dfs name was detected" },
+	{ 2675, "operation is not supported on a server-based Dfs" },
+	{ 2676, "This link is already supported by the specified server-share" },
+	{ 2677, "Can't remove the last server-share supporting this root or link" },
+	{ 2678, "operation is not supported for an Inter-DFS link" },
+	{ 2679, "internal state of the Dfs Service has become inconsistent" },
+	{ 2680, "Dfs Service has been installed on the specified server" },
+	{ 2681, "Dfs data being reconciled is identical" },
+	{ 2682, "DFS root cannot be deleted. Uninstall DFS if required" },
+	{ 2683, "A child or parent directory of the share is already in a Dfs" },
+	{ 2690, "Dfs internal error" },
+	{ 2691, "This machine is already joined to a domain" },
+	{ 2692, "This machine is not currently joined to a domain" },
+	{ 2693, "This machine is a DC and cannot be unjoined from a domain" },
+	{ 2694, "destination DC does not support creating machine accounts in OUs" },
+	{ 2695, "specified workgroup name is invalid" },
+	{ 2696, "specified computer name is incompatible with the default language used on the DC" },
+	{ 2697, "specified computer account could not be found" },
+	{ 2698, "This version of Windows cannot be joined to a domain" },
+	{ 2701, "password must change at the next logon" },
+	{ 2702, "account is locked out" },
+	{ 2703, "password is too long" },
+	{ 2704, "password does not meet the complexity policy" },
+	{ 2705, "password does not meet the requirements of the password filter DLLs" },
+};
+
+char *
+raperrstr(uint err)
+{
+	int i, match;
+	static char buf[0xff];
+
+	match = -1;
+	for(i = 0; i < nelem(RAPerrs); i++)
+		if(RAPerrs[i].err == err)
+			match = i;
+	if(match != -1)
+		snprint(buf, sizeof buf, "rap: %s", RAPerrs[match].msg);
+	else
+		snprint(buf, sizeof buf, "rap: %ud/0x%ux - unknown error",
+			err, err);
+	return buf;
+}

+ 466 - 0
sys/src/cmd/cifs/remsmb.h

@@ -0,0 +1,466 @@
+/*++
+
+Copyright (c) 1991-1992  Microsoft Corporation
+
+Module Name:
+
+    RemSmb.h
+
+Abstract:
+
+    Definition of descriptor strings for Net API remote calls.
+    Names defined in this file follow the format:
+
+                RemSmb_RemDescriptor
+
+           RemDescriptor follows one of the following formats:
+
+             StructureName_level         -  info structures
+             StructureName_level_suffix  -  special info structures
+             ApiName_P                   -  parameter descriptors
+
+Notes:
+
+    1. While the above formats should be followed, the equate names
+       cannot exceed 32 characters, and abbreviated forms should be used.
+
+    2. The remote API mechanism requires that the return parameter length
+       is less than or equal to the send parameter length. This assumption
+       is made in order to reduce the overhead in the buffer management
+       required for the API call. This restriction is not unreasonable
+       as the APIs were designed to return data in the data buffer and just
+       use return parameters for data lengths & file handles etc.
+       HOWEVER, if it has been spec'ed to return a large parameter field, it
+       is possible to pad the size of the send parameter using a REM_FILL_BYTES
+       field to meet the above restriction.
+
+Author:
+
+Environment:
+
+    Portable to just about anything.
+    Requires ANSI C extensions: slash-slash comments, long external
+names.
+
+Revision History:
+
+--*/
+
+#ifndef _REMDEF_
+#define _REMDEF_
+/*
+ * ====================================================================
+ * SMB XACT message descriptors.
+ * ====================================================================
+ */
+
+#define	REMSmb_share_info_0		"B13"
+#define	REMSmb_share_info_1		"B13BWz"
+#define	REMSmb_share_info_2		"B13BWzWWWzB9B"
+
+#define	REMSmb_share_info_90		"B13BWz"
+#define	REMSmb_share_info_92		"zzz"
+#define	REMSmb_share_info_93		"zzz"
+
+#define	REMSmb_share_info_0_setinfo	"B13"
+#define	REMSmb_share_info_1_setinfo	"B13BWz"
+#define	REMSmb_share_info_2_setinfo	"B13BWzWWOB9B"
+
+#define	REMSmb_share_info_90_setinfo	"B13BWz"
+#define	REMSmb_share_info_91_setinfo	"B13BWzWWWOB9BB9BWzWWzWW"
+
+#define	REMSmb_NetShareEnum_P		"WrLeh"
+#define	REMSmb_NetShareGetInfo_P	"zWrLh"
+#define	REMSmb_NetShareSetInfo_P	"zWsTP"
+#define	REMSmb_NetShareAdd_P		"WsT"
+#define	REMSmb_NetShareDel_P		"zW"
+#define	REMSmb_NetShareCheck_P		"zh"
+
+#define	REMSmb_session_info_0		"z"
+#define	REMSmb_session_info_1		"zzWWWDDD"
+#define	REMSmb_session_info_2		"zzWWWDDDz"
+#define	REMSmb_session_info_10		"zzDD"
+
+#define	REMSmb_NetSessionEnum_P		"WrLeh"
+#define	REMSmb_NetSessionGetInfo_P	"zWrLh"
+#define	REMSmb_NetSessionDel_P		"zW"
+
+#define	REMSmb_connection_info_0	"W"
+#define	REMSmb_connection_info_1	"WWWWDzz"
+
+#define	REMSmb_NetConnectionEnum_P	"zWrLeh"
+
+#define	REMSmb_file_info_0		"W"
+#define	REMSmb_file_info_1		"WWWzz"
+#define	REMSmb_file_info_2		"D"
+#define	REMSmb_file_info_3		"DWWzz"
+
+#define	REMSmb_NetFileEnum_P		"zWrLeh"
+#define	REMSmb_NetFileEnum2_P		"zzWrLehb8g8"
+#define	REMSmb_NetFileGetInfo_P		"WWrLh"
+#define	REMSmb_NetFileGetInfo2_P	"DWrLh"
+#define	REMSmb_NetFileClose_P		"W"
+#define	REMSmb_NetFileClose2_P		"D"
+
+#define	REMSmb_server_info_0		"B16"
+#define	REMSmb_server_info_1		"B16BBDz"
+#define	REMSmb_server_info_2		"B16BBDzDDDWWzWWWWWWWB21BzWWWWWWWWWWWWWWWWWWWWWWz"
+#define	REMSmb_server_info_3		"B16BBDzDDDWWzWWWWWWWB21BzWWWWWWWWWWWWWWWWWWWWWWzDWz"
+
+#define	REMSmb_server_info_1_setinfo	"B16BBDz"
+#define	REMSmb_server_info_2_setinfo	"B16BBDzDDDWWzWWWWWWWB21BOWWWWWWWWWWWWWWWWWWWWWWz"
+
+#define	REMSmb_server_admin_command	"B"
+
+#define	REMSmb_server_diskenum_0	"B3"
+
+#define	REMSmb_authenticator_info_0	"B8D"
+
+#define	REMSmb_server_diskft_100	"B"
+#define	REMSmb_server_diskft_101	"BBWWWWDW"
+#define	REMSmb_server_diskft_102	"BBWWWWDN"
+#define	REMSmb_server_diskfterr_0	"DWWDDW"
+#define	REMSmb_ft_info_0		"WWW"
+#define	REMSmb_ft_drivestats_0		"BBWDDDDDDD"
+#define	REMSmb_ft_error_info_1		"DWWDDWBBDD"
+
+#define	REMSmb_I_NetServerDiskEnum_P	"WrLeh"
+#define	REMSmb_I_NetServerDiskGetInfo_P	"WWrLh"
+#define	REMSmb_I_FTVerifyMirror_P	"Wz"
+#define	REMSmb_I_FTAbortVerify_P	"W"
+#define	REMSmb_I_FTGetInfo_P		"WrLh"
+#define	REMSmb_I_FTSetInfo_P		"WsTP"
+#define	REMSmb_I_FTLockDisk_P		"WWh"
+#define	REMSmb_I_FTFixError_P		"Dzhh2"
+#define	REMSmb_I_FTAbortFix_P		"D"
+#define	REMSmb_I_FTDiagnoseError_P	"Dhhhh"
+#define	REMSmb_I_FTGetDriveStats_P	"WWrLh"
+#define	REMSmb_I_FTErrorGetInfo_P	"DWrLh"
+
+#define	REMSmb_NetServerEnum_P		"WrLeh"
+#define	REMSmb_I_NetServerEnum_P	"WrLeh"
+#define	REMSmb_NetServerEnum2_P		"WrLehDz"
+#define	REMSmb_I_NetServerEnum2_P	"WrLehDz"
+#define	REMSmb_NetServerEnum3_P		"WrLehDzz"
+#define	REMSmb_NetServerGetInfo_P	"WrLh"
+#define	REMSmb_NetServerSetInfo_P	"WsTP"
+#define	REMSmb_NetServerDiskEnum_P	"WrLeh"
+#define	REMSmb_NetServerAdminCommand_P	"zhrLeh"
+#define	REMSmb_NetServerReqChalleng_P	"zb8g8"
+#define	REMSmb_NetServerAuthenticat_P	"zb8g8"
+#define	REMSmb_NetServerPasswordSet_P	"zb12g12b16"
+
+#define	REMSmb_NetAuditOpen_P		"h"
+#define	REMSmb_NetAuditClear_P		"zz"
+#define	REMSmb_NetAuditRead_P		"zb16g16DhDDrLeh"
+
+#define	REMSmb_AuditLogReturnBuf	"K"
+
+#define	REMSmb_NetErrorLogOpen_P	"h"
+#define	REMSmb_NetErrorLogClear_P	"zz"
+#define	REMSmb_NetErrorLogRead_P	"zb16g16DhDDrLeh"
+
+#define	REMSmb_ErrorLogReturnBuf	"K"
+
+#define	REMSmb_chardev_info_0		"B9"
+#define	REMSmb_chardev_info_1		"B10WB22D"
+#define	REMSmb_chardevQ_info_0		"B13"
+#define	REMSmb_chardevQ_info_1		"B14WzWW"
+
+#define	REMSmb_NetCharDevEnum_P		"WrLeh"
+#define	REMSmb_NetCharDevGetInfo_P	"zWrLh"
+#define	REMSmb_NetCharDevControl_P	"zW"
+#define	REMSmb_NetCharDevQEnum_P	"zWrLeh"
+#define	REMSmb_NetCharDevQGetInfo_P	"zzWrLh"
+#define	REMSmb_NetCharDevQSetInfo_P	"zWsTP"
+#define	REMSmb_NetCharDevQPurge_P	"z"
+#define	REMSmb_NetCharDevQPurgeSelf_P	"zz"
+
+#define	REMSmb_msg_info_0		"B16"
+#define	REMSmb_msg_info_1		"B16BBB16"
+#define	REMSmb_send_struct		"K"
+
+#define	REMSmb_NetMessageNameEnum_P	"WrLeh"
+#define	REMSmb_NetMessageNameGetInfo_P	"zWrLh"
+#define	REMSmb_NetMessageNameAdd_P	"zW"
+#define	REMSmb_NetMessageNameDel_P	"zW"
+#define	REMSmb_NetMessageNameFwd_P	"zzW"
+#define	REMSmb_NetMessageNameUnFwd_P	"z"
+#define	REMSmb_NetMessageBufferSend_P	"zsT"
+#define	REMSmb_NetMessageFileSend_P	"zz"
+#define	REMSmb_NetMessageLogFileSet_P	"zW"
+#define	REMSmb_NetMessageLogFileGet_P	"rLh"
+
+#define	REMSmb_service_info_0		"B16"
+#define	REMSmb_service_info_1		"B16WDW"
+#define	REMSmb_service_info_2		"B16WDWB64"
+#define	REMSmb_service_cmd_args		"K"
+
+#define	REMSmb_NetServiceEnum_P		"WrLeh"
+#define	REMSmb_NetServiceControl_P	"zWWrL"
+#define	REMSmb_NetServiceInstall_P	"zF88sg88T"	/* See NOTE 2 */
+#define	REMSmb_NetServiceGetInfo_P	"zWrLh"
+
+#define	REMSmb_access_info_0		"z"
+#define	REMSmb_access_info_0_setinfo	"z"
+#define	REMSmb_access_info_1		"zWN"
+#define	REMSmb_access_info_1_setinfo	"OWN"
+#define	REMSmb_access_list		"B21BW"
+
+#define	REMSmb_NetAccessEnum_P		"zWWrLeh"
+#define	REMSmb_NetAccessGetInfo_P	"zWrLh"
+#define	REMSmb_NetAccessSetInfo_P	"zWsTP"
+#define	REMSmb_NetAccessAdd_P		"WsT"
+#define	REMSmb_NetAccessDel_P		"z"
+#define	REMSmb_NetAccessGetUserPerms_P	"zzh"
+
+#define	REMSmb_group_info_0		"B21"
+#define	REMSmb_group_info_1		"B21Bz"
+#define	REMSmb_group_users_info_0	"B21"
+#define	REMSmb_group_users_info_1	"B21BN"
+
+#define	REMSmb_NetGroupEnum_P		"WrLeh"
+#define	REMSmb_NetGroupAdd_P		"WsT"
+#define	REMSmb_NetGroupDel_P		"z"
+#define	REMSmb_NetGroupAddUser_P	"zz"
+#define	REMSmb_NetGroupDelUser_P	"zz"
+#define	REMSmb_NetGroupGetUsers_P	"zWrLeh"
+#define	REMSmb_NetGroupSetUsers_P	"zWsTW"
+#define	REMSmb_NetGroupGetInfo_P	"zWrLh"
+#define	REMSmb_NetGroupSetInfo_P	"zWsTP"
+
+#define	REMSmb_user_info_0		"B21"
+#define	REMSmb_user_info_1		"B21BB16DWzzWz"
+#define	REMSmb_user_info_2		"B21BB16DWzzWzDzzzzDDDDWb21WWzWW"
+#define	REMSmb_user_info_10		"B21Bzzz"
+#define	REMSmb_user_info_11		"B21BzzzWDDzzDDWWzWzDWb21W"
+
+#define	REMSmb_user_info_100		"DWW"
+#define	REMSmb_user_info_101		"B60"
+#define	REMSmb_user_modals_info_0	"WDDDWW"
+#define	REMSmb_user_modals_info_1	"Wz"
+#define	REMSmb_user_modals_info_100	"B50"
+#define	REMSmb_user_modals_info_101	"zDDzDD"
+#define	REMSmb_user_logon_info_0	"B21B"
+#define	REMSmb_user_logon_info_1	"WB21BWDWWDDDDDDDzzzD"
+#define	REMSmb_user_logon_info_2	"B21BzzzD"
+#define	REMSmb_user_logoff_info_1	"WDW"
+
+#define	REMSmb_NetUserEnum_P		"WrLeh"
+#define	REMSmb_NetUserAdd_P		"WsTW"
+#define	REMSmb_NetUserAdd2_P		"WsTWW"
+#define	REMSmb_NetUserDel_P		"z"
+#define	REMSmb_NetUserGetInfo_P		"zWrLh"
+#define	REMSmb_NetUserSetInfo_P		"zWsTPW"
+#define	REMSmb_NetUserSetInfo2_P	"zWsTPWW"
+#define	REMSmb_NetUserPasswordSet_P	"zb16b16W"
+#define	REMSmb_NetUserPasswordSet2_P	"zb16b16WW"
+#define	REMSmb_NetUserGetGroups_P	"zWrLeh"
+#define	REMSmb_NetUserSetGroups_P	"zWsTW"
+#define	REMSmb_NetUserModalsGet_P	"WrLh"
+#define	REMSmb_NetUserModalsSet_P	"WsTP"
+#define	REMSmb_NetUserEnum2_P		"WrLDieh"
+#define	REMSmb_NetUserValidate2_P	"Wb62WWrLhWW"
+
+#define	REMSmb_wksta_info_0		"WDzzzzBBDWDWWWWWWWWWWWWWWWWWWWzzW"
+#define	REMSmb_wksta_info_0_setinfo	"WDOOOOBBDWDWWWWWWWWWWWWWWWWWWWzzW"
+#define	REMSmb_wksta_info_1		"WDzzzzBBDWDWWWWWWWWWWWWWWWWWWWzzWzzW"
+#define	REMSmb_wksta_info_1_setinfo	"WDOOOOBBDWDWWWWWWWWWWWWWWWWWWWzzWzzW"
+#define	REMSmb_wksta_info_10		"zzzBBzz"
+#define	REMSmb_wksta_annc_info		"K"
+
+#define	REMSmb_NetWkstaLogon_P		"zzirL"
+#define	REMSmb_NetWkstaLogoff_P		"zD"
+#define	REMSmb_NetWkstaSetUID_P		"zzzW"
+#define	REMSmb_NetWkstaGetInfo_P	"WrLh"
+#define	REMSmb_NetWkstaSetInfo_P	"WsTP"
+#define	REMSmb_NetWkstaUserLogon_P	"zzWb54WrLh"
+#define	REMSmb_NetWkstaUserLogoff_P	"zzWb38WrLh"
+
+#define	REMSmb_use_info_0		"B9Bz"
+#define	REMSmb_use_info_1		"B9BzzWWWW"
+
+#define	REMSmb_use_info_2		"B9BzzWWWWWWWzB16"
+
+#define	REMSmb_NetUseEnum_P		"WrLeh"
+#define	REMSmb_NetUseAdd_P		"WsT"
+#define	REMSmb_NetUseDel_P		"zW"
+#define	REMSmb_NetUseGetInfo_P		"zWrLh"
+
+#define	REMSmb_printQ_0			"B13"
+#define	REMSmb_printQ_1			"B13BWWWzzzzzWW"
+#define	REMSmb_printQ_2			"B13BWWWzzzzzWN"
+#define	REMSmb_printQ_3			"zWWWWzzzzWWzzl"
+#define	REMSmb_printQ_4			"zWWWWzzzzWNzzl"
+#define	REMSmb_printQ_5			"z"
+
+#define	REMSmb_DosPrintQEnum_P		"WrLeh"
+#define	REMSmb_DosPrintQGetInfo_P	"zWrLh"
+#define	REMSmb_DosPrintQSetInfo_P	"zWsTP"
+#define	REMSmb_DosPrintQAdd_P		"WsT"
+#define	REMSmb_DosPrintQDel_P		"z"
+#define	REMSmb_DosPrintQPause_P		"z"
+#define	REMSmb_DosPrintQPurge_P		"z"
+#define	REMSmb_DosPrintQContinue_P	"z"
+
+#define	REMSmb_print_job_0		"W"
+#define	REMSmb_print_job_1		"WB21BB16B10zWWzDDz"
+#define	REMSmb_print_job_2		"WWzWWDDzz"
+#define	REMSmb_print_job_3		"WWzWWDDzzzzzzzzzzlz"
+
+#define	REMSmb_print_job_info_1_setinfo	"WB21BB16B10zWWODDz"
+#define	REMSmb_print_job_info_3_setinfo	"WWzWWDDzzzzzOzzzzlO"
+
+#define	REMSmb_DosPrintJobEnum_P	"zWrLeh"
+#define	REMSmb_DosPrintJobGetInfo_P	"WWrLh"
+#define	REMSmb_DosPrintJobSetInfo_P	"WWsTP"
+#define	REMSmb_DosPrintJobAdd_P		"zsTF129g129h"	/* See note 2 */
+#define	REMSmb_DosPrintJobSchedule_P	"W"
+#define	REMSmb_DosPrintJobDel_P		"W"
+#define	REMSmb_DosPrintJobPause_P	"W"
+#define	REMSmb_DosPrintJobContinue_P	"W"
+
+#define	REMSmb_print_dest_0		"B9"
+#define	REMSmb_print_dest_1		"B9B21WWzW"
+#define	REMSmb_print_dest_2		"z"
+#define	REMSmb_print_dest_3		"zzzWWzzzWW"
+#define	REMSmb_print_dest_info_3_setinfo "zOzWWOzzWW"
+
+#define	REMSmb_DosPrintDestEnum_P	"WrLeh"
+#define	REMSmb_DosPrintDestGetInfo_P	"zWrLh"
+#define	REMSmb_DosPrintDestControl_P	"zW"
+#define	REMSmb_DosPrintDestAdd_P	"WsT"
+#define	REMSmb_DosPrintDestSetInfo_P	"zWsTP"
+#define	REMSmb_DosPrintDestDel_P	"z"
+
+#define	REMSmb_NetProfileSave_P		"zDW"
+#define	REMSmb_NetProfileLoad_P		"zDrLD"
+
+#define	REMSmb_profile_load_info	"WDzD"
+
+#define	REMSmb_statistics_info		"B"
+
+#define	REMSmb_statistics2_info_W	"B120"
+#define	REMSmb_stat_workstation_0	"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
+#define	REMSmb_statistics2_info_S	"B68"
+#define	REMSmb_stat_server_0		"DDDDDDDDDDDDDDDDD"
+
+#define	REMSmb_NetStatisticsGet_P	"rLeh"
+#define	REMSmb_NetStatisticsClear_P	""
+
+#define	REMSmb_NetStatisticsGet2_P	"zDWDrLh"
+
+#define	REMSmb_NetRemoteTOD_P		"rL"
+
+#define	REMSmb_time_of_day_info		"DDBBBBWWBBWB"
+
+#define	REMSmb_netbios_info_0		"B17"
+#define	REMSmb_netbios_info_1		"B17B9BBWWDWWW"
+
+#define	REMSmb_NetBiosEnum_P		"WrLeh"
+#define	REMSmb_NetBiosGetInfo_P		"zWrLh"
+
+#define	REMSmb_Spl_open_data		"zzlzzzzzz"
+#define	REMSmb_plain_data		"K"
+
+#define	REMSmb_NetSplQmAbort_P		"Di"
+#define	REMSmb_NetSplQmClose_P		"Di"
+#define	REMSmb_NetSplQmEndDoc_P		"Dhi"
+#define	REMSmb_NetSplQmOpen_P		"zTsWii"
+#define	REMSmb_NetSplQmStartDoc_P	"Dzi"
+#define	REMSmb_NetSplQmWrite_P		"DTsi"
+
+#define	REMSmb_configgetall_info	"B"
+#define	REMSmb_configget_info		"B"
+#define	REMSmb_configset_info_0		"zz"
+
+#define	REMSmb_NetConfigGetAll_P	"zzrLeh"
+#define	REMSmb_NetConfigGet_P		"zzzrLe"
+#define	REMSmb_NetConfigSet_P		"zzWWsTD"
+
+#define	REMSmb_NetBuildGetInfo_P	"DWrLh"
+#define	REMSmb_build_info_0		"WD"
+
+#define	REMSmb_NetGetDCName_P		"zrL"
+#define	REMSmb_dc_name			"B18"
+
+#define	REMSmb_challenge_info_0	"B8"
+#define	REMSmb_account_delta_info_0	"K"
+#define	REMSmb_account_sync_info_0	"K"
+
+#define	REMSmb_NetAccountDeltas_P	"zb12g12b24WWrLehg24"
+#define	REMSmb_NetAccountSync_P		"zb12g12DWrLehig24"
+
+#define	REMSmb_NetLogonEnum_P		"WrLeh"
+
+#define	REMSmb_I_NetPathType_P		"ziD"
+#define	REMSmb_I_NetPathCanonicalize_P	"zrLziDD"
+#define	REMSmb_I_NetPathCompare_P	"zzDD"
+#define	REMSmb_I_NetNameValidate_P	"zWD"
+#define	REMSmb_I_NetNameCanonicalize_P	"zrLWD"
+#define	REMSmb_I_NetNameCompare_P	"zzWD"
+
+#define	REMSmb_LocalOnlyCall		""
+
+/*
+ * The following definitions exist for DOS LANMAN--Windows 3.0.
+ * Normally, there is a  const char far * servername
+ * as the first parameter, but this will be ignored (sort of).
+ */
+#define	REMSmb_DosPrintJobGetId_P	"WrL"
+#define	REMSmb_GetPrintId		"WB16B13B"
+#define	REMSmb_NetRemoteCopy_P		"zzzzWWrL"
+#define	REMSmb_copy_info		"WB1"
+#define	REMSmb_NetRemoteMove_P		"zzzzWWrL"
+#define	REMSmb_move_info		"WB1"
+#define	REMSmb_NetHandleGetInfo_P	"WWrLh"
+#define	REMSmb_NetHandleSetInfo_P	"WWsTP"
+#define	REMSmb_handle_info_1		"DW"
+#define	REMSmb_handle_info_2		"z"
+#define	REMSmb_WWkstaGetInfo_P		"WrLhOW"
+
+/* The following strings are defined for RIPL APIs */
+
+#define	REMSmb_RplWksta_info_0		"z"
+#define	REMSmb_RplWksta_info_1		"zz"
+#define	REMSmb_RplWksta_info_2		"b13b16b15b15zN"
+#define	REMSmb_RplWksta_info_3		"b16b49"
+
+#define	REMSmb_RplWkstaEnum_P		"WzWrLehb4g4"
+#define	REMSmb_RplWkstaGetInfo_P	"zWrLh"
+#define	REMSmb_RplWkstaSetInfo_P	"zWsTPW"
+#define	REMSmb_RplWkstaAdd_P		"WsTW"
+#define	REMSmb_RplWkstaDel_P		"zW"
+
+#define	REMSmb_RplProfile_info_0	"z"
+#define	REMSmb_RplProfile_info_1	"zz"
+#define	REMSmb_RplProfile_info_2	"b16b47"
+#define	REMSmb_RplProfile_info_3	"b16b47b16"
+
+#define	REMSmb_RplProfileEnum_P		"WzWrLehb4g4"
+#define	REMSmb_RplProfileGetInfo_P	"zWrLh"
+#define	REMSmb_RplProfileSetInfo_P	"zWsTP"
+#define	REMSmb_RplProfileAdd_P		"WzsTW"
+#define	REMSmb_RplProfileDel_P		"zW"
+#define	REMSmb_RplProfileClone_P	"WzsTW"
+#define	REMSmb_RplBaseProfileEnum_P	"WrLehb4g4"
+
+
+/* LAN Manager 3.0 API strings go here */
+
+#define	REMSmb_I_GuidGetAgent_P	"g6i"
+#define	REMSmb_I_GuidSetAgent_P	"b6D"
+
+
+/* update support */
+
+#define	REMSmb_NetAccountUpdate_P	"b12g12WWrLh"
+#define	REMSmb_NetAccountConfirmUpd_P	"b12g12D"
+#define	REMSmb_update_info_0	"K"
+
+/*
+ * SamrOemChangePasswordUser2 api support
+ */
+#define	REMSmb_SamOEMChgPasswordUser2	"B516B16"  /* data that is passed */
+
+#endif	/* ndef	_REMDEF_ */

+ 120 - 0
sys/src/cmd/cifs/sid2name.c

@@ -0,0 +1,120 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+
+struct {		/* Well known security IDs */
+	char	*name;
+	char	*auth;
+	char	*rid;
+} known[] = {
+	/* default local users */
+	{ "lu.dialup",			"S-1-5-1",	nil },
+	{ "lu.network",			"S-1-5-2",	nil },
+	{ "lu.batch",			"S-1-5-3",	nil },
+	{ "lu.interactive",		"S-1-5-4",	nil },
+	{ "lu.service",			"S-1-5-6",	nil },
+	{ "lu.anon",			"S-1-5-7",	nil },
+	{ "lu.DC",			"S-1-5-8",	nil },
+	{ "lu.enterprise-domain",	"S-1-5-9",	nil },
+	{ "lu.self",			"S-1-5-10",	nil },
+	{ "lu.authenticated",		"S-1-5-11",	nil },
+	{ "lu.restricted",		"S-1-5-12",	nil },
+	{ "lu.terminal-services",	"S-1-5-13",	nil },
+	{ "lu.remote-desktop",		"S-1-5-14",	nil },
+	{ "lu.local-system",		"S-1-5-18",	nil },
+	{ "lu.local-service",		"S-1-5-19",	nil },
+	{ "lu.network-service",		"S-1-5-20",	nil },
+	{ "lu.builtin",			"S-1-5-32",	nil },
+
+	/* default local groups */
+	{ "lg.null",			"S-1-0-0",	nil },
+	{ "lg.world",			"S-1-1-0",	nil },
+	{ "lg.local",			"S-1-2-0",	nil },
+	{ "lg.creator-owner",		"S-1-3-0",	nil },
+	{ "lg.creator-group",		"S-1-3-1",	nil },
+	{ "lg.creator-owner-server",	"S-1-3-2",	nil },
+	{ "lg.creator-group-server",	"S-1-3-3",	nil },
+
+	/* default domain users */
+	{ "du.admin", 			"S-1-5",	"500" },
+	{ "du.guest",			"S-1-5",	"501" },
+	{ "du.kerberos",		"S-1-5",	"502" },
+
+	/* default domain groups */
+	{ "dg.admins", 			"S-1-5-21",	"512" },
+	{ "dg.users",			"S-1-5-21",	"513" },
+	{ "dg.guests",			"S-1-5",	"514" },
+	{ "dg.computers",		"S-1-5",	"515" },
+	{ "dg.controllers",		"S-1-5",	"516" },
+	{ "dg.cert-admins",		"S-1-5",	"517" },
+	{ "dg.schema-admins",		"S-1-5",	"518" },
+	{ "dg.enterprise-admins",	"S-1-5",	"519" },
+	{ "dg.group-policy-admins",	"S-1-5",	"520" },
+	{ "dg.remote-access",		"S-1-5",	"553" },
+
+	/* default domain aliases */
+	{ "da.admins",			"S-1-5",	"544" },
+	{ "da.users",			"S-1-5",	"545" },
+	{ "da.guests",			"S-1-5",	"546" },
+	{ "da.power-users",		"S-1-5",	"547" },
+	{ "da.account-operators",	"S-1-5",	"548" },
+	{ "da.server-operators",	"S-1-5",	"549" },
+	{ "da.print-operators",		"S-1-5",	"550" },
+	{ "da.backup-operators",	"S-1-5",	"551" },
+	{ "da.replicator",		"S-1-5",	"552" },
+	{ "da.RAS-servers",		"S-1-5",	"553" },
+
+};
+
+static char *
+sid2name(char *sid)
+{
+	int i;
+	char *rid;
+
+	if(sid == nil || (rid = strrchr(sid, '-')) == nil || *++rid == 0)
+		return estrdup9p("-");
+
+	for(i = 0; i < nelem(known); i++){
+		if(strcmp(known[i].auth, sid) == 0 && known[i].rid == nil)
+			return estrdup9p(known[i].name);
+
+		if(strlen(known[i].auth) < strlen(sid) &&
+		    strncmp(known[i].auth, sid, strlen(known[i].auth)) == 0 &&
+		    known[i].rid && strcmp(known[i].rid, rid) == 0)
+			return estrdup9p(known[i].name);
+	}
+
+	return estrdup9p(rid);
+}
+
+void
+upd_names(Session *s, Share *sp, char *path, Dir *d)
+{
+	int fh, result;
+	char *usid, *gsid;
+	FInfo fi;
+
+	if(d->uid)
+		free(d->uid);
+	if(d->gid)
+		free(d->gid);
+
+	if((fh = CIFS_NT_opencreate(s, sp, path, 0, 0, 0, READ_CONTROL,
+	    FILE_SHARE_ALL, FILE_OPEN, &result, &fi)) == -1){
+		d->uid = estrdup9p("unknown");
+		d->gid = estrdup9p("unknown");
+		return;
+	}
+	usid = nil;
+	gsid = nil;
+	TNTquerysecurity(s, sp, fh, &usid, &gsid);
+	d->uid = sid2name(usid);
+	d->gid = sid2name(gsid);
+	if(fh != -1)
+		CIFSclose(s, sp, fh);
+}

+ 787 - 0
sys/src/cmd/cifs/trans.c

@@ -0,0 +1,787 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+#include "remsmb.h"
+#include "apinums.h"
+
+static Pkt *
+thdr(Session *s, Share *sp)
+{
+	Pkt *p;
+
+	p = cifshdr(s, sp, SMB_COM_TRANSACTION);
+	p->tbase = pl16(p, 0);	/* 0  Total parameter bytes to be sent, filled later */
+	pl16(p, 0);		/* 2  Total data bytes to be sent, filled later */
+	pl16(p, 64);			/* 4  Max parameter to return */
+	pl16(p, MTU - T2HDRLEN - 128);	/* 6  Max data to return */
+	pl16(p, 1);			/* 8  Max setup count to return */
+	pl16(p, 0);			/* 10 Flags */
+	pl32(p, 1000);			/* 12 Timeout (ms) */
+	pl16(p, 0);			/* 16 Reserved */
+	pl16(p, 0);			/* 18 Parameter count, filled later */
+	pl16(p, 0);			/* 20 Parameter offset, filled later */
+	pl16(p, 0);			/* 22 Data count, filled later */
+	pl16(p, 0);			/* 24 Data offset, filled later */
+	pl16(p, 0);			/* 26 Setup count (in words) */
+	pbytes(p);			/* end of cifs words section */
+	return p;
+}
+
+static void
+ptparam(Pkt *p)
+{
+	uchar *pos;
+
+	if(((p->pos - p->tbase) % 2) != 0)
+		p8(p, 0);			/* pad to word boundry */
+	pos = p->pos;
+	p->pos = p->tbase + 20;
+	pl16(p, pos - p->buf - NBHDRLEN);	/* param offset */
+	p->tparam = p->pos = pos;
+}
+
+static void
+ptdata(Pkt *p)
+{
+	uchar *pos = p->pos;
+
+	assert(p->tparam != 0);
+	if(((p->pos - p->tbase) % 2) != 0)
+		p8(p, 0);		/* pad to word boundry */
+
+	p->pos = p->tbase + 0;
+	pl16(p, pos - p->tparam);	/* total param count */
+
+	p->pos = p->tbase + 18;
+	pl16(p, pos - p->tparam);	/* param count */
+
+	p->pos = p->tbase + 24;
+	pl16(p, pos - p->buf - NBHDRLEN); /* data offset */
+
+	p->tdata = p->pos = pos;
+}
+
+static int
+trpc(Pkt *p)
+{
+	int got;
+	uchar *pos = p->pos;
+
+	assert(p->tbase != 0);
+	assert(p->tdata != 0);
+
+	p->pos = p->tbase + 2;
+	pl16(p, pos - p->tdata);	/* total data count */
+
+	p->pos = p->tbase + 22;
+	pl16(p, pos - p->tdata);	/* data count */
+
+	p->pos = pos;
+	if((got = cifsrpc(p)) == -1)
+		return -1;
+
+	gl16(p);			/* Total parameter count */
+	gl16(p);			/* Total data count */
+	gl16(p);			/* Reserved */
+	gl16(p);			/* Parameter count in this buffer */
+	p->tparam = p->buf + NBHDRLEN + gl16(p); /* Parameter offset */
+	gl16(p);			/* Parameter displacement */
+	gl16(p);			/* Data count (this buffer); */
+	p->tdata = p->buf + NBHDRLEN + gl16(p); /* Data offset */
+	gl16(p);			/* Data displacement */
+	g8(p);				/* Setup count */
+	g8(p);				/* Reserved */
+	return got;
+}
+
+static void
+gtparam(Pkt *p)
+{
+	p->pos = p->tparam;
+}
+
+static void
+gtdata(Pkt *p)
+{
+	p->pos = p->tdata;
+}
+
+
+int
+RAPshareenum(Session *s, Share *sp, Share **ent)
+{
+	int ngot = 0, err, navail, nret;
+	char tmp[1024];
+	Pkt *p;
+	Share *q;
+
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+	ptparam(p);
+
+	pl16(p, API_WShareEnum);
+	pascii(p, REMSmb_NetShareEnum_P);	/* request descriptor */
+	pascii(p, REMSmb_share_info_0);		/* reply descriptor */
+	pl16(p, 0);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	gl16(p);				/* rx buffer offset */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	if(ngot == 0){
+		*ent = emalloc9p(sizeof(Share) * navail);
+		memset(*ent, 0, sizeof(Share) * navail);
+	}
+
+	q = *ent + ngot;
+	for (; ngot < navail && nret--; ngot++){
+		gmem(p, tmp, 13); 		/* name */
+		tmp[13] = 0;
+		q->name = estrdup9p(tmp);
+		q++;
+	}
+
+	if(ngot < navail)
+		fprint(2, "%s: %d share names too long for RAP (>13 chars)\n",
+			argv0, navail - ngot);
+
+	free(p);
+	return ngot;
+}
+
+
+int
+RAPshareinfo(Session *s, Share *sp, char *share, Shareinfo2 *si2p)
+{
+	int conv, err;
+	char tmp[1024];
+	Pkt *p;
+
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+
+	ptparam(p);
+	pl16(p, API_WShareGetInfo);
+	pascii(p, REMSmb_NetShareGetInfo_P);	/* request descriptor */
+	pascii(p, REMSmb_share_info_2);		/* reply descriptor */
+	pascii(p, share);
+	pl16(p, 1);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	conv = gl16(p);				/* rx buffer offset */
+	gl16(p);				/* number of entries returned */
+	gl16(p);				/* number of entries available */
+
+	if(err){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	memset(si2p, 0, sizeof(Shareinfo2));
+
+	gmem(p, tmp, 13);
+	tmp[13] = 0;
+	g8(p);					/* padding */
+	si2p->name = estrdup9p(tmp);
+	si2p->type = gl16(p);
+	gconv(p, conv, tmp, sizeof tmp);
+	si2p->comment = estrdup9p(tmp);
+	gl16(p);				/* comment offset high (unused) */
+	si2p->perms = gl16(p);
+	si2p->maxusrs = gl16(p);
+	si2p->activeusrs = gl16(p);
+	gconv(p, conv, tmp, sizeof tmp);
+	si2p->path = estrdup9p(tmp);
+	gl16(p);				/* path offset high (unused) */
+	gmem(p, tmp, 9);
+	tmp[9] = 0;
+	si2p->passwd = estrdup9p(tmp);
+
+	free(p);
+	return 0;
+}
+
+/*
+ * Tried to split sessionenum into two passes, one getting the names
+ * of the connected workstations and the other collecting the detailed info,
+ * however API_WSessionGetInfo doesn't seem to work agains win2k3 for infolevel
+ * ten and infolevel one and two are priviledged calls.  This means this code
+ * will work for small numbers of sessions agains win2k3 and fail for samba 3.0
+ * as it supports info levels zero and two only.
+ */
+int
+RAPsessionenum(Session *s, Share *sp, Sessinfo **sip)
+{
+	int ngot = 0, conv, err, navail, nret;
+	char tmp[1024];
+	Pkt *p;
+	Sessinfo *q;
+
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+	ptparam(p);
+
+	pl16(p, API_WSessionEnum);
+	pascii(p, REMSmb_NetSessionEnum_P);	/* request descriptor */
+	pascii(p, REMSmb_session_info_10);	/* reply descriptor */
+	pl16(p, 10);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	conv = gl16(p);				/* rx buffer offset */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	if(ngot == 0){
+		*sip = emalloc9p(sizeof(Sessinfo) * navail);
+		memset(*sip, 0, sizeof(Sessinfo) * navail);
+	}
+
+	q = *sip + ngot;
+	while(nret-- != 0){
+		gconv(p, conv, tmp, sizeof tmp);
+		q->wrkstn = estrdup9p(tmp);
+		gconv(p, conv, tmp, sizeof tmp);
+		q->user = estrdup9p(tmp);
+		q->sesstime = gl32(p);
+		q->idletime = gl32(p);
+		q++;
+		ngot++;
+	}
+	if(ngot < navail)
+		fprint(2, "warning: %d/%d - incomplete session list sent\n",
+			ngot, navail);
+	free(p);
+	return ngot;
+}
+
+
+int
+RAPgroupenum(Session *s, Share *sp, Namelist **nlp)
+{
+	int ngot, err, navail, nret;
+	char tmp[1024];
+	Pkt *p;
+	Namelist *q;
+
+	ngot = 0;
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+	ptparam(p);
+
+	pl16(p, API_WGroupEnum);
+	pascii(p, REMSmb_NetGroupEnum_P);	/* request descriptor */
+	pascii(p, REMSmb_group_info_0);		/* reply descriptor */
+	pl16(p, 0);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	gl16(p);				/* rx buffer offset */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	*nlp = emalloc9p(sizeof(Namelist) * navail);
+	memset(*nlp, 0, sizeof(Namelist) * navail);
+
+	q = *nlp + ngot;
+	while(ngot < navail && nret--){
+ 		gmem(p, tmp, 21);
+		tmp[21] = 0;
+		q->name = estrdup9p(tmp);
+		q++;
+		ngot++;
+		if(p->pos >= p->eop)	/* Windows seems to lie sometimes */
+			break;
+	}
+	free(p);
+	return ngot;
+}
+
+
+int
+RAPgroupusers(Session *s, Share *sp, char *group, Namelist **nlp)
+{
+	int ngot, err, navail, nret;
+	char tmp[1024];
+	Pkt *p;
+	Namelist *q;
+
+	ngot = 0;
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+	ptparam(p);
+
+	pl16(p, API_WGroupGetUsers);
+	pascii(p, REMSmb_NetGroupGetUsers_P);	/* request descriptor */
+	pascii(p, REMSmb_user_info_0);		/* reply descriptor */
+	pascii(p, group);			/* group name for list */
+	pl16(p, 0);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	gl16(p);				/* rx buffer offset */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	*nlp = emalloc9p(sizeof(Namelist) * navail);
+	memset(*nlp, 0, sizeof(Namelist) * navail);
+
+	q = *nlp + ngot;
+	while(ngot < navail && nret--){
+ 		gmem(p, tmp, 21);
+		tmp[21] = 0;
+		q->name = estrdup9p(tmp);
+		q++;
+		ngot++;
+		if(p->pos >= p->eop)	/* Windows seems to lie sometimes */
+			break;
+	}
+	free(p);
+	return ngot;
+}
+
+int
+RAPuserenum(Session *s, Share *sp, Namelist **nlp)
+{
+	int ngot, err, navail, nret;
+	char tmp[1024];
+	Pkt *p;
+	Namelist *q;
+
+	ngot = 0;
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+	ptparam(p);
+
+	pl16(p, API_WUserEnum);
+	pascii(p, REMSmb_NetUserEnum_P);	/* request descriptor */
+	pascii(p, REMSmb_user_info_0);		/* reply descriptor */
+	pl16(p, 0);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	gl16(p);				/* rx buffer offset */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	*nlp = emalloc9p(sizeof(Namelist) * navail);
+	memset(*nlp, 0, sizeof(Namelist) * navail);
+
+	q = *nlp + ngot;
+	while(ngot < navail && nret--){
+ 		gmem(p, tmp, 21);
+		tmp[21] = 0;
+		q->name = estrdup9p(tmp);
+		q++;
+		ngot++;
+		if(p->pos >= p->eop)	/* Windows seems to lie sometimes */
+			break;
+	}
+	free(p);
+	return ngot;
+}
+
+int
+RAPuserenum2(Session *s, Share *sp, Namelist **nlp)
+{
+	int ngot, resume, err, navail, nret;
+	char tmp[1024];
+	Pkt *p;
+	Namelist *q;
+
+	ngot = 0;
+	resume = 0;
+more:
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+	ptparam(p);
+
+	pl16(p, API_WUserEnum2);
+	pascii(p, REMSmb_NetUserEnum2_P);	/* request descriptor */
+	pascii(p, REMSmb_user_info_0);		/* reply descriptor */
+	pl16(p, 0);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	pl32(p, resume);			/* resume key to allow multiple fetches */
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	gl16(p);				/* rx buffer offset */
+	resume = gl32(p);			/* resume key returned */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	if(ngot == 0){
+		*nlp = emalloc9p(sizeof(Namelist) * navail);
+		memset(*nlp, 0, sizeof(Namelist) * navail);
+	}
+	q = *nlp + ngot;
+	while(ngot < navail && nret--){
+ 		gmem(p, tmp, 21);
+		tmp[21] = 0;
+		q->name = estrdup9p(tmp);
+		q++;
+		ngot++;
+		if(p->pos >= p->eop)	/* Windows seems to lie sometimes */
+			break;
+	}
+	free(p);
+	if(ngot < navail)
+		goto more;
+	return ngot;
+}
+
+int
+RAPuserinfo(Session *s, Share *sp, char *user, Userinfo *uip)
+{
+	int conv, err;
+	char tmp[1024];
+	Pkt *p;
+
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+	ptparam(p);
+
+	pl16(p, API_WUserGetInfo);
+	pascii(p, REMSmb_NetUserGetInfo_P);	/* request descriptor */
+	pascii(p, REMSmb_user_info_10);		/* reply descriptor */
+	pascii(p, user);			/* username */
+	pl16(p, 10);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	conv = gl16(p);				/* rx buffer offset */
+	gl16(p);				/* number of entries returned */
+	gl16(p);				/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+ 	gmem(p, tmp, 21);
+	tmp[21] = 0;
+	uip->user = estrdup9p(tmp);
+	g8(p);				/* padding */
+	gconv(p, conv, tmp, sizeof tmp);
+	uip->comment = estrdup9p(tmp);
+	gconv(p, conv, tmp, sizeof tmp);
+	uip->user_comment = estrdup9p(tmp);
+	gconv(p, conv, tmp, sizeof tmp);
+	uip->fullname = estrdup9p(tmp);
+
+	free(p);
+	return 0;
+}
+
+/*
+ * This works agains win2k3 but fails
+ * against XP with the undocumented error 71/0x47
+ */
+int
+RAPServerenum2(Session *s, Share *sp, char *workgroup, int type, int *more,
+	Serverinfo **si)
+{
+	int ngot = 0, conv, err, nret, navail;
+	char tmp[1024];
+	Pkt *p;
+	Serverinfo *q;
+
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+
+	ptparam(p);
+	pl16(p, API_NetServerEnum2);
+	pascii(p, REMSmb_NetServerEnum2_P);	/* request descriptor */
+	pascii(p, REMSmb_server_info_1);	/* reply descriptor */
+	pl16(p, 1);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	pl32(p, type);
+	pascii(p, workgroup);
+
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	conv = gl16(p);				/* rx buffer offset */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	*si = emalloc9p(sizeof(Serverinfo) * navail);
+	memset(*si, 0, sizeof(Serverinfo) * navail);
+
+	q = *si;
+	for (; nret-- != 0 && ngot < navail; ngot++){
+		gmem(p, tmp, 16);
+		tmp[16] = 0;
+		q->name = estrdup9p(tmp);
+		q->major = g8(p);
+		q->minor = g8(p);
+		q->type = gl32(p);
+		gconv(p, conv, tmp, sizeof tmp);
+		q->comment = estrdup9p(tmp);
+		q++;
+	}
+	free(p);
+	*more = err == RAP_ERR_MOREINFO;
+	return ngot;
+}
+
+int
+RAPServerenum3(Session *s, Share *sp, char *workgroup, int type, int last,
+	Serverinfo *si)
+{
+	int conv, err, ngot, nret, navail;
+	char *first, tmp[1024];
+	Pkt *p;
+	Serverinfo *q;
+
+	ngot = last +1;
+	first = si[last].name;
+more:
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+
+	ptparam(p);
+	pl16(p, API_NetServerEnum3);
+	pascii(p, REMSmb_NetServerEnum3_P);	/* request descriptor */
+	pascii(p, REMSmb_server_info_1);	/* reply descriptor */
+	pl16(p, 1);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	pl32(p, type);
+	pascii(p, workgroup);
+	pascii(p, first);
+
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	conv = gl16(p);				/* rx buffer offset */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	if(nret < 2){				/* paranoia */
+		free(p);
+		return ngot;
+	}
+
+	q = si+ngot;
+	while(nret-- != 0 && ngot < navail){
+		gmem(p, tmp, 16);
+		tmp[16] = 0;
+		q->name = estrdup9p(tmp);
+		q->major = g8(p);
+		q->minor = g8(p);
+		q->type = gl32(p);
+		gconv(p, conv, tmp, sizeof tmp);
+		tmp[sizeof tmp - 1] = 0;
+		q->comment = estrdup9p(tmp);
+		if(strcmp(first, tmp) == 0){ /* 1st one thru _may_ be a repeat */
+			free(q->name);
+			free(q->comment);
+			continue;
+		}
+		q++;
+		ngot++;
+	}
+	free(p);
+	if(ngot < navail)
+		goto more;
+	return ngot;
+}
+
+/* Only the Administrator has permission to do this */
+int
+RAPFileenum2(Session *s, Share *sp, char *user, char *path, Fileinfo **fip)
+{
+	int conv, err, ngot, resume, nret, navail;
+	char tmp[1024];
+	Pkt *p;
+	Fileinfo *q;
+
+	ngot = 0;
+	resume = 0;
+more:
+	p = thdr(s, sp);
+	pstr(p, "\\PIPE\\LANMAN");
+
+	ptparam(p);
+	pl16(p, API_WFileEnum2);
+	pascii(p, REMSmb_NetFileEnum2_P);	/* request descriptor */
+	pascii(p, REMSmb_file_info_3);		/* reply descriptor */
+	pascii(p, path);
+	pascii(p, user);
+	pl16(p, 3);				/* detail level */
+	pl16(p, MTU - 200);			/* receive buffer length */
+	pl32(p, resume);			/* resume key */
+/* FIXME: maybe the padding and resume key are the wrong way around? */
+	pl32(p, 0);				/* padding ? */
+
+	ptdata(p);
+
+	if(trpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtparam(p);
+	err = gl16(p);				/* error code */
+	conv = gl16(p);				/* rx buffer offset */
+	resume = gl32(p);			/* resume key returned */
+	nret = gl16(p);				/* number of entries returned */
+	navail = gl16(p);			/* number of entries available */
+
+	if(err && err != RAP_ERR_MOREINFO){
+		werrstr("%s", raperrstr(err));
+		free(p);
+		return -1;
+	}
+
+	if(nret < 2){				/* paranoia */
+		free(p);
+		return ngot;
+	}
+
+	if(ngot == 0){
+		*fip = emalloc9p(sizeof(Fileinfo) * navail);
+		memset(*fip, 0, sizeof(Fileinfo) * navail);
+	}
+	q = *fip + ngot;
+	for(; nret-- && ngot < navail; ngot++){
+		q->ident = gl16(p);
+		q->perms = gl16(p);
+		q->locks = gl16(p);
+		gconv(p, conv, tmp, sizeof tmp);
+		tmp[sizeof tmp - 1] = 0;
+		q->path = estrdup9p(tmp);
+		gconv(p, conv, tmp, sizeof tmp);
+		tmp[sizeof tmp - 1] = 0;
+		q->user = estrdup9p(tmp);
+		q++;
+	}
+	free(p);
+	if(ngot < navail)
+		goto more;
+	return ngot;
+}

+ 539 - 0
sys/src/cmd/cifs/trans2.c

@@ -0,0 +1,539 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+
+static Pkt *
+t2hdr(Session *s, Share *sp, int cmd)
+{
+	Pkt *p;
+
+	p = cifshdr(s, sp, SMB_COM_TRANSACTION2);
+
+	p->tbase = pl16(p, 0);	/* 0  Total parameter bytes to be sent, filled later */
+	pl16(p, 0);		/* 2  Total data bytes to be sent, filled later */
+	pl16(p, 64);			/* 4  Max parameter to return */
+	pl16(p, (MTU - T2HDRLEN)-64);	/* 6  Max data to return */
+	p8(p, 0);			/* 8  Max setup count to return */
+	p8(p, 0);			/* 9  Reserved */
+	pl16(p, 0);			/* 10 Flags */
+	pl32(p, 1000);			/* 12 Timeout (ms) */
+	pl16(p, 0);			/* 16 Reserved */
+	pl16(p, 0);			/* 18 Parameter count, filled later */
+	pl16(p, 0);			/* 20 Parameter offset, filled later */
+	pl16(p, 0);			/* 22 Data count, filled later */
+	pl16(p, 0);			/* 24 Data offset, filled later */
+	p8(p, 1);			/* 26 Setup count (in words) */
+	p8(p, 0);			/* 27 Reserved */
+	pl16(p, cmd);			/* setup[0] */
+	pbytes(p);
+	p8(p, 0);			/* padding ??!?!? */
+
+	return p;
+}
+
+static void
+pt2param(Pkt *p)
+{
+	uchar *pos = p->pos;
+
+	assert(p->tbase != 0);
+	p->pos = p->tbase + 20;
+	pl16(p, (pos - p->buf) - NBHDRLEN); /* param offset */
+
+	p->tparam = p->pos = pos;
+}
+
+static void
+pt2data(Pkt *p)
+{
+	uchar *pos = p->pos;
+
+	assert(p->tbase != 0);
+	assert(p->tparam != 0);
+
+	p->pos = p->tbase +0;
+	pl16(p, pos - p->tparam);		/* total param count */
+
+	p->pos = p->tbase +18;
+	pl16(p, pos - p->tparam);		/* param count */
+
+	p->pos = p->tbase +24;
+	pl16(p, (pos - p->buf) - NBHDRLEN);	/* data offset */
+
+	p->tdata = p->pos = pos;
+}
+
+static int
+t2rpc(Pkt *p)
+{
+	int got;
+	uchar *pos;
+
+	assert(p->tbase != 0);
+	assert(p->tdata != 0);
+
+	pos = p->pos;
+
+	p->pos = p->tbase +2;
+	pl16(p, pos - p->tdata);		/* total data count */
+
+	p->pos = p->tbase +22;
+	pl16(p, pos - p->tdata);		/* data count */
+
+	p->pos = pos;
+	if((got = cifsrpc(p)) == -1)
+		return -1;
+
+	gl16(p);			/* Total parameter count */
+	gl16(p);			/* Total data count */
+	gl16(p);			/* Reserved */
+	gl16(p);			/* Parameter count in this buffer */
+	p->tparam = p->buf +NBHDRLEN +gl16(p); /* Parameter offset */
+	gl16(p);			/* Parameter displacement */
+	gl16(p);			/* Data count (this buffer); */
+	p->tdata = p->buf +NBHDRLEN +gl16(p); /* Data offset */
+	gl16(p);			/* Data displacement */
+	g8(p);				/* Setup count */
+	g8(p);				/* Reserved */
+
+	return got;
+}
+
+static void
+gt2param(Pkt *p)
+{
+	p->pos = p->tparam;
+}
+
+static void
+gt2data(Pkt *p)
+{
+	p->pos = p->tdata;
+}
+
+
+int
+T2findfirst(Session *s, Share *sp, int slots, char *path, int *got,
+	long *resume, FInfo *fip)
+{
+	int pktlen, i, n, sh;
+	uchar *next;
+	Pkt *p;
+
+	p = t2hdr(s, sp, TRANS2_FIND_FIRST2);
+	p8(p, 'D');			/* OS/2 */
+	p8(p, ' ');			/* OS/2 */
+
+	pt2param(p);
+	pl16(p, ATTR_HIDDEN|ATTR_SYSTEM|ATTR_DIRECTORY); /* Search attributes */
+	pl16(p, slots);			/* Search count */
+	pl16(p, CIFS_SEARCH_RETURN_RESUME); /* Flags */
+	pl16(p, SMB_FIND_FILE_FULL_DIRECTORY_INFO); /* Information level */
+	pl32(p, 0);			/* SearchStorage type (?) */
+	ppath(p, path);			/* path */
+
+	pt2data(p);
+	if((pktlen = t2rpc(p)) == -1){
+		free(p);
+		return -1;
+	}
+
+	s->lastfind = nsec();
+	gt2param(p);
+
+	sh = gl16(p);			/* Sid (search handle) */
+	*got = gl16(p);			/* number of slots received */
+	gl16(p);			/* End of search flag */
+	gl16(p);			/* Offset into EA list if EA error */
+	gl16(p);			/* Offset into data to file name of last entry */
+
+	gt2data(p);
+	memset(fip, 0, slots * sizeof(FInfo));
+	for(i = 0; i < *got; i++){
+		next = p->pos;
+		next += gl32(p);	/* offset to next entry */
+		/*
+		 * bug in Windows - somtimes it lies about how many
+		 * directory entries it has put in the packet
+		 */
+		if(next - p->buf > pktlen){
+			*got = i;
+			break;
+		}
+
+		*resume = gl32(p);		/* resume key for search */
+		fip[i].created = gvtime(p);	/* creation time */
+		fip[i].accessed = gvtime(p);	/* last access time */
+		fip[i].written = gvtime(p);	/* last written time */
+		fip[i].changed = gvtime(p);	/* change time */
+		fip[i].size = gl64(p);		/* file size */
+		gl64(p);			/* bytes allocated */
+		fip[i].attribs = gl32(p);	/* extended attributes */
+		n = gl32(p);			/* name length */
+		gl32(p);			/* EA size */
+		gstr(p, fip[i].name, n); 	/* name */
+		p->pos = next;
+	}
+
+	free(p);
+	return sh;
+
+}
+
+int
+T2findnext(Session *s, Share *sp, int slots, char *path, int *got,
+	long *resume, FInfo *fip, int sh)
+{
+	int i, n;
+	uchar *next;
+	Pkt *p;
+
+	/*
+	 * So I believe from comp.protocols.smb if you send
+	 * TRANS2_FIND_NEXT2 requests too quickly to windows 95, it can
+	 * get confused and fail to reply, so we slow up a bit in these
+	 * circumstances.
+	 */
+	if(!(s->caps & CAP_NT_SMBS) && nsec() - s->lastfind < 200000000LL)
+		sleep(200);
+
+	p = t2hdr(s, sp, TRANS2_FIND_NEXT2);
+	p8(p, 'D');			/* OS/2 */
+	p8(p, ' ');			/* OS/2 */
+
+	pt2param(p);
+	pl16(p, sh);				/* search handle */
+	pl16(p, slots);				/* Search count */
+	pl16(p, SMB_FIND_FILE_FULL_DIRECTORY_INFO); /* Information level */
+	pl32(p, *resume);			/* resume key */
+	pl16(p, CIFS_SEARCH_CONTINUE_FROM_LAST); /* Flags */
+	ppath(p, path);				/* file+path to resume */
+
+	pt2data(p);
+	if(t2rpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	s->lastfind = nsec();
+
+	gt2param(p);
+	*got = gl16(p);		/* number of slots received */
+	gl16(p);		/* End of search flag */
+	gl16(p);		/* Offset into EA list if EA error */
+	gl16(p);		/* Offset into data to file name of last entry */
+
+	gt2data(p);
+	memset(fip, 0, slots * sizeof(FInfo));
+	for(i = 0; i < *got; i++){
+		next = p->pos;
+		next += gl32(p);		/* offset to next entry */
+		*resume = gl32(p);		/* resume key for search */
+		fip[i].created = gvtime(p);	/* creation time */
+		fip[i].accessed = gvtime(p);	/* last access time */
+		fip[i].written = gvtime(p);	/* last written time */
+		fip[i].changed = gvtime(p);	/* change time */
+		fip[i].size = gl64(p);		/* file size */
+		gl64(p);			/* bytes allocated */
+		fip[i].attribs = gl32(p);	/* extended attributes */
+		n = gl32(p);			/* name length */
+		gl32(p);			/* EA size */
+		gstr(p, fip[i].name, n); 	/* name */
+		p->pos = next;
+	}
+	free(p);
+	return 0;
+}
+
+
+/* supported by 2k/XP/NT4 */
+int
+T2queryall(Session *s, Share *sp, char *path, FInfo *fip)
+{
+	int n;
+	Pkt *p;
+
+	p = t2hdr(s, sp, TRANS2_QUERY_PATH_INFORMATION);
+	pt2param(p);
+	pl16(p, SMB_QUERY_FILE_ALL_INFO); /* Information level	 */
+	pl32(p, 0);			/* reserved */
+	ppath(p, path);			/* path */
+
+	pt2data(p);
+	if(t2rpc(p) == -1){
+		free(p);
+		return -1;
+	}
+	gt2data(p);
+
+	/*
+	 * The layout of this struct is wrong in the SINA
+	 * document, this layout gained by inspection.
+	 */
+	memset(fip, 0, sizeof(FInfo));
+	fip->created = gvtime(p);	/* creation time */
+	fip->accessed = gvtime(p);	/* last access time */
+	fip->written = gvtime(p);	/* last written time */
+	fip->changed = gvtime(p);	/* change time */
+	fip->attribs = gl32(p);		/* attributes */
+	gl32(p);			/* reserved */
+	gl64(p);			/* bytes allocated */
+	fip->size = gl64(p);		/* file size */
+	gl32(p);			/* number of hard links */
+	g8(p);				/* delete pending */
+	g8(p);				/* is a directory */
+	gl16(p);			/* reserved */
+	gl32(p);			/* EA size */
+
+	n = gl32(p);
+	if(n >= sizeof fip->name)
+		n = sizeof fip->name - 1;
+	gstr(p, fip->name, n);
+
+	free(p);
+	return 0;
+}
+
+/* supported by 95/98/ME */
+int
+T2querystandard(Session *s, Share *sp, char *path, FInfo *fip)
+{
+	Pkt *p;
+
+	p = t2hdr(s, sp, TRANS2_QUERY_PATH_INFORMATION);
+	pt2param(p);
+	pl16(p, SMB_INFO_STANDARD);	/* Information level */
+	pl32(p, 0);			/* reserved */
+	ppath(p, path);			/* path */
+
+	pt2data(p);
+	if(t2rpc(p) == -1){
+		free(p);
+		return -1;
+	}
+	gt2data(p);
+	memset(fip, 0, sizeof(FInfo));
+	fip->created = gdatetime(p);	/* creation time */
+	fip->accessed = gdatetime(p);	/* last access time */
+	fip->written = gdatetime(p);	/* last written time */
+	fip->changed = fip->written;	/* change time */
+	fip->size = gl32(p);		/* file size */
+	gl32(p);			/* bytes allocated */
+	fip->attribs = gl16(p);		/* attributes */
+	gl32(p);			/* EA size */
+
+	free(p);
+	return 0;
+}
+
+int
+T2setpathinfo(Session *s, Share *sp, char *path, FInfo *fip)
+{
+	int rc;
+	Pkt *p;
+
+	p = t2hdr(s, sp, TRANS2_SET_PATH_INFORMATION);
+	pt2param(p);
+	pl16(p, SMB_INFO_STANDARD);	/* Information level */
+	pl32(p, 0);			/* reserved */
+	ppath(p, path);			/* path */
+
+	pt2data(p);
+	pdatetime(p, fip->created);	/* created */
+	pdatetime(p, fip->accessed);	/* accessed */
+	pdatetime(p, fip->written);	/* written */
+	pl32(p, fip->size);		/* size */
+	pl32(p, 0);			/* allocated */
+	pl16(p, fip->attribs);		/* attributes */
+	pl32(p, 0);			/* EA size */
+	pl16(p, 0);			/* reserved */
+
+	rc = t2rpc(p);
+	free(p);
+	return rc;
+
+}
+
+int
+T2setfilelength(Session *s, Share *sp, int fh, FInfo *fip) /* FIXME: maybe broken, needs testing */
+{
+	int rc;
+	Pkt *p;
+
+	p = t2hdr(s, sp, TRANS2_SET_FILE_INFORMATION);
+	pt2param(p);
+	pl16(p, fh);			/* file handle */
+	pl16(p, SMB_SET_FILE_END_OF_FILE_INFO); /* Information level */
+	pl16(p, 0);			/* reserved */
+
+	pt2data(p);
+	pl64(p, fip->size);
+	pl32(p, 0);			/* padding ?! */
+	pl16(p, 0);
+
+	rc = t2rpc(p);
+	free(p);
+	return rc;
+}
+
+
+int
+T2fsvolumeinfo(Session *s, Share *sp, long *created, long *serialno,
+	char *label, int labellen)
+{
+	Pkt *p;
+	long ct, sn, n;
+
+	p = t2hdr(s, sp, TRANS2_QUERY_FS_INFORMATION);
+	pt2param(p);
+	pl16(p, SMB_QUERY_FS_VOLUME_INFO);	/* Information level */
+
+	pt2data(p);
+
+	if(t2rpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gt2data(p);
+	ct = gvtime(p);			/* creation time */
+	sn = gl32(p);			/* serial number */
+	n = gl32(p);			/* label name length */
+	g8(p);				/* reserved */
+	g8(p);				/* reserved */
+
+	memset(label, 0, labellen);
+	if(n < labellen && n > 0)
+		gstr(p, label, n);	/* file system label */
+	if(created)
+		*created = ct;
+	if(serialno)
+		*serialno = sn;
+
+	free(p);
+	return 0;
+}
+
+int
+T2fssizeinfo(Session *s, Share *sp, uvlong *total, uvlong *unused)
+{
+	Pkt *p;
+	uvlong t, f, n, b;
+
+	p = t2hdr(s, sp, TRANS2_QUERY_FS_INFORMATION);
+	pt2param(p);
+	pl16(p, SMB_QUERY_FS_SIZE_INFO);	/* Information level */
+
+	pt2data(p);
+
+	if(t2rpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gt2data(p);
+	t = gl64(p);		/* total blocks */
+	f = gl64(p);		/* free blocks */
+	n = gl32(p);		/* sectors per block */
+	b = gl32(p);		/* bytes per sector */
+
+	if(free)
+		*unused = f * n * b;
+	if(total)
+		*total = t * n * b;
+
+	free(p);
+	return 0;
+}
+
+int
+T2getdfsreferral(Session *s, Share *sp, char *path, int *gflags, int *used,
+	Refer *re, int nent)
+{
+	int i, vers, nret, len;
+	char tmp[1024];
+	uchar *base;
+	Pkt *p;
+
+	p = t2hdr(s, sp, TRANS2_GET_DFS_REFERRAL);
+	pt2param(p);
+	pl16(p, 3); /* max info level we understand, must be >= 3 for domain requests */
+	ppath(p, path);
+
+	pt2data(p);
+
+	if(t2rpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	memset(re, 0, sizeof *re * nent);
+	gt2data(p);
+
+	*used = gl16(p) / 2;	/* length used (/2 as Windows counts in runes) */
+	nret = gl16(p);		/* number of referrals returned */
+	*gflags = gl32(p);	/* global flags */
+
+	for(i = 0; i < nret && i < nent && i < 16; i++){
+		base = p->pos;
+		vers = gl16(p);		/* version of records */
+		len = gl16(p);		/* length of records */
+		re[i].type = gl16(p);	/* server type */
+		re[i].flags = gl16(p);	/* referal flags */
+		switch(vers){
+		case 1:
+			re[i].prox = 0;	/* nearby */
+			re[i].ttl = 5*60;	/* 5 mins */
+			gstr(p, tmp, sizeof tmp);
+			re[i].addr = estrdup9p(tmp);
+			re[i].path = estrdup9p(tmp);
+			break;
+		case 2:
+			re[i].prox = gl32(p);	/* not implemented in v2 */
+			re[i].ttl = gl32(p);
+			goff(p, base, re[i].path, sizeof tmp);
+			re[i].path = estrdup9p(tmp);
+			goff(p, base, re[i].path, sizeof tmp);/* spurious 8.3 path */
+			goff(p, base, tmp, sizeof tmp);
+			re[i].addr = estrdup9p(tmp);
+			break;
+		case 3:
+			if(re[i].flags & DFS_REFERAL_LIST){
+				re[i].prox = 0;
+				re[i].ttl = gl32(p);
+				goff(p, base, tmp, sizeof tmp);
+				re[i].path = estrdup9p(tmp);
+				gl16(p);
+				goff(p, base, tmp, sizeof tmp);
+				re[i].addr = estrdup9p(tmp);
+			}
+			else{
+				re[i].prox = 0;
+				re[i].ttl = gl32(p);
+				goff(p, base, tmp, sizeof tmp);
+				re[i].path = estrdup9p(tmp);
+				gl16(p);	/* spurious 8.3 path */
+				goff(p, base, tmp, sizeof tmp);
+				re[i].addr = estrdup9p(tmp);
+				gl16(p);	/* GUID (historic) */
+			}
+			break;
+		default:
+			/*
+			 * this should never happen as we specify our maximum
+			 * understood level in the request (above)
+			 */
+			fprint(2, "%d - unsupported DFS infolevel\n", vers);
+			re[i].path = estrdup9p(tmp);
+			re[i].addr = estrdup9p(tmp);
+			break;
+		}
+		p->pos = base+len;
+	}
+
+	free(p);
+	return i;
+}

+ 167 - 0
sys/src/cmd/cifs/transnt.c

@@ -0,0 +1,167 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "cifs.h"
+
+static Pkt *
+tnthdr(Session *s, Share *sp, int cmd)
+{
+	Pkt *p;
+
+	p = cifshdr(s, sp, SMB_COM_NT_TRANSACT);
+	p->tbase = p8(p, 0);		/*  0  Max setup count to return */
+	pl16(p, 0);			/*  1  reserved */
+	pl32(p, 0);			/*  3  Total parameter count */
+	pl32(p, 0);			/*  7  Total data count */
+	pl32(p, 64);			/* 11  Max parameter count to return */
+	pl32(p, (MTU - T2HDRLEN)-64);	/* 15  Max data count to return */
+	pl32(p, 0);			/* 19  Parameter count (in this buffer) */
+	pl32(p, 0);			/* 23  Offset to parameters (in this buffer) */
+	pl32(p, 0);			/* 27  Count of data  in this buffer */
+	pl32(p, 0);			/* 31  Offset to data in this buffer */
+	p8(p, 1);			/* 35  Count of setup words */
+	pl16(p, cmd);			/* 37  setup[0] */
+	pl16(p, 0);			/* padding ??!?!? */
+	pbytes(p);
+	return p;
+}
+
+static void
+ptntparam(Pkt *p)
+{
+	uchar *pos = p->pos;
+	assert(p->tbase != 0);
+
+	p->pos = p->tbase +23;
+	pl32(p, (pos - p->buf) - NBHDRLEN); /* param offset */
+
+	p->tparam = p->pos = pos;
+}
+
+static void
+ptntdata(Pkt *p)
+{
+	uchar *pos = p->pos;
+	assert(p->tbase != 0);
+	assert(p->tparam != 0);
+
+	p->pos = p->tbase +3;
+	pl32(p, pos - p->tparam);		/* total param count */
+
+	p->pos = p->tbase +19;
+	pl32(p, pos - p->tparam);		/* param count */
+
+	p->pos = p->tbase +31;
+	pl32(p, (pos - p->buf) - NBHDRLEN);	/* data offset */
+	p->tdata = p->pos = pos;
+}
+
+static int
+tntrpc(Pkt *p)
+{
+	int got;
+	uchar *pos;
+	assert(p->tbase != 0);
+	assert(p->tdata != 0);
+
+	pos = p->pos;
+
+	p->pos = p->tbase +7;
+	pl32(p, pos - p->tdata);		/* total data count */
+
+	p->pos = p->tbase +27;
+	pl32(p, pos - p->tdata);		/* data count */
+
+	p->pos = pos;
+	if((got = cifsrpc(p)) == -1)
+		return -1;
+
+	g8(p);				/* Reserved */
+	g8(p);				/* Reserved */
+	g8(p);				/* Reserved */
+	gl32(p);			/* Total parameter count */
+	gl32(p);			/* Total data count */
+	gl32(p);			/* Parameter count in this buffer */
+	p->tparam = p->buf +NBHDRLEN +gl32(p); /* Parameter offset */
+	gl32(p);			/* Parameter displacement */
+	gl32(p);			/* Data count (this buffer); */
+	p->tdata = p->buf +NBHDRLEN +gl32(p); /* Data offset */
+	gl32(p);			/* Data displacement */
+	g8(p);				/* Setup count */
+ 	gl16(p);			/* padding ???  */
+
+	return got;
+}
+
+static void
+gtntparam(Pkt *p)
+{
+	p->pos = p->tparam;
+}
+
+static void
+gtntdata(Pkt *p)
+{
+	p->pos = p->tdata;
+}
+
+
+int
+TNTquerysecurity(Session *s, Share *sp, int fh, char **usid, char **gsid)
+{
+	Pkt *p;
+	uchar *base;
+	Fmt fmt, *f = &fmt;
+	int n, i, off2owner, off2group;
+
+	p = tnthdr(s, sp, NT_TRANSACT_QUERY_SECURITY_DESC);
+	ptntparam(p);
+
+	pl16(p, fh); 		/* File handle */
+	pl16(p, 0); 		/* Reserved */
+	pl32(p, QUERY_OWNER_SECURITY_INFORMATION |
+		QUERY_GROUP_SECURITY_INFORMATION);
+
+	ptntdata(p);
+
+	if(tntrpc(p) == -1){
+		free(p);
+		return -1;
+	}
+
+	gtntdata(p);
+
+	base = p->pos;
+	gl16(p);			/* revision */
+	gl16(p);			/* type */
+	off2owner = gl32(p);		/* offset to owner */
+	off2group = gl32(p);		/* offset to group */
+	gl32(p);
+	gl32(p);
+
+	if(off2owner){
+		p->pos = base +  off2owner;
+		fmtstrinit(f);
+		fmtprint(f, "S-%ud", g8(p));	/* revision */
+		n = g8(p);			/* num auth */
+		fmtprint(f, "-%llud", gb48(p));	/* authority */
+		for(i = 0; i < n; i++)
+			fmtprint(f, "-%ud", gl32(p));	/* sub-authorities */
+		*usid = fmtstrflush(f);
+	}
+
+	if(off2group){
+		p->pos = base +  off2group;
+		fmtstrinit(f);
+		fmtprint(f, "S-%ud", g8(p));	/* revision */
+		n = g8(p);			/* num auth */
+		fmtprint(f, "-%llud", gb48(p));	/* authority */
+		for(i = 0; i < n; i++)
+			fmtprint(f, "-%ud", gl32(p));	/* sub-authorities */
+		*gsid = fmtstrflush(f);
+	}
+	free(p);
+	return 0;
+}

+ 8 - 2
sys/src/cmd/samterm/scroll.c

@@ -55,15 +55,21 @@ void
 scrmark(Flayer *l, Rectangle r)
 {
 	r.max.x--;
-	if(rectclip(&r, l->scroll))
+	if(rectclip(&r, l->scroll)) {
+		if (l->f.b == nil)
+			panic("scrmark: nil l->f.b");
 		draw(l->f.b, r, l->f.cols[HIGH], nil, ZP);
+	}
 }
 
 void
 scrunmark(Flayer *l, Rectangle r)
 {
-	if(rectclip(&r, l->scroll))
+	if(rectclip(&r, l->scroll)) {
+		if (l->f.b == nil)
+			panic("scrunmark: nil l->f.b");
 		draw(l->f.b, r, scrback, nil, Pt(0, r.min.y-l->scroll.min.y));
+	}
 }
 
 void