From 8350b1deb0c214b745e16044cf85326b7cfed937 Mon Sep 17 00:00:00 2001 From: Landon Fuller Date: Sat, 18 Oct 2014 14:35:49 -0600 Subject: [PATCH 1/4] Plumb user/group/path information through the logging API. --- log.c | 8 +++++++- log.h | 3 ++- pxyconn.c | 19 +++++++++---------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/log.c b/log.c index a054dac..6846cdb 100644 --- a/log.c +++ b/log.c @@ -249,8 +249,11 @@ log_content_close_singlefile(void) } 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) { + log_err_printf("log %s (%s:%s)\n", exec_path, user, group); + if (ctx->open) return; @@ -258,12 +261,15 @@ 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); + // TODO - Add something to context based on exec_path / user / group } else { char filename[1024]; char timebuf[24]; time_t epoch; struct tm *utc; + // TODO - Open a path based on exec_path / user / group + time(&epoch); utc = gmtime(&epoch); strftime(timebuf, sizeof(timebuf), "%Y%m%dT%H%M%SZ", utc); 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/pxyconn.c b/pxyconn.c index ceea6e8..a05a9bc 100644 --- a/pxyconn.c +++ b/pxyconn.c @@ -1505,7 +1505,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 */ @@ -1895,15 +1896,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; - } - - log_err_printf("Matched socket to process %s (user=%s, group=%s)\n", ctx->exec_path, ctx->user, ctx->group); + /* 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; } } } From 06c61c16ed55b98da5ad5989c60f9ccc9dbad4fa Mon Sep 17 00:00:00 2001 From: Landon Fuller Date: Sat, 18 Oct 2014 16:40:22 -0600 Subject: [PATCH 2/4] Add support for specifying log paths as a specialized format string. Format string handling is fully implemented, with the exception of support for automatically creating missing directories. --- log.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- main.c | 26 +++++++++- opts.h | 1 + 3 files changed, 172 insertions(+), 11 deletions(-) diff --git a/log.c b/log.c index 6846cdb..f0d35bd 100644 --- a/log.c +++ b/log.c @@ -40,6 +40,7 @@ #include #include #include +#include /* * Centralized logging code multiplexing thread access to the logger based @@ -219,6 +220,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 +241,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,12 +257,125 @@ 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; + } + + /* 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 = strstr(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, char *exec_path, char *user, char *group) { - log_err_printf("log %s (%s:%s)\n", exec_path, user, group); - if (ctx->open) return; @@ -261,15 +383,28 @@ 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); - // TODO - Add something to context based on exec_path / user / group + } else if (content_logspec) { + char *filename; + + filename = log_content_format_pathspec(content_logspec, + srcaddr, dstaddr, + exec_path, user, + group); + + // TODO - Create directories as required + + 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]; time_t epoch; struct tm *utc; - // TODO - Open a path based on exec_path / user / group - time(&epoch); utc = gmtime(&epoch); strftime(timebuf, sizeof(timebuf), "%Y%m%dT%H%M%SZ", utc); @@ -389,12 +524,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/main.c b/main.c index 8b87bf0..3622fc6 100644 --- a/main.c +++ b/main.c @@ -134,7 +134,20 @@ 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. Unknown directives are ignored.\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; intermediate directories will be created automatically. 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" " -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" @@ -230,7 +243,7 @@ main(int argc, char *argv[]) } while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z - "k:c:C:K:t:OPs:e:Eu:m:j:p:l:L:S:dDVh")) != -1) { + "k:c:C:K:t:OPs:e:Eu:m:j:p:l:L:S:F:dDVh")) != -1) { switch (ch) { case 'c': if (opts->cacrt) @@ -428,12 +441,21 @@ main(int argc, char *argv[]) free(opts->contentlog); opts->contentlog = strdup(optarg); opts->contentlogdir = 0; + opts->contentlogspec = 0; break; case 'S': if (opts->contentlog) free(opts->contentlog); opts->contentlog = strdup(optarg); 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 0c5140c..18b95c5 100644 --- a/opts.h +++ b/opts.h @@ -60,6 +60,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; From e6aa76b8440f9abbef2314922be69686c52c4d76 Mon Sep 17 00:00:00 2001 From: Landon Fuller Date: Sat, 18 Oct 2014 17:02:53 -0600 Subject: [PATCH 3/4] Implement automatic creation of parent directories. --- log.c | 41 +++++++++++++++++++++++++++++++++++++++-- main.c | 5 +++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/log.c b/log.c index f0d35bd..402668c 100644 --- a/log.c +++ b/log.c @@ -41,6 +41,7 @@ #include #include #include +#include /* * Centralized logging code multiplexing thread access to the logger based @@ -308,7 +309,7 @@ log_content_format_pathspec(const char *logspec, char *srcaddr, char *dstaddr, case 'x': if (exec_path) { char *match = exec_path; - while ((match = strstr(match, "/")) != NULL) { + while ((match = strchr(match, '/')) != NULL) { match++; elem = match; } @@ -391,7 +392,43 @@ log_content_open(log_content_ctx_t *ctx, char *srcaddr, char *dstaddr, exec_path, user, group); - // TODO - Create directories as required + /* 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) { diff --git a/main.c b/main.c index 3622fc6..f28ab7b 100644 --- a/main.c +++ b/main.c @@ -137,17 +137,18 @@ main_usage(void) " -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. Unknown directives are ignored.\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; intermediate directories will be created automatically. 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" From f36b06f8c195850a8c38b4591a3d4b6616ca88ce Mon Sep 17 00:00:00 2001 From: Landon Fuller Date: Sat, 18 Oct 2014 20:41:43 -0600 Subject: [PATCH 4/4] Fix stupid bug caused by leaving the path string as non-NULL terminated on initialization. This failed visibly when the allocated buffer did not already lead with \0. --- log.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/log.c b/log.c index 402668c..cd12bb9 100644 --- a/log.c +++ b/log.c @@ -274,6 +274,9 @@ log_content_format_pathspec(const char *logspec, char *srcaddr, char *dstaddr, 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++) {