From 71743feaa13a53bdc40214f01c9b8f989e203a5f Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Fri, 21 Nov 2014 23:39:51 +0100 Subject: [PATCH 01/23] Add functions to send/recv UNIX dgram socket msgs and fds --- sys.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sys.h | 3 ++ 2 files changed, 95 insertions(+) diff --git a/sys.c b/sys.c index e3c9b9f..986420c 100644 --- a/sys.c +++ b/sys.c @@ -498,4 +498,96 @@ sys_get_cpu_cores(void) #endif /* !_SC_NPROCESSORS_ONLN */ } +/* + * Send a message and optional file descriptor on a connected AF_UNIX + * SOCKET_DGRAM socket s. Returns the return value of sendmsg(). + * If fd is -1, no file descriptor is passed. + */ +ssize_t +sys_sendmsgfd(int sock, void *buf, size_t bufsz, int fd) +{ + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + iov.iov_base = buf; + iov.iov_len = bufsz; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + if (fd != -1) { + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + *((int *) CMSG_DATA(cmsg)) = fd; + } else { + msg.msg_control = NULL; + msg.msg_controllen = 0; + } +#ifdef MSG_NOSIGNAL + return sendmsg(sock, &msg, MSG_NOSIGNAL); +#else /* !MSG_NOSIGNAL */ + return sendmsg(sock, &msg, 0); +#endif /* !MSG_NOSIGNAL */ +} + +/* + * Receive a message and optional file descriptor on a connected AF_UNIX + * SOCKET_DGRAM socket s. Returns the return value of recvmsg()/recv() + * and sets errno to EINVAL if the received message is malformed. + * If pfd is NULL, no file descriptor is received; if a file descriptor was + * part of the received message and pfd is NULL, then the kernel will close it. + */ +ssize_t +sys_recvmsgfd(int sock, void *buf, size_t bufsz, int *pfd) +{ + ssize_t n; + + if (pfd) { + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + unsigned char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + iov.iov_base = buf; + iov.iov_len = bufsz; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + if ((n = recvmsg(sock, &msg, 0)) <= 0) + return n; + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + if (cmsg->cmsg_level != SOL_SOCKET) { + errno = EINVAL; + return -1; + } + if (cmsg->cmsg_type != SCM_RIGHTS) { + errno = EINVAL; + return -1; + } + *pfd = *((int *) CMSG_DATA(cmsg)); + } else { + *pfd = -1; + } + } else { + n = recv(sock, buf, bufsz, 0); + } + return n; +} + /* vim: set noet ft=c: */ + diff --git a/sys.h b/sys.h index 0af0368..815d01d 100644 --- a/sys.h +++ b/sys.h @@ -56,6 +56,9 @@ int sys_dir_eachfile(const char *, sys_dir_eachfile_cb_t, void *) NONNULL(1,2); uint32_t sys_get_cpu_cores(void) WUNRES; +ssize_t sys_sendmsgfd(int, void *, size_t, int) NONNULL(2) WUNRES; +ssize_t sys_recvmsgfd(int, void *, size_t, int *) NONNULL(2) WUNRES; + #endif /* !SYS_H */ /* vim: set noet ft=c: */ From 53096b2e61e0ac2c874eae518673bfccb4166b05 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sat, 22 Nov 2014 02:09:32 +0100 Subject: [PATCH 02/23] Add util_max() --- util.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util.h b/util.h index 818805b..46f9fa3 100644 --- a/util.h +++ b/util.h @@ -33,6 +33,8 @@ char * util_skipws(const char *) NONNULL(1) PURE; +#define util_max(a,b) ((a) > (b) ? (a) : (b)) + #endif /* !UTIL_H */ /* vim: set noet ft=c: */ From 762bd0cba1099803a5c0606f01a1e04c02e6eee9 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sun, 23 Nov 2014 15:44:20 +0100 Subject: [PATCH 03/23] Rename shortcut flag for clarity --- log.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/log.c b/log.c index 42cd469..78dd7ce 100644 --- a/log.c +++ b/log.c @@ -58,7 +58,7 @@ */ static logger_t *err_log = NULL; -static int err_started = 0; /* while 0, shortcut the thrqueue */ +static int err_shortcut_logger = 0; static int err_mode = LOG_ERR_MODE_STDERR; static ssize_t @@ -86,7 +86,7 @@ log_err_printf(const char *fmt, ...) va_end(ap); if (rv < 0) return -1; - if (err_started) { + if (err_shortcut_logger) { return logger_write_freebuf(err_log, NULL, 0, buf, strlen(buf) + 1); } else { @@ -117,7 +117,7 @@ log_dbg_write_free(void *buf, size_t sz) if (dbg_mode == LOG_DBG_MODE_NONE) return 0; - if (err_started) { + if (err_shortcut_logger) { return logger_write_freebuf(err_log, NULL, 0, buf, sz); } else { log_err_writecb(NULL, buf, sz); @@ -742,7 +742,7 @@ log_init(opts_t *opts) if (logger_start(err_log) == -1) return -1; if (!opts->debug) { - err_started = 1; + err_shortcut_logger = 1; } if (connect_log) if (logger_start(connect_log) == -1) From 86397dac89478c2af93f5201d2cf197d3f6a4616 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sun, 23 Nov 2014 15:45:55 +0100 Subject: [PATCH 04/23] Break at 80 cols --- main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index 3bb9e1c..d24d461 100644 --- a/main.c +++ b/main.c @@ -278,8 +278,8 @@ main(int argc, char *argv[]) natengine = NULL; } - while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z OPT_i - "k:c:C:K:t:OPs:r:R:e:Eu:m:j:p:l:L:S:F:dDVh")) != -1) { + while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z OPT_i "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) From 2d97659a6bd1316a5d026ac14db307e00e1d8b45 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sun, 23 Nov 2014 15:46:37 +0100 Subject: [PATCH 05/23] Check if args to -j and -S are directories --- main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/main.c b/main.c index d24d461..d72b5ab 100644 --- a/main.c +++ b/main.c @@ -483,6 +483,12 @@ main(int argc, char *argv[]) oom_die(argv0); break; case 'j': + if (!sys_isdir(optarg)) { + fprintf(stderr, "%s: '%s' is not a " + "directory\n", + argv0, optarg); + exit(EXIT_FAILURE); + } if (opts->jaildir) free(opts->jaildir); opts->jaildir = strdup(optarg); @@ -506,6 +512,12 @@ main(int argc, char *argv[]) opts->contentlog_isspec = 0; break; case 'S': + if (!sys_isdir(optarg)) { + fprintf(stderr, "%s: '%s' is not a " + "directory\n", + argv0, optarg); + exit(EXIT_FAILURE); + } if (opts->contentlog) free(opts->contentlog); opts->contentlog = strdup(optarg); From a09f42a507217c77be07365a6d9489665f4cef38 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sun, 23 Nov 2014 15:49:03 +0100 Subject: [PATCH 06/23] Handle EINTR in sys_sendmsgfd() and sys_recvmsgfd() --- sys.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/sys.c b/sys.c index 986420c..2d8e6f9 100644 --- a/sys.c +++ b/sys.c @@ -510,6 +510,7 @@ sys_sendmsgfd(int sock, void *buf, size_t bufsz, int fd) struct msghdr msg; struct cmsghdr *cmsg; char cmsgbuf[CMSG_SPACE(sizeof(int))]; + ssize_t n; iov.iov_base = buf; iov.iov_len = bufsz; @@ -533,11 +534,14 @@ sys_sendmsgfd(int sock, void *buf, size_t bufsz, int fd) msg.msg_control = NULL; msg.msg_controllen = 0; } + do { #ifdef MSG_NOSIGNAL - return sendmsg(sock, &msg, MSG_NOSIGNAL); + n = sendmsg(sock, &msg, MSG_NOSIGNAL); #else /* !MSG_NOSIGNAL */ - return sendmsg(sock, &msg, 0); + n = sendmsg(sock, &msg, 0); #endif /* !MSG_NOSIGNAL */ + } while (n == -1 && errno == EINTR); + return n; } /* @@ -567,7 +571,10 @@ sys_recvmsgfd(int sock, void *buf, size_t bufsz, int *pfd) msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); - if ((n = recvmsg(sock, &msg, 0)) <= 0) + do { + n = recvmsg(sock, &msg, 0); + } while (n == -1 && errno == EINTR); + if (n <= 0) return n; cmsg = CMSG_FIRSTHDR(&msg); if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { @@ -584,7 +591,9 @@ sys_recvmsgfd(int sock, void *buf, size_t bufsz, int *pfd) *pfd = -1; } } else { - n = recv(sock, buf, bufsz, 0); + do { + n = recv(sock, buf, bufsz, 0); + } while (n == -1 && errno == EINTR); } return n; } From db80d3460ca04433c9ce1905314bae83283e18cb Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sun, 23 Nov 2014 17:27:57 +0100 Subject: [PATCH 07/23] Remove spurious UNUSED attribute --- log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log.c b/log.c index 78dd7ce..e89245c 100644 --- a/log.c +++ b/log.c @@ -543,7 +543,7 @@ log_content_dir_closecb(void *fh) } static int -log_content_spec_opencb(UNUSED void *fh) +log_content_spec_opencb(void *fh) { log_content_ctx_t *ctx = fh; char *filedir, *filename2; From a027f87c1c9c498d4b67c9427e797dac35900ff9 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sun, 23 Nov 2014 22:52:09 +0100 Subject: [PATCH 08/23] Check if -u and -m user and group exist immediately --- main.c | 12 ++++++++++++ sys.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- sys.h | 2 ++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index d72b5ab..4748cdd 100644 --- a/main.c +++ b/main.c @@ -462,6 +462,12 @@ main(int argc, char *argv[]) exit(EXIT_SUCCESS); break; case 'u': + if (!sys_isuser(optarg)) { + fprintf(stderr, "%s: '%s' is not an " + "existing user\n", + argv0, optarg); + exit(EXIT_FAILURE); + } if (opts->dropuser) free(opts->dropuser); opts->dropuser = strdup(optarg); @@ -469,6 +475,12 @@ main(int argc, char *argv[]) oom_die(argv0); break; case 'm': + if (!sys_isgroup(optarg)) { + fprintf(stderr, "%s: '%s' is not an " + "existing group\n", + argv0, optarg); + exit(EXIT_FAILURE); + } if (opts->dropgroup) free(opts->dropgroup); opts->dropgroup = strdup(optarg); diff --git a/sys.c b/sys.c index 2d8e6f9..944ab7a 100644 --- a/sys.c +++ b/sys.c @@ -76,6 +76,7 @@ sys_privdrop(const char *username, const char *groupname, const char *jaildir) int ret = -1; if (groupname) { + errno = 0; if (!(gr = getgrnam(groupname))) { log_err_printf("Failed to getgrnam group '%s': %s\n", groupname, strerror(errno)); @@ -84,6 +85,7 @@ sys_privdrop(const char *username, const char *groupname, const char *jaildir) } if (username) { + errno = 0; if (!(pw = getpwnam(username))) { log_err_printf("Failed to getpwnam user '%s': %s\n", username, strerror(errno)); @@ -141,6 +143,42 @@ error: return ret; } +/* + * Returns 1 if username can be loaded from user database, 0 otherwise. + */ +int +sys_isuser(const char *username) +{ + errno = 0; + if (!getpwnam(username)) { + if (errno != 0 && errno != ENOENT) { + log_err_printf("Failed to load user '%s': %s (%i)\n", + username, strerror(errno), errno); + } + return 0; + } + + endpwent(); + return 1; +} + +/* + * Returns 1 if groupname can be loaded from group database, 0 otherwise. + */ +int +sys_isgroup(const char *groupname) +{ + errno = 0; + if (!getgrnam(groupname)) { + if (errno != 0 && errno != ENOENT) { + log_err_printf("Failed to load group '%s': %s (%i)\n", + groupname, strerror(errno), errno); + } + return 0; + } + return 1; +} + /* * Open and lock process ID file fn. * Returns open file descriptor on success or -1 on errors. @@ -373,8 +411,13 @@ sys_isdir(const char *path) { struct stat s; - if (stat(path, &s) == -1) + if (stat(path, &s) == -1) { + if (errno != ENOENT) { + log_err_printf("Error stating file: %s (%i)\n", + strerror(errno), errno); + } return 0; + } if (s.st_mode & S_IFDIR) return 1; return 0; diff --git a/sys.h b/sys.h index 815d01d..532d20b 100644 --- a/sys.h +++ b/sys.h @@ -41,6 +41,8 @@ int sys_pidf_open(const char *) NONNULL(1) WUNRES; int sys_pidf_write(int) WUNRES; void sys_pidf_close(int, const char *) NONNULL(2); +int sys_isuser(const char *) NONNULL(1) WUNRES; +int sys_isgroup(const char *) NONNULL(1) WUNRES; char * sys_user_str(uid_t) MALLOC; char * sys_group_str(gid_t) MALLOC; From b3f4d25619b5ffc7f849d6b9ce383682ed8914e5 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 24 Nov 2014 21:33:41 +0100 Subject: [PATCH 09/23] Make log_fini() more robust --- log.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/log.c b/log.c index e89245c..19d409d 100644 --- a/log.c +++ b/log.c @@ -760,23 +760,30 @@ log_init(opts_t *opts) 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 (content_log) logger_leave(content_log); if (connect_log) logger_leave(connect_log); - logger_leave(err_log); + if (err_log) + logger_leave(err_log); if (content_log) logger_join(content_log); if (connect_log) logger_join(connect_log); - logger_join(err_log); + if (err_log) + logger_join(err_log); if (content_log) logger_free(content_log); if (connect_log) logger_free(connect_log); - logger_free(err_log); + if (err_log) + logger_free(err_log); if (content_log) log_content_file_fini(); From c01ace1261f79215f3448c9b69e675e570fe534e Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 24 Nov 2014 22:01:52 +0100 Subject: [PATCH 10/23] Introduce privilege separation architecture Fork into a monitor parent process and an actual proxy child process, communicating over AF_UNIX sockets. Certain privileged operations are performed through the privileged parent process, like opening log files or listener sockets, while all other operations happen in the child process, which can now drop its privileges without side-effects for log file opening and other privileged operations. This is also a preparation for -l/-L logfile reopening through SIGUSR1. This means that -S and -F are no longer relative to chroot() if used with -j. This is a deliberate POLA violation. --- log.c | 139 +++++++--- log.h | 5 +- main.c | 191 +++++++++++--- nat.c | 9 + nat.h | 1 + opts.c | 3 + opts.h | 1 + privsep.c | 746 +++++++++++++++++++++++++++++++++++++++++++++++++++++ privsep.h | 43 +++ proxy.c | 62 +---- proxy.h | 2 +- sslsplit.1 | 11 +- sys.c | 131 +++++++++- sys.h | 2 + sys.t.c | 2 +- 15 files changed, 1205 insertions(+), 143 deletions(-) create mode 100644 privsep.c create mode 100644 privsep.h diff --git a/log.c b/log.c index 19d409d..262b4f1 100644 --- a/log.c +++ b/log.c @@ -31,6 +31,7 @@ #include "logger.h" #include "sys.h" #include "attrib.h" +#include "privsep.h" #include #include @@ -40,7 +41,6 @@ #include #include #include -#include #include #include @@ -242,8 +242,9 @@ struct log_content_ctx { } u; }; -logger_t *content_log = NULL; -static int content_fd = -1; /* if set, we are in single file mode */ +static logger_t *content_log = NULL; +static int content_fd = -1; /* set in 'file' mode */ +static int content_clisock = -1; /* privsep client socket for content logger */ static int log_content_file_preinit(const char *logfile) @@ -266,16 +267,71 @@ log_content_file_fini(void) } } +/* + * 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 = 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 * -log_content_format_pathspec(const char *logspec, char *srcaddr, char *dstaddr, - char *exec_path, char *user, char *group) -WUNRES MALLOC NONNULL(1,2,3); -static char * +static char * MALLOC NONNULL(1,2,3) log_content_format_pathspec(const char *logspec, char *srcaddr, char *dstaddr, char *exec_path, char *user, char *group) { @@ -521,9 +577,10 @@ log_content_dir_opencb(void *fh) { log_content_ctx_t *ctx = fh; - ctx->fd = open(ctx->u.dir.filename, O_WRONLY|O_APPEND|O_CREAT, 0660); - if (ctx->fd == -1) { - log_err_printf("Failed to open '%s': %s (%i)\n", + if ((ctx->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; } @@ -546,36 +603,14 @@ static int log_content_spec_opencb(void *fh) { log_content_ctx_t *ctx = fh; - char *filedir, *filename2; - filename2 = strdup(ctx->u.spec.filename); - if (!filename2) { - log_err_printf("Could not duplicate filname: %s (%i)\n", - strerror(errno), errno); + if ((ctx->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; } - filedir = dirname(filename2); - if (!filedir) { - log_err_printf("Could not get dirname: %s (%i)\n", - strerror(errno), errno); - free(filename2); - return -1; - } - if (sys_mkpath(filedir, 0755) == -1) { - log_err_printf("Could not create directory '%s': %s (%i)\n", - filedir, strerror(errno), errno); - free(filename2); - return -1; - } - free(filename2); - - ctx->fd = open(ctx->u.spec.filename, O_WRONLY|O_APPEND|O_CREAT, 0660); - if (ctx->fd == -1) { - log_err_printf("Failed to open '%s': %s\n", - ctx->u.spec.filename, strerror(errno)); - return -1; - } - return 0; } @@ -731,12 +766,29 @@ out: return -1; } +/* + * Close all file descriptors opened by log_preinit; used in privsep parent. + * Only undo content and connect log, 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_close(); + logger_free(connect_log); + } +} + /* * Log post-init: start logging threads. * Return -1 on errors, 0 otherwise. */ int -log_init(opts_t *opts) +log_init(opts_t *opts, int clisock) { if (err_log) if (logger_start(err_log) == -1) @@ -747,9 +799,13 @@ log_init(opts_t *opts) if (connect_log) if (logger_start(connect_log) == -1) return -1; - if (content_log) + if (content_log) { + content_clisock = clisock; if (logger_start(content_log) == -1) return -1; + } else { + privsep_client_close(clisock); + } return 0; } @@ -789,6 +845,9 @@ log_fini(void) log_content_file_fini(); if (connect_log) log_connect_close(); + + if (content_clisock != -1) + privsep_client_close(content_clisock); } /* vim: set noet ft=c: */ diff --git a/log.h b/log.h index 92fbe5e..eec9870 100644 --- a/log.h +++ b/log.h @@ -63,9 +63,12 @@ int log_content_open(log_content_ctx_t **, opts_t *, char *, char *, int log_content_submit(log_content_ctx_t *, logbuf_t *, int) NONNULL(1,2) WUNRES; int log_content_close(log_content_ctx_t **) NONNULL(1) WUNRES; +int log_content_split_pathspec(const char *, char **, + char **) NONNULL(1,2,3) WUNRES; int log_preinit(opts_t *) NONNULL(1) WUNRES; -int log_init(opts_t *) NONNULL(1) WUNRES; +void log_preinit_undo(void); +int log_init(opts_t *, int) NONNULL(1) WUNRES; void log_fini(void); #endif /* !LOG_H */ diff --git a/main.c b/main.c index 4748cdd..85ee309 100644 --- a/main.c +++ b/main.c @@ -33,6 +33,7 @@ #include "opts.h" #include "proxy.h" +#include "privsep.h" #include "ssl.h" #include "nat.h" #include "proc.h" @@ -61,6 +62,8 @@ extern int daemon(int, int); #endif /* __APPLE__ */ +#define DEFAULT_DROPUSER "nobody" + /* * Print version information to stderr. */ @@ -144,9 +147,10 @@ main_usage(void) " -s ciphers use the given OpenSSL cipher suite spec (default: ALL:-aNULL)\n" " -e engine specify default NAT engine to use (default: %s)\n" " -E list available NAT engines and exit\n" -" -u user drop privileges to user (default if run as root: nobody)\n" +" -u user drop privileges to user (default if run as root: " + DEFAULT_DROPUSER ")\n" " -m group when using -u, override group (default: primary group of user)\n" -" -j jaildir chroot() to jaildir (impacts -S and sni, see manual page)\n" +" -j jaildir chroot() to jaildir (impacts sni proxyspecs, 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/-F)\n" @@ -204,14 +208,14 @@ main_loadtgcrt(const char *filename, void *arg) log_err_printf("Failed to load cert and key from PEM file " "'%s'\n", filename); log_fini(); - exit(EXIT_FAILURE); /* XXX */ + exit(EXIT_FAILURE); } if (X509_check_private_key(cert->crt, cert->key) != 1) { log_err_printf("Cert does not match key in PEM file " "'%s':\n", filename); ERR_print_errors_fp(stderr); log_fini(); - exit(EXIT_FAILURE); /* XXX */ + exit(EXIT_FAILURE); } #ifdef DEBUG_CERTIFICATE @@ -503,9 +507,15 @@ main(int argc, char *argv[]) } if (opts->jaildir) free(opts->jaildir); - opts->jaildir = strdup(optarg); - if (!opts->jaildir) - oom_die(argv0); + opts->jaildir = realpath(optarg, NULL); + if (!opts->jaildir) { + fprintf(stderr, "%s: Failed to " + "canonicalize '%s': " + "%s (%i)\n", + argv0, optarg, + strerror(errno), errno); + exit(EXIT_FAILURE); + } break; case 'l': if (opts->connectlog) @@ -532,21 +542,89 @@ main(int argc, char *argv[]) } if (opts->contentlog) free(opts->contentlog); - opts->contentlog = strdup(optarg); - if (!opts->contentlog) - oom_die(argv0); + opts->contentlog = realpath(optarg, NULL); + if (!opts->contentlog) { + fprintf(stderr, "%s: Failed to " + "canonicalize '%s': " + "%s (%i)\n", + argv0, optarg, + strerror(errno), errno); + exit(EXIT_FAILURE); + } opts->contentlog_isdir = 1; opts->contentlog_isspec = 0; break; - case 'F': + case 'F': { + char *lhs, *rhs, *p, *q; + size_t n; + if (opts->contentlog_basedir) + free(opts->contentlog_basedir); if (opts->contentlog) free(opts->contentlog); - opts->contentlog = strdup(optarg); - if (!opts->contentlog) + if (log_content_split_pathspec(optarg, &lhs, + &rhs) == -1) { + fprintf(stderr, "%s: Failed to split " + "'%s' in lhs/rhs: " + "%s (%i)\n", + argv0, optarg, + strerror(errno), errno); + exit(EXIT_FAILURE); + } + /* eliminate %% from lhs */ + for (p = q = lhs; *p; p++, q++) { + if (q < p) + *q = *p; + if (*p == '%' && *(p+1) == '%') + p++; + } + *q = '\0'; + /* all %% in lhs resolved to % */ + if (sys_mkpath(lhs, 0777) == -1) { + fprintf(stderr, "%s: Failed to create " + "'%s': %s (%i)\n", + argv0, lhs, + strerror(errno), errno); + exit(EXIT_FAILURE); + } + opts->contentlog_basedir = realpath(lhs, NULL); + if (!opts->contentlog_basedir) { + fprintf(stderr, "%s: Failed to " + "canonicalize '%s': " + "%s (%i)\n", + argv0, lhs, + strerror(errno), errno); + exit(EXIT_FAILURE); + } + /* count '%' in opts->contentlog_basedir */ + for (n = 0, p = opts->contentlog_basedir; + *p; + p++) { + if (*p == '%') + n++; + } + free(lhs); + n += strlen(opts->contentlog_basedir); + if (!(lhs = malloc(n + 1))) + oom_die(argv0); + /* re-encoding % to %%, copying basedir to lhs */ + for (p = opts->contentlog_basedir, q = lhs; + *p; + p++, q++) { + *q = *p; + if (*q == '%') + *(++q) = '%'; + } + *q = '\0'; + /* lhs contains encoded realpathed basedir */ + if (asprintf(&opts->contentlog, + "%s/%s", lhs, rhs) < 0) oom_die(argv0); opts->contentlog_isdir = 0; opts->contentlog_isspec = 1; + free(lhs); + free(rhs); break; + } #ifdef HAVE_LOCAL_PROCINFO case 'i': opts->lprocinfo = 1; @@ -643,8 +721,8 @@ main(int argc, char *argv[]) oom_die(argv0); } if (!opts->dropuser && !geteuid() && !getuid() && - !opts->contentlog_isdir && !opts->contentlog_isspec) { - opts->dropuser = strdup("nobody"); + sys_isuser(DEFAULT_DROPUSER)) { + opts->dropuser = strdup(DEFAULT_DROPUSER); if (!opts->dropuser) oom_die(argv0); } @@ -730,48 +808,75 @@ main(int argc, char *argv[]) exit(EXIT_FAILURE); } - /* Bind listeners before dropping privileges */ - proxy_ctx_t *proxy = proxy_new(opts); - if (!proxy) { - fprintf(stderr, "%s: failed to initialize proxy.\n", argv0); - exit(EXIT_FAILURE); - } /* Load certs before dropping privs but after cachemgr_preinit() */ if (opts->tgcrtdir) { sys_dir_eachfile(opts->tgcrtdir, main_loadtgcrt, opts); } - /* Drop privs, chroot, detach from TTY */ - if (sys_privdrop(opts->dropuser, opts->dropgroup, opts->jaildir) == -1) { - fprintf(stderr, "%s: failed to drop privileges: %s\n", - argv0, strerror(errno)); - exit(EXIT_FAILURE); - } + /* Detach from tty; from this point on, only canonicalized absolute + * paths should be used (-j, -F, -S). */ if (opts->detach) { if (OPTS_DEBUG(opts)) { log_dbg_printf("Detaching from TTY, see syslog for " "errors after this point\n"); } - if (daemon(1, 0) == -1) { + if (daemon(0, 0) == -1) { fprintf(stderr, "%s: failed to detach from TTY: %s\n", argv0, strerror(errno)); exit(EXIT_FAILURE); } log_err_mode(LOG_ERR_MODE_SYSLOG); - ssl_reinit(); } + if (opts->pidfile && (sys_pidf_write(pidfd) == -1)) { + log_err_printf("Failed to write PID to PID file '%s': %s (%i)" + "\n", opts->pidfile, strerror(errno), errno); + return -1; + } + + /* Fork into parent monitor process and (potentially unprivileged) + * child process doing the actual work. We request two privsep client + * sockets: one for the content logger thread, one for the child + * process main thread (main proxy thread) */ + int clisock[2]; + if (privsep_fork(opts, clisock, 2) != 0) { + /* parent has exited the monitor loop after waiting for child, + * or an error occured */ + if (opts->pidfile) { + sys_pidf_close(pidfd, opts->pidfile); + } + goto out_parent; + } + /* child */ + + /* close pidfile in child */ + if (opts->pidfile) + close(pidfd); + +#if 0 + /* Initialize proxy before dropping privs */ + proxy_ctx_t *proxy = proxy_new(opts, clisock[0]); + if (!proxy) { + log_err_printf("Failed to initialize proxy.\n"); + exit(EXIT_FAILURE); + } +#endif + + /* Drop privs, chroot */ + if (sys_privdrop(opts->dropuser, opts->dropgroup, + opts->jaildir) == -1) { + log_err_printf("Failed to drop privileges: %s (%i)\n", + strerror(errno), errno); + exit(EXIT_FAILURE); + } + ssl_reinit(); + /* Post-privdrop/chroot/detach initialization, thread spawning */ - if (log_init(opts) == -1) { + if (log_init(opts, clisock[1]) == -1) { fprintf(stderr, "%s: failed to init log facility: %s\n", argv0, strerror(errno)); goto out_log_failed; } - if (opts->pidfile && (sys_pidf_write(pidfd) == -1)) { - log_err_printf("Failed to write PID to PID file '%s': %s\n", - opts->pidfile, strerror(errno)); - goto out_pidwrite_failed; - } if (cachemgr_init() == -1) { log_err_printf("Failed to init cache manager.\n"); goto out_cachemgr_failed; @@ -780,21 +885,27 @@ main(int argc, char *argv[]) log_err_printf("Failed to init NAT state table lookup.\n"); goto out_nat_failed; } - rv = EXIT_SUCCESS; +#if 1 + proxy_ctx_t *proxy = proxy_new(opts, clisock[0]); + if (!proxy) { + log_err_printf("Failed to initialize proxy.\n"); + goto out_proxy_new_failed; + } +#endif proxy_run(proxy); proxy_free(proxy); +#if 1 +out_proxy_new_failed: +#endif nat_fini(); out_nat_failed: cachemgr_fini(); out_cachemgr_failed: - if (opts->pidfile) { - sys_pidf_close(pidfd, opts->pidfile); - } -out_pidwrite_failed: log_fini(); out_log_failed: +out_parent: opts_free(opts); ssl_fini(); return rv; diff --git a/nat.c b/nat.c index 9816c25..6b70a04 100644 --- a/nat.c +++ b/nat.c @@ -564,6 +564,15 @@ nat_preinit(void) return 0; } +/* + * Undo nat_preinit - close all file descriptors, for use in privsep parent. + */ +void +nat_preinit_undo(void) +{ + nat_fini(); +} + /* * Initialize all NAT engines which were marked as used by previous calls to * nat_getlookupcb(). diff --git a/nat.h b/nat.h index d2da6aa..db104d2 100644 --- a/nat.h +++ b/nat.h @@ -48,6 +48,7 @@ int nat_ipv6ready(const char *) WUNRES; const char *nat_getdefaultname(void) WUNRES; void nat_list_engines(void); int nat_preinit(void) WUNRES; +void nat_preinit_undo(void); int nat_init(void) WUNRES; void nat_fini(void); void nat_version(void); diff --git a/opts.c b/opts.c index 0189af8..04daa91 100644 --- a/opts.c +++ b/opts.c @@ -105,6 +105,9 @@ opts_free(opts_t *opts) if (opts->contentlog) { free(opts->contentlog); } + if (opts->contentlog_basedir) { + free(opts->contentlog_basedir); + } memset(opts, 0, sizeof(opts_t)); free(opts); } diff --git a/opts.h b/opts.h index abbf5e8..c836efe 100644 --- a/opts.h +++ b/opts.h @@ -88,6 +88,7 @@ typedef struct opts { char *pidfile; char *connectlog; char *contentlog; + char *contentlog_basedir; /* static part of logspec, for privsep srv */ CONST_SSL_METHOD *(*sslmethod)(void); X509 *cacrt; EVP_PKEY *cakey; diff --git a/privsep.c b/privsep.c new file mode 100644 index 0000000..2caccc4 --- /dev/null +++ b/privsep.c @@ -0,0 +1,746 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2014, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * 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 unmodified, 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 AUTHOR ``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 AUTHOR 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 "privsep.h" + +#include "sys.h" +#include "util.h" +#include "log.h" +#include "attrib.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * Privilege separation functionality. + * + * The server code has limitations on the internal functionality that can be + * used, namely only those that are initialized before forking can be used. + */ + +/* maximal message sizes */ +#define PRIVSEP_MAX_REQ_SIZE 512 /* arbitrary limit */ +#define PRIVSEP_MAX_ANS_SIZE (1+sizeof(int)) +/* command byte */ +#define PRIVSEP_REQ_CLOSE 0 /* closing command socket */ +#define PRIVSEP_REQ_OPENFILE 1 /* open content log file */ +#define PRIVSEP_REQ_OPENFILE_P 2 /* open content log file w/mkpath */ +#define PRIVSEP_REQ_OPENSOCK 3 /* open socket and pass fd */ +/* response byte */ +#define PRIVSEP_ANS_SUCCESS 0 /* success */ +#define PRIVSEP_ANS_UNK_CMD 1 /* unknown command */ +#define PRIVSEP_ANS_INVALID 2 /* invalid message */ +#define PRIVSEP_ANS_DENIED 3 /* request denied */ +#define PRIVSEP_ANS_SYS_ERR 4 /* system error; arg=errno */ + +int received_sighup; +int received_sigint; +int received_sigquit; +int received_sigchld; +int selfpipe_wrfd; + +static void +privsep_server_signal_handler(int sig) +{ + int saved_errno; + + saved_errno = errno; + switch (sig) { + case SIGHUP: + received_sighup = 1; + break; + case SIGINT: + received_sigint = 1; + break; + case SIGQUIT: + received_sigquit = 1; + break; + case SIGCHLD: + received_sigchld = 1; + break; + } + if (selfpipe_wrfd != -1) { + ssize_t n; + + do { + n = write(selfpipe_wrfd, "!", 1); + } while (n == -1 && errno == EINTR); + if (n == -1) { + log_err_printf("Failed to write from signal handler: " + "%s (%i)\n", strerror(errno), errno); + /* ignore error */ + } + } + errno = saved_errno; +} + +static int WUNRES +privsep_server_openfile_verify(opts_t *opts, char *fn, int mkpath) +{ + if (mkpath && !opts->contentlog_isspec) + return -1; + if (!mkpath && !opts->contentlog_isdir) + return -1; + if (strstr(fn, mkpath ? opts->contentlog_basedir + : opts->contentlog) != fn || + strstr(fn, "/../")) + return -1; + return 0; +} + +static int WUNRES +privsep_server_openfile(char *fn, int mkpath) +{ + int fd; + + if (mkpath) { + char *filedir, *fn2; + + fn2 = strdup(fn); + if (!fn2) { + log_err_printf("Could not duplicate filname: %s (%i)\n", + strerror(errno), errno); + return -1; + } + filedir = dirname(fn2); + if (!filedir) { + log_err_printf("Could not get dirname: %s (%i)\n", + strerror(errno), errno); + free(fn2); + return -1; + } + if (sys_mkpath(filedir, 0777) == -1) { + log_err_printf("Could not create directory '%s': %s (%i)\n", + filedir, strerror(errno), errno); + free(fn2); + return -1; + } + free(fn2); + } + + fd = open(fn, O_WRONLY|O_APPEND|O_CREAT, 0666); + if (fd == -1) { + log_err_printf("Failed to open '%s': %s (%i)\n", + fn, strerror(errno), errno); + return -1; + } + return fd; +} + +static int WUNRES +privsep_server_opensock_verify(opts_t *opts, void *arg) +{ + for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) { + if (spec == arg) + return 0; + } + return 1; +} + +static int WUNRES +privsep_server_opensock(proxyspec_t *spec) +{ + evutil_socket_t fd; + int on = 1; + int rv; + + fd = socket(spec->listen_addr.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd == -1) { + log_err_printf("Error from socket(): %s (%i)\n", + strerror(errno), errno); + evutil_closesocket(fd); + return -1; + } + + rv = evutil_make_socket_nonblocking(fd); + if (rv == -1) { + log_err_printf("Error making socket nonblocking: %s (%i)\n", + strerror(errno), errno); + evutil_closesocket(fd); + return -1; + } + + rv = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on)); + if (rv == -1) { + log_err_printf("Error from setsockopt(SO_KEEPALIVE): %s (%i)\n", + strerror(errno), errno); + evutil_closesocket(fd); + return -1; + } + + rv = evutil_make_listen_socket_reuseable(fd); + if (rv == -1) { + log_err_printf("Error from setsockopt(SO_REUSABLE): %s\n", + strerror(errno)); + evutil_closesocket(fd); + return -1; + } + + if (spec->natsocket && (spec->natsocket(fd) == -1)) { + log_err_printf("Error from spec->natsocket()\n"); + evutil_closesocket(fd); + return -1; + } + + rv = bind(fd, (struct sockaddr *)&spec->listen_addr, + spec->listen_addrlen); + if (rv == -1) { + log_err_printf("Error from bind(): %s\n", strerror(errno)); + evutil_closesocket(fd); + return -1; + } + + return fd; +} + +/* + * Handle a single request on a readable server socket. + * Returns 0 on success, 1 on EOF and -1 on error. + */ +static int WUNRES +privsep_server_handle_req(opts_t *opts, int srvsock) +{ + char req[PRIVSEP_MAX_REQ_SIZE]; + char ans[PRIVSEP_MAX_ANS_SIZE]; + ssize_t n; + int mkpath = 0; + + if ((n = sys_recvmsgfd(srvsock, req, sizeof(req), + NULL)) == -1) { + if (errno == EPIPE || errno == ECONNRESET) { + /* unfriendly EOF, leave server */ + return 1; + } + log_err_printf("Failed to receive msg: %s (%i)\n", + strerror(errno), errno); + return -1; + } + if (n == 0) { + /* EOF, leave server; will not happen for SOCK_DGRAM sockets */ + return 1; + } + log_dbg_printf("Received privsep req type %02x sz %zd on srvsock %i\n", + req[0], n, srvsock); + switch (req[0]) { + case PRIVSEP_REQ_CLOSE: { + /* client indicates EOF through close message */ + return 1; + } + case PRIVSEP_REQ_OPENFILE_P: + mkpath = 1; + case PRIVSEP_REQ_OPENFILE: { + char *fn; + int fd; + + if (n < 2) { + ans[0] = PRIVSEP_ANS_INVALID; + if (sys_sendmsgfd(srvsock, ans, 1, -1) == -1) { + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + } + if (!(fn = malloc(n))) { + ans[0] = PRIVSEP_ANS_SYS_ERR; + *((int*)&ans[1]) = errno; + if (sys_sendmsgfd(srvsock, ans, 1 + sizeof(int), + -1) == -1) { + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + return 0; + } + memcpy(fn, req + 1, n - 1); + fn[n - 1] = '\0'; + if (privsep_server_openfile_verify(opts, fn, mkpath) == -1) { + free(fn); + ans[0] = PRIVSEP_ANS_DENIED; + if (sys_sendmsgfd(srvsock, ans, 1, -1) == -1) { + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + return 0; + } + if ((fd = privsep_server_openfile(fn, mkpath)) == -1) { + free(fn); + ans[0] = PRIVSEP_ANS_SYS_ERR; + *((int*)&ans[1]) = errno; + if (sys_sendmsgfd(srvsock, ans, 1 + sizeof(int), + -1) == -1) { + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + return 0; + } else { + free(fn); + ans[0] = PRIVSEP_ANS_SUCCESS; + if (sys_sendmsgfd(srvsock, ans, 1, fd) == -1) { + close(fd); + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + close(fd); + return 0; + } + /* not reached */ + break; + } + case PRIVSEP_REQ_OPENSOCK: { + proxyspec_t *arg; + int s; + + if (n != sizeof(char) + sizeof(arg)) { + ans[0] = PRIVSEP_ANS_INVALID; + if (sys_sendmsgfd(srvsock, ans, 1, -1) == -1) { + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + return 0; + } + arg = *(proxyspec_t**)(&req[1]); + if (privsep_server_opensock_verify(opts, arg) == -1) { + ans[0] = PRIVSEP_ANS_DENIED; + if (sys_sendmsgfd(srvsock, ans, 1, -1) == -1) { + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + return 0; + } + if ((s = privsep_server_opensock(arg)) == -1) { + ans[0] = PRIVSEP_ANS_SYS_ERR; + *((int*)&ans[1]) = errno; + if (sys_sendmsgfd(srvsock, ans, 1 + sizeof(int), + -1) == -1) { + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + return 0; + } else { + ans[0] = PRIVSEP_ANS_SUCCESS; + if (sys_sendmsgfd(srvsock, ans, 1, s) == -1) { + evutil_closesocket(s); + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + evutil_closesocket(s); + return 0; + } + /* not reached */ + break; + } + default: + ans[0] = PRIVSEP_ANS_UNK_CMD; + if (sys_sendmsgfd(srvsock, ans, 1, -1) == -1) { + log_err_printf("Sending message failed: %s (%i" + ")\n", strerror(errno), errno); + return -1; + } + } + return 0; +} + +/* + * sigpipe is the self-pipe trick pipe used for communicating signals to + * the main event loop and break out of select() without race conditions. + * srvsock[] is a dynamic array of connected privsep server sockets to serve. + * Caller is responsible for freeing memory after returning, if necessary. + * childpid is the pid of the child process to forward signals to. + */ +static int +privsep_server(opts_t *opts, int sigpipe, int srvsock[], size_t nsrvsock, + pid_t childpid) +{ + int srveof[nsrvsock]; + size_t i = 0; + + for (i = 0; i < nsrvsock; i++) { + srveof[i] = 0; + } + + for (;;) { + fd_set readfds; + int maxfd, rv; + + do { + FD_ZERO(&readfds); + FD_SET(sigpipe, &readfds); + maxfd = sigpipe; + for (i = 0; i < nsrvsock; i++) { + if (!srveof[i]) { + FD_SET(srvsock[i], &readfds); + maxfd = util_max(maxfd, srvsock[i]); + } + } + rv = select(maxfd + 1, &readfds, NULL, NULL, NULL); + } while (rv == -1 && errno == EINTR); + if (rv == -1) { + log_err_printf("Select failed: %s (%i)\n", + strerror(errno), errno); + return -1; + } + + if (FD_ISSET(sigpipe, &readfds)) { + char buf[16]; + /* first drain the signal pipe, then deal with + * all the individual signal flags */ + read(sigpipe, buf, sizeof(buf)); + if (received_sigquit) { + kill(childpid, SIGQUIT); + received_sigquit = 0; + } + if (received_sighup) { + kill(childpid, SIGHUP); + received_sighup = 0; + } + if (received_sigint) { + if (opts->detach) + kill(childpid, SIGINT); + received_sigint = 0; + } + if (received_sigchld) { + /* break the loop; because we are using + * SOCKET_DGRAM we don't get EOF conditions + * on the disconnected socket ends here + * unless we attempt to write or read, so + * we depend on SIGCHLD to notify us of + * our child erroring out or crashing */ + break; + } + } + + for (i = 0; i < nsrvsock; i++) { + if (FD_ISSET(srvsock[i], &readfds)) { + int rv = privsep_server_handle_req(opts, + srvsock[i]); + if (rv == -1) { + log_err_printf("Failed to handle " + "privsep req " + "on srvsock %i\n", + srvsock[i]); + return -1; + } + if (rv == 1) + srveof[i] = 1; + } + } + + /* break if all server sockets received an EOF */ + i = 0; + while (i < nsrvsock && srveof[i]) + i++; + if (i == nsrvsock) + break; + } + + return 0; +} + +int +privsep_client_openfile(int clisock, const char *fn, int mkpath) +{ + char ans[PRIVSEP_MAX_ANS_SIZE]; + char req[1 + strlen(fn)]; + int fd = -1; + ssize_t n; + + req[0] = mkpath ? PRIVSEP_REQ_OPENFILE_P : PRIVSEP_REQ_OPENFILE; + memcpy(req + 1, fn, sizeof(req) - 1); + + if (sys_sendmsgfd(clisock, req, sizeof(req), -1) == -1) { + return -1; + } + + if ((n = sys_recvmsgfd(clisock, ans, sizeof(ans), &fd)) == -1) { + return -1; + } + + if (n < 1) { + errno = EINVAL; + return -1; + } + + switch (ans[0]) { + case PRIVSEP_ANS_SUCCESS: + break; + case PRIVSEP_ANS_DENIED: + errno = EACCES; + return -1; + case PRIVSEP_ANS_SYS_ERR: + if (n < (ssize_t)(1 + sizeof(int))) { + errno = EINVAL; + return -1; + } + errno = *((int*)&ans[1]); + return -1; + case PRIVSEP_ANS_UNK_CMD: + case PRIVSEP_ANS_INVALID: + default: + return -1; + } + + return fd; +} + +int +privsep_client_opensock(int clisock, const proxyspec_t *spec) +{ + char ans[PRIVSEP_MAX_ANS_SIZE]; + char req[1 + sizeof(spec)]; + int fd = -1; + ssize_t n; + + req[0] = PRIVSEP_REQ_OPENSOCK; + *((const proxyspec_t **)&req[1]) = spec; + + if (sys_sendmsgfd(clisock, req, sizeof(req), -1) == -1) { + return -1; + } + + if ((n = sys_recvmsgfd(clisock, ans, sizeof(ans), &fd)) == -1) { + return -1; + } + + if (n < 1) { + errno = EINVAL; + return -1; + } + + switch (ans[0]) { + case PRIVSEP_ANS_SUCCESS: + break; + case PRIVSEP_ANS_DENIED: + errno = EACCES; + return -1; + case PRIVSEP_ANS_SYS_ERR: + if (n < (ssize_t)(1 + sizeof(int))) { + errno = EINVAL; + return -1; + } + errno = *((int*)&ans[1]); + return -1; + case PRIVSEP_ANS_UNK_CMD: + case PRIVSEP_ANS_INVALID: + default: + return -1; + } + + return fd; +} + +int +privsep_client_close(int clisock) +{ + char req[1]; + + req[0] = PRIVSEP_REQ_CLOSE; + + if (sys_sendmsgfd(clisock, req, sizeof(req), -1) == -1) { + close(clisock); + return -1; + } + + close(clisock); + return 0; +} + +/* + * Fork and set up privilege separated monitor process. + * Returns -1 on error before forking, 1 as parent, or 0 as child. + * The array of clisock's will get filled with nclisock privsep client + * sockets only for the child; on error and in the parent process it + * will not be touched. + */ +int +privsep_fork(opts_t *opts, int clisock[], size_t nclisock) +{ + int selfpipev[2]; /* self-pipe trick: signal handler -> select */ + int chldpipev[2]; /* el cheapo interprocess sync early after fork */ + int sockcliv[nclisock][2]; + pid_t pid; + + received_sigquit = 0; + received_sighup = 0; + received_sigint = 0; + received_sigchld = 0; + + if (pipe(selfpipev) == -1) { + log_err_printf("Failed to create self-pipe: %s (%i)\n", + strerror(errno), errno); + return -1; + } + log_dbg_printf("Created self-pipe [r=%i,w=%i]\n", + selfpipev[0], selfpipev[1]); + + if (pipe(chldpipev) == -1) { + log_err_printf("Failed to create chld-pipe: %s (%i)\n", + strerror(errno), errno); + return -1; + } + log_dbg_printf("Created chld-pipe [r=%i,w=%i]\n", + chldpipev[0], chldpipev[1]); + + for (size_t i = 0; i < nclisock; i++) { + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockcliv[i]) == -1) { + log_err_printf("Failed to create socket pair %zu: " + "%s (%i)\n", i, strerror(errno), errno); + return -1; + } + log_dbg_printf("Created socketpair %zu [p=%i,c=%i]\n", + i, sockcliv[i][0], sockcliv[i][1]); + } + + pid = fork(); + if (pid == -1) { + log_err_printf("Failed to fork: %s (%i)\n", + strerror(errno), errno); + close(selfpipev[0]); + close(selfpipev[1]); + close(chldpipev[0]); + close(chldpipev[1]); + for (size_t i = 0; i < nclisock; i++) { + close(sockcliv[i][0]); + close(sockcliv[i][1]); + } + return -1; + } else if (pid == 0) { + /* child */ + close(selfpipev[0]); + close(selfpipev[1]); + for (size_t i = 0; i < nclisock; i++) + close(sockcliv[i][0]); + /* wait until parent has installed signal handlers, + * intentionally ignoring errors */ + char buf[1]; + ssize_t n; + close(chldpipev[1]); + do { + n = read(chldpipev[0], buf, sizeof(buf)); + } while (n == -1 && errno == EINTR); + close(chldpipev[0]); + + /* return the privsep client sockets */ + for (size_t i = 0; i < nclisock; i++) + clisock[i] = sockcliv[i][1]; + return 0; + } + /* parent */ + for (size_t i = 0; i < nclisock; i++) + close(sockcliv[i][1]); + selfpipe_wrfd = selfpipev[1]; + + /* close file descriptors opened by preinit's only needed in client; + * we still call the preinit's before forking in order to provide + * better user feedback and less privsep complexity */ + nat_preinit_undo(); + log_preinit_undo(); + + /* If the child exits before the parent installs the signal handler + * here, we have a race condition; this is solved by the client + * blocking on the reading end of a pipe (chldpipev[0]). */ + if (signal(SIGHUP, privsep_server_signal_handler) == SIG_ERR) { + log_err_printf("Failed to install SIGHUP handler: %s (%i)\n", + strerror(errno), errno); + return -1; + } + if (signal(SIGINT, privsep_server_signal_handler) == SIG_ERR) { + log_err_printf("Failed to install SIGINT handler: %s (%i)\n", + strerror(errno), errno); + return -1; + } + if (signal(SIGQUIT, privsep_server_signal_handler) == SIG_ERR) { + log_err_printf("Failed to install SIGQUIT handler: %s (%i)\n", + strerror(errno), errno); + return -1; + } + if (signal(SIGCHLD, privsep_server_signal_handler) == SIG_ERR) { + log_err_printf("Failed to install SIGCHLD handler: %s (%i)\n", + strerror(errno), errno); + return -1; + } + + /* unblock the child */ + close(chldpipev[0]); + close(chldpipev[1]); + + int socksrv[nclisock]; + for (size_t i = 0; i < nclisock; i++) + socksrv[i] = sockcliv[i][0]; + if (privsep_server(opts, selfpipev[0], socksrv, nclisock, pid) == -1) { + log_err_printf("Privsep server failed: %s (%i)\n", + strerror(errno), errno); + /* fall through */ + } + + for (size_t i = 0; i < nclisock; i++) + close(sockcliv[i][0]); + selfpipe_wrfd = -1; /* tell signal handler not to write anymore */ + close(selfpipev[0]); + close(selfpipev[1]); + + int status; + wait(&status); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + log_err_printf("Child proc %lld exited with status %d\n", + (long long)pid, WEXITSTATUS(status)); + } else { + log_dbg_printf("Child proc %lld exited with status %d\n", + (long long)pid, WEXITSTATUS(status)); + } + } else if (WIFSIGNALED(status)) { + log_err_printf("Child proc %lld killed by signal %d\n", + (long long)pid, WTERMSIG(status)); + } else { + log_err_printf("Child proc %lld neither exited nor killed\n", + (long long)pid); + } + + return 1; +} + +/* vim: set noet ft=c: */ + + + diff --git a/privsep.h b/privsep.h new file mode 100644 index 0000000..e401f27 --- /dev/null +++ b/privsep.h @@ -0,0 +1,43 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2014, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * 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 unmodified, 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 AUTHOR ``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 AUTHOR 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. + */ + +#ifndef PRIVSEP_H +#define PRIVSEP_H + +#include "attrib.h" +#include "opts.h" + +int privsep_fork(opts_t *, int[], size_t); + +int privsep_client_openfile(int, const char *, int); +int privsep_client_opensock(int, const proxyspec_t *spec); +int privsep_client_close(int); + +#endif /* !PRIVSEP_H */ + +/* vim: set noet ft=c: */ diff --git a/proxy.c b/proxy.c index 78a7c9b..def08c4 100644 --- a/proxy.c +++ b/proxy.c @@ -28,6 +28,7 @@ #include "proxy.h" +#include "privsep.h" #include "pxythrmgr.h" #include "pxyconn.h" #include "cachemgr.h" @@ -161,57 +162,14 @@ proxy_debug_base(const struct event_base *ev_base) */ static proxy_listener_ctx_t * proxy_listener_setup(struct event_base *evbase, pxy_thrmgr_ctx_t *thrmgr, - proxyspec_t *spec, opts_t *opts) + proxyspec_t *spec, opts_t *opts, int clisock) { proxy_listener_ctx_t *plc; + int fd; - evutil_socket_t fd; - int on = 1; - int rv; - - fd = socket(spec->listen_addr.ss_family, SOCK_STREAM, IPPROTO_TCP); - if (fd == -1) { - log_err_printf("Error from socket(): %s\n", - strerror(errno)); - evutil_closesocket(fd); - return NULL; - } - - rv = evutil_make_socket_nonblocking(fd); - if (rv == -1) { - log_err_printf("Error making socket nonblocking: %s\n", - strerror(errno)); - evutil_closesocket(fd); - return NULL; - } - - rv = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on)); - if (rv == -1) { - log_err_printf("Error from setsockopt(SO_KEEPALIVE): %s\n", - strerror(errno)); - evutil_closesocket(fd); - return NULL; - } - - rv = evutil_make_listen_socket_reuseable(fd); - if (rv == -1) { - log_err_printf("Error from setsockopt(SO_REUSABLE): %s\n", - strerror(errno)); - evutil_closesocket(fd); - return NULL; - } - - if (spec->natsocket && (spec->natsocket(fd) == -1)) { - log_err_printf("Error from spec->natsocket()\n"); - evutil_closesocket(fd); - return NULL; - } - - rv = bind(fd, (struct sockaddr *)&spec->listen_addr, - spec->listen_addrlen); - if (rv == -1) { - log_err_printf("Error from bind(): %s\n", strerror(errno)); - evutil_closesocket(fd); + if ((fd = privsep_client_opensock(clisock, spec)) == -1) { + log_err_printf("Error opening socket: %s (%i)\n", + strerror(errno), errno); return NULL; } @@ -273,10 +231,11 @@ proxy_gc_cb(UNUSED evutil_socket_t fd, UNUSED short what, void *arg) /* * Set up the core event loop. + * Socket clisock is the privsep client socket used for binding to ports. * Returns ctx on success, or NULL on error. */ proxy_ctx_t * -proxy_new(opts_t *opts) +proxy_new(opts_t *opts, int clisock) { proxy_listener_ctx_t *head; proxy_ctx_t *ctx; @@ -341,7 +300,7 @@ proxy_new(opts_t *opts) head = ctx->lctx = NULL; for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) { head = proxy_listener_setup(ctx->evbase, ctx->thrmgr, - spec, opts); + spec, opts, clisock); if (!head) goto leave2; head->next = ctx->lctx; @@ -362,6 +321,7 @@ proxy_new(opts_t *opts) goto leave4; evtimer_add(ctx->gcev, &gc_delay); + privsep_client_close(clisock); return ctx; leave4: @@ -395,9 +355,11 @@ leave0: void proxy_run(proxy_ctx_t *ctx) { +#if 0 if (ctx->opts->detach) { event_reinit(ctx->evbase); } +#endif #ifndef PURIFY if (OPTS_DEBUG(ctx->opts)) { event_base_dump_events(ctx->evbase, stderr); diff --git a/proxy.h b/proxy.h index 164fa52..6bcd3a9 100644 --- a/proxy.h +++ b/proxy.h @@ -34,7 +34,7 @@ typedef struct proxy_ctx proxy_ctx_t; -proxy_ctx_t * proxy_new(opts_t *) NONNULL(1) MALLOC; +proxy_ctx_t * proxy_new(opts_t *, int) NONNULL(1) MALLOC; void proxy_run(proxy_ctx_t *) NONNULL(1); void proxy_free(proxy_ctx_t *) NONNULL(1); diff --git a/sslsplit.1 b/sslsplit.1 index 9d89dcf..e458ba8 100644 --- a/sslsplit.1 +++ b/sslsplit.1 @@ -121,8 +121,6 @@ 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 @@ -163,9 +161,7 @@ not been implemented yet. .TP .B \-j \fIjaildir\fP Change the root directory to \fIjaildir\fP using chroot(2) after opening files. -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). +Note that this has implications for \fBsni\fP \fIproxyspecs\fP. 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. Using \fBsni\fP proxyspecs depends on name resolution. @@ -268,8 +264,6 @@ Log connection content to separate log files under \fIlogdir\fP. 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-S\fP is used with \fB-j\fP, \fIlogdir\fP is relative to \fIjaildir\fP. -If \fB-S\fP is used with \fB-u\fP, \fIlogdir\fP must be writable by \fIuser\fP. .TP .B \-t \fIcertdir\fP Use private key, certificate and certificate chain from PEM files in @@ -288,8 +282,7 @@ Drop privileges after opening sockets and files by setting the real, effective and stored user IDs to \fIuser\fP and loading the appropriate primary and ancillary groups. If \fB-u\fP is not given, SSLsplit will drop privileges to the stored UID if EUID != UID (setuid bit scenario), or to -\fBnobody\fP if running with full \fBroot\fP privileges (EUID == UID == 0) -and \fB-S\fP is not used. +\fBnobody\fP if running with full \fBroot\fP privileges (EUID == UID == 0). .TP .B \-V Display version and compiled features information and exit. diff --git a/sys.c b/sys.c index 944ab7a..dc64d41 100644 --- a/sys.c +++ b/sys.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -470,11 +471,11 @@ sys_mkpath(const char *path, mode_t mode) return 0; } - /* * Iterate over all files in a directory hierarchy, calling the callback * cb for each file, passing the filename and arg as arguments. Files and * directories beginning with a dot are skipped, symlinks are followed. + * FIXME - there is no facility to return errors from the file handlers */ int sys_dir_eachfile(const char *dirname, sys_dir_eachfile_cb_t cb, void *arg) @@ -568,6 +569,8 @@ sys_sendmsgfd(int sock, void *buf, size_t bufsz, int fd) msg.msg_controllen = sizeof(cmsgbuf); cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg) + return -1; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; @@ -641,5 +644,131 @@ sys_recvmsgfd(int sock, void *buf, size_t bufsz, int *pfd) return n; } +/* + * Format AF_UNIX socket address into printable string. + * Returns newly allocated string that must be freed by caller. + */ +static char * +sys_afunix_str(struct sockaddr *addr, socklen_t addrlen) +{ + struct sockaddr_un *sun = (struct sockaddr_un *)addr; + char *name; + + if (addrlen == sizeof(sa_family_t)) { + asprintf(&name, "unnmd"); + } else if (sun->sun_path[0] == '\0') { + /* abstract sockets is a Linux feature */ + asprintf(&name, "abstr:%02x:%02x:%02x:%02x", + sun->sun_path[1], + sun->sun_path[2], + sun->sun_path[3], + sun->sun_path[4]); + } else { + asprintf(&name, "pname:%s", sun->sun_path); + } + + return name; +} + +/* + * Dump all open file descriptors to stdout - poor man's lsof/fstat/sockstat + */ +void +sys_dump_fds(void) +{ + int maxfd = 0; + +#ifdef F_MAXFD + if (!maxfd && ((maxfd = fcntl(0, F_MAXFD)) == -1)) { + fprintf(stderr, "fcntl(0, F_MAXFD) failed: %s (%i)\n", + strerror(errno), errno); + } +#endif /* F_MAXFD */ +#ifdef _SC_OPEN_MAX + if (!maxfd && ((maxfd = sysconf(_SC_OPEN_MAX)) == -1)) { + fprintf(stderr, "sysconf(_SC_OPEN_MAX) failed: %s (%i)\n", + strerror(errno), errno); + } +#endif /* _SC_OPEN_MAX */ + if (!maxfd) + maxfd = 65535; + + for (int fd = 0; fd <= maxfd; fd++) { + struct stat st; + + if (fstat(fd, &st) == -1) { + continue; + } + + printf("%5d:", fd); + switch (st.st_mode & S_IFMT) { + case S_IFBLK: printf(" blkdev"); break; + case S_IFCHR: printf(" chrdev"); break; + case S_IFDIR: printf(" dir "); break; + case S_IFIFO: printf(" fifo "); break; + case S_IFLNK: printf(" lnkfil"); break; + case S_IFREG: printf(" regfil"); break; + case S_IFSOCK: printf(" socket"); break; + default: printf(" unknwn"); break; + } + + if ((st.st_mode & S_IFMT) == S_IFSOCK) { + int lrv, frv; + struct sockaddr_storage lss, fss; + socklen_t lsslen = sizeof(lss); + socklen_t fsslen = sizeof(fss); + char *laddrstr, *faddrstr; + + lrv = getsockname(fd, (struct sockaddr *)&lss, &lsslen); + frv = getpeername(fd, (struct sockaddr *)&fss, &fsslen); + + switch (lss.ss_family) { + case AF_INET: + case AF_INET6: { + if (lrv == 0) { + laddrstr = sys_sockaddr_str((struct sockaddr *)&lss, lsslen); + } else { + laddrstr = strdup("n/a"); + } + if (frv == 0) { + faddrstr = sys_sockaddr_str((struct sockaddr *)&fss, fsslen); + } else { + faddrstr = strdup("n/a"); + } + printf(" %-6s %s -> %s", + lss.ss_family == AF_INET ? "in" : "in6", + laddrstr, faddrstr); + free(laddrstr); + free(faddrstr); + break; + } + case AF_UNIX: { + if (lrv == 0) { + laddrstr = sys_afunix_str((struct sockaddr *)&lss, lsslen); + } else { + laddrstr = strdup("n/a"); + } + if (frv == 0) { + faddrstr = sys_afunix_str((struct sockaddr *)&fss, fsslen); + } else { + faddrstr = strdup("n/a"); + } + printf(" unix %s -> %s", laddrstr, faddrstr); + free(laddrstr); + free(faddrstr); + break; + } + case AF_UNSPEC: { + printf(" unspec"); + break; + } + default: + printf(" (%i)", lss.ss_family); + } + } + printf("\n"); + } +} + /* vim: set noet ft=c: */ diff --git a/sys.h b/sys.h index 532d20b..8d8bead 100644 --- a/sys.h +++ b/sys.h @@ -61,6 +61,8 @@ uint32_t sys_get_cpu_cores(void) WUNRES; ssize_t sys_sendmsgfd(int, void *, size_t, int) NONNULL(2) WUNRES; ssize_t sys_recvmsgfd(int, void *, size_t, int *) NONNULL(2) WUNRES; +void sys_dump_fds(void); + #endif /* !SYS_H */ /* vim: set noet ft=c: */ diff --git a/sys.t.c b/sys.t.c index d0fab0c..8484d3c 100644 --- a/sys.t.c +++ b/sys.t.c @@ -141,7 +141,7 @@ START_TEST(sys_mkpath_01) basedir); fail_unless(!!dir, "asprintf failed"); fail_unless(!sys_isdir(dir), "dir already sys_isdir()"); - fail_unless(!sys_mkpath(dir, 0755), "sys_mkpath failed"); + fail_unless(!sys_mkpath(dir, 0777), "sys_mkpath failed"); fail_unless(sys_isdir(dir), "dir not sys_isdir()"); free(dir); } From 0e0a465f5dff55530a957d51e6e3d3b7f373f43d Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 24 Nov 2014 22:49:02 +0100 Subject: [PATCH 11/23] Fix build on OpenBSD by adding missing includes --- privsep.c | 1 + sys.c | 1 + 2 files changed, 2 insertions(+) diff --git a/privsep.c b/privsep.c index 2caccc4..a79cae4 100644 --- a/privsep.c +++ b/privsep.c @@ -35,6 +35,7 @@ #include #include +#include #include #include #include diff --git a/sys.c b/sys.c index dc64d41..cd44041 100644 --- a/sys.c +++ b/sys.c @@ -32,6 +32,7 @@ #include #include +#include #include #include #include From 476967ccdc40fa02c3e6b18edb064cfa19b334cc Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 24 Nov 2014 23:32:37 +0100 Subject: [PATCH 12/23] Add SIGUSR1 to the signals forwarded by the parent --- privsep.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/privsep.c b/privsep.c index a79cae4..44e808c 100644 --- a/privsep.c +++ b/privsep.c @@ -50,7 +50,7 @@ * Privilege separation functionality. * * The server code has limitations on the internal functionality that can be - * used, namely only those that are initialized before forking can be used. + * used, namely only those that are initialized before forking. */ /* maximal message sizes */ @@ -68,11 +68,13 @@ #define PRIVSEP_ANS_DENIED 3 /* request denied */ #define PRIVSEP_ANS_SYS_ERR 4 /* system error; arg=errno */ -int received_sighup; -int received_sigint; -int received_sigquit; -int received_sigchld; -int selfpipe_wrfd; +/* communication with signal handler */ +static int received_sighup; +static int received_sigint; +static int received_sigquit; +static int received_sigchld; +static int received_sigusr1; +static int selfpipe_wrfd; /* write end of pipe used for unblocking select */ static void privsep_server_signal_handler(int sig) @@ -93,6 +95,9 @@ privsep_server_signal_handler(int sig) case SIGCHLD: received_sigchld = 1; break; + case SIGUSR1: + received_sigusr1 = 1; + break; } if (selfpipe_wrfd != -1) { ssize_t n; @@ -383,11 +388,15 @@ privsep_server_handle_req(opts_t *opts, int srvsock) } /* + * Privilege separation server (main privileged monitor loop) + * * sigpipe is the self-pipe trick pipe used for communicating signals to * the main event loop and break out of select() without race conditions. * srvsock[] is a dynamic array of connected privsep server sockets to serve. * Caller is responsible for freeing memory after returning, if necessary. * childpid is the pid of the child process to forward signals to. + * + * Returns 0 on a successful clean exit and -1 on errors. */ static int privsep_server(opts_t *opts, int sigpipe, int srvsock[], size_t nsrvsock, @@ -435,7 +444,13 @@ privsep_server(opts_t *opts, int sigpipe, int srvsock[], size_t nsrvsock, kill(childpid, SIGHUP); received_sighup = 0; } + if (received_sigusr1) { + kill(childpid, SIGUSR1); + received_sigusr1 = 0; + } if (received_sigint) { + /* if we don't detach from the TTY, the + * child process receives SIGINT directly */ if (opts->detach) kill(childpid, SIGINT); received_sigint = 0; @@ -605,6 +620,7 @@ privsep_fork(opts_t *opts, int clisock[], size_t nclisock) received_sighup = 0; received_sigint = 0; received_sigchld = 0; + received_sigusr1 = 0; if (pipe(selfpipev) == -1) { log_err_printf("Failed to create self-pipe: %s (%i)\n", @@ -695,6 +711,11 @@ privsep_fork(opts_t *opts, int clisock[], size_t nclisock) strerror(errno), errno); return -1; } + if (signal(SIGUSR1, privsep_server_signal_handler) == SIG_ERR) { + log_err_printf("Failed to install SIGUSR1 handler: %s (%i)\n", + strerror(errno), errno); + return -1; + } if (signal(SIGCHLD, privsep_server_signal_handler) == SIG_ERR) { log_err_printf("Failed to install SIGCHLD handler: %s (%i)\n", strerror(errno), errno); From 25e3145d1f0799d68ff8bffe169d60d1b1dc7fe6 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Tue, 25 Nov 2014 00:10:51 +0100 Subject: [PATCH 13/23] Add missing headers to fix build on FreeBSD 8.4 --- privsep.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/privsep.c b/privsep.c index 44e808c..4aef514 100644 --- a/privsep.c +++ b/privsep.c @@ -33,9 +33,11 @@ #include "log.h" #include "attrib.h" -#include +#include #include +#include #include +#include #include #include #include From e69b13f2eb0352ff94fa60366d46c685debd723c Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Tue, 25 Nov 2014 23:45:40 +0100 Subject: [PATCH 14/23] SIGUSR1 re-opens -l/-L log files; add defaults.h Issue: #52 --- defaults.h | 61 ++++++++++++++ log.c | 228 ++++++++++++++++++++++++++++++++++++++--------------- log.h | 1 + logger.c | 38 +++++++-- logger.h | 8 +- main.c | 17 ++-- privsep.c | 5 +- proxy.c | 25 ++++-- ssl.c | 3 +- ssl.h | 1 - sslsplit.1 | 7 ++ sys.c | 3 +- sys.t.c | 6 +- 13 files changed, 306 insertions(+), 97 deletions(-) create mode 100644 defaults.h diff --git a/defaults.h b/defaults.h new file mode 100644 index 0000000..b641bf3 --- /dev/null +++ b/defaults.h @@ -0,0 +1,61 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2014, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * 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 unmodified, 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 AUTHOR ``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 AUTHOR 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. + */ + +#ifndef DEFAULTS_H +#define DEFAULTS_H + +/* + * Defaults for convenient tweaking or patching. + */ + +/* + * User to drop privileges to by default. + * Packagers may want to use a specific service user account instead of + * overloading nobody with yet another use case. Using nobody for source + * builds makes sense because chances are high that it exists. + */ +#define DFLT_DROPUSER "nobody" + +/* + * Default file and directory modes for newly created files and directories + * created as part of e.g. logging. The default is to use full permissions + * subject to the system's umask, as is the default for system utilities. + * Use a more restrictive mode for the PID file. + */ +#define DFLT_DIRMODE 0777 +#define DFLT_FILEMODE 0666 +#define DFLT_PIDFMODE 0644 + +/* + * Default elliptic curve for EC cipher suites. + */ +#define DFLT_CURVE "secp160r2" + +#endif /* !DEFAULTS_H */ + +/* vim: set noet ft=c: */ diff --git a/log.c b/log.c index 262b4f1..fd65b7f 100644 --- a/log.c +++ b/log.c @@ -32,6 +32,7 @@ #include "sys.h" #include "attrib.h" #include "privsep.h" +#include "defaults.h" #include #include @@ -164,14 +165,37 @@ log_dbg_mode(int mode) logger_t *connect_log = NULL; static int connect_fd = -1; +static char *connect_fn = NULL; static int -log_connect_open(const char *logfile) +log_connect_preinit(const char *logfile) { - connect_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, 0660); + 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", - logfile, strerror(errno)); + connect_fn, strerror(errno)); + free(connect_fn); + connect_fn = NULL; return -1; } return 0; @@ -207,7 +231,7 @@ log_connect_writecb(UNUSED void *fh, const void *buf, size_t sz) } static void -log_connect_close(void) +log_connect_fini(void) { close(connect_fd); } @@ -227,46 +251,25 @@ log_connect_close(void) struct log_content_ctx { unsigned int open : 1; - int fd; 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_fd = -1; /* set in 'file' mode */ static int content_clisock = -1; /* privsep client socket for content logger */ -static int -log_content_file_preinit(const char *logfile) -{ - content_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, 0660); - if (content_fd == -1) { - log_err_printf("Failed to open '%s' for writing: %s\n", - logfile, strerror(errno)); - return -1; - } - return 0; -} - -static void -log_content_file_fini(void) -{ - if (content_fd != -1) { - close(content_fd); - content_fd = -1; - } -} - /* * Split a pathname into static LHS (including final slashes) and dynamic RHS. * Returns -1 on error, 0 on success. @@ -503,7 +506,6 @@ log_content_open(log_content_ctx_t **pctx, opts_t *opts, } } else { /* single-file content log (-L) */ - ctx->fd = content_fd; if (asprintf(&ctx->u.file.header_req, "%s -> %s", srcaddr, dstaddr) < 0) { goto errout; @@ -556,30 +558,20 @@ log_content_close(log_content_ctx_t **pctx) } /* - * Callback functions that are executed in the logger thread. + * Log-type specific code. + * + * The init/fini functions are executed globally in the main thread. + * Callback functions are executed in the logger thread. */ -static ssize_t -log_content_common_writecb(void *fh, const void *buf, size_t sz) -{ - log_content_ctx_t *ctx = fh; - - if (write(ctx->fd, buf, sz) == -1) { - log_err_printf("Warning: Failed to write to content log: %s\n", - strerror(errno)); - return -1; - } - return 0; -} - static int log_content_dir_opencb(void *fh) { log_content_ctx_t *ctx = fh; - if ((ctx->fd = privsep_client_openfile(content_clisock, - ctx->u.dir.filename, - 0)) == -1) { + 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; @@ -594,19 +586,32 @@ log_content_dir_closecb(void *fh) if (ctx->u.dir.filename) free(ctx->u.dir.filename); - if (ctx->fd != 1) - close(ctx->fd); + 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 = 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 0; +} + static int log_content_spec_opencb(void *fh) { log_content_ctx_t *ctx = fh; - if ((ctx->fd = privsep_client_openfile(content_clisock, - ctx->u.spec.filename, - 1)) == -1) { + 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; @@ -621,11 +626,74 @@ log_content_spec_closecb(void *fh) if (ctx->u.spec.filename) free(ctx->u.spec.filename); - if (ctx->fd != -1) - close(ctx->fd); + 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 = 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 0; +} + +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) @@ -649,6 +717,19 @@ log_content_file_closecb(void *fh) free(ctx); } +static ssize_t +log_content_file_writecb(void *fh, const void *buf, size_t sz) +{ + UNUSED log_content_ctx_t *ctx = 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 0; +} + static logbuf_t * log_content_file_prepcb(void *fh, unsigned long prepflags, logbuf_t *lb) { @@ -711,6 +792,7 @@ out: int log_preinit(opts_t *opts) { + logger_reopen_func_t reopencb; logger_open_func_t opencb; logger_close_func_t closecb; logger_write_func_t writecb; @@ -718,39 +800,42 @@ log_preinit(opts_t *opts) if (opts->contentlog) { if (opts->contentlog_isdir) { + reopencb = NULL; opencb = log_content_dir_opencb; closecb = log_content_dir_closecb; - writecb = log_content_common_writecb; + 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_common_writecb; + 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_common_writecb; + writecb = log_content_file_writecb; prepcb = log_content_file_prepcb; } - if (!(content_log = logger_new(opencb, closecb, writecb, - prepcb))) { + if (!(content_log = logger_new(reopencb, opencb, closecb, + writecb, prepcb))) { log_content_file_fini(); goto out; } } if (opts->connectlog) { - if (log_connect_open(opts->connectlog) == -1) + if (log_connect_preinit(opts->connectlog) == -1) goto out; - if (!(connect_log = logger_new(NULL, NULL, + if (!(connect_log = logger_new(log_connect_reopencb, NULL, NULL, log_connect_writecb, NULL))) { - log_connect_close(); + log_connect_fini(); goto out; } } - if (!(err_log = logger_new(NULL, NULL, log_err_writecb, NULL))) + if (!(err_log = logger_new(NULL, NULL, NULL, log_err_writecb, NULL))) goto out; return 0; @@ -760,7 +845,7 @@ out: logger_free(content_log); } if (connect_log) { - log_connect_close(); + log_connect_fini(); logger_free(connect_log); } return -1; @@ -778,7 +863,7 @@ log_preinit_undo(void) logger_free(content_log); } if (connect_log) { - log_connect_close(); + log_connect_fini(); logger_free(connect_log); } } @@ -844,10 +929,25 @@ log_fini(void) if (content_log) log_content_file_fini(); if (connect_log) - log_connect_close(); + log_connect_fini(); if (content_clisock != -1) privsep_client_close(content_clisock); } +int +log_reopen(void) +{ + int rv = 0; + + 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: */ diff --git a/log.h b/log.h index eec9870..8b2e555 100644 --- a/log.h +++ b/log.h @@ -70,6 +70,7 @@ int log_preinit(opts_t *) NONNULL(1) WUNRES; void log_preinit_undo(void); int log_init(opts_t *, int) NONNULL(1) WUNRES; void log_fini(void); +int log_reopen(void) WUNRES; #endif /* !LOG_H */ diff --git a/logger.c b/logger.c index 3b4cb67..01b60d6 100644 --- a/logger.c +++ b/logger.c @@ -45,6 +45,7 @@ struct logger { pthread_t thr; + logger_reopen_func_t reopen; logger_open_func_t open; logger_close_func_t close; logger_prep_func_t prep; @@ -52,8 +53,9 @@ struct logger { thrqueue_t *queue; }; -#define LBFLAG_OPEN 1 -#define LBFLAG_CLOSE 2 +#define LBFLAG_REOPEN (1 << 0) +#define LBFLAG_OPEN (1 << 1) +#define LBFLAG_CLOSE (1 << 2) static void logger_clear(logger_t *logger) @@ -67,8 +69,9 @@ logger_clear(logger_t *logger) * not in the thread calling logger_submit(). */ logger_t * -logger_new(logger_open_func_t openfunc, logger_close_func_t closefunc, - logger_write_func_t writefunc, logger_prep_func_t prepfunc) +logger_new(logger_reopen_func_t reopenfunc, logger_open_func_t openfunc, + logger_close_func_t closefunc, logger_write_func_t writefunc, + logger_prep_func_t prepfunc) { logger_t *logger; @@ -76,6 +79,7 @@ logger_new(logger_open_func_t openfunc, logger_close_func_t closefunc, if (!logger) return NULL; logger_clear(logger); + logger->reopen = reopenfunc; logger->open = openfunc; logger->close = closefunc; logger->write = writefunc; @@ -115,13 +119,30 @@ logger_submit(logger_t *logger, void *fh, unsigned long prepflags, return thrqueue_enqueue(logger->queue, lb) ? 0 : -1; } +/* + * Submit a log reopen event to the logger thread. + */ +int +logger_reopen(logger_t *logger) +{ + logbuf_t *lb; + + if (!logger->reopen) + return 0; + + lb = logbuf_new(NULL, 0, NULL, NULL); + logbuf_ctl_set(lb, LBFLAG_REOPEN); + return thrqueue_enqueue(logger->queue, lb) ? 0 : -1; +} + /* * Submit a file open event to the logger thread. * fh is the file handle; an opaque unique address identifying the new file. * If no open callback is configured, returns successfully. * Returns 0 on success, -1 on failure. */ -int logger_open(logger_t *logger, void *fh) +int +logger_open(logger_t *logger, void *fh) { logbuf_t *lb; @@ -139,7 +160,8 @@ int logger_open(logger_t *logger, void *fh) * If no close callback is configured, returns successfully. * Returns 0 on success, -1 on failure. */ -int logger_close(logger_t *logger, void *fh) +int +logger_close(logger_t *logger, void *fh) { logbuf_t *lb; @@ -162,7 +184,9 @@ logger_thread(void *arg) logbuf_t *lb; while ((lb = thrqueue_dequeue(logger->queue))) { - if (logbuf_ctl_isset(lb, LBFLAG_OPEN)) { + if (logbuf_ctl_isset(lb, LBFLAG_REOPEN)) { + logger->reopen(); + } else if (logbuf_ctl_isset(lb, LBFLAG_OPEN)) { logger->open(lb->fh); } else if (logbuf_ctl_isset(lb, LBFLAG_CLOSE)) { logger->close(lb->fh); diff --git a/logger.h b/logger.h index 828265b..e0958bf 100644 --- a/logger.h +++ b/logger.h @@ -35,20 +35,22 @@ #include #include +typedef int (*logger_reopen_func_t)(void); typedef int (*logger_open_func_t)(void *); typedef void (*logger_close_func_t)(void *); typedef ssize_t (*logger_write_func_t)(void *, const void *, size_t); typedef logbuf_t * (*logger_prep_func_t)(void *, unsigned long, logbuf_t *); typedef struct logger logger_t; -logger_t * logger_new(logger_open_func_t, logger_close_func_t, - logger_write_func_t, logger_prep_func_t) - NONNULL(3) MALLOC; +logger_t * logger_new(logger_reopen_func_t, logger_open_func_t, + logger_close_func_t, logger_write_func_t, + logger_prep_func_t) NONNULL(4) MALLOC; void logger_free(logger_t *) NONNULL(1); int logger_start(logger_t *) NONNULL(1) WUNRES; void logger_leave(logger_t *) NONNULL(1); int logger_join(logger_t *) NONNULL(1); int logger_stop(logger_t *) NONNULL(1) WUNRES; +int logger_reopen(logger_t *) NONNULL(1) WUNRES; int logger_open(logger_t *, void *) NONNULL(1,2) WUNRES; int logger_close(logger_t *, void *) NONNULL(1,2) WUNRES; int logger_submit(logger_t *, void *, unsigned long, diff --git a/main.c b/main.c index 85ee309..8fe457e 100644 --- a/main.c +++ b/main.c @@ -41,6 +41,7 @@ #include "sys.h" #include "log.h" #include "version.h" +#include "defaults.h" #include #include @@ -62,7 +63,6 @@ extern int daemon(int, int); #endif /* __APPLE__ */ -#define DEFAULT_DROPUSER "nobody" /* * Print version information to stderr. @@ -131,7 +131,7 @@ main_usage(void) #define OPT_g #endif /* !OPENSSL_NO_DH */ #ifndef OPENSSL_NO_ECDH -" -G curve use ECDH named curve (default: %s for non-RSA leafkey)\n" +" -G curve use ECDH named curve (default: " DFLT_CURVE " for non-RSA leafkey)\n" #define OPT_G "G:" #else /* OPENSSL_NO_ECDH */ #define OPT_G @@ -147,8 +147,7 @@ main_usage(void) " -s ciphers use the given OpenSSL cipher suite spec (default: ALL:-aNULL)\n" " -e engine specify default NAT engine to use (default: %s)\n" " -E list available NAT engines and exit\n" -" -u user drop privileges to user (default if run as root: " - DEFAULT_DROPUSER ")\n" +" -u user drop privileges to user (default if run as root: " DFLT_DROPUSER ")\n" " -m group when using -u, override group (default: primary group of user)\n" " -j jaildir chroot() to jaildir (impacts sni proxyspecs, see manual page)\n" " -p pidfile write pid to pidfile (default: no pid file)\n" @@ -186,11 +185,7 @@ main_usage(void) " ssl 2001:db8::2 9999 pf # ssl/6; NAT engine 'pf'\n" "Example:\n" " %s -k ca.key -c ca.pem -P https 127.0.0.1 8443 https ::1 8443\n" - "%s", BNAME, -#ifndef OPENSSL_NO_ECDH - SSL_EC_KEY_CURVE_DEFAULT, -#endif /* !OPENSSL_NO_ECDH */ - dflt, BNAME, warn); + "%s", BNAME, dflt, BNAME, warn); } /* @@ -721,8 +716,8 @@ main(int argc, char *argv[]) oom_die(argv0); } if (!opts->dropuser && !geteuid() && !getuid() && - sys_isuser(DEFAULT_DROPUSER)) { - opts->dropuser = strdup(DEFAULT_DROPUSER); + sys_isuser(DFLT_DROPUSER)) { + opts->dropuser = strdup(DFLT_DROPUSER); if (!opts->dropuser) oom_die(argv0); } diff --git a/privsep.c b/privsep.c index 4aef514..c40f6b9 100644 --- a/privsep.c +++ b/privsep.c @@ -32,6 +32,7 @@ #include "util.h" #include "log.h" #include "attrib.h" +#include "defaults.h" #include #include @@ -151,7 +152,7 @@ privsep_server_openfile(char *fn, int mkpath) free(fn2); return -1; } - if (sys_mkpath(filedir, 0777) == -1) { + if (sys_mkpath(filedir, DFLT_DIRMODE) == -1) { log_err_printf("Could not create directory '%s': %s (%i)\n", filedir, strerror(errno), errno); free(fn2); @@ -160,7 +161,7 @@ privsep_server_openfile(char *fn, int mkpath) free(fn2); } - fd = open(fn, O_WRONLY|O_APPEND|O_CREAT, 0666); + fd = open(fn, O_WRONLY|O_APPEND|O_CREAT, DFLT_FILEMODE); if (fd == -1) { log_err_printf("Failed to open '%s': %s (%i)\n", fn, strerror(errno), errno); diff --git a/proxy.c b/proxy.c index def08c4..3593453 100644 --- a/proxy.c +++ b/proxy.c @@ -57,7 +57,7 @@ * Proxy engine, built around libevent 2.x. */ -static int signals[] = { SIGQUIT, SIGHUP, SIGINT, SIGPIPE }; +static int signals[] = { SIGQUIT, SIGHUP, SIGINT, SIGPIPE, SIGUSR1 }; struct proxy_ctx { pxy_thrmgr_ctx_t *thrmgr; @@ -194,7 +194,7 @@ proxy_listener_setup(struct event_base *evbase, pxy_thrmgr_ctx_t *thrmgr, } /* - * Signal handler for SIGQUIT, SIGINT, SIGHUP and SIGPIPE. + * Signal handler for SIGQUIT, SIGINT, SIGHUP, SIGPIPE and SIGUSR1. */ static void proxy_signal_cb(evutil_socket_t fd, UNUSED short what, void *arg) @@ -205,10 +205,25 @@ proxy_signal_cb(evutil_socket_t fd, UNUSED short what, void *arg) log_dbg_printf("Received signal %i\n", fd); } - if (fd == SIGPIPE) { - log_err_printf("Warning: Received SIGPIPE; ignoring.\n"); - } else { + switch(fd) { + case SIGQUIT: + case SIGINT: + case SIGHUP: event_base_loopbreak(ctx->evbase); + break; + case SIGUSR1: + if (log_reopen() == -1) { + log_err_printf("Warning: Failed to reopen logs\n"); + } else { + log_dbg_printf("Reopened log files\n"); + } + break; + case SIGPIPE: + log_err_printf("Warning: Received SIGPIPE; ignoring.\n"); + break; + default: + log_err_printf("Warning: Received unexpected signal %i\n", fd); + break; } } diff --git a/ssl.c b/ssl.c index a39b399..abb6bdc 100644 --- a/ssl.c +++ b/ssl.c @@ -29,6 +29,7 @@ #include "ssl.h" #include "log.h" +#include "defaults.h" #include #include @@ -609,7 +610,7 @@ ssl_ec_by_name(const char *curvename) int nid; if (!curvename) - curvename = SSL_EC_KEY_CURVE_DEFAULT; + curvename = DFLT_CURVE; if ((nid = OBJ_sn2nid(curvename)) == NID_undef) { return NULL; diff --git a/ssl.h b/ssl.h index 4038093..592d325 100644 --- a/ssl.h +++ b/ssl.h @@ -122,7 +122,6 @@ void ssl_dh_refcount_inc(DH *) NONNULL(1); #endif /* !OPENSSL_NO_DH */ #ifndef OPENSSL_NO_EC -#define SSL_EC_KEY_CURVE_DEFAULT "secp160r2" EC_KEY * ssl_ec_by_name(const char *) MALLOC; #endif /* !OPENSSL_NO_EC */ diff --git a/sslsplit.1 b/sslsplit.1 index e458ba8..d3a0c98 100644 --- a/sslsplit.1 +++ b/sslsplit.1 @@ -184,11 +184,13 @@ If \fB-K\fP is not given, SSLsplit will generate a random 1024-bit RSA key. .B \-l \fIlogfile\fP Log connections to \fIlogfile\fP in a single line per connection format, including addresses and ports and some HTTP and SSL information, if available. +SIGUSR1 will cause \fIlogfile\fP to be re-opened. .TP .B \-L \fIlogfile\fP Log connection content to \fIlogfile\fP. The content log will contain a parsable log format with transmitted data, prepended with headers identifying the connection and the data length of each logged segment. +SIGUSR1 will cause \fIlogfile\fP to be re-opened. .TP .B \-m When dropping privileges using \fB-u\fP, override the target primary group @@ -356,6 +358,11 @@ 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 SIGNALS +A running \fBsslsplit\fP accepts SIGINT and SIGQUIT for a clean shutdown and +SIGUSR1 to re-open the long-living log files (\fB-l\fP and \fB-L\fP). +Per-connection log files (\fB-S\fP and \fB-F\fP) are not re-opened because +their filename is specific to the connection. .SH "LOG SPECIFICATIONS" Log specifications are composed of zero or more printf-style directives; ordinary characters are included directly in the output path. diff --git a/sys.c b/sys.c index cd44041..b985a07 100644 --- a/sys.c +++ b/sys.c @@ -29,6 +29,7 @@ #include "sys.h" #include "log.h" +#include "defaults.h" #include #include @@ -190,7 +191,7 @@ sys_pidf_open(const char *fn) { int fd; - if ((fd = open(fn, O_RDWR|O_CREAT, 0640)) == -1) { + if ((fd = open(fn, O_RDWR|O_CREAT, DFLT_PIDFMODE)) == -1) { log_err_printf("Failed to open '%s': %s\n", fn, strerror(errno)); return -1; diff --git a/sys.t.c b/sys.t.c index 8484d3c..f19c93c 100644 --- a/sys.t.c +++ b/sys.t.c @@ -28,6 +28,8 @@ #include "sys.h" +#include "defaults.h" + #include #include #include @@ -61,7 +63,7 @@ sys_isdir_setup(void) perror("asprintf"); exit(EXIT_FAILURE); } - close(open(file, O_CREAT|O_WRONLY|O_APPEND, 0600)); + close(open(file, O_CREAT|O_WRONLY|O_APPEND, DFLT_FILEMODE)); symlink(file, lfile); mkdir(dir, 0700); symlink(dir, ldir); @@ -141,7 +143,7 @@ START_TEST(sys_mkpath_01) basedir); fail_unless(!!dir, "asprintf failed"); fail_unless(!sys_isdir(dir), "dir already sys_isdir()"); - fail_unless(!sys_mkpath(dir, 0777), "sys_mkpath failed"); + fail_unless(!sys_mkpath(dir, DFLT_DIRMODE), "sys_mkpath failed"); fail_unless(sys_isdir(dir), "dir not sys_isdir()"); free(dir); } From 43c0f57eecf7fb6cb86bdf721fb136f4ef32192d Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Tue, 25 Nov 2014 23:55:15 +0100 Subject: [PATCH 15/23] Update NEWS.md for feature/privsep --- NEWS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS.md b/NEWS.md index d66bc37..74b9a73 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,12 @@ +### SSLsplit feature/privsep + +- Add signal SIGUSR1 to re-open long-living -l/-L log files (issue #52). +- Introduce privilege separation architecture with privileged parent process + and unprivileged child process; all files are now opened with the + privileges of the user running SSLsplit; arguments to -S/-F are no longer + relative to the chroot() if used with the -j option. + ### SSLsplit master - Add option -F to log to separate files with printf-style % directives, From b8ecbcd773a0e4123a1ad6e8aab5911f693abec7 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Fri, 28 Nov 2014 12:09:40 +0100 Subject: [PATCH 16/23] Split out AUTHORS.md and HACKING.md from README.md --- AUTHORS.md | 20 ++++++++++++++++++++ HACKING.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 29 ++++++++++------------------- 3 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 AUTHORS.md create mode 100644 HACKING.md diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..c812eb1 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,20 @@ +# Authors + +SSLsplit was written and is being maintained by +[Daniel Roethlisberger](https://daniel.roe.ch/). + +The following individuals have contributed to the codebase by submitting +patches or pull requests, in chronological order of their first contribution: + +- Steve Wills (@swills) +- Landon Fuller (@landonf) +- Wayne Jensen (@wjjensen) + +Many more individuals have contributed by reporting bugs or feature requests. +See [issue tracker on Github][1], `NEWS.md` and `git log` for details. + +[1] https://github.com/droe/sslsplit/issues + +All your contributions are greatly appreciated; without you, SSLsplit would not +be what it is today. + diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 0000000..81c6e44 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,50 @@ +# Development + +SSLsplit is being developed on Github as [droe/sslsplit][1]. + +[1] https://github.com/droe/sslsplit + + +## Reporting bugs + +Please use the Github issue tracker for bug reports. Including the following +information will allow faster analysis of the problem: + +- Output of `sslsplit -V` +- Output of `uname -a` +- Exact command line arguments used to run SSLsplit +- The NAT redirection rules you are using, if applicable + +Before submitting a bug report, please make sure that running `make test` from +a git checkout produces no failed unit tests on your system. + + +## Contributing patches + +For patch submissions, please send me pull requests on Github. If you have +larger changes in mind, feel free to open an issue first to discuss +implications. If you are interested in contributing and don't know where to +start, take a look at the [open issues][2]. In particular, [porting features +over to not yet supported platforms][3] is always very much appreciated. When +submitting code, even though it is not a requirement, it is still appreciated +if you also update the manual page and other documentation as necessary and +include as many meaningful unit tests for your code as possible. + +[2] https://github.com/droe/sslsplit/issues +[3] https://github.com/droe/sslsplit/labels/portability + +See `HACKING.md` for the list of contributors. + + +## Branching model + +With the 0.4.10 release as a starting point, SSLsplit is using [Vincent +Driessen's branching model][4]. The default `master` branch points to the +latest tagged release, while the `develop` branch is where development happens. +When preparing a release, there may or may not be a `release/x.y.z` branch off +`develop`, but in either case, the tagged release is merged back to `master`. +Larger features are developed in feature branches off the `develop` branch. + +[4] http://nvie.com/posts/a-successful-git-branching-model/ + + diff --git a/README.md b/README.md index bb947ed..f01fbbe 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ SSLsplit currently supports the following operating systems and NAT mechanisms: - Linux: netfilter REDIRECT and TPROXY - Mac OS X: pf rdr and ipfw fwd +Support for local process information (`-i`) is currently available on Mac OS X +and FreeBSD. + +SSL/TLS features and compatibility greatly depend on the version of OpenSSL +linked against; for optimal results, use the latest 1.0.1 series release. + ## Installation @@ -59,12 +65,11 @@ You can override the default install prefix (`/usr/local`) by setting `PREFIX`. For more build options see `GNUmakefile`. -## Development +## Documentation -SSLsplit is being developed on Github. For bug reports, please use the Github -issue tracker. For patch submissions, please send me pull requests. - -https://github.com/droe/sslsplit +See NEWS.md for release notes listing significant changes between releases. +See HACKING.md for information on development and how to submit bug reports. +See AUTHORS.md for the list of contributors. ## License @@ -82,17 +87,3 @@ Marlinspike, but shares no source code with them. SSLsplit includes `khash.h` by Attractive Chaos. -## Contributors - -The following individuals have contributed to the SSLsplit codebase by -submitting patches or pull requests, in chronological order of first -contribution: - -- Daniel Roethlisberger (@droe), main author -- Steve Wills (@swills) -- Landon Fuller (@landonf) -- Wayne Jensen (@wjjensen) - -See NEWS.md and `git log` for details. - - From f2ff2ec9f5db0febb84381ccfc5f256e67810a99 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Fri, 28 Nov 2014 12:12:48 +0100 Subject: [PATCH 17/23] Link to Github author pages --- AUTHORS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index c812eb1..bed065a 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -6,9 +6,9 @@ SSLsplit was written and is being maintained by The following individuals have contributed to the codebase by submitting patches or pull requests, in chronological order of their first contribution: -- Steve Wills (@swills) -- Landon Fuller (@landonf) -- Wayne Jensen (@wjjensen) +- Steve Wills ([swills](https://github.com/swills)) +- Landon Fuller ([landonf](https://github.com/landonf)) +- Wayne Jensen ([wjjensen](https://github.com/wjjensen)) Many more individuals have contributed by reporting bugs or feature requests. See [issue tracker on Github][1], `NEWS.md` and `git log` for details. From e6dc9db6a440af4d58b306bd94e9d5f075a6080f Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Fri, 28 Nov 2014 12:15:45 +0100 Subject: [PATCH 18/23] Fix markdown links --- AUTHORS.md | 2 +- HACKING.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index bed065a..1307485 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -13,7 +13,7 @@ patches or pull requests, in chronological order of their first contribution: Many more individuals have contributed by reporting bugs or feature requests. See [issue tracker on Github][1], `NEWS.md` and `git log` for details. -[1] https://github.com/droe/sslsplit/issues +[1]: https://github.com/droe/sslsplit/issues All your contributions are greatly appreciated; without you, SSLsplit would not be what it is today. diff --git a/HACKING.md b/HACKING.md index 81c6e44..d5c0ac7 100644 --- a/HACKING.md +++ b/HACKING.md @@ -2,7 +2,7 @@ SSLsplit is being developed on Github as [droe/sslsplit][1]. -[1] https://github.com/droe/sslsplit +[1]: https://github.com/droe/sslsplit ## Reporting bugs @@ -30,8 +30,8 @@ submitting code, even though it is not a requirement, it is still appreciated if you also update the manual page and other documentation as necessary and include as many meaningful unit tests for your code as possible. -[2] https://github.com/droe/sslsplit/issues -[3] https://github.com/droe/sslsplit/labels/portability +[2]: https://github.com/droe/sslsplit/issues +[3]: https://github.com/droe/sslsplit/labels/portability See `HACKING.md` for the list of contributors. @@ -45,6 +45,6 @@ When preparing a release, there may or may not be a `release/x.y.z` branch off `develop`, but in either case, the tagged release is merged back to `master`. Larger features are developed in feature branches off the `develop` branch. -[4] http://nvie.com/posts/a-successful-git-branching-model/ +[4]: http://nvie.com/posts/a-successful-git-branching-model/ From 521adb7275100888a742cd75f7f4ebc858d4c05d Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Fri, 28 Nov 2014 12:18:40 +0100 Subject: [PATCH 19/23] Format file refs with backticks --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f01fbbe..496810e 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,9 @@ For more build options see `GNUmakefile`. ## Documentation -See NEWS.md for release notes listing significant changes between releases. -See HACKING.md for information on development and how to submit bug reports. -See AUTHORS.md for the list of contributors. +See `NEWS.md` for release notes listing significant changes between releases. +See `HACKING.md` for information on development and how to submit bug reports. +See `AUTHORS.md` for the list of contributors. ## License From 0a6ca2ac98e895e4e39571d489a9ffa4f64b0927 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sun, 30 Nov 2014 01:39:57 +0100 Subject: [PATCH 20/23] Update licensing information --- HACKING.md | 3 ++- LICENSE | 25 ----------------------- LICENSE.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 ++---- 4 files changed, 64 insertions(+), 30 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/HACKING.md b/HACKING.md index d5c0ac7..2de4829 100644 --- a/HACKING.md +++ b/HACKING.md @@ -33,7 +33,8 @@ include as many meaningful unit tests for your code as possible. [2]: https://github.com/droe/sslsplit/issues [3]: https://github.com/droe/sslsplit/labels/portability -See `HACKING.md` for the list of contributors. +See `LICENSE.md` for licensing and copyright information applying to +contributions. See `AUTHORS.md` for the list of contributors. ## Branching model diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 04a3692..0000000 --- a/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -SSLsplit - transparent and scalable SSL/TLS interception -Copyright (c) 2009-2014, Daniel Roethlisberger -All rights reserved. -http://www.roe.ch/SSLsplit - -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 unmodified, 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 AUTHOR ``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 AUTHOR 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. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9f605de --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,60 @@ +# License and copyright + +## Copyright + +Copyright (c) 2009-2014, Daniel Roethlisberger and contributors. +All rights reserved. +Licensed under the 2-clause BSD license contained herein. + + +## Third-party components + +`khash.h`: +Copyright (c) 2008, 2009, 2011, Attractive Chaos. +All rights reserved. +Licensed under the MIT license. + +`xnu/xnu-*`: +Copyright (c) 1988-2010, Apple Inc. and original copyright holders. +All rights reserved. +Licensed under the APSL. + +See the respective source and/or license files for details. + + +## Contributions + +By contributing to the software, the contributor releases their +contribution under the license and copyright terms herein. While +contributors retain copyright to their contributions, they grant the +main copyright holder of the software the irrevocable, transferable +right to relicense the software as a whole or in part, including their +contributions, under different open source licenses than the one +contained herein. + + +## License + +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 unmodified, 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 AUTHOR ``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 AUTHOR 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. + diff --git a/README.md b/README.md index 496810e..bb0cc65 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,9 @@ See `AUTHORS.md` for the list of contributors. ## License -SSLsplit is provided under the simplified BSD license. +SSLsplit is provided under a 2-clause BSD license. SSLsplit contains components licensed under the MIT and APSL licenses. -See the respective source file headers for details. +See `LICENSE.md` and the respective source file headers for details. ## Credits @@ -84,6 +84,4 @@ See the respective source file headers for details. SSLsplit was inspired by `mitm-ssl` by Claes M. Nyberg and `sslsniff` by Moxie Marlinspike, but shares no source code with them. -SSLsplit includes `khash.h` by Attractive Chaos. - From 39e9c898e5941a24827477f0b71171684936eb18 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Sun, 30 Nov 2014 22:29:40 +0100 Subject: [PATCH 21/23] Move default cipher suite spec to defaults.h --- defaults.h | 7 +++++++ main.c | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/defaults.h b/defaults.h index b641bf3..7ed2c4d 100644 --- a/defaults.h +++ b/defaults.h @@ -51,6 +51,13 @@ #define DFLT_FILEMODE 0666 #define DFLT_PIDFMODE 0644 +/* + * Default cipher suite spec. + * Use 'openssl ciphers -v spec' to see what ciphers are effectively enabled + * by a cipher suite spec with a given version of OpenSSL. + */ +#define DFLT_CIPHERS "ALL:-aNULL" + /* * Default elliptic curve for EC cipher suites. */ diff --git a/main.c b/main.c index 2d985f5..b8d8718 100644 --- a/main.c +++ b/main.c @@ -140,7 +140,7 @@ main_usage(void) #endif /* !SSL_OP_NO_COMPRESSION */ " -r proto only support one of " SSL_PROTO_SUPPORT_S "(default: all)\n" " -R proto disable one of " SSL_PROTO_SUPPORT_S "(default: none)\n" -" -s ciphers use the given OpenSSL cipher suite spec (default: ALL:-aNULL)\n" +" -s ciphers use the given OpenSSL cipher suite spec (default: " DFLT_CIPHERS ")\n" " -e engine specify default NAT engine to use (default: %s)\n" " -E list available NAT engines and exit\n" " -u user drop privileges to user (default if run as root: " DFLT_DROPUSER ")\n" @@ -715,7 +715,7 @@ main(int argc, char *argv[]) /* dynamic defaults */ if (!opts->ciphers) { - opts->ciphers = strdup("ALL:-aNULL"); + opts->ciphers = strdup(DFLT_CIPHERS); if (!opts->ciphers) oom_die(argv0); } From d6b11f61b703341ee13493ec33558edf321f25ce Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 8 Dec 2014 19:40:01 +0100 Subject: [PATCH 22/23] Clarify needed permission to open /dev/pf et al for reading Issue: #66 Reported by: Nikolay Khodov --- sslsplit.1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sslsplit.1 b/sslsplit.1 index e03d5fc..abdf1b0 100644 --- a/sslsplit.1 +++ b/sslsplit.1 @@ -413,6 +413,8 @@ SSLsplit currently supports the following NAT engines: OpenBSD packet filter (pf) \fBrdr\fP/\fBrdr-to\fP NAT redirects, also available on FreeBSD, NetBSD and Mac OS X. Fully supported, including IPv6. +Note that SSLsplit needs permission to open \fB/dev/pf\fP for reading, which by +default means that it needs to run under \fBroot\fP privileges. Assuming inbound interface \fBem0\fP, first in old (FreeBSD, Mac OS X), then in new (OpenBSD 4.7+) syntax: .LP @@ -474,6 +476,8 @@ First in IPFW, then in pf \fBdivert-to\fP syntax: .B ipfilter IPFilter (ipfilter, ipf), available on many systems, including FreeBSD, NetBSD, Linux and Solaris. +Note that SSLsplit needs permission to open \fB/dev/ipnat\fP for reading, which +by default means that it needs to run under \fBroot\fP privileges. Only supports IPv4 due to limitations in the SIOCGNATL ioctl(2) interface. Assuming inbound interface \fBbge0\fP: .LP From 8b0b1d0226106cc74b1a503276049b60b21d3589 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Fri, 12 Dec 2014 17:38:34 +0100 Subject: [PATCH 23/23] Add ssl_key_identifier_sha1() utility function Issue: #67 --- ssl.c | 27 +++++++++++++++++++++++++++ ssl.h | 2 ++ ssl.t.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/ssl.c b/ssl.c index abb6bdc..2917362 100644 --- a/ssl.c +++ b/ssl.c @@ -1032,6 +1032,33 @@ ssl_key_genrsa(const int keysize) return pkey; } +/* + * Returns the subjectKeyIdentifier compatible key id of the public key. + * keyid will receive a binary SHA-1 hash of SSL_KEY_IDSZ bytes. + * Returns 0 on success, -1 on failure. + */ +int +ssl_key_identifier_sha1(EVP_PKEY *key, unsigned char *keyid) +{ + X509_PUBKEY *pubkey = NULL; + + if (X509_PUBKEY_set(&pubkey, key) != 1) + return -1; + + ASN1_BIT_STRING *pk = pubkey->public_key; + if (!pk) + goto errout; + if (!EVP_Digest(pk->data, pk->length, keyid, NULL, EVP_sha1(), NULL)) + goto errout; + + X509_PUBKEY_free(pubkey); + return 0; + +errout: + X509_PUBKEY_free(pubkey); + return -1; +} + /* * Returns the one-line representation of the subject DN in a newly allocated * string which must be freed by the caller. diff --git a/ssl.h b/ssl.h index 592d325..8913fea 100644 --- a/ssl.h +++ b/ssl.h @@ -128,6 +128,8 @@ EC_KEY * ssl_ec_by_name(const char *) MALLOC; EVP_PKEY * ssl_key_load(const char *) NONNULL(1) MALLOC; EVP_PKEY * ssl_key_genrsa(const int) MALLOC; void ssl_key_refcount_inc(EVP_PKEY *) NONNULL(1); +#define SSL_KEY_IDSZ 20 +int ssl_key_identifier_sha1(EVP_PKEY *, unsigned char *) NONNULL(1,2); #ifndef OPENSSL_NO_TLSEXT int ssl_x509_v3ext_add(X509V3_CTX *, X509 *, char *, char *) NONNULL(1,2,3,4); diff --git a/ssl.t.c b/ssl.t.c index 5adaea6..3120efc 100644 --- a/ssl.t.c +++ b/ssl.t.c @@ -33,6 +33,7 @@ #include +#define TESTKEY "extra/pki/server.key" #define TESTCERT "extra/pki/server.crt" #define TESTCERT2 "extra/pki/rsa.crt" @@ -404,6 +405,30 @@ START_TEST(ssl_tls_clienthello_parse_sni_07) END_TEST #endif /* !OPENSSL_NO_TLSEXT */ +START_TEST(ssl_key_identifier_sha1_01) +{ + X509 *c; + EVP_PKEY *k; + unsigned char keyid[SSL_KEY_IDSZ]; + + c = ssl_x509_load(TESTCERT); + fail_unless(!!c, "loading certificate failed"); + k = ssl_key_load(TESTKEY); + fail_unless(!!k, "loading key failed"); + + fail_unless(ssl_key_identifier_sha1(k, keyid) == 0, + "ssl_key_identifier_sha1() failed"); + + int loc = X509_get_ext_by_NID(c, NID_subject_key_identifier, -1); + X509_EXTENSION *ext = X509_get_ext(c, loc); + fail_unless(!!ext, "loading ext failed"); + fail_unless(ext->value->length - 2 == SSL_KEY_IDSZ, + "extension length mismatch: %i"); + fail_unless(!memcmp(ext->value->data + 2, keyid, SSL_KEY_IDSZ), + "key id mismatch"); +} +END_TEST + START_TEST(ssl_x509_names_01) { X509 *c; @@ -602,6 +627,11 @@ ssl_suite(void) suite_add_tcase(s, tc); #endif /* !OPENSSL_NO_TLSEXT */ + tc = tcase_create("ssl_key_identifier_sha1"); + tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown); + tcase_add_test(tc, ssl_key_identifier_sha1_01); + suite_add_tcase(s, tc); + tc = tcase_create("ssl_x509_names"); tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown); tcase_add_test(tc, ssl_x509_names_01);