]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/webfs/fs.c
webfs: preauth support
[plan9front.git] / sys / src / cmd / webfs / fs.c
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <fcall.h>
5 #include <thread.h>
6 #include <9p.h>
7
8 #include "dat.h"
9 #include "fns.h"
10
11 typedef struct Webfid Webfid;
12 typedef struct Client Client;
13
14 struct Client
15 {
16         Ref;
17
18         char    request[16];
19         Url     *baseurl;
20         Url     *url;
21         Key     *hdr;
22
23         int     obody;  /* body opend */
24         int     cbody;  /* body closed */
25         Buq     *qbody;
26 };
27
28 struct Webfid
29 {
30         int     level;
31
32         Client  *client;
33         Key     *key;   /* copy for Qheader */
34         Buq     *buq;   /* reference for Qbody, Qpost */
35 };
36
37 enum {
38         Qroot,
39                 Qrctl,
40                 Qclone,
41                 Qclient,
42                         Qctl,
43                         Qbody,
44                         Qpost,
45                         Qparsed,
46                                 Qurl,
47                                 Qurlschm,
48                                 Qurluser,
49                                 Qurlpass,
50                                 Qurlhost,
51                                 Qurlport,
52                                 Qurlpath,
53                                 Qurlqwry,
54                                 Qurlfrag,
55                         Qheader,
56 };
57
58 static char *nametab[] = {
59         "/",
60                 "ctl",
61                 "clone",
62                 nil,
63                         "ctl",
64                         "body",
65                         "postbody",
66                         "parsed",
67                                 "url",
68                                 "scheme",
69                                 "user",
70                                 "passwd",
71                                 "host",
72                                 "port",
73                                 "path",
74                                 "query",
75                                 "fragment",
76                         nil,
77 };
78
79 static long time0;
80 static char *user;
81 static char *agent;
82 static Client client[64];
83 static int nclient;
84
85 #define CLIENTID(c)     (((Client*)(c)) - client)
86
87 Client*
88 newclient(void)
89 {
90         Client *cl;
91         int i;
92
93         for(i = 0; i < nclient; i++)
94                 if(client[i].ref == 0)
95                         break;
96         if(i >= nelem(client))
97                 return nil;
98         if(i == nclient)
99                 nclient++;
100         cl = &client[i];
101         incref(cl);
102
103         cl->request[0] = 0;
104         cl->baseurl = nil;
105         cl->url = nil;
106         cl->hdr = nil;
107         cl->qbody = nil;
108         
109         return cl;
110 }
111
112 void
113 freeclient(Client *cl)
114 {
115         Key *k;
116
117         if(cl == nil || decref(cl))
118                 return;
119
120         buclose(cl->qbody, 0);
121         bufree(cl->qbody);
122
123         while(k = cl->hdr){
124                 cl->hdr = k->next;
125                 free(k);
126         }
127
128         freeurl(cl->url);
129         freeurl(cl->baseurl);
130
131         memset(cl, 0, sizeof(*cl));
132 }
133
134 static Url*
135 clienturl(Client *cl)
136 {
137         static Url nullurl;
138
139         if(cl->qbody && cl->qbody->url)
140                 return cl->qbody->url;
141         if(cl->url)
142                 return cl->url;
143         return &nullurl;
144 }
145
146 static void*
147 wfaux(Webfid *f)
148 {
149         if(f->level < Qclient)
150                 return nil;
151         else if(f->level < Qurl)
152                 return f->client;
153         else if(f->level < Qheader)
154                 return clienturl(f->client);
155         else
156                 return f->key;
157 }
158
159 static void
160 fsmkqid(Qid *q, int level, void *aux)
161 {
162         q->type = 0;
163         q->vers = 0;
164         switch(level){
165         case Qroot:
166         case Qparsed:
167         case Qclient:
168                 q->type = QTDIR;
169         default:
170                 q->path = (level<<24) | (((ulong)aux ^ time0) & 0x00ffffff);
171         }
172 }
173
174 static char*
175 fshdrname(char *s)
176 {
177         char *k, *w;
178
179         for(k=w=s; *k; k++)
180                 if(isalnum(*k))
181                         *w++ = tolower(*k);
182         *w = 0;
183         return s;
184 }
185
186 static int
187 urlstr(char *buf, int nbuf, Url *u, int level)
188 {
189         char *s;
190
191         if(level == Qurl)
192                 return snprint(buf, nbuf, "%U", u);
193         if(level == Qurlpath)
194                 return snprint(buf, nbuf, "%s", Upath(u));
195         if((s = (&u->scheme)[level - Qurlschm]) == nil){
196                 buf[0] = 0;
197                 return 0;
198         }
199         return snprint(buf, nbuf, "%s", s);
200 }
201
202
203 static void
204 fsmkdir(Dir *d, int level, void *aux)
205 {
206         char buf[1024];
207
208         memset(d, 0, sizeof(*d));
209         fsmkqid(&d->qid, level, aux);
210         d->mode = 0444;
211         d->atime = d->mtime = time0;
212         d->uid = estrdup(user);
213         d->gid = estrdup(user);
214         d->muid = estrdup(user);
215         if(d->qid.type & QTDIR)
216                 d->mode |= DMDIR | 0111;
217         switch(level){
218         case Qheader:
219                 d->name = fshdrname(estrdup(((Key*)aux)->key));
220                 d->length = strlen(((Key*)aux)->val);
221                 break;
222         case Qclient:
223                 snprint(buf, sizeof(buf), "%ld", CLIENTID(aux));
224                 d->name = estrdup(buf);
225                 break;
226         case Qctl:
227         case Qrctl:
228         case Qclone:
229                 d->mode = 0666;
230                 if(0){
231         case Qpost:
232                 d->mode = 0222;
233                 }
234         default:
235                 d->name = estrdup(nametab[level]);
236                 if(level >= Qurl && level <= Qurlfrag)
237                         d->length = urlstr(buf, sizeof(buf), (Url*)aux, level);
238         }
239 }
240
241 static void
242 fsattach(Req *r)
243 {
244         Webfid *f;
245
246         if(r->ifcall.aname && r->ifcall.aname[0]){
247                 respond(r, "invalid attach specifier");
248                 return;
249         }
250         f = emalloc(sizeof(*f));
251         f->level = Qroot;
252         fsmkqid(&r->fid->qid, f->level, wfaux(f));
253         r->ofcall.qid = r->fid->qid;
254         r->fid->aux = f;
255         respond(r, nil);
256 }
257
258 static void
259 fsstat(Req *r)
260 {
261         Webfid *f;
262
263         f = r->fid->aux;
264         fsmkdir(&r->d, f->level, wfaux(f));
265         respond(r, nil);
266 }
267
268 static char*
269 fswalk1(Fid *fid, char *name, Qid *qid)
270 {
271         Webfid *f;
272         int i, j;
273
274         if(!(fid->qid.type&QTDIR))
275                 return "walk in non-directory";
276
277         f = fid->aux;
278         if(strcmp(name, "..") == 0){
279                 switch(f->level){
280                 case Qroot:
281                         break;
282                 case Qclient:
283                         freeclient(f->client);
284                         f->client = nil;
285                         break;
286                 default:
287                         if(f->level > Qparsed)
288                                 f->level = Qparsed;
289                         else
290                                 f->level = Qclient;
291                 }
292         } else {
293                 for(i=f->level+1; i < nelem(nametab); i++){
294                         if(nametab[i]){
295                                 if(strcmp(name, nametab[i]) == 0)
296                                         break;
297                                 if(i == Qbody && strncmp(name, "body.", 5) == 0)
298                                         break;
299                         }
300                         if(i == Qclient){
301                                 j = atoi(name);
302                                 if(j >= 0 && j < nclient){
303                                         f->client = &client[j];
304                                         incref(f->client);
305                                         break;
306                                 }
307                         }
308                         if(i == Qheader && f->client && f->client->qbody){
309                                 char buf[128];
310                                 Key *k;
311
312                                 for(k = f->client->qbody->hdr; k; k = k->next){
313                                         nstrcpy(buf, k->key, sizeof(buf));
314                                         if(!strcmp(name, fshdrname(buf)))
315                                                 break;
316                                 }
317                                 if(k != nil){
318                                         /* need to copy as key is owned by qbody wich might go away */
319                                         f->key = addkey(0, k->key, k->val);
320                                         break;
321                                 }
322                         }
323                 }
324                 if(i >= nelem(nametab))
325                         return "directory entry not found";
326                 f->level = i;
327         }
328         fsmkqid(qid, f->level, wfaux(f));
329         fid->qid = *qid;
330         return nil;
331 }
332
333 static char*
334 fsclone(Fid *oldfid, Fid *newfid)
335 {
336         Webfid *f, *o;
337
338         o = oldfid->aux;
339         if(o == nil || o->key || o->buq)
340                 return "bad fid";
341         f = emalloc(sizeof(*f));
342         memmove(f, o, sizeof(*f));
343         if(f->client)
344                 incref(f->client);
345         newfid->aux = f;
346         return nil;
347 }
348
349 static void
350 fsopen(Req *r)
351 {
352         Webfid *f;
353         Client *cl;
354
355         f = r->fid->aux;
356         cl = f->client;
357         switch(f->level){
358         case Qclone:
359                 if((cl = newclient()) == nil){
360                         respond(r, "no more clients");
361                         return;
362                 }
363                 f->level = Qctl;
364                 f->client = cl;
365                 fsmkqid(&r->fid->qid, f->level, wfaux(f));
366                 r->ofcall.qid = r->fid->qid;
367                 break;
368         case Qpost:
369                 if(cl->qbody && !cl->cbody){
370                 Inuse:
371                         respond(r, "client in use");
372                         return;
373                 }
374         case Qbody:
375                 if(cl->obody)
376                         goto Inuse;
377                 if(cl->cbody){
378                         bufree(cl->qbody);
379                         cl->qbody = nil;
380                         cl->cbody = 0;
381                 }
382                 if(cl->qbody == nil){
383                         char *m;
384
385                         if(cl->url == nil){
386                                 respond(r, "no url set");
387                                 return;
388                         }
389                         cl->qbody = bualloc(16*1024);
390                         if(f->level != Qbody){
391                                 f->buq = bualloc(64*1024);
392                                 if(!lookkey(cl->hdr, "Content-Type"))
393                                         cl->hdr = addkey(cl->hdr, "Content-Type", 
394                                                 "application/x-www-form-urlencoded");
395                                 m = "POST";
396                         } else
397                                 m = "GET";
398                         if(cl->request[0])
399                                 m = cl->request;
400
401                         /*
402                          * some sites give a 403 Forbidden if we dont include
403                          * a meaningless Accept header in the request.
404                          */
405                         if(!lookkey(cl->hdr, "Accept"))
406                                 cl->hdr = addkey(cl->hdr, "Accept", "*/*");
407
408                         if(!lookkey(cl->hdr, "Referer")){
409                                 char *r;
410                                 Url *u;
411
412                                 /*
413                                  * Referer header is often required on broken
414                                  * websites even if the spec makes them optional,
415                                  * so we make one up.
416                                  */
417                                 if(u = url("/", cl->url)){
418                                         if(r = smprint("%U", u)){
419                                                 cl->hdr = addkey(cl->hdr, "Referer", r);
420                                                 free(r);
421                                         }
422                                         freeurl(u);
423                                 }
424                         }
425
426                         if(!lookkey(cl->hdr, "Connection"))
427                                 cl->hdr = addkey(cl->hdr, "Connection", "keep-alive");
428
429                         if(agent && !lookkey(cl->hdr, "User-Agent"))
430                                 cl->hdr = addkey(cl->hdr, "User-Agent", agent);
431
432                         http(m, cl->url, cl->hdr, cl->qbody, f->buq);
433                         cl->request[0] = 0;
434                         cl->url = nil;
435                         cl->hdr = nil;
436                 }
437                 if(f->buq)
438                         break;
439                 cl->obody = 1;
440                 incref(cl->qbody);
441                 bureq(f->buq = cl->qbody, r);
442                 return;
443         }
444         respond(r, nil);
445 }
446
447 static int
448 rootgen(int i, Dir *d, void *)
449 {
450         i += Qroot+1;
451         if(i < Qclient){
452                 fsmkdir(d, i, 0);
453                 return 0;
454         }
455         i -= Qclient;
456         if(i < nclient){
457                 fsmkdir(d, Qclient, &client[i]);
458                 return 0;
459         }
460         return -1;
461 }
462
463 static int
464 clientgen(int i, Dir *d, void *aux)
465 {
466         i += Qclient+1;
467         if(i > Qparsed){
468                 Client *cl = aux;
469                 Key *k;
470
471                 i -= Qparsed+1;
472                 if(cl == nil || cl->qbody == nil)
473                         return -1;
474                 for(k = cl->qbody->hdr; i > 0 && k; i--, k = k->next)
475                         ;
476                 if(k == nil || i > 0)
477                         return -1;
478                 i = Qheader;
479                 aux = k;
480         }
481         fsmkdir(d, i, aux);
482         return 0;
483 }
484
485 static int
486 parsedgen(int i, Dir *d, void *aux)
487 {
488         i += Qparsed+1;
489         if(i > Qurlfrag)
490                 return -1;
491         fsmkdir(d, i, aux);
492         return 0;
493 }
494
495 static void
496 fsread(Req *r)
497 {
498         char buf[1024];
499         Webfid *f;
500
501         f = r->fid->aux;
502         switch(f->level){
503         case Qroot:
504                 dirread9p(r, rootgen, nil);
505                 respond(r, nil);
506                 return;
507         case Qclient:
508                 dirread9p(r, clientgen, f->client);
509                 respond(r, nil);
510                 return;
511         case Qparsed:
512                 dirread9p(r, parsedgen, clienturl(f->client));
513                 respond(r, nil);
514                 return;
515         case Qrctl:
516                 snprint(buf, sizeof(buf), "useragent %s\ntimeout %d\n", agent, timeout);
517         String:
518                 readstr(r, buf);
519                 respond(r, nil);
520                 return;
521         case Qctl:
522                 snprint(buf, sizeof(buf), "%ld\n", CLIENTID(f->client));
523                 goto String;
524         case Qheader:
525                 snprint(buf, sizeof(buf), "%s", f->key->val);
526                 goto String;
527         case Qurl:
528         case Qurlschm:
529         case Qurluser:
530         case Qurlpass:
531         case Qurlhost:
532         case Qurlport:
533         case Qurlpath:
534         case Qurlqwry:
535         case Qurlfrag:
536                 urlstr(buf, sizeof(buf), clienturl(f->client), f->level);
537                 goto String;
538         case Qbody:
539                 bureq(f->buq, r);
540                 return;
541         }
542         respond(r, "not implemented");
543 }
544
545 static char*
546 rootctl(Srv *fs, char *ctl, char *arg)
547 {
548         Url *u;
549
550         if(debug)
551                 fprint(2, "rootctl: %q %q\n", ctl, arg);
552
553         if(!strcmp(ctl, "useragent")){
554                 free(agent);
555                 if(arg && *arg)
556                         agent = estrdup(arg);
557                 else
558                         agent = nil;
559                 return nil;
560         }
561
562         if(!strcmp(ctl, "flushauth")){
563                 u = nil;
564                 if(arg && *arg)
565                         u = saneurl(url(arg, 0));
566                 flushauth(u, 0);
567                 freeurl(u);
568                 return nil;
569         }
570
571         if(!strcmp(ctl, "timeout")){
572                 if(arg && *arg)
573                         timeout = atoi(arg);
574                 else
575                         timeout = 0;
576                 if(timeout < 0)
577                         timeout = 0;
578                 return nil;
579         }
580
581         /* ppreemptive authentication only basic
582          * auth supported, ctl message of the form:
583          *    preauth url realm
584          */
585         if(!strcmp(ctl, "preauth")){
586                 char *a[3], buf[256];
587                 int rc;
588
589                 if(tokenize(arg, a, nelem(a)) != 2)
590                         return "preauth - bad field count";
591                 if((u = saneurl(url(a[0], 0))) == nil)
592                         return "preauth - malformed url";
593                 snprint(buf, sizeof(buf), "BASIC realm=\"%s\"", a[1]);
594                 srvrelease(fs);
595                 rc = authenticate(u, u, "GET", buf);
596                 srvacquire(fs);
597                 freeurl(u);
598                 if(rc == -1)
599                         return "preauth failed";
600                 return nil;
601         }
602
603         return "bad ctl message";
604 }
605
606 static char*
607 clientctl(Client *cl, char *ctl, char *arg)
608 {
609         char *p;
610         Url *u;
611         Key *k;
612
613         if(debug)
614                 fprint(2, "clientctl: %q %q\n", ctl, arg);
615
616         if(!strcmp(ctl, "url")){
617                 if((u = saneurl(url(arg, cl->baseurl))) == nil)
618                         return "bad url";
619                 freeurl(cl->url);
620                 cl->url = u;
621         }
622         else if(!strcmp(ctl, "baseurl")){
623                 if((u = url(arg, 0)) == nil)
624                         return "bad baseurl";
625                 freeurl(cl->baseurl);
626                 cl->baseurl = u;
627         }
628         else if(!strcmp(ctl, "request")){
629                 p = cl->request;
630                 nstrcpy(p, arg, sizeof(cl->request));
631                 for(; *p && isalpha(*p); p++)
632                         *p = toupper(*p);
633                 *p = 0;
634         }
635         else if(!strcmp(ctl, "headers")){
636                 while(arg && *arg){
637                         ctl = arg;
638                         while(*ctl && strchr(whitespace, *ctl))
639                                 ctl++;
640                         if(arg = strchr(ctl, '\n'))
641                                 *arg++ = 0;
642                         if(k = parsehdr(ctl)){
643                                 k->next = cl->hdr;
644                                 cl->hdr = k;
645                         }
646                 }
647         }
648         else {
649                 char buf[128], **t;
650                 static char *tab[] = {
651                         "User-Agent",
652                         "Content-Type",
653                         nil,
654                 };
655                 for(t = tab; *t; t++){
656                         nstrcpy(buf, *t, sizeof(buf));
657                         if(!strcmp(ctl, fshdrname(buf))){
658                                 cl->hdr = delkey(cl->hdr, *t);
659                                 if(arg && *arg)
660                                         cl->hdr = addkey(cl->hdr, *t, arg);
661                                 break;
662                         }
663                 }
664                 if(*t == nil)
665                         return "bad ctl message";
666         }
667         return nil;
668 }
669
670 static void
671 fswrite(Req *r)
672 {
673         int n;
674         Webfid *f;
675         char *s, *t;
676
677         f = r->fid->aux;
678         switch(f->level){
679         case Qrctl:
680         case Qctl:
681                 n = r->ofcall.count = r->ifcall.count;
682                 s = emalloc(n+1);
683                 memmove(s, r->ifcall.data, n);
684                 while(n > 0 && strchr("\r\n", s[n-1]))
685                         n--;
686                 s[n] = 0;
687                 t = s;
688                 while(*t && strchr(whitespace, *t)==0)
689                         t++;
690                 while(*t && strchr(whitespace, *t))
691                         *t++ = 0;
692                 if(f->level == Qctl)
693                         t = clientctl(f->client, s, t);
694                 else
695                         t = rootctl(r->srv, s, t);
696                 free(s);
697                 respond(r, t);
698                 return;
699         case Qpost:
700                 bureq(f->buq, r);
701                 return;
702         }
703         respond(r, "not implemented");
704 }
705
706 static void
707 fsflush(Req *r)
708 {
709         Webfid *f;
710         Req *o;
711
712         if(o = r->oldreq)
713         if(f = o->fid->aux)
714                 buflushreq(f->buq, o);
715         respond(r, nil);
716 }
717
718 static void
719 fsdestroyfid(Fid *fid)
720 {
721         Webfid *f;
722
723         if(f = fid->aux){
724                 fid->aux = nil;
725                 if(f->buq){
726                         buclose(f->buq, 0);
727                         if(f->client->qbody == f->buq){
728                                 f->client->obody = 0;
729                                 f->client->cbody = 1;
730                         }
731                         bufree(f->buq);
732                 }
733                 if(f->key)
734                         free(f->key);
735                 freeclient(f->client);
736                 free(f);
737         }
738 }
739
740 Srv fs = 
741 {
742         .attach=fsattach,
743         .stat=fsstat,
744         .walk1=fswalk1,
745         .clone=fsclone,
746         .open=fsopen,
747         .read=fsread,
748         .write=fswrite,
749         .flush=fsflush,
750         .destroyfid=fsdestroyfid,
751 };
752
753 void
754 usage(void)
755 {
756         fprint(2, "usage: %s [-D] [-A useragent] [-T timeout] [-m mtpt] [-s srv]\n", argv0);
757         exits("usage");
758 }
759
760 void
761 main(int argc, char *argv[])
762 {
763         char *srv, *mtpt, *s;
764
765         quotefmtinstall();
766         fmtinstall('U', Ufmt);
767         fmtinstall('E', Efmt);
768
769         srv = nil;
770         mtpt = "/mnt/web";
771         user = getuser();
772         time0 = time(0);
773         timeout = 10000;
774         agent = nil;
775
776         ARGBEGIN {
777         case 'D':
778                 chatty9p++;
779                 break;
780         case 'A':
781                 agent = EARGF(usage());
782                 break;
783         case 'T':
784                 timeout = atoi(EARGF(usage()));
785                 if(timeout < 0)
786                         timeout = 0;
787                 break;
788         case 'm':
789                 mtpt = EARGF(usage());
790                 break;
791         case 's':
792                 srv = EARGF(usage());
793                 break;
794         case 'd':
795                 debug++;
796                 break;
797         default:
798                 usage();
799         } ARGEND;
800
801         rfork(RFNOTEG);
802
803         if(agent == nil)
804                 agent = "hjdicks";
805         agent = estrdup(agent);
806
807         if(s = getenv("httpproxy")){
808                 proxy = saneurl(url(s, 0));
809                 free(s);
810         }
811
812         postmountsrv(&fs, srv, mtpt, MREPL);
813 }