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:
parent
b40b11a40e
commit
6b55e36036
6 changed files with 171 additions and 97 deletions
1
LICENSE
1
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>
|
||||
|
|
34
config.def.h
34
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;
|
||||
|
|
49
http.c
49
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 */
|
||||
|
|
121
main.c
121
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());
|
||||
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:
|
||||
|
|
30
util.c
30
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);
|
||||
}
|
||||
|
|
33
util.h
33
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 */
|
||||
|
|
Loading…
Reference in a new issue