]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/sshfs.c
sshfs: look up uid/gid from /etc/^(passwd group)
[plan9front.git] / sys / src / cmd / sshfs.c
1 #include <u.h>
2 #include <libc.h>
3 #include <fcall.h>
4 #include <thread.h>
5 #include <9p.h>
6 #include <libsec.h>
7
8 int readonly;
9 int debug;
10 #define dprint(...) if(debug) fprint(2, __VA_ARGS__)
11 #pragma varargck        type    "Σ"    int
12
13 enum {
14         MAXPACK = 34000,
15         MAXWRITE = 32768,
16         MAXATTRIB = 64,
17         VERSION = 3,
18         MAXREQID = 32,
19         HASH = 64
20 };
21
22 enum {
23         SSH_FXP_INIT = 1,
24         SSH_FXP_VERSION = 2,
25         SSH_FXP_OPEN = 3,
26         SSH_FXP_CLOSE = 4,
27         SSH_FXP_READ = 5,
28         SSH_FXP_WRITE = 6,
29         SSH_FXP_LSTAT = 7,
30         SSH_FXP_FSTAT = 8,
31         SSH_FXP_SETSTAT = 9,
32         SSH_FXP_FSETSTAT = 10,
33         SSH_FXP_OPENDIR = 11,
34         SSH_FXP_READDIR = 12,
35         SSH_FXP_REMOVE = 13,
36         SSH_FXP_MKDIR = 14,
37         SSH_FXP_RMDIR = 15,
38         SSH_FXP_REALPATH = 16,
39         SSH_FXP_STAT = 17,
40         SSH_FXP_RENAME = 18,
41         SSH_FXP_READLINK = 19,
42         SSH_FXP_SYMLINK = 20,
43         SSH_FXP_STATUS = 101,
44         SSH_FXP_HANDLE = 102,
45         SSH_FXP_DATA = 103,
46         SSH_FXP_NAME = 104,
47         SSH_FXP_ATTRS = 105,
48         SSH_FXP_EXTENDED = 200,
49         SSH_FXP_EXTENDED_REPLY = 201,
50         
51         SSH_FXF_READ = 0x00000001,
52         SSH_FXF_WRITE = 0x00000002,
53         SSH_FXF_APPEND = 0x00000004,
54         SSH_FXF_CREAT = 0x00000008,
55         SSH_FXF_TRUNC = 0x00000010,
56         SSH_FXF_EXCL = 0x00000020,
57         SSH_FILEXFER_ATTR_SIZE = 0x00000001,
58         SSH_FILEXFER_ATTR_UIDGID = 0x00000002,
59         SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004,
60         SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008,
61         SSH_FILEXFER_ATTR_EXTENDED = 0x80000000,
62
63         SSH_FX_OK = 0,
64         SSH_FX_EOF = 1,
65         SSH_FX_NO_SUCH_FILE = 2,
66         SSH_FX_PERMISSION_DENIED = 3,
67         SSH_FX_FAILURE = 4,
68         SSH_FX_BAD_MESSAGE = 5,
69         SSH_FX_NO_CONNECTION = 6,
70         SSH_FX_CONNECTION_LOST = 7,
71         SSH_FX_OP_UNSUPPORTED = 8,
72 };
73
74 char *errors[] = {
75         [SSH_FX_OK] "success",
76         [SSH_FX_EOF] "end of file",
77         [SSH_FX_NO_SUCH_FILE] "file does not exist",
78         [SSH_FX_PERMISSION_DENIED] "permission denied",
79         [SSH_FX_FAILURE] "failure",
80         [SSH_FX_BAD_MESSAGE] "bad message",
81         [SSH_FX_NO_CONNECTION] "no connection",
82         [SSH_FX_CONNECTION_LOST] "connection lost",
83         [SSH_FX_OP_UNSUPPORTED] "unsupported operation",
84 };
85
86 typedef struct SFid SFid;
87 typedef struct SReq SReq;
88 typedef struct IDEnt IDEnt;
89
90 struct SFid {
91         RWLock;
92         char *fn;
93         uchar *hand;
94         int handn;
95         Qid qid;
96         Dir *dirent;
97         int ndirent;
98         uchar direof;
99 };
100
101 struct SReq {
102         Req *req;
103         SFid *closefid;
104         int reqid;
105         SReq *next;
106 };
107
108 struct IDEnt {
109         char *name;
110         int id;
111         IDEnt *next;
112 };
113 IDEnt *uidtab[HASH], *gidtab[HASH];
114
115 int rdfd, wrfd;
116 SReq *sreqrd[MAXREQID];
117 QLock sreqidlock;
118 Rendez sreqidrend = {.l = &sreqidlock};
119
120 SReq *sreqwr, **sreqlast = &sreqwr;
121 QLock sreqwrlock;
122 Rendez writerend = {.l = &sreqwrlock};
123
124 #define PUT4(p, u) (p)[0] = (u)>>24, (p)[1] = (u)>>16, (p)[2] = (u)>>8, (p)[3] = (u)
125 #define GET4(p) ((u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24)
126
127 int
128 fxpfmt(Fmt *f)
129 {
130         int n;
131         
132         n = va_arg(f->args, int);
133         switch(n){
134         case SSH_FXP_INIT: fmtstrcpy(f, "SSH_FXP_INIT"); break;
135         case SSH_FXP_VERSION: fmtstrcpy(f, "SSH_FXP_VERSION"); break;
136         case SSH_FXP_OPEN: fmtstrcpy(f, "SSH_FXP_OPEN"); break;
137         case SSH_FXP_CLOSE: fmtstrcpy(f, "SSH_FXP_CLOSE"); break;
138         case SSH_FXP_READ: fmtstrcpy(f, "SSH_FXP_READ"); break;
139         case SSH_FXP_WRITE: fmtstrcpy(f, "SSH_FXP_WRITE"); break;
140         case SSH_FXP_LSTAT: fmtstrcpy(f, "SSH_FXP_LSTAT"); break;
141         case SSH_FXP_FSTAT: fmtstrcpy(f, "SSH_FXP_FSTAT"); break;
142         case SSH_FXP_SETSTAT: fmtstrcpy(f, "SSH_FXP_SETSTAT"); break;
143         case SSH_FXP_FSETSTAT: fmtstrcpy(f, "SSH_FXP_FSETSTAT"); break;
144         case SSH_FXP_OPENDIR: fmtstrcpy(f, "SSH_FXP_OPENDIR"); break;
145         case SSH_FXP_READDIR: fmtstrcpy(f, "SSH_FXP_READDIR"); break;
146         case SSH_FXP_REMOVE: fmtstrcpy(f, "SSH_FXP_REMOVE"); break;
147         case SSH_FXP_MKDIR: fmtstrcpy(f, "SSH_FXP_MKDIR"); break;
148         case SSH_FXP_RMDIR: fmtstrcpy(f, "SSH_FXP_RMDIR"); break;
149         case SSH_FXP_REALPATH: fmtstrcpy(f, "SSH_FXP_REALPATH"); break;
150         case SSH_FXP_STAT: fmtstrcpy(f, "SSH_FXP_STAT"); break;
151         case SSH_FXP_RENAME: fmtstrcpy(f, "SSH_FXP_RENAME"); break;
152         case SSH_FXP_READLINK: fmtstrcpy(f, "SSH_FXP_READLINK"); break;
153         case SSH_FXP_SYMLINK: fmtstrcpy(f, "SSH_FXP_SYMLINK"); break;
154         case SSH_FXP_STATUS: fmtstrcpy(f, "SSH_FXP_STATUS"); break;
155         case SSH_FXP_HANDLE: fmtstrcpy(f, "SSH_FXP_HANDLE"); break;
156         case SSH_FXP_DATA: fmtstrcpy(f, "SSH_FXP_DATA"); break;
157         case SSH_FXP_NAME: fmtstrcpy(f, "SSH_FXP_NAME"); break;
158         case SSH_FXP_ATTRS: fmtstrcpy(f, "SSH_FXP_ATTRS"); break;
159         case SSH_FXP_EXTENDED: fmtstrcpy(f, "SSH_FXP_EXTENDED"); break;
160         case SSH_FXP_EXTENDED_REPLY: fmtstrcpy(f, "SSH_FXP_EXTENDED_REPLY");
161         default: fmtprint(f, "%d", n);
162         }
163         return 0;
164 }
165
166 char *
167 idlookup(IDEnt **tab, int id)
168 {
169         IDEnt *p;
170         
171         for(p = tab[(ulong)id % HASH]; p != nil; p = p->next)
172                 if(p->id == id)
173                         return strdup(p->name);
174         return smprint("%d", id);
175 }
176
177 int
178 namelookup(IDEnt **tab, char *name)
179 {
180         IDEnt *p;
181         int i;
182         char *q;
183         
184         for(i = 0; i < HASH; i++)
185                 for(p = tab[i]; p != nil; p = p->next)
186                         if(strcmp(p->name, name) == 0)
187                                 return p->id;
188         i = strtol(name, &q, 10);
189         if(*q == 0) return i;
190         werrstr("unknown %s '%s'", tab == uidtab ? "user" : "group", name);
191         return -1;
192 }
193
194 int
195 vpack(uchar *p, int n, char *fmt, va_list a)
196 {
197         uchar *p0 = p, *e = p+n;
198         u32int u;
199         u64int v;
200         void *s;
201         int c;
202
203         for(;;){
204                 switch(c = *fmt++){
205                 case '\0':
206                         return p - p0;
207                 case '_':
208                         if(++p > e) goto err;
209                         break;
210                 case '.':
211                         *va_arg(a, void**) = p;
212                         break;
213                 case 'b':
214                         if(p >= e) goto err;
215                         *p++ = va_arg(a, int);
216                         break;
217                 case '[':
218                 case 's':
219                         s = va_arg(a, void*);
220                         u = va_arg(a, int);
221                         if(c == 's'){
222                                 if(p+4 > e) goto err;
223                                 PUT4(p, u), p += 4;
224                         }
225                         if(u > e-p) goto err;
226                         memmove(p, s, u);
227                         p += u;
228                         break;
229                 case 'u':
230                         u = va_arg(a, int);
231                         if(p+4 > e) goto err;
232                         PUT4(p, u), p += 4;
233                         break;
234                 case 'v':
235                         v = va_arg(a, vlong);
236                         if(p+8 > e) goto err;
237                         u = v>>32; PUT4(p, u), p += 4;
238                         u = v; PUT4(p, u), p += 4;
239                         break;
240                 }
241         }
242 err:
243         return -1;
244 }
245
246 int
247 vunpack(uchar *p, int n, char *fmt, va_list a)
248 {
249         uchar *p0 = p, *e = p+n;
250         u32int u;
251         u64int v;
252         void *s;
253
254         for(;;){
255                 switch(*fmt++){
256                 case '\0':
257                         return p - p0;
258                 case '_':
259                         if(++p > e) goto err;
260                         break;
261                 case '.':
262                         *va_arg(a, void**) = p;
263                         break;
264                 case 'b':
265                         if(p >= e) goto err;
266                         *va_arg(a, int*) = *p++;
267                         break;
268                 case 's':
269                         if(p+4 > e) goto err;
270                         u = GET4(p), p += 4;
271                         if(u > e-p) goto err;
272                         *va_arg(a, void**) = p;
273                         *va_arg(a, int*) = u;
274                         p += u;
275                         break;
276                 case '[':
277                         s = va_arg(a, void*);
278                         u = va_arg(a, int);
279                         if(u > e-p) goto err;
280                         memmove(s, p, u);
281                         p += u;
282                         break;
283                 case 'u':
284                         if(p+4 > e) goto err;
285                         u = GET4(p);
286                         *va_arg(a, int*) = u;
287                         p += 4;
288                         break;
289                 case 'v':
290                         if(p+8 > e) goto err;
291                         v = (u64int)GET4(p) << 32;
292                         v |= (u32int)GET4(p+4);
293                         *va_arg(a, vlong*) = v;
294                         p += 8;
295                         break;
296                 }
297         }
298 err:
299         return -1;
300 }
301
302 int
303 pack(uchar *p, int n, char *fmt, ...)
304 {
305         va_list a;
306         va_start(a, fmt);
307         n = vpack(p, n, fmt, a);
308         va_end(a);
309         return n;
310 }
311 int
312 unpack(uchar *p, int n, char *fmt, ...)
313 {
314         va_list a;
315         va_start(a, fmt);
316         n = vunpack(p, n, fmt, a);
317         va_end(a);
318         return n;
319 }
320
321 void
322 sendpkt(char *fmt, ...)
323 {
324         static uchar buf[MAXPACK];
325         int n;
326         va_list a;
327
328         va_start(a, fmt);
329         n = vpack(buf+4, sizeof(buf)-4, fmt, a);
330         va_end(a);
331         if(n < 0) {
332                 sysfatal("sendpkt: message too big");
333                 return;
334         }
335         PUT4(buf, n);
336         n += 4;
337
338         dprint("SFTP --> %Σ\n", (int)buf[4]);
339         if(write(wrfd, buf, n) != n)
340                 sysfatal("write: %r");
341 }
342
343 static uchar rxpkt[MAXPACK];
344 static int rxlen;
345
346 int
347 recvpkt(void)
348 {
349         static uchar rxbuf[MAXPACK];
350         static int rxfill;
351         int rc;
352         
353         while(rxfill < 4 || rxfill < (rxlen = GET4(rxbuf) + 4) && rxlen <= MAXPACK){
354                 rc = read(rdfd, rxbuf + rxfill, MAXPACK - rxfill);
355                 if(rc < 0) sysfatal("read: %r");
356                 if(rc == 0) sysfatal("read: eof");
357                 rxfill += rc;
358         }
359         if(rxlen > MAXPACK) sysfatal("received garbage");
360         memmove(rxpkt, rxbuf + 4, rxlen - 4);
361         memmove(rxbuf, rxbuf + rxlen, rxfill - rxlen);
362         rxfill -= rxlen;
363         rxlen -= 4;
364         dprint("SFTP <-- %Σ\n", (int)rxpkt[0]);
365         return rxpkt[0];
366 }
367
368 void
369 putsfid(SFid *s)
370 {
371         int i;
372         Dir *d;
373
374         if(s == nil) return;
375         free(s->fn);
376         free(s->hand);
377         for(i = 0; i < s->ndirent; i++){
378                 d = &s->dirent[i];
379                 free(d->name);
380                 free(d->uid);
381                 free(d->gid);
382                 free(d->muid);
383         }
384         free(s->dirent);
385         free(s);
386 }
387
388 void
389 putsreq(SReq *s)
390 {
391         if(s == nil) return;
392         if(s->reqid != -1){
393                 qlock(&sreqidlock);
394                 sreqrd[s->reqid] = nil;
395                 rwakeup(&sreqidrend);
396                 qunlock(&sreqidlock);
397         }
398         putsfid(s->closefid);
399         free(s);
400 }
401
402 void
403 submitsreq(SReq *s)
404 {
405         qlock(&sreqwrlock);
406         *sreqlast = s;
407         sreqlast = &s->next;
408         rwakeup(&writerend);
409         qunlock(&sreqwrlock);
410 }
411
412
413 void
414 submitreq(Req *r)
415 {
416         SReq *s;
417         
418         s = emalloc9p(sizeof(SReq));
419         s->reqid = -1;
420         s->req = r;
421         submitsreq(s);
422 }
423
424 char *
425 pathcat(char *p, char *c)
426 {
427         if(strcmp(p, ".") == 0)
428                 return strdup(c);
429         return smprint("%s/%s", p, c);
430 }
431
432 char *
433 parentdir(char *p)
434 {
435         char *q, *r;
436         
437         if(strcmp(p, ".") == 0) return strdup(".");
438         if(strcmp(p, "/") == 0) return strdup("/");
439         q = strdup(p);
440         r = strrchr(q, '/');
441         if(r != nil) *r = 0;
442         return q;
443 }
444
445 char *
446 finalelem(char *p)
447 {
448         char *q;
449         
450         q = strrchr(p, '/');
451         if(q == nil) return strdup(p);
452         return strdup(q+1);
453 }
454
455 u64int
456 qidcalc(char *c)
457 {
458         uchar dig[SHA1dlen];
459
460         sha1((uchar *) c, strlen(c), dig, nil);
461         return dig[0] | dig[1] << 8 | dig[2] << 16 | dig[3] << 24 | (uvlong)dig[4] << 32 | (uvlong)dig[5] << 40 | (uvlong)dig[6] << 48 | (uvlong)dig[7] << 56;
462 }
463
464 void
465 walkprocess(Req *r, int isdir, char *e)
466 {
467         char *p;
468         SFid *sf;
469         
470         sf = r->newfid->aux;
471         if(e != nil){
472                 r->ofcall.nwqid--;
473                 if(r->ofcall.nwqid == 0){
474                         respond(r, e);
475                         return;
476                 }
477                 p = r->aux;
478                 r->aux = parentdir(p);
479                 free(p);
480                 submitreq(r);
481         }else{
482                 assert(r->ofcall.nwqid > 0);
483                 if(!isdir)
484                         r->ofcall.wqid[r->ofcall.nwqid - 1].type = 0;
485                 wlock(sf);
486                 free(sf->fn);
487                 sf->fn = r->aux;
488                 r->aux = nil;
489                 sf->qid = r->ofcall.wqid[r->ofcall.nwqid - 1];
490                 wunlock(sf);
491                 respond(r, nil);
492         }
493 }
494
495 int
496 attrib2dir(uchar *p0, uchar *ep, Dir *d)
497 {
498         uchar *p;
499         int i, rc, extn, extvn;
500         u32int flags, uid, gid, perm, next;
501         uchar *exts,  *extvs;
502         
503         p = p0;
504         if(p + 4 > ep) return -1;
505         flags = GET4(p), p += 4;
506         if((flags & SSH_FILEXFER_ATTR_SIZE) != 0){
507                 rc = unpack(p, ep - p, "v", &d->length); if(rc < 0) return -1; p += rc;
508         }
509         if((flags & SSH_FILEXFER_ATTR_UIDGID) != 0){
510                 rc = unpack(p, ep - p, "uu", &uid, &gid); if(rc < 0) return -1; p += rc;
511                 d->uid = idlookup(uidtab, uid);
512                 d->gid = idlookup(gidtab, gid);
513         }else{
514                 d->uid = strdup("sshfs");
515                 d->gid = strdup("sshfs");
516         }
517         d->muid = strdup(d->uid);
518         if((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0){
519                 rc = unpack(p, ep - p, "u", &perm); if(rc < 0) return -1; p += rc;
520                 d->mode = perm & 0777;
521                 if((perm & 0040000) != 0) d->mode |= DMDIR;
522         }
523         d->qid.type = d->mode >> 24;
524         if((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0){
525                 rc = unpack(p, ep - p, "uu", &d->atime, &d->mtime); if(rc < 0) return -1; p += rc;
526         }
527         if((flags & SSH_FILEXFER_ATTR_EXTENDED) != 0){
528                 rc = unpack(p, ep - p, "u", &next); if(rc < 0) return -1; p += rc;
529                 for(i = 0; i < next; i++){
530                         rc = unpack(p, ep - p, "ss", &exts, &extn, &extvs, &extvn); if(rc < 0) return -1; p += rc;
531                         exts[extn] = extvs[extvn] = 0;
532                 }
533         }
534         return p - p0;
535 }
536
537 int
538 dir2attrib(Dir *d, uchar **rp)
539 {
540         int rc;
541         uchar *r, *p, *e;
542         u32int fl;
543         int uid, gid;
544         
545         werrstr("phase error");
546         r = emalloc9p(MAXATTRIB);
547         e = r + MAXATTRIB;
548         fl = 0;
549         p = r + 4;
550         if(d->length != (uvlong)-1){
551                 fl |= SSH_FILEXFER_ATTR_SIZE;
552                 rc = pack(p, e - p, "v", d->length); if(rc < 0) return -1; p += rc;
553         }
554         if(d->uid != nil && *d->uid != 0 || d->gid != nil && *d->gid != 0){
555                 /* FIXME: sending -1 for "don't change" works with openssh, but violates the spec */
556                 if(d->uid != nil && *d->uid != 0){
557                         uid = namelookup(uidtab, d->uid);
558                         if(uid == -1)
559                                 return -1;
560                 }else
561                         uid = -1;
562                 if(d->gid != nil && *d->gid != 0){
563                         gid = namelookup(gidtab, d->gid);
564                         if(gid == -1)
565                                 return -1;
566                 }else
567                         gid = -1;
568                 fl |= SSH_FILEXFER_ATTR_UIDGID;
569                 rc = pack(p, e - p, "uu", uid, gid); if(rc < 0) return -1; p += rc;
570         }
571         if(d->mode != (ulong)-1){
572                 fl |= SSH_FILEXFER_ATTR_PERMISSIONS;
573                 rc = pack(p, e - p, "u", d->mode); if(rc < 0) return -1; p += rc;
574         }
575         if(d->atime != (ulong)-1 || d->mtime != (ulong)-1){
576                 /* FIXME: see above */
577                 fl |= SSH_FILEXFER_ATTR_ACMODTIME;
578                 rc = pack(p, e - p, "uu", d->atime, d->mtime); if(rc < 0) return -1; p += rc;
579         }
580         PUT4(r, fl);
581         *rp = r;
582         return p - r;
583 }
584
585 int
586 parsedir(SFid *sf)
587 {
588         int i, rc;
589         Dir *d;
590         u32int c;
591         uchar *p, *ep;
592         char *fn, *ln;
593         int fns, lns;
594         char *s;
595
596         if(unpack(rxpkt, rxlen, "_____u", &c) < 0) return -1;
597         wlock(sf);
598         sf->dirent = erealloc9p(sf->dirent, (sf->ndirent + c) * sizeof(Dir));
599         d = sf->dirent + sf->ndirent;
600         p = rxpkt + 9;
601         ep = rxpkt + rxlen;
602         for(i = 0; i < c; i++){
603                 rc = unpack(p, ep - p, "ss", &fn, &fns, &ln, &lns); if(rc < 0) goto err; p += rc;
604                 memset(d, 0, sizeof(Dir));
605                 rc = attrib2dir(p, ep, d); if(rc < 0) goto err; p += rc;
606                 if(fn[0] == '.' && (fns == 1 || fns == 2 && fn[1] == '.')){
607                         free(d->uid);
608                         free(d->gid);
609                         free(d->muid);
610                         continue;
611                 }
612                 d->name = emalloc9p(fns + 1);
613                 memcpy(d->name, fn, fns);
614                 s = pathcat(sf->fn, d->name);
615                 d->qid.path = qidcalc(s);
616                 free(s);
617                 sf->ndirent++;
618                 d++;
619         }
620         wunlock(sf);
621         return 0;
622 err:
623         wunlock(sf);
624         return -1;
625 }
626
627 void
628 readprocess(Req *r)
629 {
630         int start;
631         uchar *p, *ep;
632         uint rv;
633         SFid *sf;
634
635         if((r->fid->qid.type & QTDIR) == 0){
636                 submitreq(r);
637                 return;
638         }
639
640         if(r->ifcall.offset == 0)
641                 start = 0;
642         else
643                 start = r->fid->dirindex;
644
645         p = (uchar*)r->ofcall.data;
646         ep = p+r->ifcall.count;
647         sf = r->fid->aux;
648
649         rlock(sf);
650         while(p < ep){
651                 if(start >= sf->ndirent)
652                         if(sf->direof)
653                                 break;
654                         else{
655                                 runlock(sf);
656                                 submitreq(r);
657                                 return;
658                         }
659                 rv = convD2M(&sf->dirent[start], p, ep-p);
660                 if(rv <= BIT16SZ)
661                         break;
662                 p += rv;
663                 start++;
664         }
665         runlock(sf);
666         r->fid->dirindex = start;
667         r->ofcall.count = p - (uchar*)r->ofcall.data;
668         respond(r, nil);
669 }
670
671 void
672 sshfsattach(Req *r)
673 {
674         SFid *sf;
675
676         if(r->ifcall.aname != nil && *r->ifcall.aname != 0 && r->aux == nil){
677                 submitreq(r);
678                 return;
679         }
680         sf = emalloc9p(sizeof(SFid));
681         if(r->ifcall.aname != nil && *r->ifcall.aname != 0)
682                 sf->fn = strdup(r->ifcall.aname);
683         else
684                 sf->fn = strdup(".");
685         sf->qid = (Qid){qidcalc(sf->fn), 0, QTDIR};
686         r->ofcall.qid = sf->qid;
687         r->fid->qid = sf->qid;
688         r->fid->aux = sf;
689         respond(r, nil);
690 }
691
692 void
693 sendproc(void *)
694 {
695         SReq *r;
696         SFid *sf;
697         int i;
698         int x, y;
699         char *s, *t;
700
701         threadsetname("send");
702
703         for(;;){
704                 qlock(&sreqwrlock);
705                 while(sreqwr == nil)
706                         rsleep(&writerend);
707                 r = sreqwr;
708                 sreqwr = r->next;
709                 if(sreqwr == nil) sreqlast = &sreqwr;
710                 qunlock(&sreqwrlock);
711                 
712                 qlock(&sreqidlock);
713         idagain:
714                 for(i = 0; i < MAXREQID; i++)
715                         if(sreqrd[i] == nil){
716                                 sreqrd[i] = r;
717                                 r->reqid = i;
718                                 break;
719                         }
720                 if(i == MAXREQID){
721                         rsleep(&sreqidrend);
722                         goto idagain;
723                 }
724                 qunlock(&sreqidlock);
725
726                 if(r->closefid != nil){
727                         sendpkt("bus", SSH_FXP_CLOSE, r->reqid, r->closefid->hand, r->closefid->handn);
728                         continue;
729                 }
730                 if(r->req == nil)
731                         sysfatal("nil request in queue");
732
733                 sf = r->req->fid != nil ? r->req->fid->aux : nil;
734                 switch(r->req->ifcall.type){
735                 case Tattach:
736                         sendpkt("bus", SSH_FXP_STAT, r->reqid, r->req->ifcall.aname, strlen(r->req->ifcall.aname));
737                         break;
738                 case Twalk:
739                         sendpkt("bus", SSH_FXP_STAT, r->reqid, r->req->aux, strlen(r->req->aux));
740                         break;
741                 case Topen:
742                         rlock(sf);
743                         if((r->req->ofcall.qid.type & QTDIR) != 0)
744                                 sendpkt("bus", SSH_FXP_OPENDIR, r->reqid, sf->fn, strlen(sf->fn));
745                         else{
746                                 x = r->req->ifcall.mode;
747                                 y = 0;
748                                 switch(x & 3){
749                                 case OREAD: y = SSH_FXF_READ; break;
750                                 case OWRITE: y = SSH_FXF_WRITE; break;
751                                 case ORDWR: y = SSH_FXF_READ | SSH_FXF_WRITE; break;
752                                 }
753                                 if(readonly && (y & SSH_FXF_WRITE) != 0){
754                                         respond(r->req, "mounted read-only");
755                                         runlock(sf);
756                                         putsreq(r);
757                                         break;
758                                 }
759                                 if((x & OTRUNC) != 0)
760                                         y |= SSH_FXF_TRUNC;
761                                 sendpkt("busuu", SSH_FXP_OPEN, r->reqid, sf->fn, strlen(sf->fn), y, 0);
762                         }
763                         runlock(sf);
764                         break;
765                 case Tcreate:
766                         rlock(sf);
767                         s = pathcat(sf->fn, r->req->ifcall.name);
768                         runlock(sf);
769                         if((r->req->ifcall.perm & DMDIR) != 0){
770                                 if(r->req->aux == nil){
771                                         sendpkt("busuu", SSH_FXP_MKDIR, r->reqid, s, strlen(s),
772                                                 SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
773                                         r->req->aux = (void*)-1;
774                                 }else{
775                                         sendpkt("bus", SSH_FXP_OPENDIR, r->reqid, s, strlen(s));
776                                         r->req->aux = (void*)-2;
777                                 }
778                                 free(s);
779                                 break;
780                         }
781                         x = r->req->ifcall.mode;
782                         y = SSH_FXF_CREAT | SSH_FXF_EXCL;
783                         switch(x & 3){
784                         case OREAD: y |= SSH_FXF_READ; break;
785                         case OWRITE: y |= SSH_FXF_WRITE; break;
786                         case ORDWR: y |= SSH_FXF_READ | SSH_FXF_WRITE; break;
787                         }
788                         sendpkt("busuuu", SSH_FXP_OPEN, r->reqid, s, strlen(s), y,
789                                 SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
790                         free(s);
791                         break;
792                 case Tread:
793                         rlock(sf);
794                         if((r->req->fid->qid.type & QTDIR) != 0)
795                                 sendpkt("bus", SSH_FXP_READDIR, r->reqid, sf->hand, sf->handn);
796                         else
797                                 sendpkt("busvuu", SSH_FXP_READ, r->reqid, sf->hand, sf->handn,
798                                         r->req->ifcall.offset, r->req->ifcall.count);
799                         runlock(sf);
800                         break;
801                 case Twrite:
802                         x = r->req->ifcall.count - r->req->ofcall.count;
803                         if(x >= MAXWRITE) x = MAXWRITE;
804                         rlock(sf);
805                         sendpkt("busvs", SSH_FXP_WRITE, r->reqid, sf->hand, sf->handn,
806                                 r->req->ifcall.offset + r->req->ofcall.count,
807                                 r->req->ifcall.data + r->req->ofcall.count,
808                                 x);
809                         runlock(sf);
810                         r->req->ofcall.offset = x;
811                         break;
812                 case Tstat:
813                         rlock(sf);
814                         r->req->d.name = finalelem(sf->fn);
815                         r->req->d.qid = sf->qid;
816                         if(sf->handn > 0)
817                                 sendpkt("bus", SSH_FXP_FSTAT, r->reqid, sf->hand, sf->handn);
818                         else
819                                 sendpkt("bus", SSH_FXP_STAT, r->reqid, sf->fn, strlen(sf->fn));
820                         runlock(sf);
821                         break;
822                 case Twstat:
823                         if(r->req->aux == (void *) -1){
824                                 rlock(sf);
825                                 s = parentdir(sf->fn);
826                                 t = pathcat(s, r->req->d.name);
827                                 sendpkt("buss", SSH_FXP_RENAME, r->reqid, sf->fn, strlen(sf->fn), t, strlen(t));
828                                 free(s);
829                                 r->req->aux = t;
830                                 runlock(sf);
831                                 break;
832                         }
833                         x = dir2attrib(&r->req->d, (uchar **) &s);
834                         if(x < 0){
835                                 responderror(r->req);
836                                 putsreq(r);
837                                 break;
838                         }
839                         rlock(sf);
840                         if(sf->handn > 0)
841                                 sendpkt("bus[", SSH_FXP_FSETSTAT, r->reqid, sf->hand, sf->handn, s, x);
842                         else
843                                 sendpkt("bus[", SSH_FXP_SETSTAT, r->reqid, sf->fn, strlen(sf->fn), s, x);
844                         runlock(sf);
845                         break;
846                 case Tremove:
847                         rlock(sf);
848                         if((sf->qid.type & QTDIR) != 0)
849                                 sendpkt("bus", SSH_FXP_RMDIR, r->reqid, sf->fn, strlen(sf->fn));
850                         else
851                                 sendpkt("bus", SSH_FXP_REMOVE, r->reqid, sf->fn, strlen(sf->fn));
852                         runlock(sf);
853                         break;
854                 default:
855                         fprint(2, "sendproc: unimplemented 9p request %F in queue\n", &r->req->ifcall);
856                         respond(r->req, "phase error");
857                         putsreq(r);
858                 }
859         }
860 }
861
862 void
863 recvproc(void *)
864 {
865         static char ebuf[256];
866
867         SReq *r;
868         SFid *sf;
869         int t, id;
870         u32int code;
871         char *msg, *lang, *hand;
872         int msgn, langn, handn;
873         uchar *p;
874         char *e;
875         
876         threadsetname("recv");
877         
878         for(;;){
879                 e = "phase error";
880                 switch(t = recvpkt()){
881                 case SSH_FXP_STATUS:
882                 case SSH_FXP_HANDLE:
883                 case SSH_FXP_DATA:
884                 case SSH_FXP_NAME:
885                 case SSH_FXP_ATTRS:
886                         break;
887                 default:
888                         fprint(2, "sshfs: received unexpected packet of type %Σ\n", t);
889                         continue;
890                 }
891                 id = GET4(rxpkt + 1);
892                 if(id >= MAXREQID){
893                         fprint(2, "sshfs: received response with id out of range, %d > %d\n", id, MAXREQID);
894                         continue;
895                 }
896                 qlock(&sreqidlock);
897                 r = sreqrd[id];
898                 if(r != nil){
899                         sreqrd[id] = nil;
900                         rwakeup(&sreqidrend);
901                 }
902                 qunlock(&sreqidlock);
903                 if(r == nil){
904                         fprint(2, "sshfs: received response to non-existent request (req id = %d)\n", id);
905                         continue;
906                 }
907                 if(r->closefid != nil){
908                         putsreq(r);
909                         continue;
910                 }
911                 if(r->req == nil)
912                         sysfatal("recvproc: r->req == nil");
913
914                 sf = r->req->fid != nil ? r->req->fid->aux : nil;
915                 switch(r->req->ifcall.type){
916                 case Tattach:
917                         if(t != SSH_FXP_ATTRS) goto common;
918                         if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
919                         r->req->aux = (void*)-1;
920                         if((code & 4) == 0){
921                                 fprint(2, "sshfs: can't determine if %s is a directory\n", r->req->ifcall.aname);
922                                 sshfsattach(r->req);
923                                 break;
924                         }
925                         p = rxpkt + 9;
926                         if(code & 1) p += 8;
927                         if(code & 2) p += 8;
928                         if(p + 4 > rxpkt + rxlen) goto garbage;
929                         if((GET4(p) & 0040000) == 0)
930                                 respond(r->req, "not a directory");
931                         else
932                                 sshfsattach(r->req);
933                         break;
934                 case Twalk:
935                         if(t != SSH_FXP_ATTRS) goto common;
936                         if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
937                         if((code & 4) == 0){
938                                 fprint(2, "sshfs: can't determine if %s is a directory\n", ((SFid*)r->req->fid)->fn);
939                                 walkprocess(r->req, 0, nil);
940                                 break;
941                         }
942                         p = rxpkt + 9;
943                         if(code & 1) p += 8;
944                         if(code & 2) p += 8;
945                         if(p + 4 > rxpkt + rxlen) goto garbage;
946                         walkprocess(r->req, GET4(p) & 0040000, nil);
947                         break;
948                 case Tcreate:
949                         if(t == SSH_FXP_STATUS && r->req->aux == (void*)-1){
950                                 if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
951                                 if(code != SSH_FX_OK) goto common;
952                                 submitreq(r->req);
953                                 break;
954                         }
955                         /* wet floor */
956                 case Topen:
957                         if(t != SSH_FXP_HANDLE) goto common;
958                         if(unpack(rxpkt, rxlen, "_____s", &hand, &handn) < 0) goto garbage;
959                         wlock(sf);
960                         sf->handn = handn;
961                         sf->hand = emalloc9p(sf->handn);
962                         memcpy(sf->hand, hand, sf->handn);
963                         wunlock(sf);
964                         respond(r->req, nil);
965                         break;
966                 case Tread:
967                         if((r->req->fid->qid.type & QTDIR) != 0){
968                                 if(t != SSH_FXP_NAME) goto common;
969                                 if(parsedir(sf) < 0) goto garbage;
970                                 readprocess(r->req);
971                                 break;
972                         }
973                         if(t != SSH_FXP_DATA) goto common;
974                         if(unpack(rxpkt, rxlen, "_____s", &msg, &msgn) < 0) goto garbage;
975                         if(msgn > r->req->ifcall.count) msgn = r->req->ifcall.count;
976                         r->req->ofcall.count = msgn;
977                         memcpy(r->req->ofcall.data, msg, msgn);
978                         respond(r->req, nil);
979                         break;
980                 case Twrite:
981                         if(t != SSH_FXP_STATUS) goto common;
982                         if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
983                         if(code == SSH_FX_OK){
984                                 r->req->ofcall.count += r->req->ofcall.offset;
985                                 if(r->req->ofcall.count == r->req->ifcall.count)
986                                         respond(r->req, nil);
987                                 else
988                                         submitreq(r->req);
989                                 break;
990                         }
991                         if(r->req->ofcall.count == 0) goto common;
992                         respond(r->req, nil);
993                         break;
994                 case Tstat:
995                         if(t != SSH_FXP_ATTRS) goto common;
996                         if(attrib2dir(rxpkt + 5, rxpkt + rxlen, &r->req->d) < 0) goto garbage;
997                         respond(r->req, nil);
998                         break;
999                 case Twstat:
1000                         if(t != SSH_FXP_STATUS || r->req->d.name == nil || *r->req->d.name == 0) goto common;
1001                         if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
1002                         if(code != SSH_FX_OK) goto common;
1003                         if(r->req->aux == nil){
1004                                 r->req->aux = (void *) -1;
1005                                 submitreq(r->req);
1006                         }else{
1007                                 wlock(sf);
1008                                 free(sf->fn);
1009                                 sf->fn = r->req->aux;
1010                                 wunlock(sf);
1011                                 respond(r->req, nil);
1012                         }
1013                         break;
1014                 case Tremove:
1015                         goto common;
1016                 default:
1017                         fprint(2, "sendproc: unimplemented 9p request %F in queue\n", &r->req->ifcall);
1018                         respond(r->req, "phase error");
1019                 }
1020                 putsreq(r);
1021                 continue;
1022                 
1023         common:
1024                 switch(t){
1025                 case SSH_FXP_STATUS:
1026                         if(unpack(rxpkt, rxlen, "_____uss", &code, &msg, &msgn, &lang, &langn) < 0){
1027         garbage:
1028                                 fprint(2, "sshfs: garbled packet in response to 9p request %F\n", &r->req->ifcall);
1029                                 break;
1030                         }
1031                         if(code == SSH_FX_OK)
1032                                 e = nil;
1033                         else if(code == SSH_FX_EOF && r->req->ifcall.type == Tread){
1034                                 if((r->req->fid->qid.type & QTDIR) != 0){
1035                                         wlock(sf);
1036                                         sf->direof = 1;
1037                                         wunlock(sf);
1038                                         readprocess(r->req);
1039                                         putsreq(r);
1040                                         continue;
1041                                 }
1042                                 r->req->ofcall.count = 0;
1043                                 e = nil;
1044                         }else if(msgn > 0){
1045                                 e = msg;
1046                                 e[msgn] = 0;
1047                         }else if(code < nelem(errors))
1048                                 e = errors[code];
1049                         else{
1050                                 snprint(ebuf, sizeof(ebuf), "error code %d", code);
1051                                 e = ebuf;
1052                         }
1053                         break;
1054                 default:
1055                         fprint(2, "sshfs: received unexpected packet %Σ for 9p request %F\n", t, &r->req->ifcall);
1056                 }
1057                 if(r->req->ifcall.type == Twalk)
1058                         walkprocess(r->req, 0, e);
1059                 else
1060                         respond(r->req, e);
1061                 putsreq(r);
1062                 continue;
1063         }
1064 }
1065
1066 void
1067 sshfswalk(Req *r)
1068 {
1069         SFid *s, *t;
1070         char *p, *q;
1071         int i;
1072
1073         if(r->fid != r->newfid){
1074                 r->newfid->qid = r->fid->qid;
1075                 s = r->fid->aux;
1076                 t = emalloc9p(sizeof(SFid));
1077                 t->fn = strdup(s->fn);
1078                 t->qid = s->qid;
1079                 r->newfid->aux = t;
1080         }else
1081                 t = r->fid->aux;
1082         if(r->ifcall.nwname == 0){
1083                 respond(r, nil);
1084                 return;
1085         }
1086         p = strdup(t->fn);
1087         for(i = 0; i < r->ifcall.nwname; i++){
1088                 if(strcmp(r->ifcall.wname[i], "..") == 0)
1089                         q = parentdir(p);
1090                 else
1091                         q = pathcat(p, r->ifcall.wname[i]);
1092                 free(p);
1093                 p = q;
1094                 r->ofcall.wqid[i] = (Qid){qidcalc(p), 0, QTDIR};
1095         }
1096         r->ofcall.nwqid = r->ifcall.nwname;
1097         r->aux = p;
1098         submitreq(r);
1099 }
1100
1101 void
1102 sshfsdestroyfid(Fid *f)
1103 {
1104         SFid *sf;
1105         SReq *sr;
1106
1107         sf = f->aux;
1108         if(sf == nil)
1109                 return;
1110         if(sf->hand != nil){
1111                 sr = emalloc9p(sizeof(SReq));
1112                 sr->reqid = -1;
1113                 sr->closefid = sf;
1114                 submitsreq(sr);
1115         }else
1116                 putsfid(sf);
1117 }
1118
1119 void
1120 sshfsdestroyreq(Req *r)
1121 {
1122         if(r->ifcall.type == Twalk)
1123                 free(r->aux);
1124 }
1125
1126 void
1127 sshfsend(Srv *)
1128 {
1129         dprint("sshfs: ending\n");
1130         threadexitsall(nil);
1131 }
1132
1133 Srv sshfssrv = {
1134         .attach sshfsattach,
1135         .walk sshfswalk,
1136         .open submitreq,
1137         .create submitreq,
1138         .read readprocess,
1139         .write submitreq,
1140         .stat submitreq,
1141         .wstat submitreq,
1142         .remove submitreq,
1143         .destroyfid sshfsdestroyfid,
1144         .destroyreq sshfsdestroyreq,
1145         .end sshfsend
1146 };
1147
1148 char *
1149 readfile(char *fn)
1150 {
1151         char *hand, *dat;
1152         int handn, datn;
1153         u32int code;
1154         char *p;
1155         int off;
1156         
1157         if(fn == nil) return nil;
1158         sendpkt("busuu", SSH_FXP_OPEN, 0, fn, strlen(fn), SSH_FXF_READ, 0);
1159         if(recvpkt() != SSH_FXP_HANDLE) return nil;
1160         if(unpack(rxpkt, rxlen, "_____s", &dat, &handn) < 0) return nil;
1161         hand = emalloc9p(handn);
1162         memcpy(hand, dat, handn);
1163         off = 0;
1164         p = nil;
1165         for(;;){
1166                 sendpkt("busvu", SSH_FXP_READ, 0, hand, handn, (uvlong)off, MAXWRITE);
1167                 switch(recvpkt()){
1168                 case SSH_FXP_STATUS:
1169                         if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto err;
1170                         print("%d\n", code);
1171                         if(code == SSH_FX_EOF) goto out;
1172                 default:
1173                         goto err;
1174                 case SSH_FXP_DATA:
1175                         if(unpack(rxpkt, rxlen, "_____s", &dat, &datn) < 0) goto err;
1176                         break;
1177                 }
1178                 p = erealloc9p(p, off + datn + 1);
1179                 memcpy(p + off, dat, datn);
1180                 off += datn;
1181                 p[off] = 0;
1182         }
1183 err:
1184         p = nil;
1185 out:
1186         sendpkt("bus", SSH_FXP_CLOSE, 0, hand, handn);
1187         free(hand);
1188         recvpkt();
1189         return p;
1190 }
1191
1192 void
1193 passwdparse(IDEnt **tab, char *s)
1194 {
1195         char *p;
1196         char *n;
1197         int id;
1198         IDEnt *e, **b;
1199
1200         p = s;
1201         for(;;){
1202                 n = p;
1203                 p = strpbrk(p, ":\n"); if(p == nil) break; if(*p != ':'){ p++; continue; }
1204                 *p = 0;
1205                 p = strpbrk(p+1, ":\n");
1206                 p = strpbrk(p, ":\n"); if(p == nil) break; if(*p != ':'){ p++; continue; }
1207                 id = strtol(p+1, &p, 10);
1208                 p = strchr(p, '\n');
1209                 if(p == nil) break;
1210                 p++;
1211                 e = emalloc9p(sizeof(IDEnt));
1212                 e->name = strdup(n);
1213                 e->id = id;
1214                 b = &tab[((ulong)e->id) % HASH];
1215                 e->next = *b;
1216                 *b = e;
1217         }
1218         free(s);
1219 }
1220
1221 int pfd[2];
1222 int sshargc;
1223 char **sshargv;
1224
1225 void
1226 startssh(void *)
1227 {
1228         char *f;
1229
1230         close(pfd[0]);
1231         dup(pfd[1], 0);
1232         dup(pfd[1], 1);
1233         close(pfd[1]);
1234         if(strncmp(sshargv[0], "./", 2) != 0)
1235                 f = smprint("/bin/%s", sshargv[0]);
1236         else
1237                 f = sshargv[0];
1238         procexec(nil, f, sshargv);
1239         sysfatal("exec: %r");
1240 }
1241
1242 void
1243 usage(void)
1244 {
1245         static char *common = "[-abdRUG] [-s service] [-m mtpt] [-u uidfile] [-g gidfile]";
1246         fprint(2, "usage: %s %s [-- ssh-options] host\n", argv0, common);
1247         fprint(2, "       %s %s -c cmdline\n", argv0, common);
1248         fprint(2, "       %s %s -p\n", argv0, common);
1249         exits("usage");
1250 }
1251
1252 void
1253 threadmain(int argc, char **argv)
1254 {
1255         u32int x;
1256         static int pflag, cflag;
1257         static char *svc, *mtpt;
1258         static int mflag;
1259         static char *uidfile, *gidfile;
1260         
1261         fmtinstall(L'Σ', fxpfmt);
1262         
1263         mtpt = "/n/ssh";
1264         uidfile = "/etc/passwd";
1265         gidfile = "/etc/group";
1266         ARGBEGIN{
1267         case 'R': readonly++; break;
1268         case 'd': debug++; chatty9p++; break;
1269         case 'p': pflag++; break;
1270         case 'c': cflag++; break;
1271         case 's': svc = EARGF(usage()); break;
1272         case 'a': mflag |= MAFTER; break;
1273         case 'b': mflag |= MBEFORE; break;
1274         case 'm': mtpt = EARGF(usage()); break;
1275         case 'u': uidfile = EARGF(usage()); break;
1276         case 'U': uidfile = nil; break;
1277         case 'g': gidfile = EARGF(usage()); break;
1278         case 'G': gidfile = nil; break;
1279         default: usage();
1280         }ARGEND;
1281         
1282         if(readonly){
1283                 sshfssrv.create = nil;
1284                 sshfssrv.write = nil;
1285                 sshfssrv.wstat = nil;
1286                 sshfssrv.remove = nil;
1287         }
1288         
1289         if(pflag){
1290                 rdfd = 0;
1291                 wrfd = 1;
1292         }else{
1293                 if(argc == 0) usage();
1294                 if(cflag){
1295                         sshargc = argc;
1296                         sshargv = argv;
1297                 }else{
1298                         sshargc = argc + 2;
1299                         sshargv = emalloc9p(sizeof(char *) * (sshargc + 1));
1300                         sshargv[0] = "ssh";
1301                         memcpy(sshargv + 1, argv, argc * sizeof(char *));
1302                         sshargv[sshargc - 1] = "#sftp";
1303                 }
1304                 pipe(pfd);
1305                 rdfd = wrfd = pfd[0];
1306                 procrfork(startssh, nil, mainstacksize, RFFDG|RFNOTEG);
1307                 close(pfd[1]);
1308         }
1309
1310         sendpkt("bu", SSH_FXP_INIT, VERSION);
1311         if(recvpkt() != SSH_FXP_VERSION || unpack(rxpkt, rxlen, "_u", &x) < 0) sysfatal("received garbage");
1312         if(x != VERSION) sysfatal("server replied with incompatible version %d", x);
1313         
1314         passwdparse(uidtab, readfile(uidfile));
1315         passwdparse(gidtab, readfile(gidfile));
1316         
1317         procrfork(sendproc, 0, mainstacksize, RFNOTEG);
1318         procrfork(recvproc, 0, mainstacksize, RFNOTEG);
1319         threadpostmountsrv(&sshfssrv, svc, mtpt, MCREATE | mflag);
1320 }