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