Merge branch 'issue/55'

pull/13/head
Daniel Roethlisberger 10 years ago
commit ff8ef9528d

201
log.c

@ -40,6 +40,8 @@
#include <errno.h>
#include <fcntl.h>
#include <syslog.h>
#include <assert.h>
#include <sys/stat.h>
/*
* 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,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
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;
if (content_fd != -1) {
/* single-file content log (-L) */
ctx->fd = content_fd;
asprintf(&ctx->header_in, "%s -> %s", srcaddr, dstaddr);
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 {
/* per-connection-file content log (-S) */
char filename[1024];
char timebuf[24];
time_t epoch;
@ -383,12 +572,14 @@ 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))) {

@ -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);

@ -138,14 +138,24 @@ main_usage(void)
" -j jaildir chroot() to jaildir (impacts -S and sni, see manual page)\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 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"
" -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/-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 debug mode: run in foreground, log debug messages on stderr\n"
" -V print version information and exit\n"
" -h print usage information and exit\n"
" proxyspec = type listenaddr+port [natengine|targetaddr+port|\"sni\"+port]\n"
" e.g. http 0.0.0.0 8080 www.roe.ch 80 # http/4; static hostname dst\n"
" e.g. http 0.0.0.0 8080 www.roe.ch 80 # http/4; static hostname dst\n"
" https ::1 8443 2001:db8::1 443 # https/6; static address dst\n"
" https 127.0.0.1 9443 sni 443 # https/4; SNI DNS lookups\n"
" tcp 127.0.0.1 10025 # tcp/4; default NAT engine\n"
@ -249,7 +259,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 +483,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 +492,16 @@ 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);
if (!opts->contentlog)
oom_die(argv0);
opts->contentlogdir = 0;
opts->contentlogspec = 1;
break;
case 'd':
opts->detach = 1;
@ -573,7 +594,7 @@ main(int argc, char *argv[])
oom_die(argv0);
}
if (!opts->dropuser && !geteuid() && !getuid() &&
!opts->contentlogdir) {
!opts->contentlogdir && !opts->contentlogspec) {
opts->dropuser = strdup("nobody");
if (!opts->dropuser)
oom_die(argv0);

@ -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;

@ -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;
}
}
}

@ -116,6 +116,14 @@ returned by \fB-E\fP.
List all supported NAT engines available on the system and exit. See
NAT ENGINES for a list of NAT engines currently supported by SSLsplit.
.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
Use Diffie-Hellman group parameters from \fIpemfile\fP for Ephemereal
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
.B \-j \fIjaildir\fP
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
\fIproxyspecs\fP. The directory given with \fB-S\fP will be relative to
Note that this has implications for \fB-F\fP, \fB-S\fP, and for \fBsni\fP
\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).
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.
@ -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
\fIjaildir\fP to make name resolution work from within the chroot directory.
.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"
SSLsplit currently supports the following NAT engines:
.TP

Loading…
Cancel
Save