diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2018-06-04 11:08:20 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2018-06-04 11:08:20 +0300 |
commit | d04992b76378dcdd92bf78b090d568593b1a6567 (patch) | |
tree | 59b936e4c5bfbd10e1aad06bdf1737f601f075b1 | |
parent | b488e991577f1cbd31562a0b901026c76a0b37bd (diff) | |
download | tallyman-d04992b76378dcdd92bf78b090d568593b1a6567.tar.gz tallyman-d04992b76378dcdd92bf78b090d568593b1a6567.tar.bz2 |
Implement server framework
-rw-r--r-- | configure.ac | 17 | ||||
-rw-r--r-- | src/.gitignore | 1 | ||||
-rw-r--r-- | src/Makefile.am | 24 | ||||
-rw-r--r-- | src/config.c | 203 | ||||
-rw-r--r-- | src/defs.h | 14 | ||||
-rw-r--r-- | src/err.c | 1 | ||||
-rw-r--r-- | src/pidfile.c | 72 | ||||
-rw-r--r-- | src/remoteip.c | 331 | ||||
-rw-r--r-- | src/runas.c | 163 | ||||
-rw-r--r-- | src/shttp.c | 7 | ||||
-rw-r--r-- | src/stevedore.c | 568 | ||||
-rw-r--r-- | src/stevedore.h | 23 | ||||
-rw-r--r-- | src/tallyman.c | 98 | ||||
-rw-r--r-- | src/wrapacl.c | 15 |
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 + @@ -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; |