diff options
Diffstat (limited to 'src/log.cc')
| -rw-r--r-- | src/log.cc | 1227 |
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: */ |
