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