#include #include #include #include "httpd.h" #include "httpsrv.h" typedef struct Endpoints Endpoints; typedef struct Strings Strings; struct Endpoints { char *lsys; char *lserv; char *rsys; char *rserv; }; struct Strings { char *s1; char *s2; }; char *netdir; char *webroot; char *HTTPLOG = "httpd/log"; static char netdirb[256]; static char *namespace; 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 Endpoints* getendpoints(char*); void usage(void) { fprint(2, "usage: httpd [-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', hdateconv); fmtinstall('H', httpconv); fmtinstall('U', hurlconv); ARGBEGIN{ case 'n': namespace = ARGF(); break; case 'a': address = ARGF(); break; case 'd': hmydomain = ARGF(); break; case 'w': webroot = ARGF(); break; default: usage(); break; }ARGEND if(argc) usage(); if(namespace == nil) namespace = "/lib/namespace.httpd"; if(address == nil) address = "tcp!*!http"; 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); redirectinit(); contentinit(); urlinit(); statsinit(); becomenone(namespace); dolisten(address); 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(void) { HConnect *c; c = ezalloc(sizeof(HConnect)); c->hpos = c->header; c->hstop = c->header; c->replog = writelog; return c; } static HSPriv* mkhspriv(void) { HSPriv *p; p = ezalloc(sizeof(HSPriv)); return p; } static void dolisten(char *address) { HSPriv *hp; HConnect *c; Endpoints *ends; char ndir[NETPATHLEN], dir[NETPATHLEN], *p; int ctl, nctl, data, t, ok, spotchk; 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; } /* * 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){ 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); ends = getendpoints(ndir); c = mkconnect(); hp = mkhspriv(); hp->remotesys = ends->rsys; hp->remoteserv = ends->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){ if(hparsereq(c, t) <= 0) exits(nil); ok = doreq(c); hflush(&c->hout); if(c->head.closeit || ok < 0) exits(nil); hreqcleanup(c); } exits(nil); 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, *origuri, *newpath, *hb; char virtualhost[100], logfd0[10], logfd1[10], vers[16]; int n; /* * munge uri for magic */ uri = c->req.uri; ss = stripmagic(c, uri); uri = ss.s1; magic = ss.s2; /* * for magic we exec a new program and serve no more requests */ 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, "-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 */ origuri = uri; if(hparseheaders(c, 15*60*1000) < 0) exits("failed"); if(!http11(c) && !c->head.persist) c->head.closeit = 1; 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; uri = newpath; }else if(origuri[0]=='/' && origuri[1]==0){ snprint(virtualhost, sizeof virtualhost, "http://%s/", c->head.host); uri = redirect(c, virtualhost); if(uri == nil) uri = redirect(c, origuri); }else uri = redirect(c, origuri); if(uri != nil) return hmoved(c, uri); return send(c); } static int send(HConnect *c) { Dir dir; char *w, *p; 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); ok = authcheck(c); if(ok <= 0) return ok; /* * check for directory/file mismatch with trailing /, * and send any redirections. */ n = strlen(webroot) + strlen(c->req.uri) + STRLEN("/index.html") + 1; w = halloc(c, n); strcpy(w, webroot); strcat(w, c->req.uri); fd = open(w, OREAD); if(fd < 0) return notfound(c, c->req.uri); if(dirfstat(fd, &dir) < 0){ close(fd); return hfail(c, HInternal); } p = strchr(w, '\0'); if(dir.mode & CHDIR){ 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); if(force301 && c->req.vermaj){ close(fd); close(fd1); return hmoved(c, c->req.uri); } close(fd); fd = fd1; if(dirfstat(fd, &dir) < 0){ close(fd); return hfail(c, HInternal); } }else if(p > w && p[-1] == '/'){ close(fd); *strrchr(c->req.uri, '/') = '\0'; return hmoved(c, c->req.uri); } 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; } 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; } static void getendpoint(char *dir, char *file, char **sysp, char **servp) { int fd, n; char buf[128]; char *sys, *serv; sys = serv = nil; snprint(buf, sizeof buf, "%s/%s", dir, file); fd = open(buf, OREAD); if(fd >= 0){ n = read(fd, buf, sizeof(buf)-1); if(n>0){ buf[n-1] = 0; serv = strchr(buf, '!'); if(serv){ *serv++ = 0; serv = estrdup(serv); } sys = estrdup(buf); } close(fd); } if(serv == nil) serv = estrdup("unknown"); if(sys == nil) sys = estrdup("unknown"); *servp = serv; *sysp = sys; } static Endpoints* getendpoints(char *dir) { Endpoints *ep; ep = ezalloc(sizeof(*ep)); getendpoint(dir, "local", &ep->lsys, &ep->lserv); getendpoint(dir, "remote", &ep->rsys, &ep->rserv); return ep; }