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