]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/httpd/httpd.c
Import sources from 2011-03-30 iso image
[plan9front.git] / sys / src / cmd / ip / httpd / httpd.c
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <mp.h>
5 #include <libsec.h>
6 #include "httpd.h"
7 #include "httpsrv.h"
8
9 typedef struct Strings          Strings;
10
11 struct Strings
12 {
13         char    *s1;
14         char    *s2;
15 };
16
17 char    *netdir;
18 char    *HTTPLOG = "httpd/log";
19
20 static  char            netdirb[256];
21 static  char            *namespace;
22
23 static  void            becomenone(char*);
24 static  char            *csquery(char*, char*, char*);
25 static  void            dolisten(char*);
26 static  int             doreq(HConnect*);
27 static  int             send(HConnect*);
28 static  Strings         stripmagic(HConnect*, char*);
29 static  char*           stripprefix(char*, char*);
30 static  char*           sysdom(void);
31 static  int             notfound(HConnect *c, char *url);
32
33 uchar *certificate;
34 int certlen;
35 PEMChain *certchain;    
36
37 void
38 usage(void)
39 {
40         fprint(2, "usage: httpd [-c certificate] [-C CAchain] [-a srvaddress] "
41                 "[-d domain] [-n namespace] [-w webroot]\n");
42         exits("usage");
43 }
44
45 void
46 main(int argc, char **argv)
47 {
48         char *address;
49
50         namespace = nil;
51         address = nil;
52         hmydomain = nil;
53         netdir = "/net";
54         fmtinstall('D', hdatefmt);
55         fmtinstall('H', httpfmt);
56         fmtinstall('U', hurlfmt);
57         ARGBEGIN{
58         case 'c':
59                 certificate = readcert(EARGF(usage()), &certlen);
60                 if(certificate == nil)
61                         sysfatal("reading certificate: %r");
62                 break;
63         case 'C':
64                 certchain = readcertchain(EARGF(usage()));
65                 if (certchain == nil)
66                         sysfatal("reading certificate chain: %r");
67                 break;
68         case 'n':
69                 namespace = EARGF(usage());
70                 break;
71         case 'a':
72                 address = EARGF(usage());
73                 break;
74         case 'd':
75                 hmydomain = EARGF(usage());
76                 break;
77         case 'w':
78                 webroot = EARGF(usage());
79                 break;
80         default:
81                 usage();
82                 break;
83         }ARGEND
84
85         if(argc)
86                 usage();
87
88         if(namespace == nil)
89                 namespace = "/lib/namespace.httpd";
90         if(address == nil)
91                 address = "*";
92         if(webroot == nil)
93                 webroot = "/usr/web";
94         else{
95                 cleanname(webroot);
96                 if(webroot[0] != '/')
97                         webroot = "/usr/web";
98         }
99
100         switch(rfork(RFNOTEG|RFPROC|RFFDG|RFNAMEG)) {
101         case -1:
102                 sysfatal("fork");
103         case 0:
104                 break;
105         default:
106                 exits(nil);
107         }
108
109         /*
110          * open all files we might need before castrating namespace
111          */
112         time(nil);
113         if(hmydomain == nil)
114                 hmydomain = sysdom();
115         syslog(0, HTTPLOG, nil);
116         logall[0] = open("/sys/log/httpd/0", OWRITE);
117         logall[1] = open("/sys/log/httpd/1", OWRITE);
118         logall[2] = open("/sys/log/httpd/clf", OWRITE);
119         redirectinit();
120         contentinit();
121         urlinit();
122         statsinit();
123
124         becomenone(namespace);
125         dolisten(netmkaddr(address, "tcp", certificate == nil ? "http" : "https"));
126         exits(nil);
127 }
128
129 static void
130 becomenone(char *namespace)
131 {
132         int fd;
133
134         fd = open("#c/user", OWRITE);
135         if(fd < 0 || write(fd, "none", strlen("none")) < 0)
136                 sysfatal("can't become none");
137         close(fd);
138         if(newns("none", nil) < 0)
139                 sysfatal("can't build normal namespace");
140         if(addns("none", namespace) < 0)
141                 sysfatal("can't build httpd namespace");
142 }
143
144 static HConnect*
145 mkconnect(char *scheme, char *port)
146 {
147         HConnect *c;
148
149         c = ezalloc(sizeof(HConnect));
150         c->hpos = c->header;
151         c->hstop = c->header;
152         c->replog = writelog;
153         c->scheme = scheme;
154         c->port = port;
155         return c;
156 }
157
158 static HSPriv*
159 mkhspriv(void)
160 {
161         HSPriv *p;
162
163         p = ezalloc(sizeof(HSPriv));
164         return p;
165 }
166
167 static void
168 dolisten(char *address)
169 {
170         HSPriv *hp;
171         HConnect *c;
172         NetConnInfo *nci;
173         char ndir[NETPATHLEN], dir[NETPATHLEN], *p, *scheme;
174         int ctl, nctl, data, t, ok, spotchk;
175         TLSconn conn;
176
177         spotchk = 0;
178         syslog(0, HTTPLOG, "httpd starting");
179         ctl = announce(address, dir);
180         if(ctl < 0){
181                 syslog(0, HTTPLOG, "can't announce on %s: %r", address);
182                 return;
183         }
184         strcpy(netdirb, dir);
185         p = nil;
186         if(netdir[0] == '/'){
187                 p = strchr(netdirb+1, '/');
188                 if(p != nil)
189                         *p = '\0';
190         }
191         if(p == nil)
192                 strcpy(netdirb, "/net");
193         netdir = netdirb;
194
195         for(;;){
196
197                 /*
198                  *  wait for a call (or an error)
199                  */
200                 nctl = listen(dir, ndir);
201                 if(nctl < 0){
202                         syslog(0, HTTPLOG, "can't listen on %s: %r", address);
203                         syslog(0, HTTPLOG, "ctls = %d", ctl);
204                         return;
205                 }
206
207                 /*
208                  *  start a process for the service
209                  */
210                 switch(rfork(RFFDG|RFPROC|RFNOWAIT|RFNAMEG)){
211                 case -1:
212                         close(nctl);
213                         continue;
214                 case 0:
215                         /*
216                          *  see if we know the service requested
217                          */
218                         data = accept(ctl, ndir);
219                         if(data >= 0 && certificate != nil){
220                                 memset(&conn, 0, sizeof(conn));
221                                 conn.cert = certificate;
222                                 conn.certlen = certlen;
223                                 if (certchain != nil)
224                                         conn.chain = certchain;
225                                 data = tlsServer(data, &conn);
226                                 scheme = "https";
227                         }else
228                                 scheme = "http";
229                         if(data < 0){
230                                 syslog(0, HTTPLOG, "can't open %s/data: %r", ndir);
231                                 exits(nil);
232                         }
233                         dup(data, 0);
234                         dup(data, 1);
235                         dup(data, 2);
236                         close(data);
237                         close(ctl);
238                         close(nctl);
239
240                         nci = getnetconninfo(ndir, -1);
241                         c = mkconnect(scheme, nci->lserv);
242                         hp = mkhspriv();
243                         hp->remotesys = nci->rsys;
244                         hp->remoteserv = nci->rserv;
245                         c->private = hp;
246
247                         hinit(&c->hin, 0, Hread);
248                         hinit(&c->hout, 1, Hwrite);
249
250                         /*
251                          * serve requests until a magic request.
252                          * later requests have to come quickly.
253                          * only works for http/1.1 or later.
254                          */
255                         for(t = 15*60*1000; ; t = 15*1000){
256                                 if(hparsereq(c, t) <= 0)
257                                         exits(nil);
258                                 ok = doreq(c);
259
260                                 hflush(&c->hout);
261
262                                 if(c->head.closeit || ok < 0)
263                                         exits(nil);
264
265                                 hreqcleanup(c);
266                         }
267                         /* not reached */
268
269                 default:
270                         close(nctl);
271                         break;
272                 }
273
274                 if(++spotchk > 50){
275                         spotchk = 0;
276                         redirectinit();
277                         contentinit();
278                         urlinit();
279                         statsinit();
280                 }
281         }
282 }
283
284 static int
285 doreq(HConnect *c)
286 {
287         HSPriv *hp;
288         Strings ss;
289         char *magic, *uri, *newuri, *origuri, *newpath, *hb;
290         char virtualhost[100], logfd0[10], logfd1[10], vers[16];
291         int n, nredirect;
292         uint flags;
293
294         /*
295          * munge uri for magic
296          */
297         uri = c->req.uri;
298         nredirect = 0;
299         werrstr("");
300 top:
301         if(++nredirect > 10){
302                 if(hparseheaders(c, 15*60*1000) < 0)
303                         exits("failed");
304                 werrstr("redirection loop");
305                 return hfail(c, HNotFound, uri);
306         }
307         ss = stripmagic(c, uri);
308         uri = ss.s1;
309         origuri = uri;
310         magic = ss.s2;
311         if(magic)
312                 goto magic;
313
314         /*
315          * Apply redirects.  Do this before reading headers
316          * (if possible) so that we can redirect to magic invisibly.
317          */
318         flags = 0;
319         if(origuri[0]=='/' && origuri[1]=='~'){
320                 n = strlen(origuri) + 4 + UTFmax;
321                 newpath = halloc(c, n);
322                 snprint(newpath, n, "/who/%s", origuri+2);
323                 c->req.uri = newpath;
324                 newuri = newpath;
325         }else if(origuri[0]=='/' && origuri[1]==0){
326                 /* can't redirect / until we read the headers below */
327                 newuri = nil;
328         }else
329                 newuri = redirect(c, origuri, &flags);
330
331         if(newuri != nil){
332                 if(flags & Redirsilent) {
333                         c->req.uri = uri = newuri;
334                         logit(c, "%s: silent replacement %s", origuri, uri);
335                         goto top;
336                 }
337                 if(hparseheaders(c, 15*60*1000) < 0)
338                         exits("failed");
339                 if(flags & Redirperm) {
340                         logit(c, "%s: permanently moved to %s", origuri, newuri);
341                         return hmoved(c, newuri);
342                 } else if (flags & (Redironly | Redirsubord))
343                         logit(c, "%s: top-level or many-to-one replacement %s",
344                                 origuri, uri);
345
346                 /*
347                  * try temporary redirect instead of permanent
348                  */
349                 if (http11(c))
350                         return hredirected(c, "307 Temporary Redirect", newuri);
351                 else
352                         return hredirected(c, "302 Temporary Redirect", newuri);
353         }
354
355         /*
356          * for magic we exec a new program and serve no more requests
357          */
358 magic:
359         if(magic != nil && strcmp(magic, "httpd") != 0){
360                 snprint(c->xferbuf, HBufSize, "/bin/ip/httpd/%s", magic);
361                 snprint(logfd0, sizeof(logfd0), "%d", logall[0]);
362                 snprint(logfd1, sizeof(logfd1), "%d", logall[1]);
363                 snprint(vers, sizeof(vers), "HTTP/%d.%d", c->req.vermaj, c->req.vermin);
364                 hb = hunload(&c->hin);
365                 if(hb == nil){
366                         hfail(c, HInternal);
367                         return -1;
368                 }
369                 hp = c->private;
370                 execl(c->xferbuf, magic, "-d", hmydomain, "-w", webroot,
371                         "-s", c->scheme, "-p", c->port,
372                         "-r", hp->remotesys, "-N", netdir, "-b", hb,
373                         "-L", logfd0, logfd1, "-R", c->header,
374                         c->req.meth, vers, uri, c->req.search, nil);
375                 logit(c, "no magic %s uri %s", magic, uri);
376                 hfail(c, HNotFound, uri);
377                 return -1;
378         }
379
380         /*
381          * normal case is just file transfer
382          */
383         if(hparseheaders(c, 15*60*1000) < 0)
384                 exits("failed");
385         if(origuri[0] == '/' && origuri[1] == 0){       
386                 snprint(virtualhost, sizeof virtualhost, "http://%s/", c->head.host);
387                 newuri = redirect(c, virtualhost, nil);
388                 if(newuri == nil)
389                         newuri = redirect(c, origuri, nil);
390                 if(newuri)
391                         return hmoved(c, newuri);
392         }
393         if(!http11(c) && !c->head.persist)
394                 c->head.closeit = 1;
395         return send(c);
396 }
397
398 static int
399 send(HConnect *c)
400 {
401         Dir *dir;
402         char *w, *w2, *p, *masque;
403         int fd, fd1, n, force301, ok;
404
405 /*
406         if(c->req.search)
407                 return hfail(c, HNoSearch, c->req.uri);
408  */
409         if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0)
410                 return hunallowed(c, "GET, HEAD");
411         if(c->head.expectother || c->head.expectcont)
412                 return hfail(c, HExpectFail);
413
414         masque = masquerade(c->head.host);
415
416         /*
417          * check for directory/file mismatch with trailing /,
418          * and send any redirections.
419          */
420         n = strlen(webroot) + strlen(masque) + strlen(c->req.uri) +
421                 STRLEN("/index.html") + STRLEN("/.httplogin") + 1;
422         w = halloc(c, n);
423         strcpy(w, webroot);
424         strcat(w, masque);
425         strcat(w, c->req.uri);
426
427         /*
428          *  favicon can be overridden by hostname.ico
429          */
430         if(strcmp(c->req.uri, "/favicon.ico") == 0){
431                 w2 = halloc(c, n+strlen(c->head.host)+2);
432                 strcpy(w2, webroot);
433                 strcat(w2, masque);
434                 strcat(w2, "/");
435                 strcat(w2, c->head.host);
436                 strcat(w2, ".ico");
437                 if(access(w2, AREAD)==0)
438                         w = w2;
439         }
440
441         /*
442          * don't show the contents of .httplogin
443          */
444         n = strlen(w);
445         if(strcmp(w+n-STRLEN(".httplogin"), ".httplogin") == 0)
446                 return notfound(c, c->req.uri);
447
448         fd = open(w, OREAD);
449         if(fd < 0 && strlen(masque)>0 && strncmp(c->req.uri, masque, strlen(masque)) == 0){
450                 // may be a URI from before virtual hosts;  try again without masque
451                 strcpy(w, webroot);
452                 strcat(w, c->req.uri);
453                 fd = open(w, OREAD);
454         }
455         if(fd < 0)
456                 return notfound(c, c->req.uri);
457         dir = dirfstat(fd);
458         if(dir == nil){
459                 close(fd);
460                 return hfail(c, HInternal);
461         }
462         p = strchr(w, '\0');
463         if(dir->mode & DMDIR){
464                 free(dir);
465                 if(p > w && p[-1] == '/'){
466                         strcat(w, "index.html");
467                         force301 = 0;
468                 }else{
469                         strcat(w, "/index.html");
470                         force301 = 1;
471                 }
472                 fd1 = open(w, OREAD);
473                 if(fd1 < 0){
474                         close(fd);
475                         return notfound(c, c->req.uri);
476                 }
477                 c->req.uri = w + strlen(webroot) + strlen(masque);
478                 if(force301 && c->req.vermaj){
479                         close(fd);
480                         close(fd1);
481                         return hmoved(c, c->req.uri);
482                 }
483                 close(fd);
484                 fd = fd1;
485                 dir = dirfstat(fd);
486                 if(dir == nil){
487                         close(fd);
488                         return hfail(c, HInternal);
489                 }
490         }else if(p > w && p[-1] == '/'){
491                 free(dir);
492                 close(fd);
493                 *strrchr(c->req.uri, '/') = '\0';
494                 return hmoved(c, c->req.uri);
495         }
496
497         ok = authorize(c, w);
498         if(ok <= 0){
499                 free(dir);
500                 close(fd);
501                 return ok;
502         }
503
504         return sendfd(c, fd, dir, nil, nil);
505 }
506
507 static Strings
508 stripmagic(HConnect *hc, char *uri)
509 {
510         Strings ss;
511         char *newuri, *prog, *s;
512
513         prog = stripprefix("/magic/", uri);
514         if(prog == nil){
515                 ss.s1 = uri;
516                 ss.s2 = nil;
517                 return ss;
518         }
519
520         s = strchr(prog, '/');
521         if(s == nil)
522                 newuri = "";
523         else{
524                 newuri = hstrdup(hc, s);
525                 *s = 0;
526                 s = strrchr(s, '/');
527                 if(s != nil && s[1] == 0)
528                         *s = 0;
529         }
530         ss.s1 = newuri;
531         ss.s2 = prog;
532         return ss;
533 }
534
535 static char*
536 stripprefix(char *pre, char *str)
537 {
538         while(*pre)
539                 if(*str++ != *pre++)
540                         return nil;
541         return str;
542 }
543
544 /*
545  * couldn't open a file
546  * figure out why and return and error message
547  */
548 static int
549 notfound(HConnect *c, char *url)
550 {
551         c->xferbuf[0] = 0;
552         rerrstr(c->xferbuf, sizeof c->xferbuf);
553         if(strstr(c->xferbuf, "file does not exist") != nil)
554                 return hfail(c, HNotFound, url);
555         if(strstr(c->xferbuf, "permission denied") != nil)
556                 return hfail(c, HUnauth, url);
557         return hfail(c, HNotFound, url);
558 }
559
560 static char*
561 sysdom(void)
562 {
563         char *dn;
564
565         dn = csquery("sys" , sysname(), "dom");
566         if(dn == nil)
567                 dn = "who cares";
568         return dn;
569 }
570
571 /*
572  *  query the connection server
573  */
574 static char*
575 csquery(char *attr, char *val, char *rattr)
576 {
577         char token[64+4];
578         char buf[256], *p, *sp;
579         int fd, n;
580
581         if(val == nil || val[0] == 0)
582                 return nil;
583         snprint(buf, sizeof(buf), "%s/cs", netdir);
584         fd = open(buf, ORDWR);
585         if(fd < 0)
586                 return nil;
587         fprint(fd, "!%s=%s", attr, val);
588         seek(fd, 0, 0);
589         snprint(token, sizeof(token), "%s=", rattr);
590         for(;;){
591                 n = read(fd, buf, sizeof(buf)-1);
592                 if(n <= 0)
593                         break;
594                 buf[n] = 0;
595                 p = strstr(buf, token);
596                 if(p != nil && (p == buf || *(p-1) == 0)){
597                         close(fd);
598                         sp = strchr(p, ' ');
599                         if(sp)
600                                 *sp = 0;
601                         p = strchr(p, '=');
602                         if(p == nil)
603                                 return nil;
604                         return estrdup(p+1);
605                 }
606         }
607         close(fd);
608         return nil;
609 }