diff --git a/LICENSE b/LICENSE index 90778ad..3f7cb9f 100644 --- a/LICENSE +++ b/LICENSE @@ -4,6 +4,7 @@ Copyright 2016-2018 Laslo Hunhold Copyright 2004 Ted Unangst Copyright 2004 Todd C. Miller +Copyright 2008 Otto Moerbeek Copyright 2017 Hiltjo Posthuma Copyright 2017 Quentin Rameau Copyright 2018 Josuah Demangeon diff --git a/config.def.h b/config.def.h index c7e84d8..2d3cabf 100644 --- a/config.def.h +++ b/config.def.h @@ -1,40 +1,6 @@ -static const char *host = "localhost"; -static const char *port = "80"; -static const char *servedir = "."; -static const char *docindex = "index.html"; -static const char *user = "nobody"; -static const char *group = "nogroup"; - -static int listdirs = 1; -static int vhosts = 0; - -static const int maxnprocs = 512; #define HEADER_MAX 4096 #define FIELD_MAX 200 -/* virtual hosts */ -static struct { - const char *name; - const char *regex; - const char *dir; - const char *prefix; - regex_t re; -} vhost[] = { - /* canonical host host regex directory prefix */ - { "example.org", "^(www\\.)?example\\.org$", "/example.org", NULL }, - { "example.org", "^old\\.example\\.org$", "/", "/old" }, -}; - -/* target prefix mapping */ -static const struct { - const char *vhost; - const char *from; - const char *to; -} map[] = { - /* canonical host from to */ - { "example.org", "/old/path/to/dir", "/new/path/to/dir" }, -}; - /* mime-types */ static const struct { char *ext; diff --git a/http.c b/http.c index d58d94a..0d758ad 100644 --- a/http.c +++ b/http.c @@ -325,46 +325,47 @@ http_send_response(int fd, struct request *r) /* match vhost */ vhostmatch = NULL; - if (vhosts) { - for (i = 0; i < LEN(vhost); i++) { + if (s.vhost) { + for (i = 0; i < s.vhost_len; i++) { /* switch to vhost directory if there is a match */ - if (!regexec(&vhost[i].re, r->field[REQ_HOST], 0, + if (!regexec(&s.vhost[i].re, r->field[REQ_HOST], 0, NULL, 0)) { - if (chdir(vhost[i].dir) < 0) { + if (chdir(s.vhost[i].dir) < 0) { return http_send_status(fd, (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND); } - vhostmatch = vhost[i].name; + vhostmatch = s.vhost[i].name; break; } } - if (i == LEN(vhost)) { + if (i == s.vhost_len) { return http_send_status(fd, S_NOT_FOUND); } /* if we have a vhost prefix, prepend it to the target */ - if (vhost[i].prefix) { + if (s.vhost[i].prefix) { if (snprintf(realtarget, sizeof(realtarget), "%s%s", - vhost[i].prefix, realtarget) >= sizeof(realtarget)) { + s.vhost[i].prefix, realtarget) >= sizeof(realtarget)) { return http_send_status(fd, S_REQUEST_TOO_LARGE); } } } /* apply target prefix mapping */ - for (i = 0; i < LEN(map); i++) { - len = strlen(map[i].from); - if (!strncmp(realtarget, map[i].from, len)) { + for (i = 0; i < s.map_len; i++) { + len = strlen(s.map[i].from); + if (!strncmp(realtarget, s.map[i].from, len)) { /* match canonical host if vhosts are enabled */ - if (vhosts && strcmp(map[i].vhost, vhostmatch)) { + if (s.vhost && strcmp(s.map[i].chost, vhostmatch)) { continue; } /* swap out target prefix */ - if (snprintf(realtarget, sizeof(realtarget), "%s%s", map[i].to, - realtarget + len) >= sizeof(realtarget)) { + if (snprintf(tmptarget, sizeof(tmptarget), "%s%s", s.map[i].to, + realtarget + len) >= sizeof(tmptarget)) { return http_send_status(fd, S_REQUEST_TOO_LARGE); } + memcpy(realtarget, tmptarget, sizeof(realtarget)); break; } } @@ -398,16 +399,17 @@ http_send_response(int fd, struct request *r) } /* redirect if targets differ, host is non-canonical or we prefixed */ - if (strcmp(r->target, realtarget) || (vhosts && vhostmatch && + if (strcmp(r->target, realtarget) || (s.vhost && vhostmatch && strcmp(r->field[REQ_HOST], vhostmatch))) { /* do we need to add a port to the Location? */ - hasport = strcmp(port, "80"); + hasport = s.port && strcmp(s.port, "80"); /* RFC 2732 specifies to use brackets for IPv6-addresses in * URLs, so we need to check if our host is one and honor that * later when we fill the "Location"-field */ if ((ipv6host = inet_pton(AF_INET6, r->field[REQ_HOST][0] ? - r->field[REQ_HOST] : host, &res)) < 0) { + r->field[REQ_HOST] : s.host ? s.host : + "localhost", &res)) < 0) { return http_send_status(fd, S_INTERNAL_SERVER_ERROR); } @@ -424,10 +426,11 @@ http_send_response(int fd, struct request *r) S_MOVED_PERMANENTLY, status_str[S_MOVED_PERMANENTLY], timestamp(time(NULL), t), ipv6host ? "[" : "", - r->field[REQ_HOST][0] ? (vhosts && vhostmatch) ? - vhostmatch : r->field[REQ_HOST] : host, + r->field[REQ_HOST][0] ? (s.vhost && vhostmatch) ? + vhostmatch : r->field[REQ_HOST] : s.host ? + s.host : "localhost", ipv6host ? "]" : "", hasport ? ":" : "", - hasport ? port : "", tmptarget) < 0) { + hasport ? s.port : "", tmptarget) < 0) { return S_REQUEST_TIMEOUT; } @@ -437,16 +440,16 @@ http_send_response(int fd, struct request *r) if (S_ISDIR(st.st_mode)) { /* append docindex to target */ if (snprintf(realtarget, sizeof(realtarget), "%s%s", - r->target, docindex) >= sizeof(realtarget)) { + r->target, s.docindex) >= sizeof(realtarget)) { return http_send_status(fd, S_REQUEST_TOO_LARGE); } /* stat the docindex, which must be a regular file */ if (stat(RELPATH(realtarget), &st) < 0 || !S_ISREG(st.st_mode)) { - if (listdirs) { + if (s.listdirs) { /* remove index suffix and serve dir */ realtarget[strlen(realtarget) - - strlen(docindex)] = '\0'; + strlen(s.docindex)] = '\0'; return resp_dir(fd, RELPATH(realtarget), r); } else { /* reject */ diff --git a/main.c b/main.c index 28b5163..7bcaeed 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,7 @@ /* See LICENSE file for copyright and license details. */ #include #include +#include #include #include #include @@ -19,8 +20,6 @@ #include "sock.h" #include "util.h" -#include "config.h" - static char *udsname; static void @@ -94,8 +93,12 @@ handlesignals(void(*hdl)(int)) static void usage(void) { - die("usage: %s [-l | -L] [-v | -V] [[[-h host] [-p port]] | [-U sockfile]] " - "[-d dir] [-u user] [-g group]", argv0); + const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] " + "[-i index] [-v vhost] ... [-m map] ..."; + + die("usage: %s -h host -p port %s\n" + " %s -U socket [-p port] %s", argv0, + opts, argv0, opts); } int @@ -108,37 +111,86 @@ main(int argc, char *argv[]) pid_t cpid, wpid, spid; socklen_t in_sa_len; int i, insock, status = 0, infd; + const char *err; + char *tok; + + /* defaults */ + int maxnprocs = 512; + char *servedir = "."; + char *user = "nobody"; + char *group = "nogroup"; + + s.host = s.port = NULL; + s.vhost = NULL; + s.map = NULL; + s.vhost_len = s.map_len = 0; + s.docindex = "index.html"; + s.listdirs = 0; ARGBEGIN { - case 'd': - servedir = EARGF(usage()); - break; - case 'g': - group = EARGF(usage()); - break; case 'h': - host = EARGF(usage()); - break; - case 'l': - listdirs = 0; - break; - case 'L': - listdirs = 1; + s.host = EARGF(usage()); break; case 'p': - port = EARGF(usage()); - break; - case 'u': - user = EARGF(usage()); + s.port = EARGF(usage()); break; case 'U': udsname = EARGF(usage()); break; - case 'v': - vhosts = 0; + case 'u': + user = EARGF(usage()); break; - case 'V': - vhosts = 1; + case 'g': + group = EARGF(usage()); + break; + case 'n': + maxnprocs = strtonum(EARGF(usage()), 1, INT_MAX, &err); + if (err) { + die("strtonum '%s': %s", EARGF(usage()), err); + } + break; + case 'd': + servedir = EARGF(usage()); + break; + case 'l': + s.listdirs = 1; + break; + case 'i': + s.docindex = EARGF(usage()); + if (strchr(s.docindex, '/')) { + die("The document index must not contain '/'"); + } + break; + case 'v': + if (!(tok = strdup(EARGF(usage())))) { + die("strdup:"); + } + if (!(s.vhost = reallocarray(s.vhost, s.vhost_len++, + sizeof(struct vhost)))) { + die("reallocarray:"); + } + if (!(s.vhost[s.vhost_len - 1].name = strtok(tok, " ")) || + !(s.vhost[s.vhost_len - 1].regex = strtok(NULL, " ")) || + !(s.vhost[s.vhost_len - 1].dir = strtok(NULL, " ")) || + !(s.vhost[s.vhost_len - 1].prefix = strtok(NULL, " ")) || + strtok(NULL, "")) { + usage(); + } + break; + case 'm': + if (!(tok = strdup(EARGF(usage())))) { + die("strdup:"); + } + if (!(s.map = reallocarray(s.map, s.map_len++, + sizeof(struct map)))) { + die("reallocarray:"); + } + if (!(s.map[s.map_len - 1].chost = strtok(tok, " ")) || + !(s.map[s.map_len - 1].from = strtok(NULL, " ")) || + !(s.map[s.map_len - 1].to = strtok(NULL, " ")) || + strtok(NULL, "")) { + usage(); + } break; default: usage(); @@ -148,19 +200,22 @@ main(int argc, char *argv[]) usage(); } + /* allow either host or UNIX-domain socket, force port with host */ + if ((s.host && udsname) || (s.host && !s.port)) { + usage(); + } + if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) { die("UNIX-domain socket: %s", errno ? strerror(errno) : "file exists"); } /* compile and check the supplied vhost regexes */ - if (vhosts) { - for (i = 0; i < LEN(vhost); i++) { - if (regcomp(&vhost[i].re, vhost[i].regex, - REG_EXTENDED | REG_ICASE | REG_NOSUB)) { - die("regcomp '%s': invalid regex", - vhost[i].regex); - } + for (i = 0; i < s.vhost_len; i++) { + if (regcomp(&s.vhost[i].re, s.vhost[i].regex, + REG_EXTENDED | REG_ICASE | REG_NOSUB)) { + die("regcomp '%s': invalid regex", + s.vhost[i].regex); } } @@ -186,7 +241,7 @@ main(int argc, char *argv[]) /* bind socket */ insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) : - sock_get_ips(host, port); + sock_get_ips(s.host, s.port); switch (cpid = fork()) { case -1: diff --git a/util.c b/util.c index 1529767..1fe9db8 100644 --- a/util.c +++ b/util.c @@ -2,14 +2,17 @@ #include #include #include +#include #include #include #include +#include #include #include "util.h" char *argv0; +struct server s = { 0 }; static void verr(const char *fmt, va_list ap) @@ -50,6 +53,14 @@ die(const char *fmt, ...) exit(1); } +char * +timestamp(time_t t, char buf[TIMESTAMP_LEN]) +{ + strftime(buf, TIMESTAMP_LEN, "%a, %d %b %Y %T GMT", gmtime(&t)); + + return buf; +} + #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 @@ -93,10 +104,19 @@ strtonum(const char *numstr, long long minval, long long maxval, return ll; } -char * -timestamp(time_t t, char buf[TIMESTAMP_LEN]) -{ - strftime(buf, TIMESTAMP_LEN, "%a, %d %b %Y %T GMT", gmtime(&t)); +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) - return buf; +void * +reallocarray(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + return realloc(optr, size * nmemb); } diff --git a/util.h b/util.h index 7aa8584..1f9e107 100644 --- a/util.h +++ b/util.h @@ -2,10 +2,38 @@ #ifndef UTIL_H #define UTIL_H +#include +#include #include #include "arg.h" +/* main server struct */ +struct vhost { + char *name; + char *regex; + char *dir; + char *prefix; + regex_t re; +}; + +struct map { + char *chost; + char *from; + char *to; +}; + +extern struct server { + char *host; + char *port; + char *docindex; + int listdirs; + struct vhost *vhost; + size_t vhost_len; + struct map *map; + size_t map_len; +} s; + #undef MIN #define MIN(x,y) ((x) < (y) ? (x) : (y)) #undef MAX @@ -18,10 +46,11 @@ extern char *argv0; void warn(const char *, ...); void die(const char *, ...); -long long strtonum(const char *, long long, long long, const char **); - #define TIMESTAMP_LEN 30 char *timestamp(time_t, char buf[TIMESTAMP_LEN]); +void *reallocarray(void *, size_t, size_t); +long long strtonum(const char *, long long, long long, const char **); + #endif /* UTIL_H */