diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..1307485 --- /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](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. + +[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..2de4829 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,51 @@ +# 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 `LICENSE.md` for licensing and copyright information applying to +contributions. See `AUTHORS.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/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/NEWS.md b/NEWS.md index 3284a21..3909b48 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,12 @@ +### SSLsplit develop + +- 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 0.4.10 2014-11-28 - Add option -F to log to separate files with printf-style % directives, diff --git a/README.md b/README.md index bb947ed..bb0cc65 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,19 +65,18 @@ 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 -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 @@ -79,20 +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. - - -## 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. - diff --git a/defaults.h b/defaults.h new file mode 100644 index 0000000..7ed2c4d --- /dev/null +++ b/defaults.h @@ -0,0 +1,68 @@ +/* + * 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 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. + */ +#define DFLT_CURVE "secp160r2" + +#endif /* !DEFAULTS_H */ + +/* vim: set noet ft=c: */ diff --git a/log.c b/log.c index ef14442..8d0cd7b 100644 --- a/log.c +++ b/log.c @@ -31,6 +31,8 @@ #include "logger.h" #include "sys.h" #include "attrib.h" +#include "privsep.h" +#include "defaults.h" #include #include @@ -40,7 +42,6 @@ #include #include #include -#include #include #include @@ -58,7 +59,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 +87,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 +118,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); @@ -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,43 +251,82 @@ 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; }; -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_clisock = -1; /* privsep client socket for content logger */ -static int -log_content_file_preinit(const char *logfile) +/* + * 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) { - 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)); + 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; -} -static void -log_content_file_fini(void) -{ - if (content_fd != -1) { - close(content_fd); - content_fd = -1; - } + return 0; } /* @@ -271,11 +334,7 @@ log_content_file_fini(void) * 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) { @@ -447,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; @@ -500,30 +558,21 @@ 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; - 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->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; } @@ -537,45 +586,36 @@ 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 int -log_content_spec_opencb(UNUSED void *fh) +static ssize_t +log_content_dir_writecb(void *fh, const void *buf, size_t sz) { 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 (write(ctx->u.dir.fd, buf, sz) == -1) { + log_err_printf("Warning: Failed to write to content log: %s\n", + strerror(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); + return 0; +} - 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)); +static int +log_content_spec_opencb(void *fh) +{ + log_content_ctx_t *ctx = fh; + + 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; } - return 0; } @@ -586,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) @@ -614,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) { @@ -676,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; @@ -683,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; @@ -725,31 +845,52 @@ out: logger_free(content_log); } if (connect_log) { - log_connect_close(); + log_connect_fini(); logger_free(connect_log); } 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_fini(); + 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) return -1; if (!opts->debug) { - err_started = 1; + err_shortcut_logger = 1; } 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; } @@ -760,28 +901,53 @@ 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(); 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 92fbe5e..8b2e555 100644 --- a/log.h +++ b/log.h @@ -63,10 +63,14 @@ 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); +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 037d7fd..e8b9217 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" @@ -40,6 +41,7 @@ #include "sys.h" #include "log.h" #include "version.h" +#include "defaults.h" #include #include @@ -61,6 +63,7 @@ extern int daemon(int, int); #endif /* __APPLE__ */ + /* * Print version information to stderr. */ @@ -126,7 +129,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 @@ -139,12 +142,12 @@ 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: nobody)\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 -S/-F 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" @@ -180,11 +183,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); } /* @@ -202,14 +201,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 @@ -276,8 +275,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:dDVhW:w:")) != -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:dDVhW:w:")) != -1) { switch (ch) { case 'c': if (opts->cacrt) @@ -460,6 +459,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); @@ -467,6 +472,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); @@ -481,11 +492,23 @@ 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); - 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) @@ -504,22 +527,95 @@ 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); - 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; case 'W': opts->writeorig = 1; @@ -537,6 +633,7 @@ main(int argc, char *argv[]) if (!opts->certgendir) oom_die(argv0); break; + } #ifdef HAVE_LOCAL_PROCINFO case 'i': opts->lprocinfo = 1; @@ -641,19 +738,19 @@ 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); } if (!opts->dropuser && !geteuid() && !getuid() && - !opts->contentlog_isdir && !opts->contentlog_isspec) { + sys_isuser(DFLT_DROPUSER)) { #ifdef __APPLE__ /* Apple broke ioctl(/dev/pf) for EUID != 0 so we do not * want to automatically drop privileges to nobody there * if pf has been used in any proxyspec */ if (!nat_used("pf")) { #endif /* __APPLE__ */ - opts->dropuser = strdup("nobody"); + opts->dropuser = strdup(DFLT_DROPUSER); if (!opts->dropuser) oom_die(argv0); #ifdef __APPLE__ @@ -742,48 +839,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; @@ -792,21 +916,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 ecaeb0b..6b8950a 100644 --- a/nat.c +++ b/nat.c @@ -576,6 +576,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 64d73ac..196e996 100644 --- a/nat.h +++ b/nat.h @@ -49,6 +49,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 e21cc54..ba66165 100644 --- a/opts.c +++ b/opts.c @@ -108,6 +108,9 @@ opts_free(opts_t *opts) if (opts->certgendir) { free(opts->certgendir); } + 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 cfcd415..0279376 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..c40f6b9 --- /dev/null +++ b/privsep.c @@ -0,0 +1,771 @@ +/* + * 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 "defaults.h" + +#include +#include +#include +#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. + */ + +/* 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 */ + +/* 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) +{ + 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; + case SIGUSR1: + received_sigusr1 = 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, DFLT_DIRMODE) == -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, DFLT_FILEMODE); + 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; +} + +/* + * 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, + 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_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; + } + 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; + received_sigusr1 = 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(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); + 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..3593453 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" @@ -56,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; @@ -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; } @@ -236,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) @@ -247,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; } } @@ -273,10 +246,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 +315,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 +336,7 @@ proxy_new(opts_t *opts) goto leave4; evtimer_add(ctx->gcev, &gc_delay); + privsep_client_close(clisock); return ctx; leave4: @@ -395,9 +370,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/ssl.c b/ssl.c index a39b399..2917362 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; @@ -1031,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 4038093..8913fea 100644 --- a/ssl.h +++ b/ssl.h @@ -122,13 +122,14 @@ 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 */ 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); diff --git a/sslsplit.1 b/sslsplit.1 index e698952..c17a63d 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. @@ -194,11 +190,13 @@ Same as -w, but also write original certificates .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 @@ -274,8 +272,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 @@ -294,8 +290,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). Due to an Apple bug, \fB-u\fP cannot be used with \fBpf\fP proxyspecs on Mac OS X. .TP @@ -371,6 +366,11 @@ no supported NAT engine or when running \fBsslsplit\fP on a different system 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. +.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. @@ -419,6 +419,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 @@ -480,6 +482,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 diff --git a/sys.c b/sys.c index e3c9b9f..b985a07 100644 --- a/sys.c +++ b/sys.c @@ -29,11 +29,14 @@ #include "sys.h" #include "log.h" +#include "defaults.h" #include #include +#include #include #include +#include #include #include #include @@ -76,6 +79,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 +88,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 +146,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. @@ -150,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; @@ -373,8 +414,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; @@ -427,11 +473,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) @@ -498,4 +544,233 @@ 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))]; + ssize_t n; + + 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); + if (!cmsg) + return -1; + 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; + } + do { +#ifdef MSG_NOSIGNAL + n = sendmsg(sock, &msg, MSG_NOSIGNAL); +#else /* !MSG_NOSIGNAL */ + n = sendmsg(sock, &msg, 0); +#endif /* !MSG_NOSIGNAL */ + } while (n == -1 && errno == EINTR); + return n; +} + +/* + * 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); + 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))) { + 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 { + do { + n = recv(sock, buf, bufsz, 0); + } while (n == -1 && errno == EINTR); + } + 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 0af0368..8d8bead 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; @@ -56,6 +58,11 @@ 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; + +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..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, 0755), "sys_mkpath failed"); + fail_unless(!sys_mkpath(dir, DFLT_DIRMODE), "sys_mkpath failed"); fail_unless(sys_isdir(dir), "dir not sys_isdir()"); free(dir); } 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: */