#include <u.h>
#include <libc.h>
#include <auth.h>
#include <mp.h>
#include <libsec.h>
#include "httpd.h"
#include "httpsrv.h"
enum {
Nbuckets = 256,
};
typedef struct Strings Strings;
typedef struct System System;
struct Strings
{
char *s1;
char *s2;
};
struct System {
char *rsys;
ulong reqs;
ulong first;
ulong last;
System *next; /* next in chain */
};
char *netdir;
char *HTTPLOG = "httpd/log";
static char netdirb[256];
static char *namespace;
static System syss[Nbuckets];
static void becomenone(char*);
static char *csquery(char*, char*, char*);
static void dolisten(char*);
static int doreq(HConnect*);
static int send(HConnect*);
static Strings stripmagic(HConnect*, char*);
static char* stripprefix(char*, char*);
static char* sysdom(void);
static int notfound(HConnect *c, char *url);
uchar *certificate;
int certlen;
PEMChain *certchain;
void
usage(void)
{
fprint(2, "usage: httpd [-c certificate] [-C CAchain] [-a srvaddress] "
"[-d domain] [-n namespace] [-w webroot]\n");
exits("usage");
}
void
main(int argc, char **argv)
{
char *address;
namespace = nil;
address = nil;
hmydomain = nil;
netdir = "/net";
fmtinstall('D', hdatefmt);
fmtinstall('H', httpfmt);
fmtinstall('U', hurlfmt);
ARGBEGIN{
case 'c':
certificate = readcert(EARGF(usage()), &certlen);
if(certificate == nil)
sysfatal("reading certificate: %r");
break;
case 'C':
certchain = readcertchain(EARGF(usage()));
if (certchain == nil)
sysfatal("reading certificate chain: %r");
break;
case 'n':
namespace = EARGF(usage());
break;
case 'a':
address = EARGF(usage());
break;
case 'd':
hmydomain = EARGF(usage());
break;
case 'w':
webroot = EARGF(usage());
break;
default:
usage();
break;
}ARGEND
if(argc)
usage();
if(namespace == nil)
namespace = "/lib/namespace.httpd";
if(address == nil)
address = "*";
if(webroot == nil)
webroot = "/usr/web";
else{
cleanname(webroot);
if(webroot[0] != '/')
webroot = "/usr/web";
}
switch(rfork(RFNOTEG|RFPROC|RFFDG|RFNAMEG)) {
case -1:
sysfatal("fork");
case 0:
break;
default:
exits(nil);
}
/*
* open all files we might need before castrating namespace
*/
time(nil);
if(hmydomain == nil)
hmydomain = sysdom();
syslog(0, HTTPLOG, nil);
logall[0] = open("/sys/log/httpd/0", OWRITE);
logall[1] = open("/sys/log/httpd/1", OWRITE);
logall[2] = open("/sys/log/httpd/clf", OWRITE);
redirectinit();
contentinit();
urlinit();
statsinit();
becomenone(namespace);
dolisten(netmkaddr(address, "tcp", certificate == nil ? "http" : "https"));
exits(nil);
}
static void
becomenone(char *namespace)
{
int fd;
fd = open("#c/user", OWRITE);
if(fd < 0 || write(fd, "none", strlen("none")) < 0)
sysfatal("can't become none");
close(fd);
if(newns("none", nil) < 0)
sysfatal("can't build normal namespace");
if(addns("none", namespace) < 0)
sysfatal("can't build httpd namespace");
}
static HConnect*
mkconnect(char *scheme, char *port)
{
HConnect *c;
c = ezalloc(sizeof(HConnect));
c->hpos = c->header;
c->hstop = c->header;
c->replog = writelog;
c->scheme = scheme;
c->port = port;
return c;
}
static HSPriv*
mkhspriv(void)
{
HSPriv *p;
p = ezalloc(sizeof(HSPriv));
return p;
}
static uint
hashstr(char* key)
{
/* asu works better than pjw for urls */
uchar *k = (unsigned char*)key;
uint h = 0;
while(*k!=0)
h = 65599*h + *k++;
return h;
}
static System *
hashsys(char *rsys)
{
int notme;
System *sys;
sys = syss + hashstr(rsys) % nelem(syss);
/* if the bucket is empty, just use it, else find or allocate ours */
if(sys->rsys != nil) {
/* find match or chain end */
for(; notme = (strcmp(sys->rsys, rsys) != 0) &&
sys->next != nil; sys = sys->next)
;
if(notme) {
sys->next = malloc(sizeof *sys); /* extend chain */
sys = sys->next;
} else
return sys;
}
if(sys != nil) {
memset(sys, 0, sizeof *sys);
sys->rsys = strdup(rsys);
}
return sys;
}
/*
* be sure to call this at least once per listen in the parent,
* to update the hash chains.
* it's okay to call it in the child too, but then sys->reqs only gets
* updated in the child.
*/
static int
isswamped(char *rsys)
{
ulong period;
System *sys = hashsys(rsys);
if(sys == nil)
return 0;
sys->last = time(nil);
if(sys->first == 0)
sys->first = sys->last;
period = sys->first - sys->last;
return ++sys->reqs > 30 && period > 30 && sys->reqs / period >= 2;
}
/* must only be called in child */
static void
throttle(int nctl, NetConnInfo *nci, int swamped)
{
if(swamped || isswamped(nci->rsys)) { /* shed load */
syslog(0, HTTPLOG, "overloaded by %s", nci->rsys);
sleep(30);
close(nctl);
exits(nil);
}
}
static void
dolisten(char *address)
{
HSPriv *hp;
HConnect *c;
NetConnInfo *nci;
char ndir[NETPATHLEN], dir[NETPATHLEN], *p, *scheme;
int ctl, nctl, data, t, ok, spotchk, swamped;
TLSconn conn;
spotchk = 0;
syslog(0, HTTPLOG, "httpd starting");
ctl = announce(address, dir);
if(ctl < 0){
syslog(0, HTTPLOG, "can't announce on %s: %r", address);
return;
}
strcpy(netdirb, dir);
p = nil;
if(netdir[0] == '/'){
p = strchr(netdirb+1, '/');
if(p != nil)
*p = '\0';
}
if(p == nil)
strcpy(netdirb, "/net");
netdir = netdirb;
for(;;){
/*
* wait for a call (or an error)
*/
nctl = listen(dir, ndir);
if(nctl < 0){
syslog(0, HTTPLOG, "can't listen on %s: %r", address);
syslog(0, HTTPLOG, "ctls = %d", ctl);
return;
}
swamped = 0;
nci = getnetconninfo(ndir, -1);
if (nci)
swamped = isswamped(nci->rsys);
/*
* start a process for the service
*/
switch(rfork(RFFDG|RFPROC|RFNOWAIT|RFNAMEG)){
case -1:
close(nctl);
continue;
case 0:
/*
* see if we know the service requested
*/
data = accept(ctl, ndir);
if(data >= 0 && certificate != nil){
memset(&conn, 0, sizeof(conn));
conn.cert = certificate;
conn.certlen = certlen;
if (certchain != nil)
conn.chain = certchain;
data = tlsServer(data, &conn);
scheme = "https";
}else
scheme = "http";
if(data < 0){
syslog(0, HTTPLOG, "can't open %s/data: %r", ndir);
exits(nil);
}
dup(data, 0);
dup(data, 1);
dup(data, 2);
close(data);
close(ctl);
close(nctl);
if (nci == nil)
nci = getnetconninfo(ndir, -1);
c = mkconnect(scheme, nci->lserv);
hp = mkhspriv();
hp->remotesys = nci->rsys;
hp->remoteserv = nci->rserv;
c->private = hp;
hinit(&c->hin, 0, Hread);
hinit(&c->hout, 1, Hwrite);
/*
* serve requests until a magic request.
* later requests have to come quickly.
* only works for http/1.1 or later.
*/
for(t = 15*60*1000; ; t = 15*1000){
throttle(nctl, nci, swamped);
if(hparsereq(c, t) <= 0)
exits(nil);
ok = doreq(c);
hflush(&c->hout);
if(c->head.closeit || ok < 0)
exits(nil);
hreqcleanup(c);
}
/* not reached */
default:
close(nctl);
break;
}
if(++spotchk > 50){
spotchk = 0;
redirectinit();
contentinit();
urlinit();
statsinit();
}
}
}
static int
doreq(HConnect *c)
{
HSPriv *hp;
Strings ss;
char *magic, *uri, *newuri, *origuri, *newpath, *hb;
char virtualhost[100], logfd0[10], logfd1[10], vers[16];
int n, nredirect;
uint flags;
/*
* munge uri for magic
*/
uri = c->req.uri;
nredirect = 0;
werrstr("");
top:
if(++nredirect > 10){
if(hparseheaders(c, 15*60*1000) < 0)
exits("failed");
werrstr("redirection loop");
return hfail(c, HNotFound, uri);
}
ss = stripmagic(c, uri);
uri = ss.s1;
origuri = uri;
magic = ss.s2;
if(magic)
goto magic;
/*
* Apply redirects. Do this before reading headers
* (if possible) so that we can redirect to magic invisibly.
*/
flags = 0;
if(origuri[0]=='/' && origuri[1]=='~'){
n = strlen(origuri) + 4 + UTFmax;
newpath = halloc(c, n);
snprint(newpath, n, "/who/%s", origuri+2);
c->req.uri = newpath;
newuri = newpath;
}else if(origuri[0]=='/' && origuri[1]==0){
/* can't redirect / until we read the headers below */
newuri = nil;
}else
newuri = redirect(c, origuri, &flags);
if(newuri != nil){
if(flags & Redirsilent) {
c->req.uri = uri = newuri;
logit(c, "%s: silent replacement %s", origuri, uri);
goto top;
}
if(hparseheaders(c, 15*60*1000) < 0)
exits("failed");
if(flags & Redirperm) {
logit(c, "%s: permanently moved to %s", origuri, newuri);
return hmoved(c, newuri);
} else if (flags & (Redironly | Redirsubord))
logit(c, "%s: top-level or many-to-one replacement %s",
origuri, uri);
/*
* try temporary redirect instead of permanent
*/
if (http11(c))
return hredirected(c, "307 Temporary Redirect", newuri);
else
return hredirected(c, "302 Temporary Redirect", newuri);
}
/*
* for magic we exec a new program and serve no more requests
*/
magic:
if(magic != nil && strcmp(magic, "httpd") != 0){
snprint(c->xferbuf, HBufSize, "/bin/ip/httpd/%s", magic);
snprint(logfd0, sizeof(logfd0), "%d", logall[0]);
snprint(logfd1, sizeof(logfd1), "%d", logall[1]);
snprint(vers, sizeof(vers), "HTTP/%d.%d", c->req.vermaj, c->req.vermin);
hb = hunload(&c->hin);
if(hb == nil){
hfail(c, HInternal);
return -1;
}
hp = c->private;
execl(c->xferbuf, magic, "-d", hmydomain, "-w", webroot,
"-s", c->scheme, "-p", c->port,
"-r", hp->remotesys, "-N", netdir, "-b", hb,
"-L", logfd0, logfd1, "-R", c->header,
c->req.meth, vers, uri, c->req.search, nil);
logit(c, "no magic %s uri %s", magic, uri);
hfail(c, HNotFound, uri);
return -1;
}
/*
* normal case is just file transfer
*/
if(hparseheaders(c, 15*60*1000) < 0)
exits("failed");
if(origuri[0] == '/' && origuri[1] == 0){
snprint(virtualhost, sizeof virtualhost, "http://%s/", c->head.host);
newuri = redirect(c, virtualhost, nil);
if(newuri == nil)
newuri = redirect(c, origuri, nil);
if(newuri)
return hmoved(c, newuri);
}
if(!http11(c) && !c->head.persist)
c->head.closeit = 1;
return send(c);
}
static int
send(HConnect *c)
{
Dir *dir;
char *w, *w2, *p, *masque;
int fd, fd1, n, force301, ok;
/*
if(c->req.search)
return hfail(c, HNoSearch, c->req.uri);
*/
if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0)
return hunallowed(c, "GET, HEAD");
if(c->head.expectother || c->head.expectcont)
return hfail(c, HExpectFail);
masque = masquerade(c->head.host);
/*
* check for directory/file mismatch with trailing /,
* and send any redirections.
*/
n = strlen(webroot) + strlen(masque) + strlen(c->req.uri) +
STRLEN("/index.html") + STRLEN("/.httplogin") + 1;
w = halloc(c, n);
strcpy(w, webroot);
strcat(w, masque);
strcat(w, c->req.uri);
/*
* favicon can be overridden by hostname.ico
*/
if(strcmp(c->req.uri, "/favicon.ico") == 0){
w2 = halloc(c, n+strlen(c->head.host)+2);
strcpy(w2, webroot);
strcat(w2, masque);
strcat(w2, "/");
strcat(w2, c->head.host);
strcat(w2, ".ico");
if(access(w2, AREAD)==0)
w = w2;
}
/*
* don't show the contents of .httplogin
*/
n = strlen(w);
if(strcmp(w+n-STRLEN(".httplogin"), ".httplogin") == 0)
return notfound(c, c->req.uri);
fd = open(w, OREAD);
if(fd < 0 && strlen(masque)>0 && strncmp(c->req.uri, masque, strlen(masque)) == 0){
// may be a URI from before virtual hosts; try again without masque
strcpy(w, webroot);
strcat(w, c->req.uri);
fd = open(w, OREAD);
}
if(fd < 0)
return notfound(c, c->req.uri);
dir = dirfstat(fd);
if(dir == nil){
close(fd);
return hfail(c, HInternal);
}
p = strchr(w, '\0');
if(dir->mode & DMDIR){
free(dir);
if(p > w && p[-1] == '/'){
strcat(w, "index.html");
force301 = 0;
}else{
strcat(w, "/index.html");
force301 = 1;
}
fd1 = open(w, OREAD);
if(fd1 < 0){
close(fd);
return notfound(c, c->req.uri);
}
c->req.uri = w + strlen(webroot) + strlen(masque);
if(force301 && c->req.vermaj){
close(fd);
close(fd1);
return hmoved(c, c->req.uri);
}
close(fd);
fd = fd1;
dir = dirfstat(fd);
if(dir == nil){
close(fd);
return hfail(c, HInternal);
}
}else if(p > w && p[-1] == '/'){
free(dir);
close(fd);
*strrchr(c->req.uri, '/') = '\0';
return hmoved(c, c->req.uri);
}
ok = authorize(c, w);
if(ok <= 0){
free(dir);
close(fd);
return ok;
}
return sendfd(c, fd, dir, nil, nil);
}
static Strings
stripmagic(HConnect *hc, char *uri)
{
Strings ss;
char *newuri, *prog, *s;
prog = stripprefix("/magic/", uri);
if(prog == nil){
ss.s1 = uri;
ss.s2 = nil;
return ss;
}
s = strchr(prog, '/');
if(s == nil)
newuri = "";
else{
newuri = hstrdup(hc, s);
*s = 0;
s = strrchr(s, '/');
if(s != nil && s[1] == 0)
*s = 0;
}
ss.s1 = newuri;
ss.s2 = prog;
return ss;
}
static char*
stripprefix(char *pre, char *str)
{
while(*pre)
if(*str++ != *pre++)
return nil;
return str;
}
/*
* couldn't open a file
* figure out why and return and error message
*/
static int
notfound(HConnect *c, char *url)
{
c->xferbuf[0] = 0;
rerrstr(c->xferbuf, sizeof c->xferbuf);
if(strstr(c->xferbuf, "file does not exist") != nil)
return hfail(c, HNotFound, url);
if(strstr(c->xferbuf, "permission denied") != nil)
return hfail(c, HUnauth, url);
return hfail(c, HNotFound, url);
}
static char*
sysdom(void)
{
char *dn;
dn = csquery("sys" , sysname(), "dom");
if(dn == nil)
dn = "who cares";
return dn;
}
/*
* query the connection server
*/
static char*
csquery(char *attr, char *val, char *rattr)
{
char token[64+4];
char buf[256], *p, *sp;
int fd, n;
if(val == nil || val[0] == 0)
return nil;
snprint(buf, sizeof(buf), "%s/cs", netdir);
fd = open(buf, ORDWR);
if(fd < 0)
return nil;
fprint(fd, "!%s=%s", attr, val);
seek(fd, 0, 0);
snprint(token, sizeof(token), "%s=", rattr);
for(;;){
n = read(fd, buf, sizeof(buf)-1);
if(n <= 0)
break;
buf[n] = 0;
p = strstr(buf, token);
if(p != nil && (p == buf || *(p-1) == 0)){
close(fd);
sp = strchr(p, ' ');
if(sp)
*sp = 0;
p = strchr(p, '=');
if(p == nil)
return nil;
return estrdup(p+1);
}
}
close(fd);
return nil;
}
|