xref: /plan9/sys/src/cmd/ip/httpd/httpd.c (revision aeee3693c565baabd230992c0fa6413ddb05625a)
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