From 992ce9ef8adb25fdcc47264187450a3edb287167 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Thu, 18 Dec 2008 06:14:16 +0100 Subject: [PATCH] Added a first version of the directory multiplexer. --- lib/proc.c | 45 +++++ lib/proc.h | 3 + src/.gitignore | 1 + src/Makefile.am | 3 +- src/dirplex.c | 603 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 src/dirplex.c diff --git a/lib/proc.c b/lib/proc.c index efd2767..957cfce 100644 --- a/lib/proc.c +++ b/lib/proc.c @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef HAVE_CONFIG_H #include @@ -28,6 +29,7 @@ #include #include #include +#include int stdmkchild(char **argv) { @@ -121,3 +123,46 @@ int recvfd(int sock, char **data, size_t *datalen) *datalen = ret; return(fd); } + +pid_t stdforkserve(char **argv, struct hthead *req, int fd) +{ + int i; + char *ebuf, *p; + pid_t pid; + struct charvbuf args; + + if((pid = fork()) < 0) + return(-1); + if(pid == 0) { + dup2(fd, 0); + dup2(fd, 1); + for(i = 3; i < FD_SETSIZE; i++) + close(i); + + bufinit(args); + for(i = 0; argv[i]; i++) + bufadd(args, argv[i]); + bufadd(args, req->method); + bufadd(args, req->url); + bufadd(args, req->rest); + bufadd(args, NULL); + + for(i = 0; i < req->noheaders; i++) { + ebuf = sstrdup(req->headers[i][0]); + for(p = ebuf; *p; p++) { + if(isalnum(*p)) + *p = toupper(*p); + else + *p = '_'; + } + putenv(sprintf2("REQ_%s=%s", ebuf, req->headers[i][1])); + } + putenv(sprintf2("HTTP_VERSION=%s", req->ver)); + + execvp(args.b[0], args.b); + flog(LOG_WARNING, "could not exec child program %s: %s", argv[0], strerror(errno)); + exit(127); + } + close(fd); + return(pid); +} diff --git a/lib/proc.h b/lib/proc.h index 943e598..da230b3 100644 --- a/lib/proc.h +++ b/lib/proc.h @@ -1,8 +1,11 @@ #ifndef _LIB_PROC_H #define _LIB_PROC_H +#include + int stdmkchild(char **argv); int sendfd(int sock, int fd, char *data, size_t datalen); int recvfd(int sock, char **data, size_t *datalen); +pid_t stdforkserve(char **argv, struct hthead *req, int fd); #endif diff --git a/src/.gitignore b/src/.gitignore index 89eee1e..bc7940f 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,2 +1,3 @@ /htparser /debugsink +/dirplex diff --git a/src/Makefile.am b/src/Makefile.am index b3c9a65..e8e6c70 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,8 @@ -bin_PROGRAMS = htparser +bin_PROGRAMS = htparser dirplex noinst_PROGRAMS = debugsink htparser_SOURCES = htparser.c +dirplex_SOURCES = dirplex.c debugsink_SOURCES = debugsink.c LDADD = $(top_srcdir)/lib/libht.a diff --git a/src/dirplex.c b/src/dirplex.c new file mode 100644 index 0000000..a79a78c --- /dev/null +++ b/src/dirplex.c @@ -0,0 +1,603 @@ +/* + ashd - A Sane HTTP Daemon + Copyright (C) 2008 Fredrik Tolf + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include + +#define CH_SOCKET 0 +#define CH_FORK 1 + +#define PAT_BASENAME 0 +#define PAT_PATHNAME 1 +#define PAT_ALL 2 +#define PAT_DEFAULT 3 + +struct config { + struct config *next, *prev; + char *path; + time_t mtime; + struct child *children; + struct pattern *patterns; +}; + +struct child { + struct child *next; + char *name; + int type; + char **argv; + int fd; +}; + +struct rule { + int type; + char *pattern; +}; + +struct pattern { + struct pattern *next; + char *childnm; + struct rule **rules; +}; + +struct config *cflist; + +static void freechild(struct child *ch) +{ + if(ch->fd != -1) + close(ch->fd); + if(ch->name != NULL) + free(ch->name); + if(ch->argv != NULL) + freeca(ch->argv); + free(ch); +} + +static void freepattern(struct pattern *pat) +{ + struct rule **rule; + + for(rule = pat->rules; *rule; rule++) { + if((*rule)->pattern != NULL) + free((*rule)->pattern); + free(*rule); + } + if(pat->childnm != NULL) + free(pat->childnm); + free(pat); +} + +static void freeconfig(struct config *cf) +{ + struct child *ch, *nch; + struct pattern *pat, *npat; + + if(cf->prev != NULL) + cf->prev->next = cf->next; + if(cf->next != NULL) + cf->next->prev = cf->prev; + if(cf == cflist) + cflist = cf->next; + free(cf->path); + for(ch = cf->children; ch != NULL; ch = nch) { + nch = ch->next; + freechild(ch); + } + for(pat = cf->patterns; pat != NULL; pat = npat) { + npat = pat->next; + freepattern(pat); + } + free(cf); +} + +static struct child *newchild(char *name, int type) +{ + struct child *ch; + + omalloc(ch); + ch->name = sstrdup(name); + ch->type = type; + ch->fd = -1; + return(ch); +} + +static struct child *getchild(struct config *cf, char *name) +{ + struct child *ch; + + for(ch = cf->children; ch; ch = ch->next) { + if(!strcmp(ch->name, name)) + break; + } + return(ch); +} + +static struct rule *newrule(struct pattern *pat) +{ + int i; + struct rule *rule; + + for(i = 0; pat->rules[i]; i++); + pat->rules = srealloc(pat->rules, sizeof(*pat->rules) * (i + 2)); + rule = pat->rules[i] = smalloc(sizeof(*rule)); + pat->rules[i + 1] = NULL; + return(rule); +} + +static struct pattern *newpattern(void) +{ + struct pattern *pat; + + omalloc(pat); + pat->rules = szmalloc(sizeof(*pat->rules)); + return(pat); +} + +static struct config *readconfig(char *path) +{ + int i; + struct config *cf; + FILE *s; + char line[1024]; + char *p, **w; + int ind, eof; + int lno; + int state; + int rv; + int argc; + struct child *child; + struct pattern *pat; + struct rule *rule; + struct stat sb; + + if(stat(path, &sb)) + return(NULL); + if((s = fopen(sprintf3("%s/.htrc", path), "r")) == NULL) + return(NULL); + omalloc(cf); + cf->mtime = sb.st_mtime; + cf->path = sstrdup(path); + eof = 0; + state = 0; + w = NULL; + lno = 0; + do { + if(fgets(line, sizeof(line), s) == NULL) { + eof = 1; + line[0] = 0; + } + lno++; + for(p = line; *p; p++) { + if(*p == '#') + continue; + if(!isspace(*p)) + break; + } + ind = isspace(line[0]); + w = tokenize(line); + argc = calen(w); + + retry: + if(state == 0) { + if(ind) { + flog(LOG_WARNING, "%s%i: unexpected line indentation in global scope", path, lno); + goto next; + } else { + if(!w[0]) { + } else if(!strcmp(w[0], "child")) { + if(argc < 2) { + flog(LOG_WARNING, "%s:%i: missing name in child declaration", path, lno); + goto next; + } + child = newchild(w[1], CH_SOCKET); + state = 1; + } else if(!strcmp(w[0], "fchild")) { + if(argc < 2) { + flog(LOG_WARNING, "%s:%i: missing name in child declaration", path, lno); + goto next; + } + child = newchild(w[1], CH_FORK); + state = 1; + } else if(!strcmp(w[0], "match")) { + pat = newpattern(); + state = 2; + } else { + flog(LOG_WARNING, "%s:%i: unknown directive %s", path, lno, w[0]); + } + } + } else if(state == 1) { + if(ind) { + if(!w[0]) { + } else if(!strcmp(w[0], "exec")) { + if(argc < 2) { + flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", path, lno); + goto next; + } + child->argv = szmalloc(sizeof(*child->argv) * argc); + for(i = 0; i < argc - 1; i++) + child->argv[i] = sstrdup(w[i + 1]); + } else { + flog(LOG_WARNING, "%s:%i: unknown directive %s", path, lno, w[0]); + } + } else { + state = 0; + if(child->argv == NULL) { + flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", path, lno, child->name); + freechild(child); + goto retry; + } + child->next = cf->children; + cf->children = child; + goto retry; + } + } else if(state == 2) { + if(ind) { + if(!w[0]) { + } else if(!strcmp(w[0], "filename")) { + if(argc < 2) { + flog(LOG_WARNING, "%s:%i: missing pattern for `filename' match", path, lno); + goto next; + } + rule = newrule(pat); + rule->type = PAT_BASENAME; + rule->pattern = sstrdup(w[1]); + } else if(!strcmp(w[0], "pathname")) { + if(argc < 2) { + flog(LOG_WARNING, "%s:%i: missing pattern for `pathname' match", path, lno); + goto next; + } + rule = newrule(pat); + rule->type = PAT_PATHNAME; + rule->pattern = sstrdup(w[1]); + } else if(!strcmp(w[0], "all")) { + newrule(pat)->type = PAT_ALL; + } else if(!strcmp(w[0], "default")) { + newrule(pat)->type = PAT_DEFAULT; + } else if(!strcmp(w[0], "handler")) { + if(argc < 2) { + flog(LOG_WARNING, "%s:%i: missing child name for `handler' directive", path, lno); + goto next; + } + if(pat->childnm != NULL) + free(pat->childnm); + pat->childnm = sstrdup(w[1]); + } else { + flog(LOG_WARNING, "%s:%i: unknown directive %s", path, lno, w[0]); + } + } else { + state = 0; + if(pat->rules[0] == NULL) { + flog(LOG_WARNING, "%s:%i: missing rules in match declaration", path, lno); + freepattern(pat); + goto retry; + } + if(pat->childnm == NULL) { + flog(LOG_WARNING, "%s:%i: missing handler in match declaration", path, lno); + freepattern(pat); + goto retry; + } + pat->next = cf->patterns; + cf->patterns = pat; + goto retry; + } + } + + next: + freeca(w); + w = NULL; + } while(!eof); + rv = 0; + + if(w != NULL) + freeca(w); + fclose(s); + return(cf); +} + +static struct config *getconfig(char *path) +{ + struct config *cf; + struct stat sb; + + for(cf = cflist; cf != NULL; cf = cf->next) { + if(!strcmp(cf->path, path)) { + if(stat(path, &sb)) + return(NULL); + if(sb.st_mtime != cf->mtime) { + freeconfig(cf); + break; + } + return(cf); + } + } + if((cf = readconfig(path)) != NULL) { + cf->next = cflist; + cflist = cf; + } + return(cf); +} + +static char *findfile(struct hthead *req, char *nm) +{ + char *p, *p2, *path, *ftmp, *buf, *rv, *p3; + struct stat sb; + DIR *dir; + struct dirent *dent; + + path = sstrdup("."); + p = nm; + rv = NULL; + while(1) { + if((p2 = strchr(p, '/')) == NULL) { + } else { + *(p2++) = 0; + } + + if(!*p) { + if(p2 == NULL) { + rv = sstrdup(path); + break; + } else { + goto fail; + } + } + if(*p == '.') + goto fail; + + getconfig(path); + + /* + * First, check the name verbatimely: + */ + buf = sprintf3("%s/%s", path, p); + if(!stat(buf, &sb)) { + if(S_ISDIR(sb.st_mode)) { + ftmp = path; + if(!strcmp(path, ".")) + path = sstrdup(p); + else + path = sprintf2("%s/%s", path, p); + free(ftmp); + goto next; + } + if(S_ISREG(sb.st_mode)) { + rv = sstrdup(buf); + break; + } + goto fail; + } + + /* + * Check the file extensionlessly: + */ + if(!strchr(p, '.') && ((dir = opendir(path)) != NULL)) { + while((dent = readdir(dir)) != NULL) { + buf = sprintf3("%s/%s", path, dent->d_name); + if((p3 = strchr(dent->d_name, '.')) != NULL) + *p3 = 0; + if(strcmp(dent->d_name, p)) + continue; + if(stat(buf, &sb)) + continue; + if(!S_ISREG(sb.st_mode)) + continue; + rv = sstrdup(buf); + break; + } + closedir(dir); + if(dent != NULL) + break; + } + + goto fail; + + next: + if(p2 == NULL) + break; + p = p2; + } + if(!strncmp(rv, "./", 2)) + memmove(rv, rv + 2, strlen(rv + 2) + 1); + goto out; + +fail: + rv = NULL; +out: + free(path); + return(rv); +} + +static struct child *findchild(char *file, char *name) +{ + char *buf, *p; + struct config *cf; + struct child *ch; + + buf = sstrdup(file); + while(1) { + ch = NULL; + if(!strcmp(buf, ".")) + break; + if((p = strrchr(buf, '/')) != NULL) + *p = 0; + else + strcpy(buf, "."); + cf = getconfig(buf); + if(cf == NULL) + continue; + if((ch = getchild(cf, name)) != NULL) + break; + } + free(buf); + return(ch); +} + +static struct pattern *findmatch(char *file, int trydefault) +{ + int i; + char *buf, *p, *bn; + struct config *cf; + struct pattern *pat; + struct rule *rule; + + if((bn = strrchr(file, '/')) != NULL) + bn++; + else + bn = file; + buf = sstrdup(file); + while(1) { + pat = NULL; + if(!strcmp(buf, ".")) + break; + if((p = strrchr(buf, '/')) != NULL) + *p = 0; + else + strcpy(buf, "."); + cf = getconfig(buf); + if(cf == NULL) + continue; + for(pat = cf->patterns; pat != NULL; pat = pat->next) { + for(i = 0; (rule = pat->rules[i]) != NULL; i++) { + if(rule->type == PAT_BASENAME) { + if(fnmatch(rule->pattern, bn, 0)) + break; + } else if(rule->type == PAT_PATHNAME) { + if(fnmatch(rule->pattern, file, FNM_PATHNAME)) + break; + } else if(rule->type == PAT_ALL) { + } else if(rule->type == PAT_DEFAULT) { + if(!trydefault) + break; + } + } + if(!rule) + goto out; + } + } + +out: + free(buf); + return(pat); +} + +static void forkchild(struct child *ch) +{ + ch->fd = stdmkchild(ch->argv); +} + +static void passreq(struct child *ch, struct hthead *req, int fd) +{ + if(ch->fd < 0) + forkchild(ch); + if(sendreq(ch->fd, req, fd)) { + if(errno == EPIPE) { + /* Assume that the child has crashed and restart it. */ + forkchild(ch); + if(!sendreq(ch->fd, req, fd)) + return; + } + flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(errno)); + close(ch->fd); + ch->fd = -1; + } +} + +static void serve(struct hthead *req, int fd) +{ + char *file; + struct stat sb; + struct pattern *pat; + struct child *ch; + + if((file = findfile(req, req->rest)) == NULL) { + /* XXX: Do 404 handling */ + return; + } + replrest(req, file); + if(stat(file, &sb)) { + flog(LOG_ERR, "could not stat previously found file %s: %s", file, strerror(errno)); + free(file); + return; + } + if(!S_ISREG(sb.st_mode)) { + /* XXX: Handle default files or similar stuff. */ + free(file); + return; + } + if(((pat = findmatch(file, 0)) == NULL) && ((pat = findmatch(file, 1)) == NULL)) { + /* XXX: Send a 500 error? 404? */ + free(file); + return; + } + if((ch = findchild(file, pat->childnm)) == NULL) { + /* XXX: Send a 500 error. */ + flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm); + free(file); + return; + } + + if(ch->type == CH_SOCKET) { + passreq(ch, req, fd); + } else if(ch->type == CH_FORK) { + stdforkserve(ch->argv, req, fd); + } + + free(file); +} + +int main(int argc, char **argv) +{ + struct hthead *req; + int fd; + + if(argc < 2) { + flog(LOG_ERR, "usage: dirplex DIR"); + exit(1); + } + if(chdir(argv[1])) { + flog(LOG_ERR, "could not change directory to %s: %s", argv[1], strerror(errno)); + exit(1); + } + signal(SIGCHLD, SIG_IGN); + while(1) { + if((fd = recvreq(0, &req)) < 0) { + if(errno != 0) + flog(LOG_ERR, "recvreq: %s", strerror(errno)); + break; + } + serve(req, fd); + freehthead(req); + close(fd); + } + return(0); +} -- 2.11.0