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 /* 402 if(c->req.search) 403 return hfail(c, HNoSearch, c->req.uri); 404 */ 405 if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0) 406 return hunallowed(c, "GET, HEAD"); 407 if(c->head.expectother || c->head.expectcont) 408 return hfail(c, HExpectFail); 409 410 masque = masquerade(c->head.host); 411 412 /* 413 * check for directory/file mismatch with trailing /, 414 * and send any redirections. 415 */ 416 n = strlen(webroot) + strlen(masque) + strlen(c->req.uri) + 417 STRLEN("/index.html") + STRLEN("/.httplogin") + 1; 418 w = halloc(c, n); 419 strcpy(w, webroot); 420 strcat(w, masque); 421 strcat(w, c->req.uri); 422 423 /* 424 * favicon can be overridden by hostname.ico 425 */ 426 if(strcmp(c->req.uri, "/favicon.ico") == 0){ 427 w2 = halloc(c, n+strlen(c->head.host)+2); 428 strcpy(w2, webroot); 429 strcat(w2, masque); 430 strcat(w2, "/"); 431 strcat(w2, c->head.host); 432 strcat(w2, ".ico"); 433 if(access(w2, AREAD)==0) 434 w = w2; 435 } 436 437 /* 438 * don't show the contents of .httplogin 439 */ 440 n = strlen(w); 441 if(strcmp(w+n-STRLEN(".httplogin"), ".httplogin") == 0) 442 return notfound(c, c->req.uri); 443 444 fd = open(w, OREAD); 445 if(fd < 0 && strlen(masque)>0 && strncmp(c->req.uri, masque, strlen(masque)) == 0){ 446 // may be a URI from before virtual hosts; try again without masque 447 strcpy(w, webroot); 448 strcat(w, c->req.uri); 449 fd = open(w, OREAD); 450 } 451 if(fd < 0) 452 return notfound(c, c->req.uri); 453 dir = dirfstat(fd); 454 if(dir == nil){ 455 close(fd); 456 return hfail(c, HInternal); 457 } 458 p = strchr(w, '\0'); 459 if(dir->mode & DMDIR){ 460 free(dir); 461 if(p > w && p[-1] == '/'){ 462 strcat(w, "index.html"); 463 force301 = 0; 464 }else{ 465 strcat(w, "/index.html"); 466 force301 = 1; 467 } 468 fd1 = open(w, OREAD); 469 if(fd1 < 0){ 470 close(fd); 471 return notfound(c, c->req.uri); 472 } 473 c->req.uri = w + strlen(webroot) + strlen(masque); 474 if(force301 && c->req.vermaj){ 475 close(fd); 476 close(fd1); 477 return hmoved(c, c->req.uri); 478 } 479 close(fd); 480 fd = fd1; 481 dir = dirfstat(fd); 482 if(dir == nil){ 483 close(fd); 484 return hfail(c, HInternal); 485 } 486 }else if(p > w && p[-1] == '/'){ 487 free(dir); 488 close(fd); 489 *strrchr(c->req.uri, '/') = '\0'; 490 return hmoved(c, c->req.uri); 491 } 492 493 ok = authorize(c, w); 494 if(ok <= 0){ 495 free(dir); 496 close(fd); 497 return ok; 498 } 499 500 return sendfd(c, fd, dir, nil, nil); 501 } 502 503 static Strings 504 stripmagic(HConnect *hc, char *uri) 505 { 506 Strings ss; 507 char *newuri, *prog, *s; 508 509 prog = stripprefix("/magic/", uri); 510 if(prog == nil){ 511 ss.s1 = uri; 512 ss.s2 = nil; 513 return ss; 514 } 515 516 s = strchr(prog, '/'); 517 if(s == nil) 518 newuri = ""; 519 else{ 520 newuri = hstrdup(hc, s); 521 *s = 0; 522 s = strrchr(s, '/'); 523 if(s != nil && s[1] == 0) 524 *s = 0; 525 } 526 ss.s1 = newuri; 527 ss.s2 = prog; 528 return ss; 529 } 530 531 static char* 532 stripprefix(char *pre, char *str) 533 { 534 while(*pre) 535 if(*str++ != *pre++) 536 return nil; 537 return str; 538 } 539 540 /* 541 * couldn't open a file 542 * figure out why and return and error message 543 */ 544 static int 545 notfound(HConnect *c, char *url) 546 { 547 c->xferbuf[0] = 0; 548 rerrstr(c->xferbuf, sizeof c->xferbuf); 549 if(strstr(c->xferbuf, "file does not exist") != nil) 550 return hfail(c, HNotFound, url); 551 if(strstr(c->xferbuf, "permission denied") != nil) 552 return hfail(c, HUnauth, url); 553 return hfail(c, HNotFound, url); 554 } 555 556 static char* 557 sysdom(void) 558 { 559 char *dn; 560 561 dn = csquery("sys" , sysname(), "dom"); 562 if(dn == nil) 563 dn = "who cares"; 564 return dn; 565 } 566 567 /* 568 * query the connection server 569 */ 570 static char* 571 csquery(char *attr, char *val, char *rattr) 572 { 573 char token[64+4]; 574 char buf[256], *p, *sp; 575 int fd, n; 576 577 if(val == nil || val[0] == 0) 578 return nil; 579 snprint(buf, sizeof(buf), "%s/cs", netdir); 580 fd = open(buf, ORDWR); 581 if(fd < 0) 582 return nil; 583 fprint(fd, "!%s=%s", attr, val); 584 seek(fd, 0, 0); 585 snprint(token, sizeof(token), "%s=", rattr); 586 for(;;){ 587 n = read(fd, buf, sizeof(buf)-1); 588 if(n <= 0) 589 break; 590 buf[n] = 0; 591 p = strstr(buf, token); 592 if(p != nil && (p == buf || *(p-1) == 0)){ 593 close(fd); 594 sp = strchr(p, ' '); 595 if(sp) 596 *sp = 0; 597 p = strchr(p, '='); 598 if(p == nil) 599 return nil; 600 return estrdup(p+1); 601 } 602 } 603 close(fd); 604 return nil; 605 } 606