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