]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/httpd/httpd.c
ip/tftpd: remove sunkernel hack
[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
176         spotchk = 0;
177         syslog(0, HTTPLOG, "httpd starting");
178         ctl = announce(address, dir);
179         if(ctl < 0){
180                 syslog(0, HTTPLOG, "can't announce on %s: %r", address);
181                 return;
182         }
183         strcpy(netdirb, dir);
184         p = nil;
185         if(netdir[0] == '/'){
186                 p = strchr(netdirb+1, '/');
187                 if(p != nil)
188                         *p = '\0';
189         }
190         if(p == nil)
191                 strcpy(netdirb, "/net");
192         netdir = netdirb;
193
194         for(;;){
195
196                 /*
197                  *  wait for a call (or an error)
198                  */
199                 nctl = listen(dir, ndir);
200                 if(nctl < 0){
201                         syslog(0, HTTPLOG, "can't listen on %s: %r", address);
202                         syslog(0, HTTPLOG, "ctls = %d", ctl);
203                         return;
204                 }
205
206                 /*
207                  *  start a process for the service
208                  */
209                 switch(rfork(RFFDG|RFPROC|RFNOWAIT|RFNAMEG)){
210                 case -1:
211                         close(nctl);
212                         continue;
213                 case 0:
214                         /*
215                          *  see if we know the service requested
216                          */
217                         data = accept(ctl, ndir);
218                         if(data >= 0 && certificate != nil){
219                                 TLSconn conn;
220
221                                 memset(&conn, 0, sizeof(conn));
222                                 conn.cert = certificate;
223                                 conn.certlen = certlen;
224                                 if (certchain != nil)
225                                         conn.chain = certchain;
226                                 data = tlsServer(data, &conn);
227                                 free(conn.cert);
228                                 free(conn.sessionID);
229                                 scheme = "https";
230                         }else
231                                 scheme = "http";
232                         if(data < 0){
233                                 syslog(0, HTTPLOG, "can't open %s/data: %r", ndir);
234                                 exits(nil);
235                         }
236                         dup(data, 0);
237                         dup(data, 1);
238                         dup(data, 2);
239                         close(data);
240                         close(ctl);
241                         close(nctl);
242
243                         nci = getnetconninfo(ndir, -1);
244                         c = mkconnect(scheme, nci->lserv);
245                         hp = mkhspriv();
246                         hp->remotesys = nci->rsys;
247                         hp->remoteserv = nci->rserv;
248                         c->private = hp;
249
250                         hinit(&c->hin, 0, Hread);
251                         hinit(&c->hout, 1, Hwrite);
252
253                         /*
254                          * serve requests until a magic request.
255                          * later requests have to come quickly.
256                          * only works for http/1.1 or later.
257                          */
258                         for(t = 15*60*1000; ; t = 15*1000){
259                                 if(hparsereq(c, t) <= 0)
260                                         exits(nil);
261                                 ok = doreq(c);
262
263                                 hflush(&c->hout);
264
265                                 if(c->head.closeit || ok < 0)
266                                         exits(nil);
267
268                                 hreqcleanup(c);
269                         }
270                         /* not reached */
271
272                 default:
273                         close(nctl);
274                         break;
275                 }
276
277                 if(++spotchk > 50){
278                         spotchk = 0;
279                         redirectinit();
280                         contentinit();
281                         urlinit();
282                         statsinit();
283                 }
284         }
285 }
286
287 static int
288 doreq(HConnect *c)
289 {
290         HSPriv *hp;
291         Strings ss;
292         char *magic, *uri, *newuri, *origuri, *newpath, *hb;
293         char virtualhost[100], logfd0[10], logfd1[10], vers[16];
294         int n, nredirect;
295         uint flags;
296
297         /*
298          * munge uri for magic
299          */
300         uri = c->req.uri;
301         nredirect = 0;
302         werrstr("");
303 top:
304         if(++nredirect > 10){
305                 if(hparseheaders(c, 15*60*1000) < 0)
306                         exits("failed");
307                 werrstr("redirection loop");
308                 return hfail(c, HNotFound, uri);
309         }
310         ss = stripmagic(c, uri);
311         uri = ss.s1;
312         origuri = uri;
313         magic = ss.s2;
314         if(magic)
315                 goto magic;
316
317         /*
318          * Apply redirects.  Do this before reading headers
319          * (if possible) so that we can redirect to magic invisibly.
320          */
321         flags = 0;
322         if(origuri[0]=='/' && origuri[1]=='~'){
323                 n = strlen(origuri) + 4 + UTFmax;
324                 newpath = halloc(c, n);
325                 snprint(newpath, n, "/who/%s", origuri+2);
326                 c->req.uri = newpath;
327                 newuri = newpath;
328         }else if(origuri[0]=='/' && origuri[1]==0){
329                 /* can't redirect / until we read the headers below */
330                 newuri = nil;
331         }else
332                 newuri = redirect(c, origuri, &flags);
333
334         if(newuri != nil){
335                 if(flags & Redirsilent) {
336                         c->req.uri = uri = newuri;
337                         logit(c, "%s: silent replacement %s", origuri, uri);
338                         goto top;
339                 }
340                 if(hparseheaders(c, 15*60*1000) < 0)
341                         exits("failed");
342                 if(flags & Redirperm) {
343                         logit(c, "%s: permanently moved to %s", origuri, newuri);
344                         return hmoved(c, newuri);
345                 } else if (flags & (Redironly | Redirsubord))
346                         logit(c, "%s: top-level or many-to-one replacement %s",
347                                 origuri, uri);
348
349                 /*
350                  * try temporary redirect instead of permanent
351                  */
352                 if (http11(c))
353                         return hredirected(c, "307 Temporary Redirect", newuri);
354                 else
355                         return hredirected(c, "302 Temporary Redirect", newuri);
356         }
357
358         /*
359          * for magic we exec a new program and serve no more requests
360          */
361 magic:
362         if(magic != nil && strcmp(magic, "httpd") != 0){
363                 snprint(c->xferbuf, HBufSize, "/bin/ip/httpd/%s", magic);
364                 snprint(logfd0, sizeof(logfd0), "%d", logall[0]);
365                 snprint(logfd1, sizeof(logfd1), "%d", logall[1]);
366                 snprint(vers, sizeof(vers), "HTTP/%d.%d", c->req.vermaj, c->req.vermin);
367                 hb = hunload(&c->hin);
368                 if(hb == nil){
369                         hfail(c, HInternal);
370                         return -1;
371                 }
372                 hp = c->private;
373                 execl(c->xferbuf, magic, "-d", hmydomain, "-w", webroot,
374                         "-s", c->scheme, "-p", c->port,
375                         "-r", hp->remotesys, "-N", netdir, "-b", hb,
376                         "-L", logfd0, logfd1, "-R", c->header,
377                         c->req.meth, vers, uri, c->req.search, nil);
378                 logit(c, "no magic %s uri %s", magic, uri);
379                 hfail(c, HNotFound, uri);
380                 return -1;
381         }
382
383         /*
384          * normal case is just file transfer
385          */
386         if(hparseheaders(c, 15*60*1000) < 0)
387                 exits("failed");
388         if(origuri[0] == '/' && origuri[1] == 0){       
389                 snprint(virtualhost, sizeof virtualhost, "http://%s/", c->head.host);
390                 newuri = redirect(c, virtualhost, nil);
391                 if(newuri == nil)
392                         newuri = redirect(c, origuri, nil);
393                 if(newuri)
394                         return hmoved(c, newuri);
395         }
396         if(!http11(c) && !c->head.persist)
397                 c->head.closeit = 1;
398         return send(c);
399 }
400
401 static int
402 send(HConnect *c)
403 {
404         Dir *dir;
405         char *w, *w2, *p, *masque;
406         int fd, fd1, n, force301, ok;
407
408 /*
409         if(c->req.search)
410                 return hfail(c, HNoSearch, c->req.uri);
411  */
412         if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0)
413                 return hunallowed(c, "GET, HEAD");
414         if(c->head.expectother || c->head.expectcont)
415                 return hfail(c, HExpectFail);
416
417         masque = masquerade(c->head.host);
418
419         /*
420          * check for directory/file mismatch with trailing /,
421          * and send any redirections.
422          */
423         n = strlen(webroot) + strlen(masque) + strlen(c->req.uri) +
424                 STRLEN("/index.html") + STRLEN("/.httplogin") + 1;
425         w = halloc(c, n);
426         strcpy(w, webroot);
427         strcat(w, masque);
428         strcat(w, c->req.uri);
429
430         /*
431          *  favicon can be overridden by hostname.ico
432          */
433         if(strcmp(c->req.uri, "/favicon.ico") == 0){
434                 w2 = halloc(c, n+strlen(c->head.host)+2);
435                 strcpy(w2, webroot);
436                 strcat(w2, masque);
437                 strcat(w2, "/");
438                 strcat(w2, c->head.host);
439                 strcat(w2, ".ico");
440                 if(access(w2, AREAD)==0)
441                         w = w2;
442         }
443
444         /*
445          * don't show the contents of .httplogin
446          */
447         n = strlen(w);
448         if(strcmp(w+n-STRLEN(".httplogin"), ".httplogin") == 0)
449                 return notfound(c, c->req.uri);
450
451         fd = open(w, OREAD);
452         if(fd < 0 && strlen(masque)>0 && strncmp(c->req.uri, masque, strlen(masque)) == 0){
453                 // may be a URI from before virtual hosts;  try again without masque
454                 strcpy(w, webroot);
455                 strcat(w, c->req.uri);
456                 fd = open(w, OREAD);
457         }
458         if(fd < 0)
459                 return notfound(c, c->req.uri);
460         dir = dirfstat(fd);
461         if(dir == nil){
462                 close(fd);
463                 return hfail(c, HInternal);
464         }
465         p = strchr(w, '\0');
466         if(dir->mode & DMDIR){
467                 free(dir);
468                 if(p > w && p[-1] == '/'){
469                         strcat(w, "index.html");
470                         force301 = 0;
471                 }else{
472                         strcat(w, "/index.html");
473                         force301 = 1;
474                 }
475                 fd1 = open(w, OREAD);
476                 if(fd1 < 0){
477                         close(fd);
478                         return notfound(c, c->req.uri);
479                 }
480                 c->req.uri = w + strlen(webroot) + strlen(masque);
481                 if(force301 && c->req.vermaj){
482                         close(fd);
483                         close(fd1);
484                         return hmoved(c, c->req.uri);
485                 }
486                 close(fd);
487                 fd = fd1;
488                 dir = dirfstat(fd);
489                 if(dir == nil){
490                         close(fd);
491                         return hfail(c, HInternal);
492                 }
493         }else if(p > w && p[-1] == '/'){
494                 free(dir);
495                 close(fd);
496                 *strrchr(c->req.uri, '/') = '\0';
497                 return hmoved(c, c->req.uri);
498         }
499
500         ok = authorize(c, w);
501         if(ok <= 0){
502                 free(dir);
503                 close(fd);
504                 return ok;
505         }
506
507         return sendfd(c, fd, dir, nil, nil);
508 }
509
510 static Strings
511 stripmagic(HConnect *hc, char *uri)
512 {
513         Strings ss;
514         char *newuri, *prog, *s;
515
516         prog = stripprefix("/magic/", uri);
517         if(prog == nil){
518                 ss.s1 = uri;
519                 ss.s2 = nil;
520                 return ss;
521         }
522
523         s = strchr(prog, '/');
524         if(s == nil)
525                 newuri = "";
526         else{
527                 newuri = hstrdup(hc, s);
528                 *s = 0;
529                 s = strrchr(s, '/');
530                 if(s != nil && s[1] == 0)
531                         *s = 0;
532         }
533         ss.s1 = newuri;
534         ss.s2 = prog;
535         return ss;
536 }
537
538 static char*
539 stripprefix(char *pre, char *str)
540 {
541         while(*pre)
542                 if(*str++ != *pre++)
543                         return nil;
544         return str;
545 }
546
547 /*
548  * couldn't open a file
549  * figure out why and return and error message
550  */
551 static int
552 notfound(HConnect *c, char *url)
553 {
554         c->xferbuf[0] = 0;
555         rerrstr(c->xferbuf, sizeof c->xferbuf);
556         if(strstr(c->xferbuf, "file does not exist") != nil)
557                 return hfail(c, HNotFound, url);
558         if(strstr(c->xferbuf, "permission denied") != nil)
559                 return hfail(c, HUnauth, url);
560         return hfail(c, HNotFound, url);
561 }
562
563 static char*
564 sysdom(void)
565 {
566         char *dn;
567
568         dn = csquery("sys" , sysname(), "dom");
569         if(dn == nil)
570                 dn = "who cares";
571         return dn;
572 }
573
574 /*
575  *  query the connection server
576  */
577 static char*
578 csquery(char *attr, char *val, char *rattr)
579 {
580         char token[64+4];
581         char buf[256], *p, *sp;
582         int fd, n;
583
584         if(val == nil || val[0] == 0)
585                 return nil;
586         snprint(buf, sizeof(buf), "%s/cs", netdir);
587         fd = open(buf, ORDWR);
588         if(fd < 0)
589                 return nil;
590         fprint(fd, "!%s=%s", attr, val);
591         seek(fd, 0, 0);
592         snprint(token, sizeof(token), "%s=", rattr);
593         for(;;){
594                 n = read(fd, buf, sizeof(buf)-1);
595                 if(n <= 0)
596                         break;
597                 buf[n] = 0;
598                 p = strstr(buf, token);
599                 if(p != nil && (p == buf || *(p-1) == 0)){
600                         close(fd);
601                         sp = strchr(p, ' ');
602                         if(sp)
603                                 *sp = 0;
604                         p = strchr(p, '=');
605                         if(p == nil)
606                                 return nil;
607                         return estrdup(p+1);
608                 }
609         }
610         close(fd);
611         return nil;
612 }