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.
This commit is contained in:
Laslo Hunhold 2018-03-05 00:14:25 +01:00
parent b40b11a40e
commit 6b55e36036
6 changed files with 171 additions and 97 deletions

View file

@ -4,6 +4,7 @@ Copyright 2016-2018 Laslo Hunhold <dev@frign.de>
Copyright 2004 Ted Unangst <tedu@openbsd.org> Copyright 2004 Ted Unangst <tedu@openbsd.org>
Copyright 2004 Todd C. Miller <Todd.Miller@courtesan.com> 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 Hiltjo Posthuma <hiltjo@codemadness.org>
Copyright 2017 Quentin Rameau <quinq@fifth.space> Copyright 2017 Quentin Rameau <quinq@fifth.space>
Copyright 2018 Josuah Demangeon <mail@josuah.net> Copyright 2018 Josuah Demangeon <mail@josuah.net>

View file

@ -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 HEADER_MAX 4096
#define FIELD_MAX 200 #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 */ /* mime-types */
static const struct { static const struct {
char *ext; char *ext;

49
http.c
View file

@ -325,46 +325,47 @@ http_send_response(int fd, struct request *r)
/* match vhost */ /* match vhost */
vhostmatch = NULL; vhostmatch = NULL;
if (vhosts) { if (s.vhost) {
for (i = 0; i < LEN(vhost); i++) { for (i = 0; i < s.vhost_len; i++) {
/* switch to vhost directory if there is a match */ /* 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)) { NULL, 0)) {
if (chdir(vhost[i].dir) < 0) { if (chdir(s.vhost[i].dir) < 0) {
return http_send_status(fd, (errno == EACCES) ? return http_send_status(fd, (errno == EACCES) ?
S_FORBIDDEN : S_NOT_FOUND); S_FORBIDDEN : S_NOT_FOUND);
} }
vhostmatch = vhost[i].name; vhostmatch = s.vhost[i].name;
break; break;
} }
} }
if (i == LEN(vhost)) { if (i == s.vhost_len) {
return http_send_status(fd, S_NOT_FOUND); return http_send_status(fd, S_NOT_FOUND);
} }
/* if we have a vhost prefix, prepend it to the target */ /* 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", 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); return http_send_status(fd, S_REQUEST_TOO_LARGE);
} }
} }
} }
/* apply target prefix mapping */ /* apply target prefix mapping */
for (i = 0; i < LEN(map); i++) { for (i = 0; i < s.map_len; i++) {
len = strlen(map[i].from); len = strlen(s.map[i].from);
if (!strncmp(realtarget, map[i].from, len)) { if (!strncmp(realtarget, s.map[i].from, len)) {
/* match canonical host if vhosts are enabled */ /* match canonical host if vhosts are enabled */
if (vhosts && strcmp(map[i].vhost, vhostmatch)) { if (s.vhost && strcmp(s.map[i].chost, vhostmatch)) {
continue; continue;
} }
/* swap out target prefix */ /* swap out target prefix */
if (snprintf(realtarget, sizeof(realtarget), "%s%s", map[i].to, if (snprintf(tmptarget, sizeof(tmptarget), "%s%s", s.map[i].to,
realtarget + len) >= sizeof(realtarget)) { realtarget + len) >= sizeof(tmptarget)) {
return http_send_status(fd, S_REQUEST_TOO_LARGE); return http_send_status(fd, S_REQUEST_TOO_LARGE);
} }
memcpy(realtarget, tmptarget, sizeof(realtarget));
break; break;
} }
} }
@ -398,16 +399,17 @@ http_send_response(int fd, struct request *r)
} }
/* redirect if targets differ, host is non-canonical or we prefixed */ /* 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))) { strcmp(r->field[REQ_HOST], vhostmatch))) {
/* do we need to add a port to the Location? */ /* 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 /* RFC 2732 specifies to use brackets for IPv6-addresses in
* URLs, so we need to check if our host is one and honor that * URLs, so we need to check if our host is one and honor that
* later when we fill the "Location"-field */ * later when we fill the "Location"-field */
if ((ipv6host = inet_pton(AF_INET6, r->field[REQ_HOST][0] ? 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); return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
} }
@ -424,10 +426,11 @@ http_send_response(int fd, struct request *r)
S_MOVED_PERMANENTLY, S_MOVED_PERMANENTLY,
status_str[S_MOVED_PERMANENTLY], status_str[S_MOVED_PERMANENTLY],
timestamp(time(NULL), t), ipv6host ? "[" : "", timestamp(time(NULL), t), ipv6host ? "[" : "",
r->field[REQ_HOST][0] ? (vhosts && vhostmatch) ? r->field[REQ_HOST][0] ? (s.vhost && vhostmatch) ?
vhostmatch : r->field[REQ_HOST] : host, vhostmatch : r->field[REQ_HOST] : s.host ?
s.host : "localhost",
ipv6host ? "]" : "", hasport ? ":" : "", ipv6host ? "]" : "", hasport ? ":" : "",
hasport ? port : "", tmptarget) < 0) { hasport ? s.port : "", tmptarget) < 0) {
return S_REQUEST_TIMEOUT; return S_REQUEST_TIMEOUT;
} }
@ -437,16 +440,16 @@ http_send_response(int fd, struct request *r)
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
/* append docindex to target */ /* append docindex to target */
if (snprintf(realtarget, sizeof(realtarget), "%s%s", 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); return http_send_status(fd, S_REQUEST_TOO_LARGE);
} }
/* stat the docindex, which must be a regular file */ /* stat the docindex, which must be a regular file */
if (stat(RELPATH(realtarget), &st) < 0 || !S_ISREG(st.st_mode)) { if (stat(RELPATH(realtarget), &st) < 0 || !S_ISREG(st.st_mode)) {
if (listdirs) { if (s.listdirs) {
/* remove index suffix and serve dir */ /* remove index suffix and serve dir */
realtarget[strlen(realtarget) - realtarget[strlen(realtarget) -
strlen(docindex)] = '\0'; strlen(s.docindex)] = '\0';
return resp_dir(fd, RELPATH(realtarget), r); return resp_dir(fd, RELPATH(realtarget), r);
} else { } else {
/* reject */ /* reject */

117
main.c
View file

@ -1,6 +1,7 @@
/* See LICENSE file for copyright and license details. */ /* See LICENSE file for copyright and license details. */
#include <errno.h> #include <errno.h>
#include <grp.h> #include <grp.h>
#include <limits.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <pwd.h> #include <pwd.h>
#include <regex.h> #include <regex.h>
@ -19,8 +20,6 @@
#include "sock.h" #include "sock.h"
#include "util.h" #include "util.h"
#include "config.h"
static char *udsname; static char *udsname;
static void static void
@ -94,8 +93,12 @@ handlesignals(void(*hdl)(int))
static void static void
usage(void) usage(void)
{ {
die("usage: %s [-l | -L] [-v | -V] [[[-h host] [-p port]] | [-U sockfile]] " const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] "
"[-d dir] [-u user] [-g group]", argv0); "[-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 int
@ -108,37 +111,86 @@ main(int argc, char *argv[])
pid_t cpid, wpid, spid; pid_t cpid, wpid, spid;
socklen_t in_sa_len; socklen_t in_sa_len;
int i, insock, status = 0, infd; 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 { ARGBEGIN {
case 'd':
servedir = EARGF(usage());
break;
case 'g':
group = EARGF(usage());
break;
case 'h': case 'h':
host = EARGF(usage()); s.host = EARGF(usage());
break;
case 'l':
listdirs = 0;
break;
case 'L':
listdirs = 1;
break; break;
case 'p': case 'p':
port = EARGF(usage()); s.port = EARGF(usage());
break;
case 'u':
user = EARGF(usage());
break; break;
case 'U': case 'U':
udsname = EARGF(usage()); udsname = EARGF(usage());
break; break;
case 'v': case 'u':
vhosts = 0; user = EARGF(usage());
break; break;
case 'V': case 'g':
vhosts = 1; 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; break;
default: default:
usage(); usage();
@ -148,19 +200,22 @@ main(int argc, char *argv[])
usage(); 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)) { if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) {
die("UNIX-domain socket: %s", errno ? die("UNIX-domain socket: %s", errno ?
strerror(errno) : "file exists"); strerror(errno) : "file exists");
} }
/* compile and check the supplied vhost regexes */ /* compile and check the supplied vhost regexes */
if (vhosts) { for (i = 0; i < s.vhost_len; i++) {
for (i = 0; i < LEN(vhost); i++) { if (regcomp(&s.vhost[i].re, s.vhost[i].regex,
if (regcomp(&vhost[i].re, vhost[i].regex,
REG_EXTENDED | REG_ICASE | REG_NOSUB)) { REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
die("regcomp '%s': invalid regex", die("regcomp '%s': invalid regex",
vhost[i].regex); s.vhost[i].regex);
}
} }
} }
@ -186,7 +241,7 @@ main(int argc, char *argv[])
/* bind socket */ /* bind socket */
insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) : 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()) { switch (cpid = fork()) {
case -1: case -1:

30
util.c
View file

@ -2,14 +2,17 @@
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/types.h>
#include <time.h> #include <time.h>
#include "util.h" #include "util.h"
char *argv0; char *argv0;
struct server s = { 0 };
static void static void
verr(const char *fmt, va_list ap) verr(const char *fmt, va_list ap)
@ -50,6 +53,14 @@ die(const char *fmt, ...)
exit(1); 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 INVALID 1
#define TOOSMALL 2 #define TOOSMALL 2
#define TOOLARGE 3 #define TOOLARGE 3
@ -93,10 +104,19 @@ strtonum(const char *numstr, long long minval, long long maxval,
return ll; return ll;
} }
char * /*
timestamp(time_t t, char buf[TIMESTAMP_LEN]) * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
{ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
strftime(buf, TIMESTAMP_LEN, "%a, %d %b %Y %T GMT", gmtime(&t)); */
#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);
} }

33
util.h
View file

@ -2,10 +2,38 @@
#ifndef UTIL_H #ifndef UTIL_H
#define UTIL_H #define UTIL_H
#include <regex.h>
#include <stddef.h>
#include <time.h> #include <time.h>
#include "arg.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 #undef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y)) #define MIN(x,y) ((x) < (y) ? (x) : (y))
#undef MAX #undef MAX
@ -18,10 +46,11 @@ extern char *argv0;
void warn(const char *, ...); void warn(const char *, ...);
void die(const char *, ...); void die(const char *, ...);
long long strtonum(const char *, long long, long long, const char **);
#define TIMESTAMP_LEN 30 #define TIMESTAMP_LEN 30
char *timestamp(time_t, char buf[TIMESTAMP_LEN]); 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 */ #endif /* UTIL_H */