Merge branch 'issue/55'

This commit is contained in:
Daniel Roethlisberger 2014-11-13 23:40:50 +01:00
commit ff8ef9528d
6 changed files with 277 additions and 21 deletions

201
log.c
View File

@ -40,6 +40,8 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <syslog.h> #include <syslog.h>
#include <assert.h>
#include <sys/stat.h>
/* /*
* Centralized logging code multiplexing thread access to the logger based * Centralized logging code multiplexing thread access to the logger based
@ -219,6 +221,7 @@ log_connect_close(void)
logger_t *content_log = NULL; logger_t *content_log = NULL;
static int content_fd = -1; /* if set, we are in single file mode */ static int content_fd = -1; /* if set, we are in single file mode */
static const char *content_basedir = NULL; static const char *content_basedir = NULL;
static const char *content_logspec = NULL;
static int static int
log_content_open_singlefile(const char *logfile) log_content_open_singlefile(const char *logfile)
@ -239,6 +242,13 @@ log_content_open_logdir(const char *basedir)
return 0; return 0;
} }
static int
log_content_open_logspec(const char *logspec)
{
content_logspec = logspec;
return 0;
}
static void static void
log_content_close_singlefile(void) log_content_close_singlefile(void)
{ {
@ -248,17 +258,196 @@ 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.
*/
#define PATH_BUF_INC 1024
static char * WUNRES NONNULL(1,2,3)
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 = PATH_BUF_INC;
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 PATH_BUF_INC chunks.
* Note that the use of `PATH_BUF_INC' provides our
* gauranteed space for a trailing '\0' */
path_buflen += elem_len + PATH_BUF_INC;
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;
}
#undef PATH_BUF_INC
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)
{ {
if (ctx->open) if (ctx->open)
return; return;
if (content_fd != -1) { if (content_fd != -1) {
/* single-file content log (-L) */
ctx->fd = content_fd; ctx->fd = content_fd;
asprintf(&ctx->header_in, "%s -> %s", srcaddr, dstaddr); asprintf(&ctx->header_in, "%s -> %s", srcaddr, dstaddr);
asprintf(&ctx->header_out, "%s -> %s", dstaddr, srcaddr); asprintf(&ctx->header_out, "%s -> %s", dstaddr, srcaddr);
} else if (content_logspec) {
/* per-connection-file content log with logspec (-F) */
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 { } else {
/* per-connection-file content log (-S) */
char filename[1024]; char filename[1024];
char timebuf[24]; char timebuf[24];
time_t epoch; time_t epoch;
@ -383,12 +572,14 @@ int
log_preinit(opts_t *opts) log_preinit(opts_t *opts)
{ {
if (opts->contentlog) { if (opts->contentlog) {
if (!opts->contentlogdir) { if (opts->contentlogdir) {
if (log_content_open_singlefile(opts->contentlog) if (log_content_open_logdir(opts->contentlog) == -1)
== -1) goto out;
} else if (opts->contentlogspec) {
if (log_content_open_logspec(opts->contentlog) == -1)
goto out; goto out;
} else { } else {
if (log_content_open_logdir(opts->contentlog) == -1) if (log_content_open_singlefile(opts->contentlog) == -1)
goto out; goto out;
} }
if (!(content_log = logger_new(log_content_writecb))) { if (!(content_log = logger_new(log_content_writecb))) {

3
log.h
View File

@ -64,7 +64,8 @@ typedef struct log_content_ctx {
char *header_in; char *header_in;
char *header_out; char *header_out;
} log_content_ctx_t; } 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_submit(log_content_ctx_t *, logbuf_t *, int) NONNULL(1,2);
void log_content_close(log_content_ctx_t *) NONNULL(1); void log_content_close(log_content_ctx_t *) NONNULL(1);

29
main.c
View File

@ -138,8 +138,18 @@ main_usage(void)
" -j jaildir chroot() to jaildir (impacts -S and sni, see manual page)\n" " -j jaildir chroot() to jaildir (impacts -S and sni, see manual page)\n"
" -p pidfile write pid to pidfile (default: no pid file)\n" " -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 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" " -L logfile content log: full data to file or named pipe (excludes -S/-F)\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 sep files with %% subst (excl. -L/-S):\n"
" %%T - initial connection time as an ISO 8601 UTC timestamp\n"
" %%d - dest address:port\n"
" %%s - source address:port\n"
" %%x - base name of local process (skipped if unavailable)\n"
" %%X - full path to local process (skipped if unavailable)\n"
" %%u - user name or id of local process (skipped if unavailable)\n"
" %%g - group name or id of local process (skipped if unavailable)\n"
" %%%% - literal '%%'\n"
" e.g. \"/var/log/sslsplit/%%X/%%u-%%s-%%d-%%T\"\n"
" -d daemon mode: run in background, log error messages to syslog\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" " -D debug mode: run in foreground, log debug messages on stderr\n"
" -V print version information and exit\n" " -V print version information and exit\n"
@ -249,7 +259,7 @@ main(int argc, char *argv[])
} }
while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z 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) { switch (ch) {
case 'c': case 'c':
if (opts->cacrt) if (opts->cacrt)
@ -473,6 +483,7 @@ main(int argc, char *argv[])
if (!opts->contentlog) if (!opts->contentlog)
oom_die(argv0); oom_die(argv0);
opts->contentlogdir = 0; opts->contentlogdir = 0;
opts->contentlogspec = 0;
break; break;
case 'S': case 'S':
if (opts->contentlog) if (opts->contentlog)
@ -481,6 +492,16 @@ main(int argc, char *argv[])
if (!opts->contentlog) if (!opts->contentlog)
oom_die(argv0); oom_die(argv0);
opts->contentlogdir = 1; opts->contentlogdir = 1;
opts->contentlogspec = 0;
break;
case 'F':
if (opts->contentlog)
free(opts->contentlog);
opts->contentlog = strdup(optarg);
if (!opts->contentlog)
oom_die(argv0);
opts->contentlogdir = 0;
opts->contentlogspec = 1;
break; break;
case 'd': case 'd':
opts->detach = 1; opts->detach = 1;
@ -573,7 +594,7 @@ main(int argc, char *argv[])
oom_die(argv0); oom_die(argv0);
} }
if (!opts->dropuser && !geteuid() && !getuid() && if (!opts->dropuser && !geteuid() && !getuid() &&
!opts->contentlogdir) { !opts->contentlogdir && !opts->contentlogspec) {
opts->dropuser = strdup("nobody"); opts->dropuser = strdup("nobody");
if (!opts->dropuser) if (!opts->dropuser)
oom_die(argv0); oom_die(argv0);

1
opts.h
View File

@ -75,6 +75,7 @@ typedef struct opts {
unsigned int passthrough : 1; unsigned int passthrough : 1;
unsigned int deny_ocsp : 1; unsigned int deny_ocsp : 1;
unsigned int contentlogdir : 1; unsigned int contentlogdir : 1;
unsigned int contentlogspec : 1;
char *ciphers; char *ciphers;
char *tgcrtdir; char *tgcrtdir;
char *dropuser; char *dropuser;

View File

@ -1580,7 +1580,8 @@ pxy_bev_eventcb(struct bufferevent *bev, short events, void *arg)
} }
if (WANT_CONTENT_LOG(ctx)) { if (WANT_CONTENT_LOG(ctx)) {
log_content_open(&ctx->logctx, ctx->src_str, 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 */ /* log connection if we don't analyze any headers */
@ -1970,8 +1971,9 @@ pxy_conn_setup(evutil_socket_t fd,
if (!ctx->src_str) if (!ctx->src_str)
goto memout; goto memout;
if (ctx->pid != -1) { /* fetch process info */
if (sys_proc_info(ctx->pid, &ctx->exec_path, &ctx->uid, &ctx->gid) == 0) { 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->user = sys_user_str(ctx->uid);
ctx->group = sys_group_str(ctx->gid); ctx->group = sys_group_str(ctx->gid);
if (!ctx->user || !ctx->group) { if (!ctx->user || !ctx->group) {
@ -1979,7 +1981,6 @@ pxy_conn_setup(evutil_socket_t fd,
} }
} }
} }
}
/* for SSL, defer dst connection setup to initial_readcb */ /* for SSL, defer dst connection setup to initial_readcb */
if (ctx->spec->ssl) { if (ctx->spec->ssl) {

View File

@ -116,6 +116,14 @@ returned by \fB-E\fP.
List all supported NAT engines available on the system and exit. See List all supported NAT engines available on the system and exit. See
NAT ENGINES for a list of NAT engines currently supported by SSLsplit. NAT ENGINES for a list of NAT engines currently supported by SSLsplit.
.TP .TP
.B \-F \fIlogspec\fP
Log connection content to separate log files with the given path specification
(see LOG SPECIFICATIONS below). For each connection, a log file will be
written, which will contain both directions of data as transmitted.
Information about the connection will be contained in the filename only.
If \fB-F\fP is used with \fB-j\fP, \fIlogspec\fP is relative to \fIjaildir\fP.
If \fB-F\fP is used with \fB-u\fP, \fIlogspec\fP must be writable by \fIuser\fP.
.TP
.B \-g \fIpemfile\fP .B \-g \fIpemfile\fP
Use Diffie-Hellman group parameters from \fIpemfile\fP for Ephemereal Use Diffie-Hellman group parameters from \fIpemfile\fP for Ephemereal
Diffie-Hellman (EDH/DHE) cipher suites. If \fB-g\fP is not given, SSLsplit Diffie-Hellman (EDH/DHE) cipher suites. If \fB-g\fP is not given, SSLsplit
@ -147,8 +155,8 @@ Display help on usage and exit.
.TP .TP
.B \-j \fIjaildir\fP .B \-j \fIjaildir\fP
Change the root directory to \fIjaildir\fP using chroot(2) after opening files. Change the root directory to \fIjaildir\fP using chroot(2) after opening files.
Note that this has implications for both \fB-S\fP and for \fBsni\fP Note that this has implications for \fB-F\fP, \fB-S\fP, and for \fBsni\fP
\fIproxyspecs\fP. The directory given with \fB-S\fP will be relative to \fIproxyspecs\fP. The path given with \fB-S\fP or \fB-F\fP will be relative to
\fIjaildir\fP since the log files cannot be opened before calling chroot(2). \fIjaildir\fP since the log files cannot be opened before calling chroot(2).
Depending on your operating system, you will need to copy files such as Depending on your operating system, you will need to copy files such as
\fB/etc/resolv.conf\fP to \fIjaildir\fP in order for name resolution to work. \fB/etc/resolv.conf\fP to \fIjaildir\fP in order for name resolution to work.
@ -347,6 +355,39 @@ than the NAT rules redirecting the actual connections.
Note that when using \fB-j\fP with \fBsni\fP, you may need to prepare Note that when using \fB-j\fP with \fBsni\fP, you may need to prepare
\fIjaildir\fP to make name resolution work from within the chroot directory. \fIjaildir\fP to make name resolution work from within the chroot directory.
.LP .LP
.SH "LOG SPECIFICATIONS"
Log specifications are composed of zero or more printf-style directives;
ordinary characters are included directly in the output path.
SSLsplit current supports the following directives:
.TP
.I %d
The destination address and port.
.TP
.I %s
The source address and port.
.TP
.I %x
The name of the local process. If process information is unavailable,
this directive will be omitted from the output path.
.TP
.I %X
The full path of the local process. If process information is unavailable,
this directive will be omitted from the output path.
.TP
.I %u
The username or numeric uid of the local process. If process information is unavailable,
this directive will be omitted from the output path.
.TP
.I %g
The group name or numeric gid of the local process. If process information is unavailable,
this directive will be omitted from the output path.
.TP
.I %T
The initial connection time as an ISO 8601 UTC timestamp.
.TP
.I %%
A literal '%' character.
.LP
.SH "NAT ENGINES" .SH "NAT ENGINES"
SSLsplit currently supports the following NAT engines: SSLsplit currently supports the following NAT engines:
.TP .TP