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 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 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 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* 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* 172 mkhspriv(void) 173 { 174 HSPriv *p; 175 176 p = ezalloc(sizeof(HSPriv)); 177 return p; 178 } 179 180 static uint 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 * 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 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 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 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 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 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 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* 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 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* 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* 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