Another copy of my dotfiles. Because I don't completely trust GitHub.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

364 lines
8.5 KiB

/* See LICENSE file for copyright and license details. */
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <regex.h>
#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "arg.h"
#include "server.h"
#include "sock.h"
#include "util.h"
static char *udsname;
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))
{
struct sigaction sa = {
.sa_handler = hdl,
};
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
}
static void
usage(void)
{
const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] "
"[-i file] [-v vhost] ... [-m map] ...";
die("usage: %s -p port [-h host] %s\n"
" %s -U file [-p port] %s", argv0,
opts, argv0, opts);
}
int
main(int argc, char *argv[])
{
struct group *grp = NULL;
struct passwd *pwd = NULL;
struct rlimit rlim;
struct server srv = {
.docindex = "index.html",
};
size_t i;
int insock, status = 0;
const char *err;
char *tok[4];
/* defaults */
size_t nthreads = 4;
size_t nslots = 64;
char *servedir = ".";
char *user = "nobody";
char *group = "nobody";
ARGBEGIN {
case 'd':
servedir = EARGF(usage());
break;
case 'g':
group = EARGF(usage());
break;
case 'h':
srv.host = EARGF(usage());
break;
case 'i':
srv.docindex = EARGF(usage());
if (strchr(srv.docindex, '/')) {
die("The document index must not contain '/'");
}
break;
case 'l':
srv.listdirs = 1;
break;
case 'm':
if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) {
usage();
}
if (!(srv.map = reallocarray(srv.map, ++srv.map_len,
sizeof(struct map)))) {
die("reallocarray:");
}
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];
break;
case 's':
err = NULL;
nslots = strtonum(EARGF(usage()), 1, INT_MAX, &err);
if (err) {
die("strtonum '%s': %s", EARGF(usage()), err);
}
break;
case 't':
err = NULL;
nthreads = strtonum(EARGF(usage()), 1, INT_MAX, &err);
if (err) {
die("strtonum '%s': %s", EARGF(usage()), err);
}
break;
case 'p':
srv.port = EARGF(usage());
break;
case 'U':
udsname = EARGF(usage());
break;
case 'u':
user = EARGF(usage());
break;
case 'v':
if (spacetok(EARGF(usage()), tok, 4) || !tok[0] || !tok[1] ||
!tok[2]) {
usage();
}
if (!(srv.vhost = reallocarray(srv.vhost, ++srv.vhost_len,
sizeof(*srv.vhost)))) {
die("reallocarray:");
}
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];
break;
default:
usage();
} ARGEND
if (argc) {
usage();
}
/* can't have both host and UDS but must have one of port or UDS*/
if ((srv.host && udsname) || !(srv.port || udsname)) {
usage();
}
if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) {
die("UNIX-domain socket '%s': %s", udsname, errno ?
strerror(errno) : "File exists");
}
/* compile and check the supplied vhost regexes */
for (i = 0; i < srv.vhost_len; i++) {
if (regcomp(&srv.vhost[i].re, srv.vhost[i].regex,
REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
die("regcomp '%s': invalid regex",
srv.vhost[i].regex);
}
}
/* validate user and group */
errno = 0;
if (!user || !(pwd = getpwnam(user))) {
die("getpwnam '%s': %s", user ? user : "null",
errno ? strerror(errno) : "Entry not found");
}
errno = 0;
if (!group || !(grp = getgrnam(group))) {
die("getgrnam '%s': %s", group ? group : "null",
errno ? strerror(errno) : "Entry not found");
}
/* open a new process group */
setpgid(0, 0);
handlesignals(sigcleanup);
/*
* set the maximum number of open file descriptors as needed
* - 3 initial fd's
* - nthreads fd's for the listening socket
* - (nthreads * nslots) fd's for the connection-fd
* - (5 * nthreads) fd's for general purpose thread-use
*/
rlim.rlim_cur = rlim.rlim_max = 3 + nthreads + nthreads * nslots +
5 * nthreads;
if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
if (errno == EPERM) {
die("You need to run as root or have "
"CAP_SYS_RESOURCE set, or are asking for more "
"file descriptors than the system can offer");
} else {
die("setrlimit:");
}
}
/*
* create the (non-blocking) listening socket
*
* we could use SO_REUSEPORT and create a listening socket for
* each thread (for better load-balancing, given each thread
* would get his own kernel-queue), but this increases latency
* (as a thread might get stuck on a larger request, making all
* other request wait in line behind it).
*
* socket contention with a single listening socket is a
* non-issue and thread-load-balancing is better fixed in the
* kernel by changing epoll-sheduling from a FIFO- to a
* LIFO-model, especially as it doesn't affect performance
*/
insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) :
sock_get_ips(srv.host, srv.port);
if (sock_set_nonblocking(insock)) {
return 1;
}
/*
* before dropping privileges, we fork, as we need to remove
* the UNIX-domain socket when we shut down, which we need
* privileges for
*/
switch (fork()) {
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");
}
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
die("signal: Failed to set SIG_IGN on SIGPIPE");
}
/*
* try increasing the thread-limit by the number
* of threads we need (which is the only reliable
* workaround I know given the thread-limit is per user
* rather than per process), but ignore EPERM errors,
* because this most probably means the user has already
* set the value to the kernel's limit, and there's not
* much we can do in any other case.
* There's also no danger of overflow as the value
* returned by getrlimit() is way below the limits of the
* rlim_t datatype.
*/
if (getrlimit(RLIMIT_NPROC, &rlim) < 0) {
die("getrlimit:");
}
if (rlim.rlim_max == RLIM_INFINITY) {
if (rlim.rlim_cur != RLIM_INFINITY) {
/* try increasing current limit by nthreads */
rlim.rlim_cur += nthreads;
}
} else {
/* try increasing current and hard limit by nthreads */
rlim.rlim_cur = rlim.rlim_max += nthreads;
}
if (setrlimit(RLIMIT_NPROC, &rlim) < 0 && errno != EPERM) {
die("setrlimit()");
}
/* limit ourselves to reading the servedir and block further unveils */
eunveil(servedir, "r");
eunveil(NULL, NULL);
/* chroot */
if (chdir(servedir) < 0) {
die("chdir '%s':", servedir);
}
if (chroot(".") < 0) {
if (errno == EPERM) {
die("You need to run as root or have "
"CAP_SYS_CHROOT set");
} else {
die("chroot:");
}
}
/* drop root */
if (pwd->pw_uid == 0 || grp->gr_gid == 0) {
die("Won't run under root %s for hopefully obvious reasons",
(pwd->pw_uid == 0) ? (grp->gr_gid == 0) ?
"user and group" : "user" : "group");
}
if (setgroups(1, &(grp->gr_gid)) < 0) {
if (errno == EPERM) {
die("You need to run as root or have "
"CAP_SETGID set");
} else {
die("setgroups:");
}
}
if (setgid(grp->gr_gid) < 0) {
if (errno == EPERM) {
die("You need to run as root or have "
"CAP_SETGID set");
} else {
die("setgid:");
}
}
if (setuid(pwd->pw_uid) < 0) {
if (errno == EPERM) {
die("You need to run as root or have "
"CAP_SETUID set");
} else {
die("setuid:");
}
}
if (udsname) {
epledge("stdio rpath proc unix", NULL);
} else {
epledge("stdio rpath proc inet", NULL);
}
/* accept incoming connections */
server_init_thread_pool(insock, nthreads, nslots, &srv);
exit(0);
default:
/* limit ourselves even further while we are waiting */
if (udsname) {
eunveil(udsname, "c");
eunveil(NULL, NULL);
epledge("stdio cpath", NULL);
} else {
eunveil("/", "");
eunveil(NULL, NULL);
epledge("stdio", NULL);
}
while (wait(&status) > 0)
;
}
cleanup();
return status;
}