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