Subversion Repositories planix.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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
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
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
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*
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*
172
mkhspriv(void)
173
{
174
	HSPriv *p;
175
 
176
	p = ezalloc(sizeof(HSPriv));
177
	return p;
178
}
179
 
180
static uint 
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 *
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
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
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
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
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
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
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*
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
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*
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*
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
}