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