Add vhost support

As given in the config, we match a regex of hosts to a canonical host
which points to an internal directory.
Regexes are compiled on initialization, so we can error out early.
The rest is just modifications to use relative directories rather than
absolute ones, as we chdir() into the vhost directories dynamically.

Given we normalize the targets beforehand, there is no danger of
malformed requests escaping the vhost-context.
This commit is contained in:
Laslo Hunhold 2017-07-11 12:34:55 +02:00
parent 9e9facc9bf
commit 000553d8c5
2 changed files with 62 additions and 13 deletions

View file

@ -2,14 +2,25 @@ static const char *host = "localhost";
static const char *port = "80";
static const char *servedir = ".";
static const char *docindex = "index.html";
static int listdirs = 1;
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
static const struct {
char *name;
char *regex;
char *dir;
} vhost[] = {
{ "example.org", "^(www.)example.org$", "/example.org" },
};
static const struct {
char *ext;
char *type;

62
quark.c
View file

@ -16,6 +16,7 @@
#include <limits.h>
#include <netdb.h>
#include <pwd.h>
#include <regex.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
@ -32,6 +33,10 @@ char *argv0;
#undef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#undef LEN
#define LEN(x) (sizeof (x) / sizeof *(x))
#undef RELPATH
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
#define TIMESTAMP_LEN 30
@ -97,6 +102,9 @@ static char *status_str[] = {
[S_VERSION_NOT_SUPPORTED] = "HTTP Version not supported",
};
/* vhost regex compilate */
static regex_t vhost_regex[LEN(vhost)];
long long strtonum(const char *, long long, long long, const char **);
static char *
@ -555,6 +563,23 @@ sendresponse(int fd, struct request *r)
char *p, *q, *mime;
const char *err;
/* match vhost */
if (vhosts) {
for (i = 0; i < LEN(vhost); i++) {
if (!regexec(&vhost_regex[i], r->field[REQ_HOST], 0,
NULL, 0)) {
break;
}
}
if (i < LEN(vhost)) {
/* switch to vhost directory */
if (chdir(vhost[i].dir) < 0) {
return sendstatus(fd, (errno == EACCES) ?
S_FORBIDDEN : S_NOT_FOUND);
}
}
}
/* normalize target */
memcpy(realtarget, r->target, sizeof(realtarget));
if (normabspath(realtarget)) {
@ -567,7 +592,7 @@ sendresponse(int fd, struct request *r)
}
/* stat the target */
if (stat(realtarget, &st) < 0) {
if (stat(RELPATH(realtarget), &st) < 0) {
return sendstatus(fd, (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND);
}
@ -583,8 +608,9 @@ sendresponse(int fd, struct request *r)
}
}
/* redirect if targets differ */
if (strcmp(r->target, realtarget)) {
/* redirect if targets differ or host is non-canonical */
if (strcmp(r->target, realtarget) || (vhosts && r->field[REQ_HOST][0] &&
strcmp(r->field[REQ_HOST], vhost[i].name))) {
/* do we need to add a port to the Location? */
hasport = strcmp(port, "80");
@ -609,7 +635,8 @@ sendresponse(int fd, struct request *r)
S_MOVED_PERMANENTLY,
status_str[S_MOVED_PERMANENTLY],
timestamp(time(NULL), t), ipv6host ? "[" : "",
r->field[REQ_HOST][0] ? r->field[REQ_HOST] : host,
r->field[REQ_HOST][0] ? (vhosts && i < LEN(vhost)) ?
vhost[i].name : r->field[REQ_HOST] : host,
ipv6host ? "]" : "", hasport ? ":" : "",
hasport ? port : "", tmptarget) < 0) {
return S_REQUEST_TIMEOUT;
@ -626,12 +653,12 @@ sendresponse(int fd, struct request *r)
}
/* stat the docindex, which must be a regular file */
if (stat(realtarget, &st) < 0 || !S_ISREG(st.st_mode)) {
if (stat(RELPATH(realtarget), &st) < 0 || !S_ISREG(st.st_mode)) {
if (listdirs) {
/* remove index suffix and serve dir */
realtarget[strlen(realtarget) -
strlen(docindex)] = '\0';
return senddir(fd, realtarget, r);
return senddir(fd, RELPATH(realtarget), r);
} else {
/* reject */
if (!S_ISREG(st.st_mode) || errno == EACCES) {
@ -721,7 +748,7 @@ sendresponse(int fd, struct request *r)
}
}
return sendfile(fd, realtarget, r, &st, mime, lower, upper);
return sendfile(fd, RELPATH(realtarget), r, &st, mime, lower, upper);
}
static void
@ -782,14 +809,14 @@ serve(int insock)
inet_ntop(AF_INET,
&(((struct sockaddr_in *)&in_sa)->sin_addr),
inip4, sizeof(inip4));
printf("%s\t%s\t%d\t%s\n", tstmp, inip4,
status, r.target);
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inip4,
status, r.field[REQ_HOST], r.target);
} else {
inet_ntop(AF_INET6,
&(((struct sockaddr_in6*)&in_sa)->sin6_addr),
inip6, sizeof(inip6));
printf("%s\t%s\t%d\t%s\n", tstmp, inip6,
status, r.target);
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inip6,
status, r.field[REQ_HOST], r.target);
}
/* clean up and finish */
@ -902,7 +929,7 @@ main(int argc, char *argv[])
struct passwd *pwd = NULL;
struct group *grp = NULL;
struct rlimit rlim;
int insock;
int i, insock;
char *udsname = NULL;
ARGBEGIN {
@ -941,6 +968,17 @@ main(int argc, char *argv[])
usage();
}
/* compile and check the supplied vhost regexes */
if (vhosts) {
for (i = 0; i < LEN(vhost); i++) {
if (regcomp(&vhost_regex[i], vhost[i].regex,
REG_ICASE | REG_NOSUB)) {
die("%s: regcomp '%s': invalid regex\n", argv0,
vhost[i].regex);
}
}
}
/* reap children automatically */
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
fprintf(stderr, "%s: signal: Failed to set SIG_IGN on"