Introduce flag-centric usage - quark - quark web server | |
git clone git://git.suckless.org/quark | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit 6b55e36036d52c07803824048080a1e350fe6c8c | |
parent b40b11a40e9b8b85c5d95ab674f0df4c35d36f62 | |
Author: Laslo Hunhold <[email protected]> | |
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: | |
M LICENSE | 1 + | |
M config.def.h | 34 -----------------------------… | |
M http.c | 49 ++++++++++++++++-------------… | |
M main.c | 117 +++++++++++++++++++++++------… | |
M util.c | 30 +++++++++++++++++++++++++----- | |
M 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 <[email protected]> | |
Copyright 2004 Ted Unangst <[email protected]> | |
Copyright 2004 Todd C. Miller <[email protected]> | |
+Copyright 2008 Otto Moerbeek <[email protected]> | |
Copyright 2017 Hiltjo Posthuma <[email protected]> | |
Copyright 2017 Quentin Rameau <[email protected]> | |
Copyright 2018 Josuah Demangeon <[email protected]> | |
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 … | |
- { "example.org", "^(www\\.)?example\\.org$", "/example.org", … | |
- { "example.org", "^old\\.example\\.org$", "/", … | |
-}; | |
- | |
-/* 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 == … | |
S_FORBIDDEN : … | |
} | |
- 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(realtarge… | |
return http_send_status(fd, S_REQUEST_TOO_LARG… | |
} | |
} | |
} | |
/* 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", m… | |
- realtarget + len) >= sizeof(realtarget)) { | |
+ if (snprintf(tmptarget, sizeof(tmptarget), "%s%s", s.m… | |
+ realtarget + len) >= sizeof(tmptarget)) { | |
return http_send_status(fd, S_REQUEST_TOO_LARG… | |
} | |
+ 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)) < … | |
+ 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 sockfi… | |
- "[-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 m… | |
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 */ |