xref: /plan9/sys/src/cmd/cifs/main.c (revision 4ac58c07183c445f67bd3f184ee8741461ecc4cb)
1 #include <u.h>
2 #include <libc.h>
3 #include <fcall.h>
4 #include <thread.h>
5 #include <libsec.h>
6 #include <9p.h>
7 #include "cifs.h"
8 
9 #define max(a,b)	(((a) > (b))? (a): (b))
10 #define min(a,b)	(((a) < (b))? (a): (b))
11 
12 typedef struct Aux Aux;
13 struct Aux {
14 	Aux	*next;
15 	Aux	*prev;
16 	char	*path;		/* full path fo file */
17 	Share	*sp;		/* this share's info */
18 	long	expire;		/* expiration time of cache */
19 	long	off;		/* file pos of start of cache */
20 	long	end;		/* file pos of end of cache */
21 	char	*cache;
22 	int	fh;		/* file handle */
23 	int	sh;		/* search handle */
24 	long	srch;		/* find first's internal state */
25 };
26 
27 extern int chatty9p;
28 
29 int Checkcase = 1;		/* enforce case significance on filenames */
30 int Dfstout = 100;		/* timeout (in ms) for ping of dfs servers (assume they are local)  */
31 int Billtrog = 1;		/* enable file owner/group resolution */
32 int Attachpid;			/* pid of proc that attaches (ugh !) */
33 char *Debug = nil;		/* messages */
34 Qid Root;			/* root of remote system */
35 Share Ipc;			/* Share info of IPC$ share */
36 Session *Sess;			/* current session */
37 int Active = IDLE_TIME;		/* secs until next keepalive is sent */
38 static int Keeppid;		/* process ID of keepalive thread */
39 Share Shares[MAX_SHARES]; 	/* table of connected shares */
40 int Nshares = 0;		/* number of Shares connected */
41 Aux *Auxroot = nil;		/* linked list of Aux structs */
42 char *Host = nil;		/* host we are connected to */
43 
44 static char *Ipcname = "IPC$";
45 
46 #define ptype(x)	(((x) & 0xf))
47 #define pindex(x)	(((x) & 0xff0) >> 4)
48 
49 void
setup(void)50 setup(void)
51 {
52 	int fd;
53 	char buf[32];
54 
55 	/*
56 	 * This is revolting but I cannot see any other way to get
57 	 * the pid of the server.  We need this as Windows doesn't
58 	 * drop the TCP connection when it closes a connection.
59 	 * Thus we keepalive() to detect when/if we are thrown off.
60 	 */
61 	Attachpid = getpid();
62 
63 	snprint(buf, sizeof buf, "#p/%d/args", getpid());
64 	if((fd = open(buf, OWRITE)) >= 0){
65 		fprint(fd, "%s network", Host);
66 		close(fd);
67 	}
68 }
69 
70 int
filetableinfo(Fmt * f)71 filetableinfo(Fmt *f)
72 {
73 	Aux *ap;
74 	char *type;
75 
76 	if((ap = Auxroot) != nil)
77 		do{
78 			type = "walked";
79 			if(ap->sh != -1)
80 				type = "opendir";
81 			if(ap->fh != -1)
82 				type = "openfile";
83 			fmtprint(f, "%-9s %s\n", type, ap->path);
84 			ap = ap->next;
85 		}while(ap != Auxroot);
86 	return 0;
87 }
88 
89 Qid
mkqid(char * s,int is_dir,long vers,int subtype,long path)90 mkqid(char *s, int is_dir, long vers, int subtype, long path)
91 {
92 	Qid q;
93 	union {				/* align digest suitably */
94 		uchar	digest[SHA1dlen];
95 		uvlong	uvl;
96 	} u;
97 
98 	sha1((uchar *)s, strlen(s), u.digest, nil);
99 	q.type = (is_dir)? QTDIR: 0;
100 	q.vers = vers;
101 	if(subtype){
102 		q.path = *((uvlong *)u.digest) & ~0xfffL;
103 		q.path |= ((path & 0xff) << 4);
104 		q.path |= (subtype & 0xf);
105 	}
106 	else
107 		q.path = *((uvlong *)u.digest) & ~0xfL;
108 	return q;
109 }
110 
111 /*
112  * used only for root dir and shares
113  */
114 static void
V2D(Dir * d,Qid qid,char * name)115 V2D(Dir *d, Qid qid, char *name)
116 {
117 	memset(d, 0, sizeof(Dir));
118 	d->type = 'C';
119 	d->dev = 1;
120 	d->name = estrdup9p(name);
121 	d->uid = estrdup9p("bill");
122 	d->muid = estrdup9p("boyd");
123 	d->gid = estrdup9p("trog");
124 	d->mode = 0755 | DMDIR;
125 	d->atime = time(nil);
126 	d->mtime = d->atime;
127 	d->length = 0;
128 	d->qid = qid;
129 }
130 
131 static void
I2D(Dir * d,Share * sp,char * path,FInfo * fi)132 I2D(Dir *d, Share *sp, char *path, FInfo *fi)
133 {
134 	char *name;
135 
136 	if((name = strrchr(fi->name, '\\')) != nil)
137 		name++;
138 	else
139 		name = fi->name;
140 	d->name = estrdup9p(name);
141 	d->type = 'C';
142 	d->dev = sp->tid;
143 	d->uid = estrdup9p("bill");
144 	d->gid = estrdup9p("trog");
145 	d->muid = estrdup9p("boyd");
146 	d->atime = fi->accessed;
147 	d->mtime = fi->written;
148 
149 	if(fi->attribs & ATTR_READONLY)
150 		d->mode = 0444;
151 	else
152 		d->mode = 0666;
153 
154 	d->length = fi->size;
155 	d->qid = mkqid(path, fi->attribs & ATTR_DIRECTORY, fi->changed, 0, 0);
156 
157 	if(fi->attribs & ATTR_DIRECTORY){
158 		d->length = 0;
159 		d->mode |= DMDIR|0111;
160 	}
161 }
162 
163 static void
responderrstr(Req * r)164 responderrstr(Req *r)
165 {
166 	char e[ERRMAX];
167 
168 	*e = 0;
169 	rerrstr(e, sizeof e);
170 	respond(r, e);
171 }
172 
173 static char *
newpath(char * path,char * name)174 newpath(char *path, char *name)
175 {
176 	char *p, *q;
177 
178 	assert((p = strrchr(path, '/')) != nil);
179 
180 	if(strcmp(name, "..") == 0){
181 		if(p == path)
182 			return estrdup9p("/");
183 		q = emalloc9p((p-path)+1);
184 		strecpy(q, q+(p-path)+1, path);
185 		return q;
186 	}
187 	if(strcmp(path, "/") == 0)
188 		return smprint("/%s", name);
189 	return smprint("%s/%s", path, name);
190 }
191 
192 static int
dirgen(int slot,Dir * d,void * aux)193 dirgen(int slot, Dir *d, void *aux)
194 {
195 	long off;
196 	FInfo *fi;
197 	int rc, got;
198 	Aux *a = aux;
199 	char *npath;
200 	int numinf = numinfo();
201 	int slots = min(Sess->mtu, MTU) / sizeof(FInfo);
202 
203 	if(strcmp(a->path, "/") == 0){
204 		if(slot < numinf){
205 			dirgeninfo(slot, d);
206 			return 0;
207 		} else
208 			slot -= numinf;
209 
210 		if(slot >= Nshares)
211 			return -1;
212 		V2D(d, mkqid(Shares[slot].name, 1, 1, Pshare, slot),
213 			Shares[slot].name);
214 		return 0;
215 	}
216 
217 	off = slot * sizeof(FInfo);
218 	if(off >= a->off && off < a->end && time(nil) < a->expire)
219 		goto from_cache;
220 
221 	if(off == 0){
222 		fi = (FInfo *)a->cache;
223 		npath = smprint("%s/*", mapfile(a->path));
224 		a->sh = T2findfirst(Sess, a->sp, slots, npath, &got, &a->srch,
225 			(FInfo *)a->cache);
226 		free(npath);
227 		if(a->sh == -1)
228 			return -1;
229 
230 		a->off = 0;
231 		a->end = got * sizeof(FInfo);
232 
233 		if(got >= 2 && strcmp(fi[0].name, ".") == 0 &&
234 		    strcmp(fi[1].name, "..") == 0){
235 			a->end = (got - 2) * sizeof(FInfo);
236 			memmove(a->cache, a->cache + sizeof(FInfo)*2,
237 				a->end - a->off);
238 		}
239 	}
240 
241 	while(off >= a->end && a->sh != -1){
242 		fi = (FInfo *)(a->cache + (a->end - a->off) - sizeof(FInfo));
243 		a->off = a->end;
244 		npath = smprint("%s/%s", mapfile(a->path), fi->name);
245 		rc = T2findnext(Sess, a->sp, slots, npath,
246 			&got, &a->srch, (FInfo *)a->cache, a->sh);
247 		free(npath);
248 		if(rc == -1 || got == 0)
249 			break;
250 		a->end = a->off + got * sizeof(FInfo);
251 	}
252 	a->expire = time(nil) + CACHETIME;
253 
254 	if(got < slots){
255 		if(a->sh != -1)
256 			CIFSfindclose2(Sess, a->sp, a->sh);
257 		a->sh = -1;
258 	}
259 
260 	if(off >= a->end)
261 		return -1;
262 
263 from_cache:
264 	fi = (FInfo *)(a->cache + (off - a->off));
265 	npath = smprint("%s/%s", mapfile(a->path), fi->name);
266 	I2D(d, a->sp, npath, fi);
267 	if(Billtrog == 0)
268 		upd_names(Sess, a->sp, npath, d);
269 	free(npath);
270 	return 0;
271 }
272 
273 static void
fsattach(Req * r)274 fsattach(Req *r)
275 {
276 	Aux *a;
277 	static int first = 1;
278 	char *spec = r->ifcall.aname;
279 
280 	if(first)
281 		setup();
282 
283 	if(spec && *spec){
284 		respond(r, "invalid attach specifier");
285 		return;
286 	}
287 
288 	r->ofcall.qid = mkqid("/", 1, 1, Proot, 0);
289 	r->fid->qid = r->ofcall.qid;
290 
291 	a = r->fid->aux = emalloc9p(sizeof(Aux));
292 	memset(a, 0, sizeof(Aux));
293 	a->path = estrdup9p("/");
294 	a->sp = nil;
295 	a->fh = -1;
296 	a->sh = -1;
297 
298 	if(Auxroot){
299 		a->prev = Auxroot;
300 		a->next = Auxroot->next;
301 		Auxroot->next->prev = a;
302 		Auxroot->next = a;
303 	} else {
304 		Auxroot = a;
305 		a->next = a;
306 		a->prev = a;
307 	}
308 	respond(r, nil);
309 }
310 
311 static char*
fsclone(Fid * ofid,Fid * fid)312 fsclone(Fid *ofid, Fid *fid)
313 {
314 	Aux *oa = ofid->aux;
315 	Aux *a = emalloc9p(sizeof(Aux));
316 
317 	fid->aux = a;
318 
319 	memset(a, 0, sizeof(Aux));
320 	a->sh = -1;
321 	a->fh = -1;
322 	a->sp = oa->sp;
323 	a->path = estrdup9p(oa->path);
324 
325 	if(Auxroot){
326 		a->prev = Auxroot;
327 		a->next = Auxroot->next;
328 		Auxroot->next->prev = a;
329 		Auxroot->next = a;
330 	} else {
331 		Auxroot = a;
332 		a->next = a;
333 		a->prev = a;
334 	}
335 	return nil;
336 }
337 
338 /*
339  * for some weird reason T2queryall() returns share names
340  * in lower case so we have to do an extra test against
341  * our share table to validate filename case.
342  *
343  * on top of this here (snell & Wilcox) most of our
344  * redirections point to a share of the same name,
345  * but some do not, thus the tail of the filename
346  * returned by T2queryall() is not the same as
347  * the name we wanted.
348  *
349  * We work around this by not validating the names
350  * or files which resolve to share names as they must
351  * be correct, having been enforced in the dfs layer.
352  */
353 static int
validfile(char * found,char * want,char * winpath,Share * sp)354 validfile(char *found, char *want, char *winpath, Share *sp)
355 {
356 	char *share;
357 
358 	if(strcmp(want, "..") == 0)
359 		return 1;
360 	if(strcmp(winpath, "/") == 0){
361 		share = trimshare(sp->name);
362 		if(cistrcmp(want, share) == 0)
363 			return strcmp(want, share) == 0;
364 		/*
365 		 * OK, a DFS redirection points us from a directory XXX
366 		 * to a share named YYY.  There is no case checking we can
367 		 * do so we allow either case - it's all we can do.
368 		 */
369 		return 1;
370 	}
371 	if(cistrcmp(found, want) != 0)
372 		return 0;
373 	if(!Checkcase)
374 		return 1;
375 	if(strcmp(found, want) == 0)
376 		return 1;
377 	return 0;
378 }
379 
380 
381 static char*
fswalk1(Fid * fid,char * name,Qid * qid)382 fswalk1(Fid *fid, char *name, Qid *qid)
383 {
384 	FInfo fi;
385 	int rc, n, i;
386 	Aux *a = fid->aux;
387 	static char e[ERRMAX];
388 	char *p, *npath, *winpath;
389 
390 	*e = 0;
391 	npath = newpath(a->path, name);
392 	if(strcmp(npath, "/") == 0){			/* root dir */
393 		*qid = mkqid("/", 1, 1, Proot, 0);
394 		free(a->path);
395 		a->path = npath;
396 		fid->qid = *qid;
397 		return nil;
398 	}
399 
400 	if(strrchr(npath, '/') == npath){		/* top level dir */
401 		if((n = walkinfo(name)) != -1){		/* info file */
402 			*qid = mkqid(npath, 0, 1, Pinfo, n);
403 		}
404 		else {					/* volume name */
405 			for(i = 0; i < Nshares; i++){
406 				n = strlen(Shares[i].name);
407 				if(cistrncmp(npath+1, Shares[i].name, n) != 0)
408 					continue;
409 				if(Checkcase && strncmp(npath+1, Shares[i].name, n) != 0)
410 					continue;
411 				if(npath[n+1] != 0 && npath[n+1] != '/')
412 					continue;
413 				break;
414 			}
415 			if(i >= Nshares){
416 				free(npath);
417 				return "not found";
418 			}
419 			a->sp = Shares+i;
420 			*qid = mkqid(npath, 1, 1, Pshare, i);
421 		}
422 		free(a->path);
423 		a->path = npath;
424 		fid->qid = *qid;
425 		return nil;
426 	}
427 
428 	/* must be a vanilla file or directory */
429 again:
430 	if(mapshare(npath, &a->sp) == -1){
431 		rerrstr(e, sizeof(e));
432 		free(npath);
433 		return e;
434 	}
435 
436 	winpath = mapfile(npath);
437 	memset(&fi, 0, sizeof fi);
438 	if(Sess->caps & CAP_NT_SMBS)
439 		rc = T2queryall(Sess, a->sp, winpath, &fi);
440 	else
441 		rc = T2querystandard(Sess, a->sp, winpath, &fi);
442 
443 	if(rc == -1){
444 		rerrstr(e, sizeof(e));
445 		free(npath);
446 		return e;
447 	}
448 
449 	if((a->sp->options & SMB_SHARE_IS_IN_DFS) != 0 &&
450 	    (fi.attribs & ATTR_REPARSE) != 0){
451 		if(redirect(Sess, a->sp, npath) != -1)
452 			goto again;
453 	}
454 
455 	if((p = strrchr(fi.name, '/')) == nil && (p = strrchr(fi.name, '\\')) == nil)
456 		p = fi.name;
457 	else
458 		p++;
459 
460 	if(! validfile(p, name, winpath, a->sp)){
461 		free(npath);
462 		return "not found";
463 
464 	}
465 	*qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0);
466 
467 	free(a->path);
468 	a->path = npath;
469 	fid->qid = *qid;
470 	return nil;
471 }
472 
473 static void
fsstat(Req * r)474 fsstat(Req *r)
475 {
476 	int rc;
477 	FInfo fi;
478 	Aux *a = r->fid->aux;
479 
480 	if(ptype(r->fid->qid.path) == Proot)
481 		V2D(&r->d, r->fid->qid, "");
482 	else if(ptype(r->fid->qid.path) == Pinfo)
483 		dirgeninfo(pindex(r->fid->qid.path), &r->d);
484 	else if(ptype(r->fid->qid.path) == Pshare)
485 		V2D(&r->d, r->fid->qid, a->path +1);
486 	else{
487 		memset(&fi, 0, sizeof fi);
488 		if(Sess->caps & CAP_NT_SMBS)
489 			rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi);
490 		else
491 			rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi);
492 		if(rc == -1){
493 			responderrstr(r);
494 			return;
495 		}
496 		I2D(&r->d, a->sp, a->path, &fi);
497 		if(Billtrog == 0)
498 			upd_names(Sess, a->sp, mapfile(a->path), &r->d);
499 	}
500 	respond(r, nil);
501 }
502 
503 static int
smbcreateopen(Aux * a,char * path,int mode,int perm,int is_create,int is_dir,FInfo * fip)504 smbcreateopen(Aux *a, char *path, int mode, int perm, int is_create,
505 	int is_dir, FInfo *fip)
506 {
507 	int rc, action, attrs, access, result;
508 
509 	if(is_create && is_dir){
510 		if(CIFScreatedirectory(Sess, a->sp, path) == -1)
511 			return -1;
512 		return 0;
513 	}
514 
515 	if(mode & DMAPPEND) {
516 		werrstr("filesystem does not support DMAPPEND");
517 		return -1;
518 	}
519 
520 	if(is_create)
521 		action = 0x12;
522 	else if(mode & OTRUNC)
523 		action = 0x02;
524 	else
525 		action = 0x01;
526 
527 	if(perm & 0222)
528 		attrs = ATTR_NORMAL;
529 	else
530 		attrs = ATTR_NORMAL|ATTR_READONLY;
531 
532 	switch (mode & OMASK){
533 	case OREAD:
534 		access = 0;
535 		break;
536 	case OWRITE:
537 		access = 1;
538 		break;
539 	case ORDWR:
540 		access = 2;
541 		break;
542 	case OEXEC:
543 		access = 3;
544 		break;
545 	default:
546 		werrstr("%d bad open mode", mode & OMASK);
547 		return -1;
548 		break;
549 	}
550 
551 	if(mode & DMEXCL == 0)
552 		access |= 0x10;
553 	else
554 		access |= 0x40;
555 
556 	if((a->fh = CIFS_SMB_opencreate(Sess, a->sp, path, access, attrs,
557 	    action, &result)) == -1)
558 		return -1;
559 
560 	if(Sess->caps & CAP_NT_SMBS)
561 		rc = T2queryall(Sess, a->sp, mapfile(a->path), fip);
562 	else
563 		rc = T2querystandard(Sess, a->sp, mapfile(a->path), fip);
564 	if(rc == -1){
565 		fprint(2, "internal error: stat of newly open/created file failed\n");
566 		return -1;
567 	}
568 
569 	if((mode & OEXCL) && (result & 0x8000) == 0){
570 		werrstr("%d bad open mode", mode & OMASK);
571 		return -1;
572 	}
573 	return 0;
574 }
575 
576 /* Uncle Bill, you have a lot to answer for... */
577 static int
ntcreateopen(Aux * a,char * path,int mode,int perm,int is_create,int is_dir,FInfo * fip)578 ntcreateopen(Aux *a, char *path, int mode, int perm, int is_create,
579 	int is_dir, FInfo *fip)
580 {
581 	int options, result, attrs, flags, access, action, share;
582 
583 	if(mode & DMAPPEND){
584 		werrstr("CIFSopen, DMAPPEND not supported");
585 		return -1;
586 	}
587 
588 	if(is_create){
589 		if(mode & OEXCL)
590 			action = FILE_OPEN;
591 		else if(mode & OTRUNC)
592 			action = FILE_CREATE;
593 		else
594 			action = FILE_OVERWRITE_IF;
595 	} else {
596 		if(mode & OTRUNC)
597 			action = FILE_OVERWRITE_IF;
598 		else
599 			action = FILE_OPEN_IF;
600 	}
601 
602 	flags = 0;		/* FIXME: really not sure */
603 
604 	if(mode & OEXCL)
605 		share = FILE_NO_SHARE;
606 	else
607 		share = FILE_SHARE_ALL;
608 
609 	switch (mode & OMASK){
610 	case OREAD:
611 		access = GENERIC_READ;
612 		break;
613 	case OWRITE:
614 		access = GENERIC_WRITE;
615 		break;
616 	case ORDWR:
617 		access = GENERIC_ALL;
618 		break;
619 	case OEXEC:
620 		access = GENERIC_EXECUTE;
621 		break;
622 	default:
623 		werrstr("%d bad open mode", mode & OMASK);
624 		return -1;
625 		break;
626 	}
627 
628 	if(is_dir){
629 		action = FILE_CREATE;
630 		options = FILE_DIRECTORY_FILE;
631 		if(perm & 0222)
632 			attrs = ATTR_DIRECTORY;
633 		else
634 			attrs = ATTR_DIRECTORY|ATTR_READONLY;
635 	} else {
636 		options = FILE_NON_DIRECTORY_FILE;
637 		if(perm & 0222)
638 			attrs = ATTR_NORMAL;
639 		else
640 			attrs = ATTR_NORMAL|ATTR_READONLY;
641 	}
642 
643 	if(mode & ORCLOSE){
644 		options |= FILE_DELETE_ON_CLOSE;
645 		attrs |= ATTR_DELETE_ON_CLOSE;
646 	}
647 
648 	if((a->fh = CIFS_NT_opencreate(Sess, a->sp, path, flags, options,
649 	    attrs, access, share, action, &result, fip)) == -1)
650 		return -1;
651 
652 	if((mode & OEXCL) && (result & 0x8000) == 0){
653 		werrstr("%d bad open mode", mode & OMASK);
654 		return -1;
655 	}
656 
657 	return 0;
658 }
659 
660 static void
fscreate(Req * r)661 fscreate(Req *r)
662 {
663 	FInfo fi;
664 	int rc, is_dir;
665 	char *npath;
666 	Aux *a = r->fid->aux;
667 
668 	a->end = a->off = 0;
669 	a->cache = emalloc9p(max(Sess->mtu, MTU));
670 
671 	is_dir = (r->ifcall.perm & DMDIR) == DMDIR;
672 	npath = smprint("%s/%s", a->path, r->ifcall.name);
673 
674 	if(Sess->caps & CAP_NT_SMBS)
675 		rc = ntcreateopen(a, mapfile(npath), r->ifcall.mode,
676 			r->ifcall.perm, 1, is_dir, &fi);
677 	else
678 		rc = smbcreateopen(a, mapfile(npath), r->ifcall.mode,
679 			r->ifcall.perm, 1, is_dir, &fi);
680 	if(rc == -1){
681 		free(npath);
682 		responderrstr(r);
683 		return;
684 	}
685 
686 	r->fid->qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0);
687 
688 	r->ofcall.qid = r->fid->qid;
689 	free(a->path);
690 	a->path = npath;
691 
692 	respond(r, nil);
693 }
694 
695 static void
fsopen(Req * r)696 fsopen(Req *r)
697 {
698 	int rc;
699 	FInfo fi;
700 	Aux *a = r->fid->aux;
701 
702 	a->end = a->off = 0;
703 	a->cache = emalloc9p(max(Sess->mtu, MTU));
704 
705 	if(ptype(r->fid->qid.path) == Pinfo){
706 		if(makeinfo(pindex(r->fid->qid.path)) != -1)
707 			respond(r, nil);
708 		else
709 			respond(r, "cannot generate info");
710 		return;
711 	}
712 
713 	if(r->fid->qid.type & QTDIR){
714 		respond(r, nil);
715 		return;
716 	}
717 
718 	if(Sess->caps & CAP_NT_SMBS)
719 		rc = ntcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777,
720 			0, 0, &fi);
721 	else
722 		rc = smbcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777,
723 			0, 0, &fi);
724 	if(rc == -1){
725 		responderrstr(r);
726 		return;
727 	}
728 	respond(r, nil);
729 }
730 
731 static void
fswrite(Req * r)732 fswrite(Req *r)
733 {
734 	vlong n, m, got;
735 	Aux *a = r->fid->aux;
736 	vlong len = r->ifcall.count;
737 	vlong off = r->ifcall.offset;
738 	char *buf = r->ifcall.data;
739 
740 	got = 0;
741 	n = Sess->mtu -OVERHEAD;
742 	do{
743 		if(len - got < n)
744 			n = len - got;
745 		m = CIFSwrite(Sess, a->sp, a->fh, off + got, buf + got, n);
746 		if(m != -1)
747 			got += m;
748 	} while(got < len && m >= n);
749 
750 	r->ofcall.count = got;
751 	if(m == -1)
752 		responderrstr(r);
753 	else
754 		respond(r, nil);
755 }
756 
757 static void
fsread(Req * r)758 fsread(Req *r)
759 {
760 	vlong n, m, got;
761 	Aux *a = r->fid->aux;
762 	char *buf = r->ofcall.data;
763 	vlong len = r->ifcall.count;
764 	vlong off = r->ifcall.offset;
765 
766 	if(ptype(r->fid->qid.path) == Pinfo){
767 		r->ofcall.count = readinfo(pindex(r->fid->qid.path), buf, len,
768 			off);
769 		respond(r, nil);
770 		return;
771 	}
772 
773 	if(r->fid->qid.type & QTDIR){
774 		dirread9p(r, dirgen, a);
775 		respond(r, nil);
776 		return;
777 	}
778 
779 	got = 0;
780 	n = Sess->mtu -OVERHEAD;
781 	do{
782 		if(len - got < n)
783 			n = len - got;
784 		m = CIFSread(Sess, a->sp, a->fh, off + got, buf + got, n, len);
785 		if(m != -1)
786 			got += m;
787 	} while(got < len && m >= n);
788 
789 	r->ofcall.count = got;
790 	if(m == -1)
791 		responderrstr(r);
792 	else
793 		respond(r, nil);
794 }
795 
796 static void
fsdestroyfid(Fid * f)797 fsdestroyfid(Fid *f)
798 {
799 	Aux *a = f->aux;
800 
801 	if(ptype(f->qid.path) == Pinfo)
802 		freeinfo(pindex(f->qid.path));
803 	f->omode = -1;
804 	if(! a)
805 		return;
806 	if(a->fh != -1)
807 		if(CIFSclose(Sess, a->sp, a->fh) == -1)
808 			fprint(2, "%s: close failed fh=%d %r\n", argv0, a->fh);
809 	if(a->sh != -1)
810 		if(CIFSfindclose2(Sess, a->sp, a->sh) == -1)
811 			fprint(2, "%s: findclose failed sh=%d %r\n",
812 				argv0, a->sh);
813 	if(a->path)
814 		free(a->path);
815 	if(a->cache)
816 		free(a->cache);
817 
818 	if(a == Auxroot)
819 		Auxroot = a->next;
820 	a->prev->next = a->next;
821 	a->next->prev = a->prev;
822 	if(a->next == a->prev)
823 		Auxroot = nil;
824 	if(a)
825 		free(a);
826 }
827 
828 int
rdonly(Session * s,Share * sp,char * path,int rdonly)829 rdonly(Session *s, Share *sp, char *path, int rdonly)
830 {
831 	int rc;
832 	FInfo fi;
833 
834 	if(Sess->caps & CAP_NT_SMBS)
835 		rc = T2queryall(s, sp, path, &fi);
836 	else
837 		rc = T2querystandard(s, sp, path, &fi);
838 	if(rc == -1)
839 		return -1;
840 
841 	if((rdonly && !(fi.attribs & ATTR_READONLY)) ||
842 	    (!rdonly && (fi.attribs & ATTR_READONLY))){
843 		fi.attribs &= ~ATTR_READONLY;
844 		fi.attribs |= rdonly? ATTR_READONLY: 0;
845 		rc = CIFSsetinfo(s, sp, path, &fi);
846 	}
847 	return rc;
848 }
849 
850 static void
fsremove(Req * r)851 fsremove(Req *r)
852 {
853 	int try, rc;
854 	char e[ERRMAX];
855 	Aux *ap, *a = r->fid->aux;
856 
857 	*e = 0;
858 	if(ptype(r->fid->qid.path) == Proot ||
859 	   ptype(r->fid->qid.path) == Pshare){
860 		respond(r, "illegal operation");
861 		return;
862 	}
863 
864 	/* close all instences of this file/dir */
865 	if((ap = Auxroot) != nil)
866 		do{
867 			if(strcmp(ap->path, a->path) == 0){
868 				if(ap->sh != -1)
869 					CIFSfindclose2(Sess, ap->sp, ap->sh);
870 				ap->sh = -1;
871 				if(ap->fh != -1)
872 					CIFSclose(Sess, ap->sp, ap->fh);
873 				ap->fh = -1;
874 			}
875 			ap = ap->next;
876 		}while(ap != Auxroot);
877 	try = 0;
878 again:
879 	if(r->fid->qid.type & QTDIR)
880 		rc = CIFSdeletedirectory(Sess, a->sp, mapfile(a->path));
881 	else
882 		rc = CIFSdeletefile(Sess, a->sp, mapfile(a->path));
883 
884 	rerrstr(e, sizeof(e));
885 	if(rc == -1 && try++ == 0 && strcmp(e, "permission denied") == 0 &&
886 	    rdonly(Sess, a->sp, mapfile(a->path), 0) == 0)
887 		goto again;
888 	if(rc == -1)
889 		responderrstr(r);
890 	else
891 		respond(r, nil);
892 }
893 
894 static void
fswstat(Req * r)895 fswstat(Req *r)
896 {
897 	int fh, result, rc;
898 	FInfo fi, tmpfi;
899 	char *p, *from, *npath;
900 	Aux *a = r->fid->aux;
901 
902 	if(ptype(r->fid->qid.path) == Proot ||
903 	   ptype(r->fid->qid.path) == Pshare){
904 		respond(r, "illegal operation");
905 		return;
906 	}
907 
908 	if((r->d.uid && r->d.uid[0]) || (r->d.gid && r->d.gid[0])){
909 		respond(r, "cannot change ownership");
910 		return;
911 	}
912 
913 	/*
914 	 * get current info
915 	 */
916 	if(Sess->caps & CAP_NT_SMBS)
917 		rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi);
918 	else
919 		rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi);
920 	if(rc == -1){
921 		werrstr("(query) - %r");
922 		responderrstr(r);
923 		return;
924 	}
925 
926 	/*
927 	 * always clear the readonly attribute if set,
928 	 * before trying to set any other fields.
929 	 * wstat() fails if the file/dir is readonly
930 	 * and this function is so full of races - who cares about one more?
931 	 */
932 	rdonly(Sess, a->sp, mapfile(a->path), 0);
933 
934 	/*
935 	 * rename - one piece of joy, renaming open files
936 	 * is legal (sharing permitting).
937 	 */
938 	if(r->d.name && r->d.name[0]){
939 		if((p = strrchr(a->path, '/')) == nil){
940 			respond(r, "illegal path");
941 			return;
942 		}
943 		npath = emalloc9p((p-a->path)+strlen(r->d.name)+2);
944 		strecpy(npath, npath+(p- a->path)+2, a->path);
945 		strcat(npath, r->d.name);
946 
947 		from = estrdup9p(mapfile(a->path));
948 		if(CIFSrename(Sess, a->sp, from, mapfile(npath)) == -1){
949 			werrstr("(rename) - %r");
950 			responderrstr(r);
951 			free(npath);
952 			free(from);
953 			return;
954 		}
955 		free(from);
956 		free(a->path);
957 		a->path = npath;
958 	}
959 
960 	/*
961 	 * set the files length, do this before setting
962 	 * the file times as open() will alter them
963 	 */
964 	if(~r->d.length){
965 		fi.size = r->d.length;
966 
967 		if(Sess->caps & CAP_NT_SMBS){
968 			if((fh = CIFS_NT_opencreate(Sess, a->sp, mapfile(a->path),
969 			    0, FILE_NON_DIRECTORY_FILE,
970 	    		    ATTR_NORMAL, GENERIC_WRITE, FILE_SHARE_ALL,
971 			    FILE_OPEN_IF, &result, &tmpfi)) == -1){
972 				werrstr("(set length, open) - %r");
973 				responderrstr(r);
974 				return;
975 			}
976 			rc = T2setfilelength(Sess, a->sp, fh, &fi);
977 			CIFSclose(Sess, a->sp, fh);
978 			if(rc == -1){
979 				werrstr("(set length), set) - %r");
980 				responderrstr(r);
981 				return;
982 			}
983 		} else {
984 			if((fh = CIFS_SMB_opencreate(Sess, a->sp, mapfile(a->path),
985 			    1, ATTR_NORMAL, 1, &result)) == -1){
986 				werrstr("(set length, open) failed - %r");
987 				responderrstr(r);
988 				return;
989 			}
990 			rc = CIFSwrite(Sess, a->sp, fh, fi.size, 0, 0);
991 			CIFSclose(Sess, a->sp, fh);
992 			if(rc == -1){
993 				werrstr("(set length, write) - %r");
994 				responderrstr(r);
995 				return;
996 			}
997 		}
998 	}
999 
1000 	/*
1001 	 * This doesn't appear to set length or
1002 	 * attributes, no idea why, so I do those seperately
1003 	 */
1004 	if(~r->d.mtime || ~r->d.atime){
1005 		if(~r->d.mtime)
1006 			fi.written = r->d.mtime;
1007 		if(~r->d.atime)
1008 			fi.accessed = r->d.atime;
1009 		if(T2setpathinfo(Sess, a->sp, mapfile(a->path), &fi) == -1){
1010 			werrstr("(set path info) - %r");
1011 			responderrstr(r);
1012 			return;
1013 		}
1014 	}
1015 
1016 	/*
1017 	 * always update the readonly flag as
1018 	 * we may have cleared it above.
1019 	 */
1020 	if(~r->d.mode){
1021 		if(r->d.mode & 0222)
1022 			fi.attribs &= ~ATTR_READONLY;
1023 		else
1024 			fi.attribs |= ATTR_READONLY;
1025 	}
1026 	if(rdonly(Sess, a->sp, mapfile(a->path), fi.attribs & ATTR_READONLY) == -1){
1027 		werrstr("(set info) - %r");
1028 		responderrstr(r);
1029 		return;
1030 	}
1031 
1032 	/*
1033 	 * Win95 has a broken write-behind cache for metadata
1034 	 * on open files (writes go to the cache, reads bypass
1035 	 * the cache), so we must flush the file.
1036 	 */
1037 	if(r->fid->omode != -1 && CIFSflush(Sess, a->sp, a->fh) == -1){
1038 		werrstr("(flush) %r");
1039 		responderrstr(r);
1040 		return;
1041 	}
1042 	respond(r, nil);
1043 }
1044 
1045 static void
fsend(Srv * srv)1046 fsend(Srv *srv)
1047 {
1048 	int i;
1049 	USED(srv);
1050 
1051 	for(i = 0; i < Nshares; i++)
1052 		CIFStreedisconnect(Sess, Shares+i);
1053 	CIFSlogoff(Sess);
1054 	postnote(PNPROC, Keeppid, "die");
1055 }
1056 
1057 Srv fs = {
1058 	.destroyfid =	fsdestroyfid,
1059 	.attach=	fsattach,
1060 	.open=		fsopen,
1061 	.create=	fscreate,
1062 	.read=		fsread,
1063 	.write=		fswrite,
1064 	.remove=	fsremove,
1065 	.stat=		fsstat,
1066 	.wstat=		fswstat,
1067 	.clone= 	fsclone,
1068 	.walk1= 	fswalk1,
1069 	.end=		fsend,
1070 };
1071 
1072 void
usage(void)1073 usage(void)
1074 {
1075 	fprint(2, "usage: %s [-d name] [-Dvb] [-a auth-method] [-s srvname] "
1076 		"[-n called-name] [-k factotum-params] [-m mntpnt] "
1077 		"host [share...]\n", argv0);
1078 	exits("usage");
1079 }
1080 
1081 /*
1082  * SMBecho looks like the function to use for keepalives,
1083  * sadly the echo packet does not seem to reload the
1084  * idle timer in Microsoft's servers.  Instead we use
1085  * "get file system size" on each share until we get one that succeeds.
1086  */
1087 static void
keepalive(void)1088 keepalive(void)
1089 {
1090 	char buf[32];
1091 	uvlong tot, fre;
1092 	int fd, i, slot, rc;
1093 
1094 	snprint(buf, sizeof buf, "#p/%d/args", getpid());
1095 	if((fd = open(buf, OWRITE)) >= 0){
1096 		fprint(fd, "%s keepalive", Host);
1097 		close(fd);
1098 	}
1099 
1100 	rc = 0;
1101 	slot = 0;
1102 	do{
1103 		sleep(6000);
1104 		if(Active-- != 0)
1105 			continue;
1106 		for(i = 0; i < Nshares; i++){
1107 			if((rc = T2fssizeinfo(Sess, &Shares[slot], &tot, &fre)) == 0)
1108 				break;
1109 			if(++slot >= Nshares)
1110 				slot = 0;
1111 		}
1112 	}while(rc != -1);
1113 	postnote(PNPROC, Attachpid, "die");
1114 }
1115 
1116 
1117 static void
ding(void * u,char * msg)1118 ding(void *u, char *msg)
1119 {
1120 	USED(u);
1121 	if(strstr(msg, "alarm") != nil)
1122 		noted(NCONT);
1123 	noted(NDFLT);
1124 }
1125 
1126 void
dmpkey(char * s,void * v,int n)1127 dmpkey(char *s, void *v, int n)
1128 {
1129 	int i;
1130 	unsigned char *p = (unsigned char *)v;
1131 
1132 	print("%s", s);
1133 	for(i = 0; i < n; i++)
1134 		print("%02ux ", *p++);
1135 	print("\n");
1136 }
1137 
1138 void
main(int argc,char ** argv)1139 main(int argc, char **argv)
1140 {
1141 	int i, n;
1142 	long svrtime;
1143 	char windom[64], cname[64];
1144 	char *method, *sysname, *keyp, *mtpt, *svs;
1145 	static char *sh[1024];
1146 
1147 	*cname = 0;
1148 	keyp = "";
1149 	method = nil;
1150 	strcpy(windom, "unknown");
1151 	mtpt = svs = nil;
1152 
1153 	notify(ding);
1154 
1155 	ARGBEGIN{
1156 	case 'a':
1157 		method = EARGF(autherr());
1158 		break;
1159 	case 'b':
1160 		Billtrog ^= 1;
1161 		break;
1162 	case 'D':
1163 		chatty9p++;
1164 		break;
1165 	case 'd':
1166 		Debug = EARGF(usage());
1167 		break;
1168 	case 'i':
1169 		Checkcase = 0;
1170 		break;
1171 	case 'k':
1172 		keyp = EARGF(usage());
1173 		break;
1174 	case 'm':
1175 		mtpt = EARGF(usage());
1176 		break;
1177 	case 'n':
1178 		strncpy(cname, EARGF(usage()), sizeof(cname));
1179 		cname[sizeof(cname) - 1] = 0;
1180 		break;
1181 	case 's':
1182 		svs = EARGF(usage());
1183 		break;
1184 	case 't':
1185 		Dfstout = atoi(EARGF(usage()));
1186 		break;
1187 	default:
1188 		usage();
1189 		break;
1190 	}ARGEND
1191 
1192 	if(argc < 1)
1193 		usage();
1194 
1195 	Host = argv[0];
1196 
1197 	if(mtpt == nil && svs == nil)
1198 		mtpt = smprint("/n/%s", Host);
1199 
1200 	if((sysname = getenv("sysname")) == nil)
1201 		sysname = "unknown";
1202 
1203 	if(*cname && (Sess = cifsdial(Host, cname, sysname)) != nil)
1204 		goto connected;
1205 
1206 	if(calledname(Host, cname) == 0 &&
1207 	    (Sess = cifsdial(Host, cname, sysname)) != nil)
1208 		goto connected;
1209 
1210 	strcpy(cname, Host);
1211 	if((Sess = cifsdial(Host, Host, sysname)) != nil ||
1212 	   (Sess = cifsdial(Host, "*SMBSERVER", sysname)) != nil)
1213 		goto connected;
1214 
1215 	sysfatal("%s - cannot dial, %r\n", Host);
1216 connected:
1217 	if(CIFSnegotiate(Sess, &svrtime, windom, sizeof windom, cname, sizeof cname) == -1)
1218 		sysfatal("%s - cannot negioate common protocol, %r\n", Host);
1219 
1220 #ifndef DEBUG_MAC
1221 	Sess->secmode &= ~SECMODE_SIGN_ENABLED;
1222 #endif
1223 
1224 	Sess->auth = getauth(method, windom, keyp, Sess->secmode, Sess->chal,
1225 		Sess->challen);
1226 
1227 	if(CIFSsession(Sess) < 0)
1228 		sysfatal("session authentication failed, %r\n");
1229 
1230 	Sess->slip = svrtime - time(nil);
1231 	Sess->cname = estrdup9p(cname);
1232 
1233 	if(CIFStreeconnect(Sess, cname, Ipcname, &Ipc) == -1)
1234 		fprint(2, "%s, %r - can't connect\n", Ipcname);
1235 
1236 	Nshares = 0;
1237 	if(argc == 1){
1238 		Share *sip;
1239 
1240 		if((n = RAPshareenum(Sess, &Ipc, &sip)) < 1)
1241 			sysfatal("can't enumerate shares: %r - specify share "
1242 				"names on command line\n");
1243 
1244 		for(i = 0; i < n; i++){
1245 #ifdef NO_HIDDEN_SHARES
1246 			int l = strlen(sip[i].name);
1247 
1248 			if(l > 1 && sip[i].name[l-1] == '$'){
1249 				free(sip[i].name);
1250 				continue;
1251 			}
1252 #endif
1253 			memcpy(Shares+Nshares, sip+i, sizeof(Share));
1254 			if(CIFStreeconnect(Sess, Sess->cname,
1255 			    Shares[Nshares].name, Shares+Nshares) == -1){
1256 				free(Shares[Nshares].name);
1257 				continue;
1258 			}
1259 			Nshares++;
1260 		}
1261 		free(sip);
1262 	} else
1263 		for(i = 1; i < argc; i++){
1264 			if(CIFStreeconnect(Sess, Sess->cname, argv[i],
1265 			    Shares+Nshares) == -1){
1266 				fprint(2, "%s: %s  %q - can't connect to share"
1267 					", %r\n", argv0, Host, argv[i]);
1268 				continue;
1269 			}
1270 			Shares[Nshares].name = strlwr(estrdup9p(argv[i]));
1271 			Nshares++;
1272 		}
1273 
1274 	if(Nshares == 0)
1275 		fprint(2, "no available shares\n");
1276 
1277 	if((Keeppid = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG|RFNAMEG)) == 0){
1278 		keepalive();
1279 		exits(nil);
1280 	}
1281 	postmountsrv(&fs, svs, mtpt, MREPL|MCREATE);
1282 	exits(nil);
1283 }
1284