summaryrefslogtreecommitdiff
path: root/src/log.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/log.cc')
-rw-r--r--src/log.cc1227
1 files changed, 1227 insertions, 0 deletions
diff --git a/src/log.cc b/src/log.cc
new file mode 100644
index 0000000..2a5c856
--- /dev/null
+++ b/src/log.cc
@@ -0,0 +1,1227 @@
+/*-
+ * SSLsplit - transparent SSL/TLS interception
+ * https://www.roe.ch/SSLsplit
+ *
+ * Copyright (c) 2009-2018, Daniel Roethlisberger <[email protected]>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "log.h"
+
+#include "logger.h"
+#include "sys.h"
+#include "attrib.h"
+#include "privsep.h"
+#include "defaults.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+
+/*
+ * Centralized logging code multiplexing thread access to the logger based
+ * logging in separate threads. Some log types are switchable to different
+ * backends, such as syslog and stderr.
+ */
+
+
+/*
+ * Common code for all logs.
+ */
+static proxy_ctx_t *proxy_ctx = NULL;
+
+void
+log_exceptcb(void)
+{
+ if (proxy_ctx) {
+ proxy_loopbreak(proxy_ctx);
+ proxy_ctx = NULL;
+ }
+}
+
+/*
+ * Error log.
+ * Switchable between stderr and syslog.
+ * Uses logger thread.
+ */
+
+static logger_t *err_log = NULL;
+static int err_shortcut_logger = 0;
+static int err_mode = LOG_ERR_MODE_STDERR;
+
+static ssize_t
+log_err_writecb(UNUSED void *fh, const void *buf, size_t sz)
+{
+ switch (err_mode) {
+ case LOG_ERR_MODE_STDERR:
+ return fwrite(buf, sz - 1, 1, stderr);
+ case LOG_ERR_MODE_SYSLOG:
+ syslog(LOG_ERR, "%s", (const char *)buf);
+ return sz;
+ }
+ return -1;
+}
+
+int
+log_err_printf(const char *fmt, ...)
+{
+ va_list ap;
+ char *buf;
+ int rv;
+
+ va_start(ap, fmt);
+ rv = vasprintf(&buf, fmt, ap);
+ va_end(ap);
+ if (rv < 0)
+ return -1;
+ if (err_shortcut_logger) {
+ return logger_write_freebuf(err_log, NULL, 0,
+ buf, strlen(buf) + 1);
+ } else {
+ log_err_writecb(NULL, (unsigned char*)buf, strlen(buf) + 1);
+ free(buf);
+ }
+ return 0;
+}
+
+void
+log_err_mode(int mode)
+{
+ err_mode = mode;
+}
+
+
+/*
+ * Debug log. Redirects logging to error log.
+ * Switchable between error log or no logging.
+ * Uses the error log logger thread.
+ */
+
+static int dbg_mode = LOG_DBG_MODE_NONE;
+
+int
+log_dbg_write_free(void *buf, size_t sz)
+{
+ if (dbg_mode == LOG_DBG_MODE_NONE)
+ return 0;
+
+ if (err_shortcut_logger) {
+ return logger_write_freebuf(err_log, NULL, 0, buf, sz);
+ } else {
+ log_err_writecb(NULL, buf, sz);
+ free(buf);
+ }
+ return 0;
+}
+
+int
+log_dbg_print_free(char *s)
+{
+ return log_dbg_write_free(s, strlen(s) + 1);
+}
+
+int
+log_dbg_printf(const char *fmt, ...)
+{
+ va_list ap;
+ char *buf;
+ int rv;
+
+ if (dbg_mode == LOG_DBG_MODE_NONE)
+ return 0;
+
+ va_start(ap, fmt);
+ rv = vasprintf(&buf, fmt, ap);
+ va_end(ap);
+ if (rv < 0)
+ return -1;
+ return log_dbg_print_free(buf);
+}
+
+void
+log_dbg_mode(int mode)
+{
+ dbg_mode = mode;
+}
+
+
+/*
+ * Master key log. Logs master keys in SSLKEYLOGFILE format.
+ * Uses a logger thread.
+ */
+
+logger_t *masterkey_log = NULL;
+static int masterkey_fd = -1;
+static char *masterkey_fn = NULL;
+
+static int
+log_masterkey_preinit(const char *logfile)
+{
+ masterkey_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, DFLT_FILEMODE);
+ if (masterkey_fd == -1) {
+ log_err_printf("Failed to open '%s' for writing: %s (%i)\n",
+ logfile, strerror(errno), errno);
+ return -1;
+ }
+ if (!(masterkey_fn = realpath(logfile, NULL))) {
+ log_err_printf("Failed to realpath '%s': %s (%i)\n",
+ logfile, strerror(errno), errno);
+ close(masterkey_fd);
+ masterkey_fd = -1;
+ return -1;
+ }
+ return 0;
+}
+
+static int
+log_masterkey_reopencb(void)
+{
+ close(masterkey_fd);
+ masterkey_fd = open(masterkey_fn, O_WRONLY|O_APPEND|O_CREAT,
+ DFLT_FILEMODE);
+ if (masterkey_fd == -1) {
+ log_err_printf("Failed to open '%s' for writing: %s\n",
+ masterkey_fn, strerror(errno));
+ free(masterkey_fn);
+ masterkey_fn = NULL;
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Do the actual write to the open master key log file descriptor.
+ */
+static ssize_t
+log_masterkey_writecb(UNUSED void *fh, const void *buf, size_t sz)
+{
+ if (write(masterkey_fd, buf, sz) == -1) {
+ log_err_printf("Warning: Failed to write to masterkey log:"
+ " %s\n", strerror(errno));
+ return -1;
+ }
+ return sz;
+}
+
+static void
+log_masterkey_fini(void)
+{
+ close(masterkey_fd);
+}
+
+
+/*
+ * Connection log. Logs a one-liner to a file-based connection log.
+ * Uses a logger thread.
+ */
+
+logger_t *connect_log = NULL;
+static int connect_fd = -1;
+static char *connect_fn = NULL;
+
+static int
+log_connect_preinit(const char *logfile)
+{
+ connect_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, DFLT_FILEMODE);
+ if (connect_fd == -1) {
+ log_err_printf("Failed to open '%s' for writing: %s (%i)\n",
+ logfile, strerror(errno), errno);
+ return -1;
+ }
+ if (!(connect_fn = realpath(logfile, NULL))) {
+ log_err_printf("Failed to realpath '%s': %s (%i)\n",
+ logfile, strerror(errno), errno);
+ close(connect_fd);
+ connect_fd = -1;
+ return -1;
+ }
+ return 0;
+}
+
+static int
+log_connect_reopencb(void)
+{
+ close(connect_fd);
+ connect_fd = open(connect_fn, O_WRONLY|O_APPEND|O_CREAT, DFLT_FILEMODE);
+ if (connect_fd == -1) {
+ log_err_printf("Failed to open '%s' for writing: %s\n",
+ connect_fn, strerror(errno));
+ free(connect_fn);
+ connect_fn = NULL;
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Do the actual write to the open connection log file descriptor.
+ * We prepend a timestamp here, which means that timestamps are slightly
+ * delayed from the time of actual logging. Since we only have second
+ * resolution that should not make any difference.
+ */
+static ssize_t
+log_connect_writecb(UNUSED void *fh, const void *buf, size_t sz)
+{
+ char timebuf[32];
+ time_t epoch;
+ struct tm *utc;
+ size_t n;
+
+ time(&epoch);
+ utc = gmtime(&epoch);
+ n = strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S UTC ", utc);
+ if (n == 0) {
+ log_err_printf("Error from strftime(): buffer too small\n");
+ return -1;
+ }
+ if ((write(connect_fd, timebuf, n) == -1) ||
+ (write(connect_fd, buf, sz) == -1)) {
+ log_err_printf("Warning: Failed to write to connect log: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ return sz;
+}
+
+static void
+log_connect_fini(void)
+{
+ close(connect_fd);
+}
+
+
+/*
+ * Content log.
+ * Logs connection content to either a single file or a directory containing
+ * per-connection logs.
+ * Uses a logger thread; the actual logging happens in a separate thread.
+ * To ensure ordering of requests (open, write, ..., close), logging for a
+ * single connection must happen from a single thread.
+ * This is guaranteed by the current pxythr architecture.
+ */
+
+#define PREPFLAG_REQUEST 1
+#define PREPFLAG_EOF 2
+
+struct log_content_ctx {
+ unsigned int open : 1;
+ union {
+ struct {
+ char *header_req;
+ char *header_resp;
+ } file;
+ struct {
+ int fd;
+ char *filename;
+ } dir;
+ struct {
+ int fd;
+ char *filename;
+ } spec;
+ } u;
+};
+
+static logger_t *content_log = NULL;
+static int content_clisock = -1; /* privsep client socket for content logger */
+
+/*
+ * Split a pathname into static LHS (including final slashes) and dynamic RHS.
+ * Returns -1 on error, 0 on success.
+ * On success, fills in lhs and rhs with newly allocated buffers that must
+ * be freed by the caller.
+ */
+int
+log_content_split_pathspec(const char *path, char **lhs, char **rhs)
+{
+ const char *p, *q, *r;
+
+ p = strchr(path, '%');
+ /* at first % or EOS */
+
+ /* skip % if next char is % (and implicitly not \0) */
+ while (p && p[1] == '%') {
+ p = strchr(p + 2, '%');
+ }
+ /* at first % that is not %%, or at EOS */
+
+ if (!p || !p[1]) {
+ /* EOS: no % that is not %% in path */
+ p = path + strlen(path);
+ }
+ /* at first hot % or at '\0' */
+
+ /* find last / before % */
+ for (r = q = strchr(path, '/'); q && (q < p); q = strchr(q + 1, '/')) {
+ r = q;
+ }
+ if (!(p = r)) {
+ /* no / found, use dummy ./ as LHS */
+ *lhs = strdup("./");
+ if (!*lhs)
+ return -1;
+ *rhs = strdup(path);
+ if (!*rhs) {
+ free(*lhs);
+ return -1;
+ }
+ return 0;
+ }
+ /* at last / terminating the static part of path */
+
+ p++; /* skip / */
+ *lhs = (char *)malloc(p - path + 1 /* for terminating null */);
+ if (!*lhs)
+ return -1;
+ memcpy(*lhs, path, p - path);
+ (*lhs)[p - path] = '\0';
+ *rhs = strdup(p);
+ if (!*rhs) {
+ free(*lhs);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Generate a log path based on the given log spec.
+ * Returns an allocated buffer which must be freed by caller, or NULL on error.
+ */
+#define PATH_BUF_INC 1024
+static char * MALLOC NONNULL(1,2,3)
+log_content_format_pathspec(const char *logspec,
+ char *srchost, char *srcport,
+ char *dsthost, char *dstport,
+ char *exec_path, char *user, char *group)
+{
+ /* set up buffer to hold our generated file path */
+ size_t path_buflen = PATH_BUF_INC;
+ char *path_buf = (char *)malloc(path_buflen);
+ if (path_buf == NULL) {
+ log_err_printf("failed to allocate path buffer\n");
+ return NULL;
+ }
+
+ /* initialize the buffer as an empty C string */
+ path_buf[0] = '\0';
+
+ /* iterate over format specifiers */
+ size_t path_len = 0;
+ for (const char *p = logspec; *p != '\0'; p++) {
+ const char *elem = NULL;
+ size_t elem_len = 0;
+
+ const char iso8601[] = "%Y%m%dT%H%M%SZ";
+ char timebuf[24]; /* sized for ISO 8601 format */
+ char addrbuf[INET6_ADDRSTRLEN + 8]; /* [host]:port */
+
+ /* parse the format string and generate the next path element */
+ switch (*p) {
+ case '%':
+ p++;
+ /* handle format specifiers. */
+ switch (*p) {
+ case '\0':
+ /* unexpected eof; backtrack and discard
+ * invalid format spec */
+ p--;
+ elem_len = 0;
+ break;
+ case '%':
+ elem = p;
+ elem_len = 1;
+ break;
+ case 'd':
+ if (snprintf(addrbuf, sizeof(addrbuf),
+ "%s,%s", dsthost, dstport) < 0) {
+ addrbuf[0] = '?';
+ addrbuf[1] = '\0';
+ }
+ elem = addrbuf;
+ elem_len = strlen(addrbuf);
+ break;
+ case 'D':
+ elem = dsthost;
+ elem_len = strlen(dsthost);
+ break;
+ case 'p':
+ elem = dstport;
+ elem_len = strlen(dstport);
+ break;
+ case 's':
+ if (snprintf(addrbuf, sizeof(addrbuf),
+ "%s,%s", srchost, srcport) < 0) {
+ addrbuf[0] = '?';
+ addrbuf[1] = '\0';
+ }
+ elem = addrbuf;
+ elem_len = strlen(addrbuf);
+ break;
+ case 'S':
+ elem = srchost;
+ elem_len = strlen(srchost);
+ break;
+ case 'q':
+ elem = srcport;
+ elem_len = strlen(srcport);
+ break;
+ case 'x':
+ if (exec_path) {
+ char *match = exec_path;
+ while ((match = strchr(match, '/')) != NULL) {
+ match++;
+ elem = match;
+ }
+ elem_len = elem ? strlen(elem) : 0;
+ } else {
+ elem_len = 0;
+ }
+ break;
+ case 'X':
+ elem = exec_path;
+ elem_len = exec_path ? strlen(exec_path) : 0;
+ break;
+ case 'u':
+ elem = user;
+ elem_len = user ? strlen(user) : 0;
+ break;
+ case 'g':
+ elem = group;
+ elem_len = group ? strlen(group) : 0;
+ break;
+ case 'T': {
+ time_t epoch;
+ struct tm *utc;
+
+ time(&epoch);
+ utc = gmtime(&epoch);
+ strftime(timebuf, sizeof(timebuf), iso8601, utc);
+
+ elem = timebuf;
+ elem_len = sizeof(timebuf);
+ break;
+ }}
+ break;
+ default:
+ elem = p;
+ elem_len = 1;
+ break;
+ }
+
+ if (elem_len > 0) {
+ /* growing the buffer to fit elem_len + terminating \0 */
+ if (path_buflen - path_len < elem_len + 1) {
+ /* Grow in PATH_BUF_INC chunks.
+ * Note that the use of `PATH_BUF_INC' provides
+ * our guaranteed space for a trailing '\0' */
+ path_buflen += elem_len + PATH_BUF_INC;
+ char *newbuf = (char *)realloc(path_buf, path_buflen);
+ if (newbuf == NULL) {
+ log_err_printf("failed to reallocate"
+ " path buffer\n");
+ free(path_buf);
+ return NULL;
+ }
+ path_buf = newbuf;
+ }
+
+ strncat(path_buf, elem, elem_len);
+ path_len += elem_len;
+ }
+ }
+
+ /* apply terminating NUL */
+ assert(path_buflen > path_len);
+ path_buf[path_len] = '\0';
+ return path_buf;
+}
+#undef PATH_BUF_INC
+
+int
+log_content_open(log_content_ctx_t **pctx, struct tfe_config *opts,
+ char *srchost, char *srcport,
+ char *dsthost, char *dstport,
+ char *exec_path, char *user, char *group)
+{
+ log_content_ctx_t *ctx;
+
+ if (*pctx)
+ return 0;
+
+ *pctx = (log_content_ctx_t *)malloc(sizeof(log_content_ctx_t));
+ if (!*pctx)
+ return -1;
+ ctx = *pctx;
+
+ if (opts->contentlog_isdir) {
+ /* per-connection-file content log (-S) */
+ char timebuf[24];
+ time_t epoch;
+ struct tm *utc;
+ char *dsthost_clean, *srchost_clean;
+
+ if (time(&epoch) == -1) {
+ log_err_printf("Failed to get time\n");
+ goto errout;
+ }
+ if ((utc = gmtime(&epoch)) == NULL) {
+ log_err_printf("Failed to convert time: %s (%i)\n",
+ strerror(errno), errno);
+ goto errout;
+ }
+ if (!strftime(timebuf, sizeof(timebuf),
+ "%Y%m%dT%H%M%SZ", utc)) {
+ log_err_printf("Failed to format time: %s (%i)\n",
+ strerror(errno), errno);
+ goto errout;
+ }
+ srchost_clean = sys_ip46str_sanitize(srchost);
+ if (!srchost_clean) {
+ log_err_printf("Failed to sanitize srchost\n");
+ goto errout;
+ }
+ dsthost_clean = sys_ip46str_sanitize(dsthost);
+ if (!dsthost_clean) {
+ log_err_printf("Failed to sanitize dsthost\n");
+ free(srchost_clean);
+ goto errout;
+ }
+ if (asprintf(&ctx->u.dir.filename, "%s/%s-%s,%s-%s,%s.log",
+ opts->contentlog, timebuf,
+ srchost_clean, srcport,
+ dsthost_clean, dstport) < 0) {
+ log_err_printf("Failed to format filename: %s (%i)\n",
+ strerror(errno), errno);
+ free(srchost_clean);
+ free(dsthost_clean);
+ goto errout;
+ }
+ free(srchost_clean);
+ free(dsthost_clean);
+ } else if (opts->contentlog_isspec) {
+ /* per-connection-file content log with logspec (-F) */
+ char *dsthost_clean, *srchost_clean;
+ srchost_clean = sys_ip46str_sanitize(srchost);
+ if (!srchost_clean) {
+ log_err_printf("Failed to sanitize srchost\n");
+ goto errout;
+ }
+ dsthost_clean = sys_ip46str_sanitize(dsthost);
+ if (!dsthost_clean) {
+ log_err_printf("Failed to sanitize dsthost\n");
+ free(srchost_clean);
+ goto errout;
+ }
+ ctx->u.spec.filename = log_content_format_pathspec(
+ opts->contentlog,
+ srchost_clean, srcport,
+ dsthost_clean, dstport,
+ exec_path, user, group);
+ free(srchost_clean);
+ free(dsthost_clean);
+ if (!ctx->u.spec.filename) {
+ goto errout;
+ }
+ } else {
+ /* single-file content log (-L) */
+ if (asprintf(&ctx->u.file.header_req, "[%s]:%s -> [%s]:%s",
+ srchost, srcport, dsthost, dstport) < 0) {
+ goto errout;
+ }
+ if (asprintf(&ctx->u.file.header_resp, "[%s]:%s -> [%s]:%s",
+ dsthost, dstport, srchost, srcport) < 0) {
+ free(ctx->u.file.header_req);
+ goto errout;
+ }
+ }
+
+ /* submit an open event */
+ if (logger_open(content_log, ctx) == -1)
+ goto errout;
+ ctx->open = 1;
+ return 0;
+errout:
+ free(ctx);
+ *pctx = NULL;
+ return -1;
+}
+
+int
+log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int is_request)
+{
+ unsigned long prepflags = 0;
+
+ if (!ctx->open) {
+ log_err_printf("log_content_submit called on closed ctx\n");
+ return -1;
+ }
+
+ if (is_request)
+ prepflags |= PREPFLAG_REQUEST;
+ return logger_submit(content_log, ctx, prepflags, lb);
+}
+
+int
+log_content_close(log_content_ctx_t **pctx, int by_requestor)
+{
+ int rv = 0;
+ unsigned long prepflags = PREPFLAG_EOF;
+
+ if (!(*pctx) || !(*pctx)->open)
+ return -1;
+ if (by_requestor)
+ prepflags |= PREPFLAG_REQUEST;
+ if (logger_submit(content_log, (*pctx), prepflags, NULL) == -1) {
+ rv = -1;
+ goto out;
+ }
+ if (logger_close(content_log, *pctx) == -1) {
+ rv = -1;
+ }
+out:
+ *pctx = NULL;
+ return rv;
+}
+
+/*
+ * Log-type specific code.
+ *
+ * The init/fini functions are executed globally in the main thread.
+ * Callback functions are executed in the logger thread.
+ */
+
+static int
+log_content_dir_opencb(void *fh)
+{
+ log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+
+ if ((ctx->u.dir.fd = privsep_client_openfile(content_clisock,
+ ctx->u.dir.filename,
+ 0)) == -1) {
+ log_err_printf("Opening logdir file '%s' failed: %s (%i)\n",
+ ctx->u.dir.filename, strerror(errno), errno);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+log_content_dir_closecb(void *fh)
+{
+ log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+
+ if (ctx->u.dir.filename)
+ free(ctx->u.dir.filename);
+ if (ctx->u.dir.fd != 1)
+ close(ctx->u.dir.fd);
+ free(ctx);
+}
+
+static ssize_t
+log_content_dir_writecb(void *fh, const void *buf, size_t sz)
+{
+ log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+
+ if (write(ctx->u.dir.fd, buf, sz) == -1) {
+ log_err_printf("Warning: Failed to write to content log: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ return sz;
+}
+
+static int
+log_content_spec_opencb(void *fh)
+{
+ log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+
+ if ((ctx->u.spec.fd = privsep_client_openfile(content_clisock,
+ ctx->u.spec.filename,
+ 1)) == -1) {
+ log_err_printf("Opening logspec file '%s' failed: %s (%i)\n",
+ ctx->u.spec.filename, strerror(errno), errno);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+log_content_spec_closecb(void *fh)
+{
+ log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+
+ if (ctx->u.spec.filename)
+ free(ctx->u.spec.filename);
+ if (ctx->u.spec.fd != -1)
+ close(ctx->u.spec.fd);
+ free(ctx);
+}
+
+static ssize_t
+log_content_spec_writecb(void *fh, const void *buf, size_t sz)
+{
+ log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+
+ if (write(ctx->u.spec.fd, buf, sz) == -1) {
+ log_err_printf("Warning: Failed to write to content log: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ return sz;
+}
+
+static int content_file_fd = -1;
+static char *content_file_fn = NULL;
+
+static int
+log_content_file_preinit(const char *logfile)
+{
+ content_file_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT,
+ DFLT_FILEMODE);
+ if (content_file_fd == -1) {
+ log_err_printf("Failed to open '%s' for writing: %s (%i)\n",
+ logfile, strerror(errno), errno);
+ return -1;
+ }
+ if (!(content_file_fn = realpath(logfile, NULL))) {
+ log_err_printf("Failed to realpath '%s': %s (%i)\n",
+ logfile, strerror(errno), errno);
+ close(content_file_fd);
+ connect_fd = -1;
+ return -1;
+ }
+ return 0;
+}
+
+static void
+log_content_file_fini(void)
+{
+ if (content_file_fn) {
+ free(content_file_fn);
+ content_file_fn = NULL;
+ }
+ if (content_file_fd != -1) {
+ close(content_file_fd);
+ content_file_fd = -1;
+ }
+}
+
+static int
+log_content_file_reopencb(void)
+{
+ close(content_file_fd);
+ content_file_fd = open(content_file_fn,
+ O_WRONLY|O_APPEND|O_CREAT, DFLT_FILEMODE);
+ if (content_file_fd == -1) {
+ log_err_printf("Failed to open '%s' for writing: %s (%i)\n",
+ content_file_fn, strerror(errno), errno);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+static int
+log_content_file_opencb(void *fh)
+{
+ return 0;
+}
+*/
+
+static void
+log_content_file_closecb(void *fh)
+{
+ log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+
+ if (ctx->u.file.header_req) {
+ free(ctx->u.file.header_req);
+ }
+ if (ctx->u.file.header_resp) {
+ free(ctx->u.file.header_resp);
+ }
+
+ free(ctx);
+}
+
+static ssize_t
+log_content_file_writecb(void *fh, const void *buf, size_t sz)
+{
+ UNUSED log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+
+ if (write(content_file_fd, buf, sz) == -1) {
+ log_err_printf("Warning: Failed to write to content log: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ return sz;
+}
+
+static logbuf_t *
+log_content_file_prepcb(void *fh, unsigned long prepflags, logbuf_t *lb)
+{
+ log_content_ctx_t *ctx = (log_content_ctx_t *)fh;
+ int is_request = !!(prepflags & PREPFLAG_REQUEST);
+ logbuf_t *head;
+ time_t epoch;
+ struct tm *utc;
+ char *header;
+
+ if (!(header = is_request ? ctx->u.file.header_req
+ : ctx->u.file.header_resp))
+ goto out;
+
+ /* prepend size tag or EOF, and newline */
+ if (prepflags & PREPFLAG_EOF) {
+ head = logbuf_new_printf(NULL, NULL, " (EOF)\n");
+ } else {
+ head = logbuf_new_printf(lb->fh, lb, " (%zu):\n",
+ logbuf_size(lb));
+ }
+ if (!head) {
+ log_err_printf("Failed to allocate memory\n");
+ logbuf_free(lb);
+ return NULL;
+ }
+ lb = head;
+
+ /* prepend header */
+ head = logbuf_new_copy(header, strlen(header), lb->fh, lb);
+ if (!head) {
+ log_err_printf("Failed to allocate memory\n");
+ logbuf_free(lb);
+ return NULL;
+ }
+ lb = head;
+
+ /* prepend timestamp */
+ head = logbuf_new_alloc(32, lb->fh, lb);
+ if (!head) {
+ log_err_printf("Failed to allocate memory\n");
+ logbuf_free(lb);
+ return NULL;
+ }
+ lb = head;
+ time(&epoch);
+ utc = gmtime(&epoch);
+ lb->sz = strftime((char*)lb->buf, lb->sz, "%Y-%m-%d %H:%M:%S UTC ",
+ utc);
+
+out:
+ return lb;
+}
+
+
+/*
+ * Certificate writer for -w/-W options.
+ */
+static logger_t *cert_log = NULL;
+static int cert_clisock = -1; /* privsep client socket for cert logger */
+
+int
+log_cert_submit(const char *fn, X509 *crt)
+{
+ void *fh;
+ logbuf_t *lb;
+ char *pem;
+
+ if (!(fh = strdup(fn)))
+ goto errout1;
+ if (!(pem = ssl_x509_to_pem(crt)))
+ goto errout2;
+ if (!(lb = logbuf_new(pem, strlen(pem), NULL, NULL)))
+ goto errout3;
+ return logger_submit(cert_log, fh, 0, lb);
+errout3:
+ free(pem);
+errout2:
+ free(fh);
+errout1:
+ return -1;
+}
+
+static ssize_t
+log_cert_writecb(void *fh, const void *buf, size_t sz)
+{
+ char *fn = (char *)fh;
+ int fd;
+
+ if ((fd = privsep_client_certfile(cert_clisock, fn)) == -1) {
+ if (errno != EEXIST) {
+ log_err_printf("Failed to open '%s': %s (%i)\n",
+ fn, strerror(errno), errno);
+ return -1;
+ }
+ return sz;
+ }
+ if (write(fd, buf, sz) == -1) {
+ log_err_printf("Warning: Failed to write to '%s': %s (%i)\n",
+ fn, strerror(errno), errno);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return sz;
+}
+
+
+/*
+ * Initialization and destruction.
+ */
+
+/*
+ * Log pre-init: open all log files but don't start any threads, since we may
+ * fork() after pre-initialization.
+ * Return -1 on errors, 0 otherwise.
+ */
+int
+log_preinit(struct tfe_config *opts)
+{
+ logger_reopen_func_t reopencb;
+ logger_open_func_t opencb;
+ logger_close_func_t closecb;
+ logger_write_func_t writecb;
+ logger_prep_func_t prepcb;
+
+ if (opts->contentlog) {
+ if (opts->contentlog_isdir) {
+ reopencb = NULL;
+ opencb = log_content_dir_opencb;
+ closecb = log_content_dir_closecb;
+ writecb = log_content_dir_writecb;
+ prepcb = NULL;
+ } else if (opts->contentlog_isspec) {
+ reopencb = NULL;
+ opencb = log_content_spec_opencb;
+ closecb = log_content_spec_closecb;
+ writecb = log_content_spec_writecb;
+ prepcb = NULL;
+ } else {
+ if (log_content_file_preinit(opts->contentlog) == -1)
+ goto out;
+ reopencb = log_content_file_reopencb;
+ opencb = NULL;
+ closecb = log_content_file_closecb;
+ writecb = log_content_file_writecb;
+ prepcb = log_content_file_prepcb;
+ }
+ if (!(content_log = logger_new(reopencb, opencb, closecb,
+ writecb, prepcb,
+ log_exceptcb))) {
+ log_content_file_fini();
+ goto out;
+ }
+ }
+ if (opts->connectlog) {
+ if (log_connect_preinit(opts->connectlog) == -1)
+ goto out;
+ if (!(connect_log = logger_new(log_connect_reopencb,
+ NULL, NULL,
+ log_connect_writecb, NULL,
+ log_exceptcb))) {
+ log_connect_fini();
+ goto out;
+ }
+ }
+ if (opts->masterkeylog) {
+ if (log_masterkey_preinit(opts->masterkeylog) == -1)
+ goto out;
+ if (!(masterkey_log = logger_new(log_masterkey_reopencb,
+ NULL, NULL,
+ log_masterkey_writecb, NULL,
+ log_exceptcb))) {
+ log_masterkey_fini();
+ goto out;
+ }
+ }
+ if (opts->certgendir) {
+ if (!(cert_log = logger_new(NULL, NULL, NULL, log_cert_writecb,
+ NULL, log_exceptcb)))
+ goto out;
+ }
+ if (!(err_log = logger_new(NULL, NULL, NULL, log_err_writecb, NULL,
+ log_exceptcb)))
+ goto out;
+ return 0;
+
+out:
+ if (content_log) {
+ log_content_file_fini();
+ logger_free(content_log);
+ }
+ if (connect_log) {
+ log_connect_fini();
+ logger_free(connect_log);
+ }
+ if (cert_log) {
+ logger_free(cert_log);
+ }
+ if (masterkey_log) {
+ log_masterkey_fini();
+ logger_free(masterkey_log);
+ }
+ return -1;
+}
+
+/*
+ * Close all file descriptors opened by log_preinit; used in privsep parent.
+ * Only undo content, connect and masterkey logs, leave error and debug log
+ * functional.
+ */
+void
+log_preinit_undo(void)
+{
+ if (content_log) {
+ log_content_file_fini();
+ logger_free(content_log);
+ }
+ if (connect_log) {
+ log_connect_fini();
+ logger_free(connect_log);
+ }
+ if (masterkey_log) {
+ log_masterkey_fini();
+ logger_free(masterkey_log);
+ }
+}
+
+/*
+ * Log post-init: start logging threads.
+ * Return -1 on errors, 0 otherwise.
+ */
+int
+log_init(struct tfe_config *opts, proxy_ctx_t *ctx, int clisock1, int clisock2)
+{
+ proxy_ctx = ctx;
+ if (err_log)
+ if (logger_start(err_log) == -1)
+ return -1;
+ if (!opts->debug) {
+ err_shortcut_logger = 1;
+ }
+ if (masterkey_log)
+ if (logger_start(masterkey_log) == -1)
+ return -1;
+ if (connect_log)
+ if (logger_start(connect_log) == -1)
+ return -1;
+ if (content_log) {
+ content_clisock = clisock1;
+ if (logger_start(content_log) == -1)
+ return -1;
+ } else {
+ privsep_client_close(clisock1);
+ }
+ if (cert_log) {
+ cert_clisock = clisock2;
+ if (logger_start(cert_log) == -1)
+ return -1;
+ } else {
+ privsep_client_close(clisock2);
+ }
+ return 0;
+}
+
+/*
+ * Drain and cleanup. Tell all loggers to leave, then join all logger threads,
+ * and finally free resources and close log files.
+ */
+void
+log_fini(void)
+{
+ /* switch back to direct logging so we can still log errors while
+ * tearing down the logging infrastructure */
+ err_shortcut_logger = 1;
+
+ if (cert_log)
+ logger_leave(cert_log);
+ if (masterkey_log)
+ logger_leave(masterkey_log);
+ if (content_log)
+ logger_leave(content_log);
+ if (connect_log)
+ logger_leave(connect_log);
+ if (err_log)
+ logger_leave(err_log);
+
+ if (cert_log)
+ logger_join(cert_log);
+ if (masterkey_log)
+ logger_join(masterkey_log);
+ if (content_log)
+ logger_join(content_log);
+ if (connect_log)
+ logger_join(connect_log);
+ if (err_log)
+ logger_join(err_log);
+
+ if (cert_log)
+ logger_free(cert_log);
+ if (masterkey_log)
+ logger_free(masterkey_log);
+ if (content_log)
+ logger_free(content_log);
+ if (connect_log)
+ logger_free(connect_log);
+ if (err_log)
+ logger_free(err_log);
+
+ if (masterkey_log)
+ log_masterkey_fini();
+ if (content_log)
+ log_content_file_fini();
+ if (connect_log)
+ log_connect_fini();
+
+ if (cert_clisock != -1)
+ privsep_client_close(cert_clisock);
+ if (content_clisock != -1)
+ privsep_client_close(content_clisock);
+}
+
+int
+log_reopen(void)
+{
+ int rv = 0;
+
+ if (masterkey_log)
+ if (logger_reopen(masterkey_log) == -1)
+ rv = -1;
+ if (content_log)
+ if (logger_reopen(content_log) == -1)
+ rv = -1;
+ if (connect_log)
+ if (logger_reopen(connect_log) == -1)
+ rv = -1;
+
+ return rv;
+}
+
+/* vim: set noet ft=c: */