aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2018-06-04 11:08:20 +0300
committerSergey Poznyakoff <gray@gnu.org>2018-06-04 11:08:20 +0300
commitd04992b76378dcdd92bf78b090d568593b1a6567 (patch)
tree59b936e4c5bfbd10e1aad06bdf1737f601f075b1
parentb488e991577f1cbd31562a0b901026c76a0b37bd (diff)
downloadtallyman-d04992b76378dcdd92bf78b090d568593b1a6567.tar.gz
tallyman-d04992b76378dcdd92bf78b090d568593b1a6567.tar.bz2
Implement server framework
-rw-r--r--configure.ac17
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am24
-rw-r--r--src/config.c203
-rw-r--r--src/defs.h14
-rw-r--r--src/err.c1
-rw-r--r--src/pidfile.c72
-rw-r--r--src/remoteip.c331
-rw-r--r--src/runas.c163
-rw-r--r--src/shttp.c7
-rw-r--r--src/stevedore.c568
-rw-r--r--src/stevedore.h23
-rw-r--r--src/tallyman.c98
-rw-r--r--src/wrapacl.c15
14 files changed, 1514 insertions, 23 deletions
diff --git a/configure.ac b/configure.ac
index 1eea95c..04d12a4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -12,9 +12,20 @@ AC_PROG_CC
AC_PROG_RANLIB
# Checks for libraries.
+save_LIBS=$LIBS
+AC_CHECK_LIB([microhttpd],[MHD_start_daemon],[],
+ [AC_MSG_ERROR([required library microhttpd not found
+
+Please see https://www.gnu.org/software/libmicrohttpd for download instructions.])])
+AC_CHECK_LIB([pthread],[pthread_sigmask])
+AC_CHECK_LIB([wrap], [main])
+AC_CHECK_LIB([nsl], [main])
+DAEMON_LIBS="$LIBS"
+LIBS=$save_LIBS
+AC_SUBST(DAEMON_LIBS)
# Checks for header files.
-AC_CHECK_HEADERS([arpa/inet.h limits.h stdlib.h string.h unistd.h])
+AC_CHECK_HEADERS([tcpd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_SIZE_T
@@ -22,8 +33,10 @@ AC_TYPE_SSIZE_T
# Checks for library functions.
-GRECS_SETUP(,[json])
+GRECS_SETUP(,[tree-api json])
RUNCAP_SETUP
+AM_CONDITIONAL([TMD_WRAP],[test x$ac_cv_lib_wrap_main = xyes && test x$ac_cv_header_tcpd_h = xyes])
+
AC_OUTPUT([Makefile
src/Makefile])
diff --git a/src/.gitignore b/src/.gitignore
index 2c8c2ed..b144557 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -1 +1,2 @@
+stevedore
tallyman
diff --git a/src/Makefile.am b/src/Makefile.am
index 5e8d213..a3bc68c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,4 +1,5 @@
bin_PROGRAMS=tallyman
+sbin_PROGRAMS=stevedore
tallyman_SOURCES = \
tallyman.c\
@@ -8,5 +9,24 @@ tallyman_SOURCES = \
err.c\
shttp.c
-AM_CPPFLAGS=@GRECS_INCLUDES@ @RUNCAP_INC@
-LDADD=@GRECS_LDADD@ @RUNCAP_LDADD@
+stevedore_SOURCES = \
+ stevedore.c\
+ stevedore.h\
+ config.c\
+ pidfile.c\
+ runas.c\
+ remoteip.c
+
+AM_CPPFLAGS=@GRECS_INCLUDES@ @RUNCAP_INC@ -DSYSCONFDIR="\"$(sysconfdir)\""
+tallyman_LDADD=@GRECS_LDADD@ @RUNCAP_LDADD@
+stevedore_LDADD=@GRECS_LDADD@ @DAEMON_LIBS@
+tallyman_LDFLAGS=-static
+
+
+noinst_HEADERS = defs.h
+
+if TMD_WRAP
+ stevedore_SOURCES += wrapacl.c
+ AM_CPPFLAGS += -DHAVE_LIBWRAP
+endif
+
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..f7e0821
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,203 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include "grecs.h"
+#include "stevedore.h"
+#include "defs.h"
+
+int log_facility = LOG_DAEMON;
+char *syslog_tag;
+char *listen_address;
+char *pidfile;
+char *runas_user;
+char *runas_group;
+
+struct keyword {
+ char *name;
+ int tok;
+};
+
+static int
+keyword_to_tok(const char *str, struct keyword *kw, int *pres)
+{
+ for (; kw->name; kw++)
+ if (strcmp(kw->name, str) == 0) {
+ *pres = kw->tok;
+ return 0;
+ }
+ return 1;
+}
+
+static int
+tok_to_keyword(int tok, struct keyword *kw, const char **pres)
+{
+ for (; kw->name; kw++)
+ if (kw->tok == tok) {
+ *pres = kw->name;
+ return 0;
+ }
+ return 1;
+}
+
+static struct keyword kwfac[] = {
+ { "USER", LOG_USER },
+ { "DAEMON", LOG_DAEMON },
+ { "AUTH", LOG_AUTH },
+ { "AUTHPRIV",LOG_AUTHPRIV },
+ { "MAIL", LOG_MAIL },
+ { "CRON", LOG_CRON },
+ { "LOCAL0", LOG_LOCAL0 },
+ { "LOCAL1", LOG_LOCAL1 },
+ { "LOCAL2", LOG_LOCAL2 },
+ { "LOCAL3", LOG_LOCAL3 },
+ { "LOCAL4", LOG_LOCAL4 },
+ { "LOCAL5", LOG_LOCAL5 },
+ { "LOCAL6", LOG_LOCAL6 },
+ { "LOCAL7", LOG_LOCAL7 },
+ { NULL }
+};
+
+static struct keyword kwpri[] = {
+ { "EMERG", LOG_EMERG },
+ { "ALERT", LOG_ALERT },
+ { "CRIT", LOG_CRIT },
+ { "ERR", LOG_ERR },
+ { "WARNING", LOG_WARNING },
+ { "NOTICE", LOG_NOTICE },
+ { "INFO", LOG_INFO },
+ { "DEBUG", LOG_DEBUG },
+ { NULL }
+};
+
+static int
+stv_strtofac(const char *str)
+{
+ int res;
+ if (keyword_to_tok(str, kwfac, &res))
+ return -1;
+ return res;
+}
+
+static int
+stv_strtopri(const char *str)
+{
+ int res;
+ if (keyword_to_tok(str, kwpri, &res))
+ return -1;
+ return res;
+}
+#if 0
+static const char *
+stv_pritostr(int pri)
+{
+ const char *res;
+ if (tok_to_keyword(pri, kwpri, &res))
+ return NULL;
+ return res;
+}
+
+static const char *
+stv_factostr(int fac)
+{
+ const char *res;
+ if (tok_to_keyword(fac, kwfac, &res))
+ return NULL;
+ return res;
+}
+#endif
+
+static int
+stv_assert_string_arg(grecs_locus_t *locus,
+ enum grecs_callback_command cmd,
+ const grecs_value_t *value)
+{
+ if (cmd != grecs_callback_set_value) {
+ grecs_error(locus, 0, "Unexpected block statement");
+ return 1;
+ }
+ if (!value || value->type != GRECS_TYPE_STRING) {
+ grecs_error(&value->locus, 0,
+ "expected scalar value as a tag");
+ return 1;
+ }
+ return 0;
+}
+
+static int
+cb_syslog_facility(enum grecs_callback_command cmd, grecs_node_t *node,
+ void *varptr, void *cb_data)
+{
+ grecs_locus_t *locus = &node->locus;
+ grecs_value_t *value = node->v.value;
+ int fac;
+
+ if (stv_assert_string_arg(locus, cmd, value))
+ return 1;
+
+ fac = stv_strtofac(value->v.string);
+ if (fac == -1)
+ grecs_error(&value->locus, 0,
+ "Unknown syslog facility `%s'",
+ value->v.string);
+ else
+ *(int*)varptr = fac;
+ return 0;
+}
+
+static struct grecs_keyword syslog_kw[] = {
+ { "facility",
+ "name",
+ "Set syslog facility. Arg is one of the following: user, daemon, "
+ "auth, authpriv, mail, cron, local0 through local7 "
+ "(case-insensitive), or a facility number.",
+ grecs_type_string, GRECS_DFLT,
+ &log_facility, 0, cb_syslog_facility },
+ { "tag", "string", "Tag syslog messages with this string",
+ grecs_type_string, GRECS_DFLT,
+ &syslog_tag },
+ { NULL },
+};
+
+static struct grecs_keyword tallymand_kw[] = {
+ { "listen", "socket", "Listen on this address",
+ grecs_type_sockaddr, GRECS_DFLT, &listen_address, },
+ { "pidfile", "file", "Store PID in that file",
+ grecs_type_string, GRECS_DFLT, &pidfile },
+ { "user", "name|+uid", "Run as this user",
+ grecs_type_string, GRECS_DFLT, &runas_user },
+ { "group", "name|+gid", "Run with this group privileges",
+ grecs_type_string, GRECS_DFLT, &runas_group },
+ { "syslog", NULL, "Configure syslog logging",
+ grecs_type_section, GRECS_DFLT, NULL, 0, NULL, NULL, syslog_kw },
+ { NULL }
+};
+
+void
+config_help(void)
+{
+ static char docstring[] =
+ "Configuration file structure for tallymand.\n"
+ "For more information, see tallymand(8).";
+ grecs_print_docstring(docstring, 0, stdout);
+ grecs_print_statement_array(tallymand_kw, 1, 0, stdout);
+}
+
+void
+readconfig(char const *file)
+{
+ struct grecs_node *tree;
+
+ grecs_log_to_stderr = 1;
+ tree = grecs_parse(file);
+ if (!tree)
+ exit(1);
+ if (grecs_tree_process(tree, tallymand_kw))
+ exit(1);
+
+ if (!syslog_tag)
+ syslog_tag = progname;
+
+ if (!listen_address)
+ listen_address = DEFAULT_ADDRESS ":" DEFAULT_PORT;
+}
diff --git a/src/defs.h b/src/defs.h
new file mode 100644
index 0000000..4c10465
--- /dev/null
+++ b/src/defs.h
@@ -0,0 +1,14 @@
+#ifndef SYSCONFDIR
+# define SYSCONFDIR "/etc"
+#endif
+
+#define DEFAULT_CONFIG_FILE_NAME SYSCONFDIR "/tallymand.conf"
+
+#ifndef DEFAULT_ADDRESS
+# define DEFAULT_ADDRESS "0.0.0.0"
+#endif
+
+#ifndef DEFAULT_PORT
+# define DEFAULT_PORT "8990"
+#endif
+
diff --git a/src/err.c b/src/err.c
index 0213772..c6f9d15 100644
--- a/src/err.c
+++ b/src/err.c
@@ -39,7 +39,6 @@ setprogname(char const *s)
grecs_print_diag_fun = tallyman_print_diag;
}
-
void
error(char const *fmt, ...)
{
diff --git a/src/pidfile.c b/src/pidfile.c
new file mode 100644
index 0000000..0e958ac
--- /dev/null
+++ b/src/pidfile.c
@@ -0,0 +1,72 @@
+#include "config.h"
+#include "stevedore.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+void
+pidfile_remove(void)
+{
+ if (pidfile && unlink(pidfile))
+ error("cannot unlink pidfile `%s': %s",
+ pidfile, strerror(errno));
+}
+
+void
+pidfile_create(void)
+{
+ FILE *fp;
+
+ if (!pidfile)
+ return;
+
+ fp = fopen(pidfile, "w");
+ if (!fp) {
+ error("cannot create pidfile `%s': %s",
+ pidfile, strerror(errno));
+ exit(1);
+ }
+ fprintf(fp, "%lu", (unsigned long) getpid());
+ fclose(fp);
+}
+
+/* Check whether pidfile exists and if so, whether its PID is still
+ active. Exit if it is. */
+void
+pidfile_check(void)
+{
+ unsigned long pid;
+ FILE *fp;
+
+ if (!pidfile)
+ return;
+
+ fp = fopen(pidfile, "r");
+
+ if (fp) {
+ if (fscanf(fp, "%lu", &pid) != 1) {
+ error("cannot get pid from pidfile `%s'", pidfile);
+ } else {
+ if (kill(pid, 0) == 0) {
+ error("%s appears to run with pid %lu. "
+ "If it does not, remove `%s' and retry.",
+ progname, pid, pidfile);
+ exit(1);
+ }
+ }
+
+ fclose(fp);
+ if (unlink(pidfile)) {
+ error("cannot unlink pidfile `%s': %s",
+ pidfile, strerror(errno));
+ exit(1);
+ }
+ } else if (errno != ENOENT) {
+ error("cannot open pidfile `%s': %s",
+ pidfile, strerror(errno));
+ exit(1);
+ }
+}
diff --git a/src/remoteip.c b/src/remoteip.c
new file mode 100644
index 0000000..d0b7fcf
--- /dev/null
+++ b/src/remoteip.c
@@ -0,0 +1,331 @@
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <microhttpd.h>
+#include "stevedore.h"
+#include "grecs.h"
+
+/* Returns 1 if ADDR is a valid string representation of IPv4 address */
+static int
+str_is_ipv4(const char *addr)
+{
+ int dot_count = 0;
+ int digit_count = 0;
+
+ for (; *addr; addr++) {
+ if (!isascii(*addr))
+ return 0;
+ if (*addr == '.') {
+ if (++dot_count > 3)
+ break;
+ digit_count = 0;
+ }
+ else if (!(isdigit(*addr) && ++digit_count <= 3))
+ return 0;
+ }
+ return (dot_count == 3);
+}
+
+#define PFXSTR_IPV4_MAPPED "::ffff:"
+#define PFXLEN_IPV4_MAPPED (sizeof PFXSTR_IPV4_MAPPED - 1)
+
+static int
+str_is_ipv4mapped(const char *addr)
+{
+ return strlen(addr) > PFXLEN_IPV4_MAPPED
+ && strncasecmp(PFXSTR_IPV4_MAPPED, addr, PFXLEN_IPV4_MAPPED)
+ == 0
+ && str_is_ipv4(addr + PFXLEN_IPV4_MAPPED);
+}
+
+/* Returns 1 if ADDR is a valid IPv6 address */
+static int
+str_is_ipv6(const char *addr)
+{
+ int col_count = 0; /* Number of colons */
+ int dcol = 0; /* Did we encounter a double-colon? */
+ int dig_count = 0; /* Number of digits in the last group */
+
+ for (; *addr; addr++) {
+ if (!isascii (*addr))
+ return 0;
+ else if (isxdigit(*addr)) {
+ if (++dig_count > 4)
+ return 0;
+ } else if (*addr == ':') {
+ if (col_count && dig_count == 0 && ++dcol > 1)
+ return 0;
+ if (++col_count > 7)
+ return 0;
+ dig_count = 0;
+ }
+ else
+ return 0;
+ }
+ return (col_count == 7 || dcol);
+}
+
+/* Max. number of bytes in an IPv6 address */
+#define MU_INADDR_BYTES 16
+
+/* CIDR representation */
+struct cidr {
+ int family; /* Address family */
+ int len; /* Number of bytes in the address */
+ unsigned char address[MU_INADDR_BYTES];
+ unsigned char netmask[MU_INADDR_BYTES];
+ struct cidr *next; /* Next CIDR in list */
+};
+
+static void
+uint32_to_bytes(unsigned char *bytes, uint32_t u)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ bytes[i] = u & 0xff;
+ u >>= 8;
+ }
+}
+
+static int
+inaddr_to_bytes(int af, void *buf, unsigned char *bytes)
+{
+ uint32_t u;
+
+ switch (af) {
+ case AF_INET:
+ memcpy(&u, buf, sizeof u);
+ uint32_to_bytes (bytes, u);
+ return 4;
+
+ case AF_INET6:
+ memcpy(bytes, buf, 16);
+ return 16;
+ }
+
+ abort();
+}
+
+/* Fill in the BUF (LEN bytes long) with a network mask corresponding to
+ the mask length MASKLEN */
+static void
+masklen_to_netmask(unsigned char *buf, size_t len, size_t masklen)
+{
+ int i, cnt;
+
+ cnt = masklen / 8;
+ for (i = 0; i < cnt; i++)
+ buf[i] = 0xff;
+ if (i == len)
+ return;
+ cnt = 8 - masklen % 8;
+ buf[i++] = (0xff >> cnt) << cnt;
+ for (; i < len; i++)
+ buf[i] = 0;
+}
+
+/* Convert string STR to CIDR. Return 0 on success and -1 on failure. */
+static int
+str_to_cidr(char const *str, struct cidr *cidr)
+{
+ int rc;
+ char *ipbuf, *ipstart;
+ union {
+ struct in_addr in;
+ struct in6_addr in6;
+ } inaddr;
+ size_t len;
+ char *p;
+
+ p = strrchr(str, '/');
+ if (p)
+ len = p - str;
+ else
+ len = strlen (str);
+
+ ipbuf = grecs_malloc(len+1);
+ memcpy(ipbuf, str, len);
+ ipbuf[len] = 0;
+ ipstart = ipbuf;
+
+ if (str_is_ipv4(ipstart))
+ cidr->family = AF_INET;
+ else if (str_is_ipv4mapped(ipstart)) {
+ cidr->family = AF_INET;
+ ipstart += PFXLEN_IPV4_MAPPED;
+ } else if (str_is_ipv6(ipbuf))
+ cidr->family = AF_INET6;
+ else {
+ free(ipbuf);
+ error("invalid CIDR: %s", str);
+ return -1;
+ }
+
+ rc = inet_pton(cidr->family, ipstart, &inaddr);
+ free(ipbuf);
+
+ if (rc != 1) {
+ if (rc == -1) {
+ error("invalid address family");
+ abort();
+ }
+ if (rc == 0) {
+ error("invalid IPv%s address: %s",
+ cidr->family == AF_INET ? "4" : "6",
+ str);
+ return -1;
+ }
+ }
+
+ cidr->len = inaddr_to_bytes(cidr->family, &inaddr, cidr->address);
+
+ if (p) {
+ char *end;
+ unsigned long masklen;
+
+ p++;
+
+ masklen = strtoul(p, &end, 10);
+ if (*end == 0)
+ masklen_to_netmask(cidr->netmask, cidr->len, masklen);
+ else if ((cidr->family == AF_INET && str_is_ipv4(p))
+ || (cidr->family == AF_INET6 && str_is_ipv6(ipbuf))) {
+ rc = inet_pton(cidr->family, p, &inaddr);
+ if (rc != 1) {
+ error("bad netmask: %s", p);
+ return -1;
+ }
+ inaddr_to_bytes(cidr->family, &inaddr, cidr->netmask);
+ } else {
+ error("bad CIDR (near %s)", p);
+ return -1;
+ }
+ } else
+ masklen_to_netmask(cidr->netmask, cidr->len, cidr->len * 8);
+ return 0;
+}
+
+/* A is a valid CIDR, and B is a valid IP address represented as CIDR.
+ Return 0 if B falls within A */
+static int
+cidr_match(struct cidr *a, struct cidr *b)
+{
+ int i;
+
+ if (a->family != b->family)
+ return 1;
+ for (i = 0; i < a->len; i++) {
+ if (a->address[i] != (b->address[i] & a->netmask[i]))
+ return 1;
+ }
+ return 0;
+}
+
+struct cidr *trusted_ip_list;
+
+void
+trusted_ip_add(char const *s)
+{
+ struct cidr *cidr = grecs_malloc(sizeof(cidr[0]));
+
+ if (str_to_cidr(s, cidr))
+ exit(1);
+ cidr->next = trusted_ip_list;
+ trusted_ip_list = cidr;
+}
+
+static int
+is_trusted_ip(char const *ipstr)
+{
+ struct cidr cidr;
+ struct cidr *p;
+
+ if (str_to_cidr(ipstr, &cidr))
+ return 0;
+ for (p = trusted_ip_list; p; p = p->next) {
+ if (cidr_match(p, &cidr) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static char *
+scan_forwarded_header(char const *hdr)
+{
+ char const *end;
+ char *buf = NULL;
+ size_t bufsize = 0;
+
+ end = hdr + strlen(hdr);
+ while (end > hdr) {
+ char const *p;
+ size_t len, i, j;
+
+ for (p = end - 1; p > hdr && *p != ','; p--)
+ ;
+ len = end - p;
+ if (len + 1 > bufsize) {
+ char *newbuf = realloc(buf, len + 1);
+ if (newbuf) {
+ buf = newbuf;
+ bufsize = len + 1;
+ } else {
+ free(buf);
+ return NULL;
+ }
+ }
+
+ i = 0;
+ j = 0;
+ if (*p == ',')
+ j++;
+ while (j < len && isspace(p[j]))
+ j++;
+ while (j < len) {
+ if (isspace(p[j]))
+ break;
+ buf[i++] = p[j++];
+ }
+ buf[i] = 0;
+
+ if (!is_trusted_ip(buf))
+ return buf;
+
+ end = p;
+ }
+ free(buf);
+ return 0;
+}
+
+char *
+get_remote_ip(struct MHD_Connection *conn)
+{
+ union MHD_ConnectionInfo const *ci;
+ char ipstr[NI_MAXHOST];
+ char const *hdr;
+ char *ret;
+
+ hdr = MHD_lookup_connection_value(conn,
+ MHD_HEADER_KIND,
+ "x-forwarded-from");
+ if (hdr && (ret = scan_forwarded_header(hdr)) != NULL)
+ return ret;
+
+ ci = MHD_get_connection_info(conn,
+ MHD_CONNECTION_INFO_CLIENT_ADDRESS,
+ NULL);
+ if (!ci)
+ return NULL;
+
+ if (getnameinfo(ci->client_addr, sizeof(struct sockaddr),
+ ipstr, sizeof(ipstr), NULL, 0,
+ NI_NUMERICHOST))
+ return NULL;
+ return strdup(ipstr);
+}
diff --git a/src/runas.c b/src/runas.c
new file mode 100644
index 0000000..3576947
--- /dev/null
+++ b/src/runas.c
@@ -0,0 +1,163 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include "grecs.h"
+#include "stevedore.h"
+
+#ifndef SIZE_T_MAX
+# define SIZE_T_MAX ((size_t)-1)
+#endif
+
+static void
+addgid(gid_t **pgv, size_t *pgc, size_t *pgi, gid_t gid)
+{
+ gid_t *gv = *pgv;
+ size_t gc = *pgc;
+ size_t gi = *pgi;
+
+ if (gi == gc) {
+ if (gc == 0) {
+ gc = 16;
+ gv = grecs_calloc(gc, sizeof(*gv));
+ } else if (gc <= SIZE_T_MAX / 2 / sizeof(*gv)) {
+ gc *= 2;
+ gv = grecs_realloc(gv, gc * sizeof(*gv));
+ } else
+ grecs_alloc_die();
+ }
+ gv[gi++] = gid;
+ *pgv = gv;
+ *pgc = gc;
+ *pgi = gi;
+}
+
+static int
+member(gid_t *gv, size_t gc, gid_t gid)
+{
+ size_t i;
+
+ for (i = 0; i < gc; i++)
+ if (gv[i] == gid)
+ return 1;
+ return 0;
+}
+
+static size_t
+get_user_groups(const char *user, gid_t **pgv, size_t *pgc)
+{
+ struct group *gr;
+ size_t gi = 0;
+
+ setgrent();
+ while ((gr = getgrent())) {
+ char **p;
+ for (p = gr->gr_mem; *p; p++)
+ if (strcmp(*p, user) == 0)
+ addgid(pgv, pgc, &gi, gr->gr_gid);
+ }
+ endgrent();
+ return gi;
+}
+
+void
+runas(char const *username, char const *groupname)
+{
+ struct passwd *pw;
+ struct group *gr;
+ uid_t uid;
+ gid_t gid;
+ gid_t *gv;
+ size_t gc, gn;
+
+ if (!(username || groupname))
+ return;
+ if (getuid() != 0) {
+ error("not root: can't switch to user privileges");
+ exit(1);
+ }
+
+ if (!username) {
+ pw = getpwuid(0);
+ username = "root";
+ } else if (username[0] == '+') {
+ char *end;
+ unsigned long n;
+
+ errno = 0;
+ n = strtoul(username + 1, &end, 10);
+ if (errno || *end) {
+ error("invalid user name %s", username);
+ exit(1);
+ }
+
+ pw = getpwuid(n);
+ } else
+ pw = getpwnam(username);
+
+ if (!pw) {
+ error("%s: no such user", username);
+ exit(1);
+ }
+ username = pw->pw_name;
+
+ uid = pw->pw_uid;
+ gid = pw->pw_gid;
+
+ if (groupname) {
+ if (groupname[0] == '+') {
+ char *end;
+ unsigned long n;
+
+ errno = 0;
+ n = strtoul(groupname + 1, &end, 10);
+ if (errno || *end) {
+ error("invalid group name %s", groupname);
+ exit(1);
+ }
+
+ gr = getgrgid(n);
+ } else
+ gr = getgrnam(groupname);
+
+ if (!gr) {
+ error("%s: no such group", groupname);
+ exit(1);
+ }
+
+ gid = gr->gr_gid;
+ }
+
+ gv = NULL;
+ gc = 0;
+ gn = get_user_groups(username, &gv, &gc);
+ if (!member(gv, gn, gid))
+ addgid(&gv, &gc, &gn, gid);
+
+ /* Reset group permissions */
+ if (setgroups(gn, gv)) {
+ error("setgroups failed: %s", strerror(errno));
+ exit(1);
+ }
+ free(gv);
+
+ if (gid) {
+ /* Switch to the user's gid. */
+ if (setgid(gid)) {
+ error("setgid(%lu) failed: %s",
+ (unsigned long) gid, strerror(errno));
+ exit(1);
+ }
+ }
+
+ /* Now reset uid */
+ if (uid) {
+ if (setuid(uid)) {
+ error("setuid(%lu) failed: %s",
+ (unsigned long) uid, strerror(errno));
+ exit(1);
+ }
+ }
+}
diff --git a/src/shttp.c b/src/shttp.c
index 57b8678..e6caada 100644
--- a/src/shttp.c
+++ b/src/shttp.c
@@ -436,13 +436,6 @@ shttp_get_reply(struct shttp_connection *conn)
}
static void
-printer(void *d, char const *buf, size_t size)
-{
- FILE *fp = d;
- fwrite(buf, size, 1, fp);
-}
-
-static void
acc_writer(void *closure, char const *text, size_t len)
{
grecs_txtacc_grow((struct grecs_txtacc *)closure, text, len);
diff --git a/src/stevedore.c b/src/stevedore.c
new file mode 100644
index 0000000..d818a81
--- /dev/null
+++ b/src/stevedore.c
@@ -0,0 +1,568 @@
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <errno.h>
+#include <syslog.h>
+#include <signal.h>
+#include <time.h>
+#define MHD_PLATFORM_H
+#include <microhttpd.h>
+#include "defs.h"
+#include "stevedore.h"
+#include "grecs.h"
+#include "grecs/json.h"
+
+int debug;
+char *progname;
+
+enum {
+ OPT_CONFIG_HELP = 256
+};
+
+
+struct option longopts[] = {
+ { "help", no_argument, 0, '?' },
+ { "config-file", required_argument, 0, 'f' },
+ { "config-help", no_argument, 0, OPT_CONFIG_HELP },
+ { "debug", no_argument, 0, 'd' },
+ { "foreground", no_argument, 0, 'F' },
+ { "single", no_argument, 0, 's' },
+ { NULL }
+};
+static char shortopts[] = "?df:Fs";
+
+void
+help(void)
+{
+ printf("NO HELP SO FAR\n");
+}
+
+static void
+vlog_syslog(int prio, char const *fmt, va_list ap)
+{
+ vsyslog(prio, fmt, ap);
+}
+
+static void
+vlog_stderr(int prio, char const *fmt, va_list ap)
+{
+ fprintf(stderr, "%s: ", progname);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+}
+
+static void (*vlog)(int prio, char const *fmt, va_list ap) = vlog_stderr;
+
+void
+error(char const *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vlog(LOG_ERR, fmt, ap);
+ va_end(ap);
+}
+
+void
+info(char const *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vlog(LOG_INFO, fmt, ap);
+ va_end(ap);
+}
+
+void
+fileserv_logger(void *arg, const char *fmt, va_list ap)
+{
+ vlog(LOG_ERR, fmt, ap);
+}
+
+void
+syslog_enable(void)
+{
+ openlog(syslog_tag, LOG_PID, log_facility);
+ vlog = vlog_syslog;
+ grecs_log_to_stderr = 0;
+}
+
+int
+open_node(char const *node, char const *serv, struct sockaddr **saddr)
+{
+ struct addrinfo hints;
+ struct addrinfo *res;
+ int reuse;
+ int fd;
+ int rc;
+
+ memset(&hints, 0, sizeof (hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ rc = getaddrinfo(node, serv, &hints, &res);
+ if (rc) {
+ error("%s: %s", node, gai_strerror(rc));
+ exit(1);
+ }
+
+ fd = socket(res->ai_family, res->ai_socktype, 0);
+ if (fd == -1) {
+ error("socket: %s", strerror(errno));
+ exit(1);
+ }
+
+ reuse = 1;
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ &reuse, sizeof(reuse));
+ if (bind(fd, res->ai_addr, res->ai_addrlen) == -1) {
+ error("bind: %s", strerror(errno));
+ exit(1);
+ }
+
+ if (listen(fd, 8) == -1) {
+ error("listen: %s", strerror(errno));
+ exit(1);
+ }
+
+ if (saddr) {
+ *saddr = grecs_malloc(res->ai_addrlen);
+ memcpy(*saddr, res->ai_addr, res->ai_addrlen);
+ }
+ freeaddrinfo(res);
+
+ return fd;
+}
+
+int
+open_listener(char const *addr, struct sockaddr **saddr)
+{
+ size_t len;
+ int fd;
+
+ len = strcspn(addr, ":");
+ if (len == 0)
+ fd = open_node(DEFAULT_ADDRESS, addr + 1, saddr);
+ else if (addr[len] == 0)
+ fd = open_node(addr, DEFAULT_PORT, saddr);
+ else {
+ char *node;
+ node = grecs_malloc(len + 1);
+ memcpy(node, addr, len);
+ node[len] = 0;
+ fd = open_node(node, addr + len + 1, saddr);
+ free(node);
+ }
+ return fd;
+}
+
+static void
+stv_panic(void *cls, const char *file, unsigned int line,
+ const char *reason)
+{
+ if (reason)
+ error("%s:%d: MHD PANIC: %s", file, line, reason);
+ else
+ error("%s:%d: MHD PANIC", file, line);
+ abort();
+}
+
+static void
+stv_logger(void *arg, const char *fmt, va_list ap)
+{
+ vlog(LOG_ERR, fmt, ap);
+}
+
+#if HAVE_LIBWRAP
+extern int stv_acl(void *cls, const struct sockaddr *addr, socklen_t addrlen);
+#else
+# define stv_acl NULL
+#endif
+
+
+void
+http_log(struct MHD_Connection *connection,
+ char const *method, char const *url,
+ int status, struct stat const *st)
+{
+ char *ipstr;
+ char const *host, *referer, *user_agent;
+ time_t t;
+ struct tm *tm;
+ char tbuf[30];
+
+ ipstr = get_remote_ip(connection);
+
+ host = MHD_lookup_connection_value(connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_HOST);
+ referer = MHD_lookup_connection_value(connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_REFERER);
+ user_agent = MHD_lookup_connection_value(connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_USER_AGENT);
+
+ t = time(NULL);
+ tm = localtime(&t);
+ strftime(tbuf, sizeof(tbuf), "[%d/%b/%Y:%H:%M:%S %z]", tm);
+
+ info("%s %s - - %s \"%s %s\" %3d %lu \"%s\" \"%s\"",
+ host, ipstr, tbuf, method, url, status, st ? st->st_size : 0,
+ referer ? referer : "",
+ user_agent ? user_agent : "");
+ free(ipstr);
+}
+
+int
+http_error(struct MHD_Connection *conn,
+ char const *method, char const *url, int status)
+{
+ int ret;
+ struct MHD_Response *resp =
+ MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ http_log(conn, method, url, status, NULL);
+ ret = MHD_queue_response(conn, status, resp);
+ MHD_destroy_response(resp);
+ return ret;
+}
+
+static void
+acc_writer(void *closure, char const *text, size_t len)
+{
+ grecs_txtacc_grow((struct grecs_txtacc *)closure, text, len);
+}
+
+static void
+json_log(struct json_value *obj)
+{
+ struct grecs_txtacc *acc = grecs_txtacc_create();
+ struct json_format fmt = {
+ .indent = 0,
+ .precision = 0,
+ .write = acc_writer,
+ .data = acc
+ };
+ char *str;
+
+ json_format_value(obj, &fmt);
+ grecs_txtacc_grow_char(acc, 0);
+ str = grecs_txtacc_finish(acc, 0);
+ info("GOT %s",str);
+ grecs_txtacc_free(acc);
+}
+
+static int
+stv_handler(void *cls,
+ struct MHD_Connection *conn,
+ const char *url, const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **con_cls)
+{
+ grecs_txtacc_t acc;
+
+ if (strcmp(method, MHD_HTTP_METHOD_POST))
+ return http_error(conn, method, url,
+ MHD_HTTP_METHOD_NOT_ALLOWED);
+ if (strcmp(url, "/"))
+ return http_error(conn, method, url, MHD_HTTP_FORBIDDEN);
+
+ if (*con_cls == NULL) {
+ acc = grecs_txtacc_create();
+ *con_cls = acc;
+ return MHD_YES;
+ } else {
+ acc = *con_cls;
+ if (*upload_data_size != 0) {
+ grecs_txtacc_grow(acc, upload_data,
+ *upload_data_size);
+ *upload_data_size = 0;
+ } else {
+ char *json;
+ struct json_value *obj;
+ struct MHD_Response *resp;
+ int ret;