moved to develop branch

pull/13/head
PsychoMario 10 years ago
commit b34336ab4b

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

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

@ -1,25 +0,0 @@
SSLsplit - transparent and scalable SSL/TLS interception
Copyright (c) 2009-2014, Daniel Roethlisberger <daniel@roe.ch>
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.

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

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

@ -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
SSLsplit is being developed on Github. For bug reports, please use the Github
issue tracker. For patch submissions, please send me pull requests.
## Documentation
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.

@ -0,0 +1,68 @@
/*
* SSLsplit - transparent and scalable SSL/TLS interception
* Copyright (c) 2009-2014, Daniel Roethlisberger <daniel@roe.ch>
* 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: */

354
log.c

@ -31,6 +31,8 @@
#include "logger.h"
#include "sys.h"
#include "attrib.h"
#include "privsep.h"
#include "defaults.h"
#include <stdio.h>
#include <stdlib.h>
@ -40,7 +42,6 @@
#include <errno.h>
#include <fcntl.h>
#include <syslog.h>
#include <libgen.h>
#include <assert.h>
#include <sys/stat.h>
@ -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));
return -1;
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, '%');
}
return 0;
}
/* at first % that is not %%, or at EOS */
static void
log_content_file_fini(void)
{
if (content_fd != -1) {
close(content_fd);
content_fd = -1;
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;
}
/*
@ -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,15 +558,45 @@ 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 int
log_content_dir_opencb(void *fh)
{
log_content_ctx_t *ctx = fh;
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;
}
return 0;
}
static void
log_content_dir_closecb(void *fh)
{
log_content_ctx_t *ctx = fh;
if (ctx->u.dir.filename)
free(ctx->u.dir.filename);
if (ctx->u.dir.fd != 1)
close(ctx->u.dir.fd);
free(ctx);
}
static ssize_t
log_content_common_writecb(void *fh, const void *buf, size_t sz)
log_content_dir_writecb(void *fh, const void *buf, size_t sz)
{
log_content_ctx_t *ctx = fh;
if (write(ctx->fd, buf, sz) == -1) {
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;
@ -517,78 +605,93 @@ log_content_common_writecb(void *fh, const void *buf, size_t sz)
}
static int
log_content_dir_opencb(void *fh)
log_content_spec_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",
ctx->u.dir.filename, strerror(errno), errno);
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;
}
static void
log_content_dir_closecb(void *fh)
log_content_spec_closecb(void *fh)
{
log_content_ctx_t *ctx = fh;
if (ctx->u.dir.filename)
free(ctx->u.dir.filename);
if (ctx->fd != 1)
close(ctx->fd);
if (ctx->u.spec.filename)
free(ctx->u.spec.filename);
if (ctx->u.spec.fd != -1)
close(ctx->u.spec.fd);
free(ctx);
}
static int
log_content_spec_opencb(UNUSED void *fh)
static ssize_t
log_content_spec_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);
return -1;
}
filedir = dirname(filename2);
if (!filedir) {
log_err_printf("Could not get dirname: %s (%i)\n",
strerror(errno), errno);
free(filename2);
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;
}
if (sys_mkpath(filedir, 0755) == -1) {
log_err_printf("Could not create directory '%s': %s (%i)\n",
filedir, strerror(errno), errno);
free(filename2);
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;
}
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));
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_spec_closecb(void *fh)
log_content_file_fini(void)
{
log_content_ctx_t *ctx = fh;
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;
}
}
if (ctx->u.spec.filename)
free(ctx->u.spec.filename);
if (ctx->fd != -1)
close(ctx->fd);
free(ctx);
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;
}
/*
@ -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: */

@ -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 */

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

@ -35,20 +35,22 @@
#include <unistd.h>
#include <pthread.h>
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,

230
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 <stdlib.h>
#include <stdio.h>
@ -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;

@ -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().

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

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

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

@ -0,0 +1,771 @@
/*
* SSLsplit - transparent and scalable SSL/TLS interception
* Copyright (c) 2009-2014, Daniel Roethlisberger <daniel@roe.ch>
* 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 <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#include <fcntl.h>
/*
* 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: */

@ -0,0 +1,43 @@
/*
* SSLsplit - transparent and scalable SSL/TLS interception
* Copyright (c) 2009-2014, Daniel Roethlisberger <daniel@roe.ch>
* 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: */

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

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

30
ssl.c

@ -29,6 +29,7 @@
#include "ssl.h"
#include "log.h"
#include "defaults.h"
#include <sys/types.h>
#include <fcntl.h>
@ -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.

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

@ -33,6 +33,7 @@
#include <check.h>
#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);

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

281
sys.c

@ -29,11 +29,14 @@
#include "sys.h"
#include "log.h"
#include "defaults.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
@ -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: */

@ -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: */

@ -28,6 +28,8 @@
#include "sys.h"
#include "defaults.h"
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
@ -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);
}

@ -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: */

Loading…
Cancel
Save