diff --git a/log.c b/log.c index a054dac..cd12bb9 100644 --- a/log.c +++ b/log.c @@ -40,6 +40,8 @@ #include #include #include +#include +#include /* * Centralized logging code multiplexing thread access to the logger based @@ -219,6 +221,7 @@ log_connect_close(void) logger_t *content_log = NULL; static int content_fd = -1; /* if set, we are in single file mode */ static const char *content_basedir = NULL; +static const char *content_logspec = NULL; static int log_content_open_singlefile(const char *logfile) @@ -239,6 +242,13 @@ log_content_open_logdir(const char *basedir) return 0; } +static int +log_content_open_logspec(const char *logspec) +{ + content_logspec = logspec; + return 0; +} + static void log_content_close_singlefile(void) { @@ -248,8 +258,127 @@ log_content_close_singlefile(void) } } +/* + * Generate a log path based on the given log spec. + * Returns an allocated buffer which must be freed by caller, or NULL on error. + */ +static char * +log_content_format_pathspec(const char *logspec, char *srcaddr, char *dstaddr, + char *exec_path, char *user, char *group) +{ + /* set up buffer to hold our generated file path */ + size_t path_buflen = 1024; + char *path_buf = 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 + + /* 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': + elem = dstaddr; + elem_len = strlen(dstaddr); + break; + case 's': + elem = srcaddr; + elem_len = strlen(srcaddr); + break; + case 'x': + if (exec_path) { + char *match = exec_path; + while ((match = strchr(match, '/')) != NULL) { + match++; + elem = match; + } + elem_len = strlen(elem); + } 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; + } + + /* growing the buffer to fit elem_len + terminating \0 */ + if (path_buflen - path_len < elem_len + 1) { + /* grow in 1024 chunks. note that the use of `1024' provides our gauranteed space for a trailing '\0' */ + path_buflen += elem_len + 1024; + char *newbuf = realloc(path_buf, path_buflen); + if (newbuf == NULL) { + log_err_printf("failed to reallocate path buffer"); + 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; +} + void -log_content_open(log_content_ctx_t *ctx, char *srcaddr, char *dstaddr) +log_content_open(log_content_ctx_t *ctx, char *srcaddr, char *dstaddr, + char *exec_path, char *user, char *group) { if (ctx->open) return; @@ -258,6 +387,58 @@ log_content_open(log_content_ctx_t *ctx, char *srcaddr, char *dstaddr) ctx->fd = content_fd; asprintf(&ctx->header_in, "%s -> %s", srcaddr, dstaddr); asprintf(&ctx->header_out, "%s -> %s", dstaddr, srcaddr); + } else if (content_logspec) { + char *filename; + + filename = log_content_format_pathspec(content_logspec, + srcaddr, dstaddr, + exec_path, user, + group); + + /* statefully create parent directories by iteratively rewriting + * the path at each directory seperator */ + char parent[strlen(filename)+1]; + char *p; + + memcpy(parent, filename, sizeof(parent)); + + /* skip leading '/' characters */ + p = parent; + while (*p == '/') p++; + + while ((p = strchr(p, '/')) != NULL) { + /* overwrite '/' to terminate the string at the next parent directory */ + *p = '\0'; + + struct stat sbuf; + if (stat(parent, &sbuf) != 0) { + if (mkdir(parent, 0755) != 0) { + log_err_printf("Could not create directory '%s': %s\n", + parent, strerror(errno)); + ctx->fd = -1; + return; + } + } else if (!S_ISDIR(sbuf.st_mode)) { + log_err_printf("Failed to open '%s': %s is not a directory\n", + filename, parent); + ctx->fd = -1; + return; + } + + /* replace the overwritten slash */ + *p = '/'; + p++; + + /* skip leading '/' characters */ + while (*p == '/') p++; + } + + ctx->fd = open(filename, O_WRONLY|O_APPEND|O_CREAT, 0660); + if (ctx->fd == -1) { + log_err_printf("Failed to open '%s': %s\n", + filename, strerror(errno)); + } + } else { char filename[1024]; char timebuf[24]; @@ -383,12 +564,15 @@ int log_preinit(opts_t *opts) { if (opts->contentlog) { - if (!opts->contentlogdir) { - if (log_content_open_singlefile(opts->contentlog) - == -1) + if (opts->contentlogdir) { + if (log_content_open_logdir(opts->contentlog) == -1) + goto out; + } else if (opts->contentlogspec) { + if (log_content_open_logspec(opts->contentlog) == -1) goto out; } else { - if (log_content_open_logdir(opts->contentlog) == -1) + if (log_content_open_singlefile(opts->contentlog) + == -1) goto out; } if (!(content_log = logger_new(log_content_writecb))) { diff --git a/log.h b/log.h index 98cfa35..ea9aab6 100644 --- a/log.h +++ b/log.h @@ -64,7 +64,8 @@ typedef struct log_content_ctx { char *header_in; char *header_out; } log_content_ctx_t; -void log_content_open(log_content_ctx_t *, char *, char *) NONNULL(1,2,3); +void log_content_open(log_content_ctx_t *, char *, char *, char *, + char *, char *) NONNULL(1,2,3); void log_content_submit(log_content_ctx_t *, logbuf_t *, int) NONNULL(1,2); void log_content_close(log_content_ctx_t *) NONNULL(1); diff --git a/main.c b/main.c index ed3f650..70abcd0 100644 --- a/main.c +++ b/main.c @@ -139,7 +139,21 @@ main_usage(void) " -p pidfile write pid to pidfile (default: no pid file)\n" " -l logfile connect log: log one line summary per connection to logfile\n" " -L logfile content log: full data to file or named pipe (excludes -S)\n" -" -S logdir content log: full data to separate files in dir (excludes -L)\n" +" -S logdir content log: full data to separate files in dir (excludes -L/-F)\n" +" -F pathspec content log: full data to separate files with the given path spec (excludes -L/-S):\n" +" pathspec = \n" +" The following directives are supported.\n" +" %%d - dest address:port\n" +" %%s - source address:port\n" +" %%x - base name of local process. if unavailable, will be skipped.\n" +" %%X - full path to local process; . if unavailable, will be skipped.\n" +" %%u - username or uid of local process. if unavailable, will be skipped\n" +" %%g - group or gid of local process. if unavailable, will be skipped\n" +" %%T - initial connection time as an ISO 8601 UTC timestamp\n" +" %%%% - literal '%%'\n" +" e.g.\n" +" \"/var/log/sslsplit/%%X/%%u-%%A:%%P-%%a:%%p-%%T\"\n" +" Unknown directives are ignored, and intermediate directories will be created automatically\n" " -d daemon mode: run in background, log error messages to syslog\n" " -D debug mode: run in foreground, log debug messages on stderr\n" " -V print version information and exit\n" @@ -249,7 +263,7 @@ main(int argc, char *argv[]) } while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z - "k:c:C:K:t:OPs:r:R:e:Eu:m:j:p:l:L:S:dDVh")) != -1) { + "k:c:C:K:t:OPs:r:R:e:Eu:m:j:p:l:L:S:F:dDVh")) != -1) { switch (ch) { case 'c': if (opts->cacrt) @@ -473,6 +487,7 @@ main(int argc, char *argv[]) if (!opts->contentlog) oom_die(argv0); opts->contentlogdir = 0; + opts->contentlogspec = 0; break; case 'S': if (opts->contentlog) @@ -481,6 +496,14 @@ main(int argc, char *argv[]) if (!opts->contentlog) oom_die(argv0); opts->contentlogdir = 1; + opts->contentlogspec = 0; + break; + case 'F': + if (opts->contentlog) + free(opts->contentlog); + opts->contentlog = strdup(optarg); + opts->contentlogspec = 1; + opts->contentlogdir = 0; break; case 'd': opts->detach = 1; diff --git a/opts.h b/opts.h index b475b12..6b88e18 100644 --- a/opts.h +++ b/opts.h @@ -75,6 +75,7 @@ typedef struct opts { unsigned int passthrough : 1; unsigned int deny_ocsp : 1; unsigned int contentlogdir : 1; + unsigned int contentlogspec : 1; char *ciphers; char *tgcrtdir; char *dropuser; diff --git a/pxyconn.c b/pxyconn.c index 9946d96..d1f7510 100644 --- a/pxyconn.c +++ b/pxyconn.c @@ -1580,7 +1580,8 @@ pxy_bev_eventcb(struct bufferevent *bev, short events, void *arg) } if (WANT_CONTENT_LOG(ctx)) { log_content_open(&ctx->logctx, ctx->src_str, - ctx->dst_str); + ctx->dst_str, ctx->exec_path, + ctx->user, ctx->group); } /* log connection if we don't analyze any headers */ @@ -1970,13 +1971,13 @@ pxy_conn_setup(evutil_socket_t fd, if (!ctx->src_str) goto memout; - if (ctx->pid != -1) { - if (sys_proc_info(ctx->pid, &ctx->exec_path, &ctx->uid, &ctx->gid) == 0) { - ctx->user = sys_user_str(ctx->uid); - ctx->group = sys_group_str(ctx->gid); - if (!ctx->user || !ctx->group) { - goto memout; - } + /* fetch process info */ + if (ctx->pid != -1 && sys_proc_info(ctx->pid, &ctx->exec_path, &ctx->uid, &ctx->gid) == 0) { + /* fetch user/group names */ + ctx->user = sys_user_str(ctx->uid); + ctx->group = sys_group_str(ctx->gid); + if (!ctx->user || !ctx->group) { + goto memout; } } }