quark

quark web server
git clone git://git.suckless.org/quark
Log | Files | Refs | LICENSE

commit 6b55e36036d52c07803824048080a1e350fe6c8c
parent b40b11a40e9b8b85c5d95ab674f0df4c35d36f62
Author: Laslo Hunhold <dev@frign.de>
Date:   Mon,  5 Mar 2018 00:14:25 +0100

Introduce flag-centric usage

The config.h-interface has proven to be very effective for a lot of
suckless tools, but it just does not make too much sense for a web
server like quark.

 $ quark

If you run multiple instances of it, you want to see in the command line
(or top) what it does, and given the amount of options it's logical to
just express them as options given in the command line.
It also is a problem if you can modify quark via the config.h,
contradicting the manual. Just saying "Well, then don't touch config.h"
is also not good, as the vhost and map options were only exposed via
this interface.

What is left in config.h are mime-types and two constants relating to
the incoming HTTP-header-limits.

In order to introduce these changes, some structs and safe utility
functions were added and imported from OpenBSD respectively.

Diffstat:
LICENSE | 1+
config.def.h | 34----------------------------------
http.c | 49++++++++++++++++++++++++++-----------------------
main.c | 117++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
util.c | 30+++++++++++++++++++++++++-----
util.h | 33+++++++++++++++++++++++++++++++--
6 files changed, 169 insertions(+), 95 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -4,6 +4,7 @@ Copyright 2016-2018 Laslo Hunhold <dev@frign.de> Copyright 2004 Ted Unangst <tedu@openbsd.org> Copyright 2004 Todd C. Miller <Todd.Miller@courtesan.com> +Copyright 2008 Otto Moerbeek <otto@drijf.net> Copyright 2017 Hiltjo Posthuma <hiltjo@codemadness.org> Copyright 2017 Quentin Rameau <quinq@fifth.space> Copyright 2018 Josuah Demangeon <mail@josuah.net> diff --git 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 @@ -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 @@ -1,6 +1,7 @@ /* See LICENSE file for copyright and license details. */ #include <errno.h> #include <grp.h> +#include <limits.h> #include <netinet/in.h> #include <pwd.h> #include <regex.h> @@ -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()); + s.port = EARGF(usage()); + break; + case 'U': + udsname = EARGF(usage()); break; case 'u': user = EARGF(usage()); break; - case 'U': - udsname = EARGF(usage()); + 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': - vhosts = 0; + 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 'V': - vhosts = 1; + 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 @@ -2,14 +2,17 @@ #include <errno.h> #include <limits.h> #include <stdarg.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/types.h> #include <time.h> #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 @@ -2,10 +2,38 @@ #ifndef UTIL_H #define UTIL_H +#include <regex.h> +#include <stddef.h> #include <time.h> #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 */