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