2018-02-04 20:27:33 +00:00
|
|
|
/* See LICENSE file for copyright and license details. */
|
|
|
|
#include <errno.h>
|
|
|
|
#include <grp.h>
|
2018-03-04 23:14:25 +00:00
|
|
|
#include <limits.h>
|
2018-02-04 20:27:33 +00:00
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <regex.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <sys/resource.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2020-08-28 21:29:54 +00:00
|
|
|
#include "data.h"
|
2018-02-04 20:27:33 +00:00
|
|
|
#include "http.h"
|
|
|
|
#include "sock.h"
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
static char *udsname;
|
|
|
|
|
|
|
|
static void
|
2020-08-29 11:02:51 +00:00
|
|
|
logmsg(const struct connection *c)
|
2018-02-04 20:27:33 +00:00
|
|
|
{
|
2020-08-29 11:02:51 +00:00
|
|
|
char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
|
2018-04-02 23:23:00 +00:00
|
|
|
char tstmp[21];
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2020-08-29 11:02:51 +00:00
|
|
|
/* create timestamp */
|
|
|
|
if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
|
|
|
|
gmtime(&(time_t){time(NULL)}))) {
|
|
|
|
warn("strftime: Exceeded buffer capacity");
|
|
|
|
/* continue anyway (we accept the truncation) */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* generate address-string */
|
|
|
|
if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) {
|
|
|
|
warn("sock_get_inaddr_str: Couldn't generate adress-string");
|
|
|
|
inaddr_str[0] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr_str, c->res.status,
|
|
|
|
c->req.field[REQ_HOST], c->req.uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
serve(struct connection *c, const struct server *srv)
|
|
|
|
{
|
|
|
|
enum status s;
|
|
|
|
|
2018-02-05 16:15:29 +00:00
|
|
|
/* set connection timeout */
|
2020-08-29 11:02:51 +00:00
|
|
|
if (sock_set_timeout(c->fd, 30)) {
|
2018-02-05 16:15:29 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2018-02-05 16:15:29 +00:00
|
|
|
/* handle request */
|
2020-08-29 11:02:51 +00:00
|
|
|
if ((s = http_recv_header(c->fd, c->header, LEN(c->header), &c->off)) ||
|
|
|
|
(s = http_parse_header(c->header, &c->req))) {
|
|
|
|
http_prepare_error_response(&c->req, &c->res, s);
|
2020-08-28 20:48:32 +00:00
|
|
|
} else {
|
2020-08-29 11:02:51 +00:00
|
|
|
http_prepare_response(&c->req, &c->res, srv);
|
2020-08-28 20:32:47 +00:00
|
|
|
}
|
|
|
|
|
2020-08-29 11:02:51 +00:00
|
|
|
if ((s = http_send_header(c->fd, &c->res)) ||
|
|
|
|
(s = http_send_body(c->fd, &c->res, &c->req))) {
|
|
|
|
c->res.status = s;
|
2018-02-05 16:15:29 +00:00
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2020-08-29 11:02:51 +00:00
|
|
|
logmsg(c);
|
2018-02-05 16:15:29 +00:00
|
|
|
cleanup:
|
|
|
|
/* clean up and finish */
|
2020-08-29 11:02:51 +00:00
|
|
|
shutdown(c->fd, SHUT_RD);
|
|
|
|
shutdown(c->fd, SHUT_WR);
|
|
|
|
close(c->fd);
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cleanup(void)
|
|
|
|
{
|
|
|
|
if (udsname)
|
|
|
|
sock_rem_uds(udsname);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
sigcleanup(int sig)
|
|
|
|
{
|
|
|
|
cleanup();
|
|
|
|
kill(0, sig);
|
|
|
|
_exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
handlesignals(void(*hdl)(int))
|
|
|
|
{
|
2019-05-30 21:15:47 +00:00
|
|
|
struct sigaction sa = {
|
|
|
|
.sa_handler = hdl,
|
|
|
|
};
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
|
|
sigaction(SIGHUP, &sa, NULL);
|
|
|
|
sigaction(SIGINT, &sa, NULL);
|
|
|
|
sigaction(SIGQUIT, &sa, NULL);
|
|
|
|
}
|
|
|
|
|
2019-02-24 20:50:39 +00:00
|
|
|
static int
|
|
|
|
spacetok(const char *s, char **t, size_t tlen)
|
|
|
|
{
|
|
|
|
const char *tok;
|
|
|
|
size_t i, j, toki, spaces;
|
|
|
|
|
|
|
|
/* fill token-array with NULL-pointers */
|
|
|
|
for (i = 0; i < tlen; i++) {
|
|
|
|
t[i] = NULL;
|
|
|
|
}
|
|
|
|
toki = 0;
|
|
|
|
|
|
|
|
/* don't allow NULL string or leading spaces */
|
|
|
|
if (!s || *s == ' ') {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
start:
|
|
|
|
/* skip spaces */
|
|
|
|
for (; *s == ' '; s++)
|
|
|
|
;
|
|
|
|
|
|
|
|
/* don't allow trailing spaces */
|
|
|
|
if (*s == '\0') {
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* consume token */
|
|
|
|
for (tok = s, spaces = 0; ; s++) {
|
|
|
|
if (*s == '\\' && *(s + 1) == ' ') {
|
|
|
|
spaces++;
|
|
|
|
s++;
|
|
|
|
continue;
|
|
|
|
} else if (*s == ' ') {
|
|
|
|
/* end of token */
|
|
|
|
goto token;
|
|
|
|
} else if (*s == '\0') {
|
|
|
|
/* end of string */
|
|
|
|
goto token;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
token:
|
|
|
|
if (toki >= tlen) {
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (!(t[toki] = malloc(s - tok - spaces + 1))) {
|
|
|
|
die("malloc:");
|
|
|
|
}
|
|
|
|
for (i = 0, j = 0; j < s - tok - spaces + 1; i++, j++) {
|
|
|
|
if (tok[i] == '\\' && tok[i + 1] == ' ') {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
t[toki][j] = tok[i];
|
|
|
|
}
|
|
|
|
t[toki][s - tok - spaces] = '\0';
|
|
|
|
toki++;
|
|
|
|
|
|
|
|
if (*s == ' ') {
|
|
|
|
s++;
|
|
|
|
goto start;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
for (i = 0; i < tlen; i++) {
|
|
|
|
free(t[i]);
|
|
|
|
t[i] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
static void
|
|
|
|
usage(void)
|
|
|
|
{
|
2018-03-04 23:14:25 +00:00
|
|
|
const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] "
|
2018-03-05 08:51:29 +00:00
|
|
|
"[-i file] [-v vhost] ... [-m map] ...";
|
2018-03-04 23:14:25 +00:00
|
|
|
|
2020-04-21 15:04:37 +00:00
|
|
|
die("usage: %s -p port [-h host] %s\n"
|
2018-03-05 08:51:29 +00:00
|
|
|
" %s -U file [-p port] %s", argv0,
|
2018-03-04 23:14:25 +00:00
|
|
|
opts, argv0, opts);
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
struct group *grp = NULL;
|
2018-02-05 16:15:29 +00:00
|
|
|
struct passwd *pwd = NULL;
|
2018-02-04 20:27:33 +00:00
|
|
|
struct rlimit rlim;
|
2020-08-23 09:02:38 +00:00
|
|
|
struct server srv = {
|
2020-08-17 09:37:25 +00:00
|
|
|
.docindex = "index.html",
|
|
|
|
};
|
2018-03-04 23:30:53 +00:00
|
|
|
size_t i;
|
2020-08-29 11:02:51 +00:00
|
|
|
int insock, status = 0;
|
2018-03-04 23:14:25 +00:00
|
|
|
const char *err;
|
2019-02-24 20:50:39 +00:00
|
|
|
char *tok[4];
|
2018-03-04 23:14:25 +00:00
|
|
|
|
|
|
|
/* defaults */
|
|
|
|
int maxnprocs = 512;
|
|
|
|
char *servedir = ".";
|
|
|
|
char *user = "nobody";
|
|
|
|
char *group = "nogroup";
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
ARGBEGIN {
|
2019-02-23 12:50:59 +00:00
|
|
|
case 'd':
|
|
|
|
servedir = EARGF(usage());
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
|
|
group = EARGF(usage());
|
|
|
|
break;
|
2018-02-04 20:27:33 +00:00
|
|
|
case 'h':
|
2020-08-23 09:02:38 +00:00
|
|
|
srv.host = EARGF(usage());
|
2018-02-04 20:27:33 +00:00
|
|
|
break;
|
2019-02-23 12:50:59 +00:00
|
|
|
case 'i':
|
2020-08-23 09:02:38 +00:00
|
|
|
srv.docindex = EARGF(usage());
|
|
|
|
if (strchr(srv.docindex, '/')) {
|
2019-02-23 12:50:59 +00:00
|
|
|
die("The document index must not contain '/'");
|
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
break;
|
2019-02-23 12:50:59 +00:00
|
|
|
case 'l':
|
2020-08-23 09:02:38 +00:00
|
|
|
srv.listdirs = 1;
|
2018-02-04 20:27:33 +00:00
|
|
|
break;
|
2019-02-23 12:50:59 +00:00
|
|
|
case 'm':
|
2019-02-24 20:50:39 +00:00
|
|
|
if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) {
|
|
|
|
usage();
|
2019-02-23 12:50:59 +00:00
|
|
|
}
|
2020-08-23 09:02:38 +00:00
|
|
|
if (!(srv.map = reallocarray(srv.map, ++srv.map_len,
|
2019-02-23 12:50:59 +00:00
|
|
|
sizeof(struct map)))) {
|
|
|
|
die("reallocarray:");
|
|
|
|
}
|
2020-08-23 09:02:38 +00:00
|
|
|
srv.map[srv.map_len - 1].from = tok[0];
|
|
|
|
srv.map[srv.map_len - 1].to = tok[1];
|
|
|
|
srv.map[srv.map_len - 1].chost = tok[2];
|
2018-03-04 23:14:25 +00:00
|
|
|
break;
|
|
|
|
case 'n':
|
|
|
|
maxnprocs = strtonum(EARGF(usage()), 1, INT_MAX, &err);
|
|
|
|
if (err) {
|
|
|
|
die("strtonum '%s': %s", EARGF(usage()), err);
|
|
|
|
}
|
|
|
|
break;
|
2019-02-23 12:50:59 +00:00
|
|
|
case 'p':
|
2020-08-23 09:02:38 +00:00
|
|
|
srv.port = EARGF(usage());
|
2018-03-04 23:14:25 +00:00
|
|
|
break;
|
2019-02-23 12:50:59 +00:00
|
|
|
case 'U':
|
|
|
|
udsname = EARGF(usage());
|
2018-03-04 23:14:25 +00:00
|
|
|
break;
|
2019-02-23 12:50:59 +00:00
|
|
|
case 'u':
|
|
|
|
user = EARGF(usage());
|
2018-02-04 20:27:33 +00:00
|
|
|
break;
|
|
|
|
case 'v':
|
2019-02-24 20:50:39 +00:00
|
|
|
if (spacetok(EARGF(usage()), tok, 4) || !tok[0] || !tok[1] ||
|
|
|
|
!tok[2]) {
|
|
|
|
usage();
|
2018-03-04 23:14:25 +00:00
|
|
|
}
|
2020-08-23 09:02:38 +00:00
|
|
|
if (!(srv.vhost = reallocarray(srv.vhost, ++srv.vhost_len,
|
|
|
|
sizeof(*srv.vhost)))) {
|
2018-03-04 23:14:25 +00:00
|
|
|
die("reallocarray:");
|
|
|
|
}
|
2020-08-23 09:02:38 +00:00
|
|
|
srv.vhost[srv.vhost_len - 1].chost = tok[0];
|
|
|
|
srv.vhost[srv.vhost_len - 1].regex = tok[1];
|
|
|
|
srv.vhost[srv.vhost_len - 1].dir = tok[2];
|
|
|
|
srv.vhost[srv.vhost_len - 1].prefix = tok[3];
|
2018-02-04 20:27:33 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
usage();
|
|
|
|
} ARGEND
|
|
|
|
|
|
|
|
if (argc) {
|
|
|
|
usage();
|
|
|
|
}
|
|
|
|
|
2020-04-21 15:04:37 +00:00
|
|
|
/* can't have both host and UDS but must have one of port or UDS*/
|
2020-08-23 09:02:38 +00:00
|
|
|
if ((srv.host && udsname) || !(srv.port || udsname)) {
|
2018-03-04 23:14:25 +00:00
|
|
|
usage();
|
|
|
|
}
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) {
|
2020-08-09 21:20:06 +00:00
|
|
|
die("UNIX-domain socket '%s': %s", udsname, errno ?
|
|
|
|
strerror(errno) : "File exists");
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* compile and check the supplied vhost regexes */
|
2020-08-23 09:02:38 +00:00
|
|
|
for (i = 0; i < srv.vhost_len; i++) {
|
|
|
|
if (regcomp(&srv.vhost[i].re, srv.vhost[i].regex,
|
2018-03-04 23:14:25 +00:00
|
|
|
REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
|
|
|
|
die("regcomp '%s': invalid regex",
|
2020-08-23 09:02:38 +00:00
|
|
|
srv.vhost[i].regex);
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* raise the process limit */
|
|
|
|
rlim.rlim_cur = rlim.rlim_max = maxnprocs;
|
|
|
|
if (setrlimit(RLIMIT_NPROC, &rlim) < 0) {
|
|
|
|
die("setrlimit RLIMIT_NPROC:");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* validate user and group */
|
|
|
|
errno = 0;
|
2020-08-09 21:20:06 +00:00
|
|
|
if (!user || !(pwd = getpwnam(user))) {
|
|
|
|
die("getpwnam '%s': %s", user ? user : "null",
|
|
|
|
errno ? strerror(errno) : "Entry not found");
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
errno = 0;
|
2020-08-09 21:20:06 +00:00
|
|
|
if (!group || !(grp = getgrnam(group))) {
|
|
|
|
die("getgrnam '%s': %s", group ? group : "null",
|
|
|
|
errno ? strerror(errno) : "Entry not found");
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
2020-08-05 17:14:10 +00:00
|
|
|
/* open a new process group */
|
2020-08-05 21:27:05 +00:00
|
|
|
setpgid(0, 0);
|
2018-07-02 02:08:08 +00:00
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
handlesignals(sigcleanup);
|
|
|
|
|
|
|
|
/* bind socket */
|
|
|
|
insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) :
|
2020-08-23 09:02:38 +00:00
|
|
|
sock_get_ips(srv.host, srv.port);
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2020-08-09 20:43:46 +00:00
|
|
|
switch (fork()) {
|
2018-02-04 20:27:33 +00:00
|
|
|
case -1:
|
|
|
|
warn("fork:");
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
/* restore default handlers */
|
|
|
|
handlesignals(SIG_DFL);
|
|
|
|
|
|
|
|
/* reap children automatically */
|
|
|
|
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
|
|
|
|
die("signal: Failed to set SIG_IGN on SIGCHLD");
|
|
|
|
}
|
|
|
|
|
2019-09-23 14:56:28 +00:00
|
|
|
/* limit ourselves to reading the servedir and block further unveils */
|
|
|
|
eunveil(servedir, "r");
|
|
|
|
eunveil(NULL, NULL);
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
/* chroot */
|
|
|
|
if (chdir(servedir) < 0) {
|
|
|
|
die("chdir '%s':", servedir);
|
|
|
|
}
|
|
|
|
if (chroot(".") < 0) {
|
|
|
|
die("chroot .:");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* drop root */
|
2020-08-09 21:20:06 +00:00
|
|
|
if (setgroups(1, &(grp->gr_gid)) < 0) {
|
2018-02-04 20:27:33 +00:00
|
|
|
die("setgroups:");
|
|
|
|
}
|
2020-08-09 21:20:06 +00:00
|
|
|
if (setgid(grp->gr_gid) < 0) {
|
2018-02-04 20:27:33 +00:00
|
|
|
die("setgid:");
|
|
|
|
}
|
2020-08-09 21:20:06 +00:00
|
|
|
if (setuid(pwd->pw_uid) < 0) {
|
2018-02-04 20:27:33 +00:00
|
|
|
die("setuid:");
|
|
|
|
}
|
2019-09-23 14:56:28 +00:00
|
|
|
|
|
|
|
if (udsname) {
|
|
|
|
epledge("stdio rpath proc unix", NULL);
|
|
|
|
} else {
|
|
|
|
epledge("stdio rpath proc inet", NULL);
|
|
|
|
}
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
if (getuid() == 0) {
|
2018-02-04 20:40:51 +00:00
|
|
|
die("Won't run as root user", argv0);
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
if (getgid() == 0) {
|
2018-02-04 20:40:51 +00:00
|
|
|
die("Won't run as root group", argv0);
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
2018-02-05 16:15:29 +00:00
|
|
|
/* accept incoming connections */
|
|
|
|
while (1) {
|
2020-08-29 11:02:51 +00:00
|
|
|
struct connection c = { 0 };
|
|
|
|
|
|
|
|
if ((c.fd = accept(insock, (struct sockaddr *)&c.ia,
|
|
|
|
&(socklen_t){sizeof(c.ia)})) < 0) {
|
2018-02-05 16:15:29 +00:00
|
|
|
warn("accept:");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fork and handle */
|
2020-08-09 20:43:46 +00:00
|
|
|
switch (fork()) {
|
2018-02-05 16:15:29 +00:00
|
|
|
case 0:
|
2020-08-29 11:02:51 +00:00
|
|
|
serve(&c, &srv);
|
2018-02-23 21:29:00 +00:00
|
|
|
exit(0);
|
2018-02-05 16:15:29 +00:00
|
|
|
break;
|
2018-02-23 21:40:47 +00:00
|
|
|
case -1:
|
|
|
|
warn("fork:");
|
|
|
|
/* fallthrough */
|
2018-02-05 16:15:29 +00:00
|
|
|
default:
|
|
|
|
/* close the connection in the parent */
|
2020-08-29 11:02:51 +00:00
|
|
|
close(c.fd);
|
2018-02-05 16:15:29 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
exit(0);
|
|
|
|
default:
|
2019-09-23 14:56:28 +00:00
|
|
|
/* limit ourselves even further while we are waiting */
|
|
|
|
if (udsname) {
|
2020-03-20 19:35:34 +00:00
|
|
|
eunveil(udsname, "c");
|
|
|
|
eunveil(NULL, NULL);
|
2019-09-23 14:56:28 +00:00
|
|
|
epledge("stdio cpath", NULL);
|
|
|
|
} else {
|
2020-03-20 19:35:34 +00:00
|
|
|
eunveil("/", "");
|
|
|
|
eunveil(NULL, NULL);
|
2019-09-23 14:56:28 +00:00
|
|
|
epledge("stdio", NULL);
|
|
|
|
}
|
|
|
|
|
2020-08-09 20:43:46 +00:00
|
|
|
while (wait(&status) > 0)
|
2018-02-04 20:27:33 +00:00
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup();
|
|
|
|
return status;
|
|
|
|
}
|