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