Add lp listening program and testproxy tests under extra

pull/48/head
Soner Tari 5 years ago
parent c54cb627a1
commit e8f9f216a9

@ -0,0 +1,6 @@
all:
@gmake $(.TARGETS)
$(.TARGETS): all
.PHONY: all

@ -0,0 +1,423 @@
### Variable overrides
# You can change many aspects of the build behaviour without modifying this
# make file simply by setting environment variables.
#
# Dependencies and features are auto-detected, but can be overridden:
#
# LIBEVENT_BASE Prefix of libevent library and headers to build against
# PKGCONFIG Name/path of pkg-config program to use for auto-detection
# PCFLAGS Additional pkg-config flags
# XNU_VERSION Version of included XNU headers to build against (OS X only)
# FEATURES Enable optional or force-enable undetected features (see below)
#
# Where and how to install to:
#
# PREFIX Prefix to install under (default /usr/local)
# DESTDIR Destination root under which prefix is located (default /)
# MANDIR Subdir of PREFIX that contains man section dirs
# INSTALLUID UID to use for installed files if installing as root
# INSTALLGID GID to use for installed files if installing as root
#
# Standard compiler variables are respected, e.g.:
#
# CC Compiler, e.g. for cross-compiling, ccache or ccc-analyzer
# CFLAGS Additional compiler flags, e.g. optimization flags
# CPPFLAGS Additional pre-processor flags
# LDFLAGS Additional linker flags
# LIBS Additional libraries to link against
# SOURCE_DATE_EPOCH Set to epoch time to make the build reproducible
#
# On macOS, the following build environment variables are respected:
#
# DEVELOPER_DIR Override Xcode Command Line Developer Tools directory
# MACOSX_VERSION_MIN Minimal version of macOS to target, e.g. 10.11
# SDK SDK name to build against, e.g. macosx, macosx10.11
#
# Examples:
#
# Build against custom installed libraries under /opt:
# % LIBEVENT_BASE=/opt/libevent make
#
# Create a statically linked binary:
# % PCFLAGS='--static' CFLAGS='-static' LDFLAGS='-static' make
#
# Build a macOS binary for El Capitan using the default SDK from Xcode 7.3.1:
# % MACOSX_VERSION_MIN=10.11 DEVELOPER_DIR=/Applications/Xcode-7.3.1.app/Contents/Developer make
### Debugging
# These flags are added to CFLAGS iff building from a git repo.
DEBUG_CFLAGS?= -g
#DEBUG_CFLAGS+= -Werror
# Define to remove false positives when debugging memory allocation.
#FEATURES+= -DPURIFY
# Define to add proxy state machine debugging; dump state in debug mode.
#FEATURES+= -DDEBUG_PROXY
# Define to add thread debugging; dump thread state when choosing a thread.
#FEATURES+= -DDEBUG_THREAD
# Define to add privilege separation server event loop debugging.
#FEATURES+= -DDEBUG_PRIVSEP_SERVER
# Define to add diagnostic output for debugging option parsing.
#FEATURES+= -DDEBUG_OPTS
### Mac OS X header selection
# First, try to use the exact XNU version reported by the kernel. If they
# are not available, try to look up a suitable XNU version that we have
# headers for based on the OS X release reported by sw_vers. Then as a last
# resort, fall back to the latest version of XNU that we have headers for,
# which may or may not work, depending on if there were API or ABI changes
# in the DIOCNATLOOK ioctl interface to the NAT state table in the kernel.
#
# Note that you can override the XNU headers used by defining XNU_VERSION.
ifeq ($(shell uname),Darwin)
include Mk/xcode.mk
ifneq ($(wildcard /usr/include/libproc.h),)
FEATURES+= -DHAVE_DARWIN_LIBPROC
endif
OSX_VERSION= $(shell sw_vers -productVersion)
ifneq ($(XNU_VERSION),)
XNU_METHOD= override
XNU_HAVE= $(shell uname -a|sed 's/^.*root:xnu-//g'|sed 's/~.*$$//')
else
XNU_METHOD= uname
XNU_VERSION= $(shell uname -a|sed 's/^.*root:xnu-//g'|sed 's/~.*$$//')
XNU_HAVE:= $(XNU_VERSION)
endif
ifeq ($(wildcard xnu/xnu-$(XNU_VERSION)),)
XNU_METHOD= sw_vers
XNU_VERSION= $(shell awk '/^XNU_RELS.*\# $(OSX_VERSION)$$/ {print $$2}' xnu/GNUmakefile)
endif
ifeq ($(wildcard xnu/xnu-$(XNU_VERSION)),)
XNU_METHOD= fallback
XNU_VERSION= $(shell awk '/^XNU_RELS/ {print $$2}' xnu/GNUmakefile|tail -1)
endif
ifneq ($(wildcard xnu/xnu-$(XNU_VERSION)),)
FEATURES+= -DHAVE_PF
PKG_CPPFLAGS+= -I./xnu/xnu-$(XNU_VERSION)
BUILD_INFO+= OSX:$(OSX_VERSION) XNU:$(XNU_VERSION):$(XNU_METHOD):$(XNU_HAVE)
endif
endif
### Autodetected features
# Autodetect pf
ifneq ($(wildcard /usr/include/net/pfvar.h),)
FEATURES+= -DHAVE_PF
# OpenBSD 4.7+ and FreeBSD 9.0+ also include ipfw-style divert-to in pf
FEATURES+= -DHAVE_IPFW
endif
# Autodetect ipfw
ifneq ($(wildcard /sbin/ipfw),)
FEATURES+= -DHAVE_IPFW
endif
# Autodetect ipfilter
ifneq ($(wildcard /usr/include/netinet/ip_fil.h),)
FEATURES+= -DHAVE_IPFILTER
endif
# Autodetect netfilter
ifneq ($(wildcard /usr/include/linux/netfilter.h),)
FEATURES+= -DHAVE_NETFILTER
endif
### Variables you might need to override
PREFIX?= /usr/local
MANDIR?= share/man
EXAMPLESDIR?= share/examples
INSTALLUID?= 0
INSTALLGID?= 0
BINUID?= $(INSTALLUID)
BINGID?= $(INSTALLGID)
BINMODE?= 0755
CNFUID?= $(INSTALLUID)
CNFGID?= $(INSTALLGID)
CNFMODE?= 0644
MANUID?= $(INSTALLUID)
MANGID?= $(INSTALLGID)
MANMODE?= 0644
EXAMPLESMODE?= 0444
ifeq ($(shell id -u),0)
BINOWNERFLAGS?= -o $(BINUID) -g $(BINGID)
CNFOWNERFLAGS?= -o $(CNFUID) -g $(CNFGID)
MANOWNERFLAGS?= -o $(MANUID) -g $(MANGID)
else
BINOWNERFLAGS?=
CNFOWNERFLAGS?=
MANOWNERFLAGS?=
endif
PKGCONFIG?= $(shell command -v pkg-config||echo false)
ifeq ($(PKGCONFIG),false)
$(warning pkg-config not found - guessing paths/flags for dependencies)
endif
BASENAME?= basename
CAT?= cat
CHECKNR?= checknr
CUT?= cut
GREP?= grep
INSTALL?= install
MKDIR?= mkdir
SED?= sed
SORT?= sort
### Variables only used for developer targets
GPGSIGNKEY?= 0xE1520675375F5E35
CPPCHECK?= cppcheck
GPG?= gpg
GIT?= git
WGET?= wget
BZIP2?= bzip2
COL?= col
LN?= ln
MAN?= man
TAR?= tar
### You should not need to touch anything below this line
PKGLABEL:= Lp
PKGNAME:= lp
TARGET:= $(PKGNAME)
SRCS:= $(filter-out $(wildcard *.t.c),$(wildcard *.c))
HDRS:= $(wildcard *.h)
OBJS:= $(SRCS:.c=.o)
MKFS= $(wildcard GNUmakefile Mk/*.mk)
FEATURES:= $(sort $(FEATURES))
include Mk/buildinfo.mk
VERSION:= $(BUILD_VERSION)
ifdef GITDIR
CFLAGS+= $(DEBUG_CFLAGS)
endif
# Autodetect dependencies known to pkg-config
PKGS:=
ifndef LIBEVENT_BASE
PKGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --exists libevent \
&& echo libevent)
PKGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --exists libevent_pthreads \
&& echo libevent_pthreads)
endif
# Function: Generate list of base paths to search when locating packages
# $1 packagename
bases= /usr/local/opt/$(1) \
/opt/local \
/usr/local \
/usr
# Function: Locate base path for a package we depend on
# $1 packagename, $2 pattern suffix, $3 override path(s)
locate= $(subst /$(2),,$(word 1,$(wildcard \
$(addsuffix /$(2),$(if $(3),$(3),$(call bases,$(1)))))))
# Autodetect dependencies not known to pkg-config
ifeq (,$(filter libevent,$(PKGS)))
LIBEVENT_FOUND:=$(call locate,libevent,include/event2/event.h,$(LIBEVENT_BASE))
ifndef LIBEVENT_FOUND
$(error dependency 'libevent 2.x' not found; \
install it or point LIBEVENT_BASE to base path)
endif
endif
ifdef LIBEVENT_FOUND
PKG_CPPFLAGS+= -I$(LIBEVENT_FOUND)/include
PKG_LDFLAGS+= -L$(LIBEVENT_FOUND)/lib
PKG_LIBS+= -levent
endif
ifeq (,$(filter libevent_pthreads,$(PKGS)))
PKG_LIBS+= -levent_pthreads
endif
ifneq (,$(strip $(PKGS)))
PKG_CFLAGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --cflags-only-other $(PKGS))
PKG_CPPFLAGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --cflags-only-I $(PKGS))
PKG_LDFLAGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --libs-only-L \
--libs-only-other $(PKGS))
PKG_LIBS+= $(shell $(PKGCONFIG) $(PCFLAGS) --libs-only-l $(PKGS))
endif
CPPDEFS+= -D_GNU_SOURCE \
-D"PKGLABEL=\"$(PKGLABEL)\""
CPPCHECKFLAGS+= $(CPPDEFS)
ifneq (ccc-analyzer,$(notdir $(CC)))
PKG_CPPFLAGS:= $(subst -I,-isystem,$(PKG_CPPFLAGS))
endif
CFLAGS+= $(PKG_CFLAGS) \
-std=c99 -Wall -Wextra -pedantic \
-D_FORTIFY_SOURCE=2 -fstack-protector-all
CPPFLAGS+= $(PKG_CPPFLAGS) $(CPPDEFS) $(FEATURES)
LDFLAGS+= $(PKG_LDFLAGS)
LIBS+= $(PKG_LIBS)
ifneq ($(shell uname),Darwin)
CFLAGS+= -pthread
LDFLAGS+= -pthread
endif
# _FORTIFY_SOURCE requires -O on Linux
ifeq (,$(findstring -O,$(CFLAGS)))
CFLAGS+= -O2
endif
export VERSION
export MKDIR
export WGET
ifndef MAKE_RESTARTS
$(info ------------------------------------------------------------------------------)
$(info $(PKGLABEL) $(VERSION))
$(info ------------------------------------------------------------------------------)
$(info Report bugs at https://github.com/sonertari/SSLproxy/issues/new)
$(info Please supply this header for diagnostics when reporting build issues)
$(info Before reporting bugs, make sure to try the latest develop branch first:)
$(info % git clone -b develop https://github.com/sonertari/SSLproxy.git)
$(info ------------------------------------------------------------------------------)
$(info Via pkg-config: $(strip $(PKGS)))
ifdef LIBEVENT_FOUND
$(info LIBEVENT_BASE: $(strip $(LIBEVENT_FOUND)))
endif
$(info Build options: $(FEATURES))
$(info Build info: $(BUILD_INFO))
ifeq ($(shell uname),Darwin)
$(info OSX_VERSION: $(OSX_VERSION))
$(info XNU_VERSION: $(XNU_VERSION) ($(XNU_METHOD), have $(XNU_HAVE)))
endif
$(info uname -a: $(shell uname -a))
$(info ------------------------------------------------------------------------------)
endif
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
build.o: CPPFLAGS+=$(BUILD_CPPFLAGS)
build.o: build.c FORCE
%.o: %.c $(HDRS) $(MKFS)
$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
clean:
$(RM) -f $(TARGET) *.o .*.o *.core *~
$(RM) -rf *.dSYM
install: $(TARGET)
test -d $(DESTDIR)$(PREFIX)/bin || $(MKDIR) -p $(DESTDIR)$(PREFIX)/bin
test -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man1 || \
$(MKDIR) -p $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
test -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man5 || \
$(MKDIR) -p $(DESTDIR)$(PREFIX)/$(MANDIR)/man5
test -d $(DESTDIR)$(PREFIX)/$(EXAMPLESDIR)/$(TARGET) || \
$(MKDIR) -p $(DESTDIR)$(PREFIX)/$(EXAMPLESDIR)/$(TARGET)
$(INSTALL) $(BINOWNERFLAGS) -m $(BINMODE) \
$(TARGET) $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) $(MANOWNERFLAGS) -m $(MANMODE) \
$(TARGET).1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1/
$(INSTALL) $(MANOWNERFLAGS) -m $(MANMODE) \
$(TARGET).conf.5 $(DESTDIR)$(PREFIX)/$(MANDIR)/man5/
$(INSTALL) $(MANOWNERFLAGS) -m $(EXAMPLESMODE) \
$(TARGET).conf $(DESTDIR)$(PREFIX)/$(EXAMPLESDIR)/$(TARGET)/
deinstall:
$(RM) -f $(DESTDIR)$(PREFIX)/bin/$(TARGET) $(DESTDIR)$(PREFIX)/$(MANDIR)/man1/$(TARGET).1 \
$(DESTDIR)$(PREFIX)/$(MANDIR)/man5/$(TARGET).conf.5
$(RM) -rf $(DESTDIR)$(PREFIX)/$(EXAMPLESDIR)/$(TARGET)/
ifdef GITDIR
lint:
$(CPPCHECK) $(CPPCHECKFLAGS) --force --enable=all --error-exitcode=1 .
manlint: $(TARGET).1 $(TARGET).conf.5
$(CHECKNR) $(TARGET).1
mantest: $(TARGET).1 $(TARGET).conf.5
$(RM) -f man1 man5
$(LN) -sf . man1
$(LN) -sf . man5
$(MAN) -M . 1 $(TARGET)
$(MAN) -M . 5 $(TARGET).conf
$(RM) man1 man5
copyright: *.c *.h *.1 *.5
Mk/bin/copyright.py $^
$(PKGNAME)-$(VERSION).1.txt: $(TARGET).1
$(RM) -f man1
$(LN) -sf . man1
$(MAN) -M . 1 $(TARGET) | $(COL) -b >$@
$(RM) man1
$(PKGNAME)-$(VERSION).conf.5.txt: $(TARGET).conf.5
$(RM) -f man5
$(LN) -sf . man5
$(MAN) -M . 5 $(TARGET).conf | $(COL) -b >$@
$(RM) man5
man: $(PKGNAME)-$(VERSION).1.txt $(PKGNAME)-$(VERSION).conf.5.txt
manclean:
$(RM) -f $(PKGNAME)-*.1.txt $(PKGNAME)-*.conf.5.txt
fetchdeps:
$(MAKE) -C xnu fetch
dist: $(PKGNAME)-$(VERSION).tar.bz2 $(PKGNAME)-$(VERSION).tar.bz2.asc
%.asc: %
$(GPG) -u $(GPGSIGNKEY) --armor --output $@ --detach-sig $<
$(PKGNAME)-$(VERSION).tar.bz2:
$(MKDIR) -p $(PKGNAME)-$(VERSION)
echo $(VERSION) >$(PKGNAME)-$(VERSION)/VERSION
$(OPENSSL) dgst -sha1 -r *.[hc] | $(SORT) -k 2 \
>$(PKGNAME)-$(VERSION)/HASHES
$(GIT) archive --prefix=$(PKGNAME)-$(VERSION)/ HEAD \
>$(PKGNAME)-$(VERSION).tar
$(TAR) -f $(PKGNAME)-$(VERSION).tar -r $(PKGNAME)-$(VERSION)/VERSION
$(TAR) -f $(PKGNAME)-$(VERSION).tar -r $(PKGNAME)-$(VERSION)/HASHES
$(BZIP2) <$(PKGNAME)-$(VERSION).tar >$(PKGNAME)-$(VERSION).tar.bz2
$(RM) $(PKGNAME)-$(VERSION).tar
$(RM) -r $(PKGNAME)-$(VERSION)
disttest: $(PKGNAME)-$(VERSION).tar.bz2 $(PKGNAME)-$(VERSION).tar.bz2.asc
$(GPG) --verify $<.asc $<
$(BZIP2) -d < $< | $(TAR) -x -f -
cd $(PKGNAME)-$(VERSION) && $(MAKE) && $(MAKE) test && ./$(TARGET) -V
$(RM) -r $(PKGNAME)-$(VERSION)
distclean:
$(RM) -f $(PKGNAME)-*.tar.bz2*
realclean: distclean manclean clean
endif
FORCE:
.PHONY: all config clean buildtest travis lint \
install deinstall copyright manlint mantest man manclean fetchdeps \
dist disttest distclean realclean

@ -0,0 +1,85 @@
# in: PKGNAME
# in: FEATURES (optional)
# in: BUILD_INFO (optional)
# in: OPENSSL (optional)
# in: OPENSSL_FOUND (optional)
# in: SOURCE_DATE_EPOCH (optional)
ifndef PKGNAME
$(error PKGNAME not defined)
endif
ifndef OPENSSL
ifdef OPENSSL_FOUND
OPENSSL= $(OPENSSL_FOUND)/bin/openssl
else
OPENSSL= openssl
endif
endif
BASENAME?= basename
CUT?= cut
DATE?= date
DIFF?= diff
GIT?= git
GREP?= grep
RM?= rm
SED?= sed
SORT?= sort
TR?= tr
WC?= wc
GITDIR:= $(wildcard .git)
VERSION_FILE:= $(wildcard VERSION)
HASHES_FILE:= $(wildcard HASHES)
NEWS_FILE:= $(firstword $(wildcard NEWS*))
ifdef GITDIR
BUILD_VERSION:= $(shell $(GIT) describe --tags --dirty --always)
BUILD_INFO+= V:GIT
else
ifdef VERSION_FILE
BUILD_VERSION:= $(shell $(CAT) VERSION)
BUILD_INFO+= V:FILE
else
BUILD_VERSION:= $(shell $(BASENAME) $(PWD)|\
$(GREP) $(PKGNAME)-|\
$(SED) 's/.*$(PKGNAME)-\(.*\)/\1/g')
BUILD_INFO+= V:DIR
endif
ifdef HASHES_FILE
BUILD_INFO+= HDIFF:$(shell $(OPENSSL) dgst -sha1 -r *.[hc]|\
$(SORT) -k 2 >HASHES~;\
$(DIFF) -u HASHES HASHES~|\
$(GREP) '^-[^-]'|$(WC) -l|$(TR) -d ' ';\
$(RM) HASHES~)
endif
ifdef NEWS_FILE
NEWS_SHA:= $(shell $(OPENSSL) dgst -sha1 -r $(NEWS_FILE) |\
$(CUT) -c -7)
BUILD_INFO+= N:$(NEWS_SHA)
endif
endif # GITDIR
ifdef SOURCE_DATE_EPOCH
BUILD_DATE:= $(shell \
$(DATE) -u -d "@$(SOURCE_DATE_EPOCH)" "+%Y-%m-%d" 2>/dev/null||\
$(DATE) -u -r "$(SOURCE_DATE_EPOCH)" "+%Y-%m-%d" 2>/dev/null||\
$(DATE) -u "+%Y-%m-%d")
else
BUILD_DATE:= $(shell date +%Y-%m-%d)
endif
BUILD_CPPFLAGS+=-D"BUILD_PKGNAME=\"$(PKGNAME)\"" \
-D"BUILD_VERSION=\"$(BUILD_VERSION)\"" \
-D"BUILD_DATE=\"$(BUILD_DATE)\"" \
-D"BUILD_INFO=\"$(BUILD_INFO)\"" \
-D"BUILD_FEATURES=\"$(FEATURES)\""
# out: NEWS_FILE
# out: NEWS_SHA
# out: VERSION_FILE
# out: GITDIR
# out: BUILD_VERSION
# out: BUILD_DATE
# out: BUILD_INFO
# out: BUILD_CPPFLAGS

@ -0,0 +1,42 @@
# macOS Xcode and SDK selection makefile
# Authored 2018, Daniel Roethlisberger
# Provided under the Unlicense
# https://github.com/droe/example.kext
# DEVELOPER_DIR override Xcode Command Line Developer Tools directory
# MACOSX_VERSION_MIN minimal version of macOS to target, e.g. 10.11
# SDK SDK name to build against (e.g. macosx, macosx10.11, ...);
# for kernel extensions, use macosx$(MACOSX_VERSION_MIN)
# target specific macOS min version
ifdef MACOSX_VERSION_MIN
CFLAGS+= -mmacosx-version-min=$(MACOSX_VERSION_MIN)
LDFLAGS+= -mmacosx-version-min=$(MACOSX_VERSION_MIN)
endif
# select specific Xcode
ifdef DEVELOPER_DIR
ifndef SDK
SDK:= macosx
endif
else
DEVELOPER_DIR:= $(shell xcode-select -p)
endif
# activate the selected Xcode and SDK
ifdef SDK
SDKPATH:= $(shell DEVELOPER_DIR="$(DEVELOPER_DIR)" xcrun -find -sdk $(SDK) --show-sdk-path||echo none)
ifeq "$(SDKPATH)" "none"
$(error SDK not found)
endif
CPPFLAGS+= -isysroot $(SDKPATH)
LDFLAGS+= -isysroot $(SDKPATH)
CC:= $(shell DEVELOPER_DIR="$(DEVELOPER_DIR)" xcrun -find -sdk $(SDK) cc||echo false)
CXX:= $(shell DEVELOPER_DIR="$(DEVELOPER_DIR)" xcrun -find -sdk $(SDK) c++||echo false)
CODESIGN:= $(shell DEVELOPER_DIR="$(DEVELOPER_DIR)" xcrun -find -sdk $(SDK) codesign||echo false)
else
CC?= cc
CXX?= c++
CODESIGN?= codesign
endif

@ -0,0 +1,73 @@
/*
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 ATTRIB_H
#define ATTRIB_H
/*
* GCC attributes and built-ins for improved compile-time error checking
* and performance optimization.
*
* All of these are fully optional and are automatically disabled on non-GCC
* and non-LLVM/clang compilers.
*/
/*
* Attributes.
* These serve to improve the compiler warnings or optimizations.
*/
#if !defined(__GNUC__) && !defined(__clang__)
#define __attribute__(x)
#endif
#define UNUSED __attribute__((unused))
#define NORET __attribute__((noreturn))
#define PRINTF(f,a) __attribute__((format(printf,(f),(a))))
#define SCANF(f,a) __attribute__((format(scanf,(f),(a))))
#define WUNRES __attribute__((warn_unused_result))
#define MALLOC __attribute__((malloc)) WUNRES
#define NONNULL(...) __attribute__((nonnull(__VA_ARGS__)))
#define PURE __attribute__((pure))
/*
* Branch prediction macros.
* These serve to tell the compiler which of the branches is more likely.
*/
#if !defined(__GNUC__) && !defined(__clang__)
#define likely(expr) (expr)
#define unlikely(expr) (expr)
#else
#define likely(expr) __builtin_expect((expr), 1)
#define unlikely(expr) __builtin_expect((expr), 0)
#endif
#endif /* !ATTRIB_H */
/* vim: set noet ft=c: */

@ -0,0 +1,41 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "build.h"
/*
* Volatile build-time information which can change between make runs.
*/
const char *build_pkgname = BUILD_PKGNAME;
const char *build_version = BUILD_VERSION;
const char *build_date = BUILD_DATE;
const char *build_info = BUILD_INFO;
const char *build_features = BUILD_FEATURES;
/* vim: set noet ft=c: */

@ -0,0 +1,40 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 BUILD_H
#define BUILD_H
extern const char *build_pkgname;
extern const char *build_version;
extern const char *build_date;
extern const char *build_info;
extern const char *build_features;
#endif /* !BUILD_H */
/* vim: set noet ft=c: */

@ -0,0 +1,62 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. This user needs to be allowed to
* create outbound TCP connections, and in some configurations, perform DNS
* resolution.
*
* 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. Good practice
* is to create a dedicated user for sslsplit.
*
* Make sure to also patch the manual page if you patch this.
*/
#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
#endif /* !DEFAULTS_H */
/* vim: set noet ft=c: */

File diff suppressed because it is too large Load Diff

@ -0,0 +1,104 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 LOG_H
#define LOG_H
#include "opts.h"
#include "proxy.h"
#include "logger.h"
#include "attrib.h"
int log_err_printf(const char *, ...) PRINTF(1,2);
int log_err_level_printf(int, const char *, ...) PRINTF(2,3);
void log_err_mode(int);
#define LOG_ERR_MODE_STDERR 0
#define LOG_ERR_MODE_SYSLOG 1
int log_dbg_printf(const char *, ...) PRINTF(1,2);
int log_dbg_level_printf(int, const char *, ...) PRINTF(2,3);
int log_dbg_print_free(char *);
int log_dbg_write_free(void *, size_t);
void log_dbg_mode(int);
#define LOG_DBG_MODE_NONE 0
#define LOG_DBG_MODE_ERRLOG 1
#define LOG_DBG_MODE_FINE 2
#define LOG_DBG_MODE_FINER 3
#define LOG_DBG_MODE_FINEST 4
extern logger_t *masterkey_log;
#define log_masterkey_printf(fmt, ...) \
logger_printf(masterkey_log, NULL, 0, (fmt), __VA_ARGS__)
#define log_masterkey_print(s) \
logger_print(masterkey_log, NULL, 0, (s))
#define log_masterkey_write(buf, sz) \
logger_write(masterkey_log, NULL, 0, (buf), (sz))
#define log_masterkey_print_free(s) \
logger_print_freebuf(masterkey_log, NULL, 0, (s))
#define log_masterkey_write_free(buf, sz) \
logger_write_freebuf(masterkey_log, NULL, 0, (buf), (sz))
extern logger_t *connect_log;
#define log_connect_printf(fmt, ...) \
logger_printf(connect_log, NULL, 0, (fmt), __VA_ARGS__)
#define log_connect_print(s) \
logger_print(connect_log, NULL, 0, (s))
#define log_connect_write(buf, sz) \
logger_write(connect_log, NULL, 0, (buf), (sz))
#define log_connect_print_free(s) \
logger_print_freebuf(connect_log, NULL, 0, (s))
#define log_connect_write_free(buf, sz) \
logger_write_freebuf(connect_log, NULL, 0, (buf), (sz))
int log_stats(const char *);
int log_conn(const char *);
typedef struct log_content_ctx log_content_ctx_t;
struct log_content_file_ctx;
struct log_content_ctx {
struct log_content_file_ctx *file;
};
int log_content_open(log_content_ctx_t *, opts_t *,
char *, char *, char *, char *,
char *, char *, char *) NONNULL(1,2) WUNRES;
int log_content_submit(log_content_ctx_t *, logbuf_t *, int)
NONNULL(1,2) WUNRES;
int log_content_close(log_content_ctx_t *, int) 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;
void log_preinit_undo(void);
int log_init(opts_t *, proxy_ctx_t *, int[3]) NONNULL(1,2) WUNRES;
void log_fini(void);
int log_reopen(void) WUNRES;
void log_exceptcb(void);
#endif /* !LOG_H */
/* vim: set noet ft=c: */

@ -0,0 +1,276 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "logbuf.h"
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
/*
* Dynamic log buffer with zero-copy chaining, generic void * file handle
* and ctl for status control flags.
* Logbuf always owns the internal allocated buffer.
*/
/*
* Create new logbuf from provided, pre-allocated buffer, set fd and next.
* The provided buffer will be freed by logbuf_free() if non-NULL, and by
* logbuf_new() in case it fails returning NULL.
*/
logbuf_t *
logbuf_new(int level, void *buf, size_t sz, logbuf_t *next)
{
logbuf_t *lb;
if (!(lb = malloc(sizeof(logbuf_t)))) {
if (buf)
free(buf);
return NULL;
}
lb->prio = level;
lb->buf = buf;
lb->sz = sz;
if (next) {
lb->fh = next->fh;
lb->ctl = next->ctl;
lb->next = next;
} else {
lb->fh = NULL;
lb->ctl = 0;
lb->next = NULL;
}
return lb;
}
/*
* Create new logbuf, allocating sz bytes into the internal buffer.
*/
logbuf_t *
logbuf_new_alloc(size_t sz, logbuf_t *next)
{
logbuf_t *lb;
if (!(lb = malloc(sizeof(logbuf_t))))
return NULL;
if (!(lb->buf = malloc(sz))) {
free(lb);
return NULL;
}
lb->sz = sz;
if (next) {
lb->fh = next->fh;
lb->ctl = next->ctl;
lb->next = next;
} else {
lb->fh = NULL;
lb->ctl = 0;
lb->next = NULL;
}
return lb;
}
/*
* Create new logbuf, copying buf into a newly allocated internal buffer.
*/
logbuf_t *
logbuf_new_copy(const void *buf, size_t sz, logbuf_t *next)
{
logbuf_t *lb;
if (!(lb = malloc(sizeof(logbuf_t))))
return NULL;
if (!(lb->buf = malloc(sz))) {
free(lb);
return NULL;
}
memcpy(lb->buf, buf, sz);
lb->sz = sz;
if (next) {
lb->fh = next->fh;
lb->ctl = next->ctl;
lb->next = next;
} else {
lb->fh = NULL;
lb->ctl = 0;
lb->next = NULL;
}
return lb;
}
/*
* Create new logbuf using printf.
*/
logbuf_t *
logbuf_new_printf(logbuf_t *next, const char *fmt, ...)
{
va_list ap;
logbuf_t *lb;
if (!(lb = malloc(sizeof(logbuf_t))))
return NULL;
va_start(ap, fmt);
lb->sz = vasprintf((char**)&lb->buf, fmt, ap);
va_end(ap);
if (lb->sz < 0) {
free(lb);
return NULL;
}
if (next) {
lb->fh = next->fh;
lb->ctl = next->ctl;
lb->next = next;
} else {
lb->fh = NULL;
lb->ctl = 0;
lb->next = NULL;
}
return lb;
}
/*
* Create new logbuf from lb. If combine is set, combine all the buffer
* segments into a single contiguous one. Otherwise, copy segment by segment.
*/
logbuf_t *
logbuf_new_deepcopy(logbuf_t *lb, int combine)
{
logbuf_t *lbnew;
unsigned char *p;
if (!lb)
return NULL;
if (combine) {
lbnew = logbuf_new_alloc(logbuf_size(lb), NULL);
if (!lbnew)
return NULL;
lbnew->fh = lb->fh;
lbnew->ctl = lb->ctl;
p = lbnew->buf;
while (lb) {
memcpy(p, lb->buf, lb->sz);
p += lb->sz;
lb = lb->next;
}
} else {
lbnew = logbuf_new_copy(lb->buf, lb->sz, NULL);
if (!lbnew)
return NULL;
lbnew->fh = lb->fh;
lbnew->ctl = lb->ctl;
lbnew->next = logbuf_new_deepcopy(lb->next, 0);
}
return lbnew;
}
logbuf_t *
logbuf_make_contiguous(logbuf_t *lb) {
unsigned char *p;
logbuf_t *lbtmp;
if (!lb)
return NULL;
if (!lb->next)
return lb;
p = realloc(lb->buf, logbuf_size(lb));
if (!p)
return NULL;
lb->buf = p;
lbtmp = lb;
p += lbtmp->sz;
while ((lbtmp = lbtmp->next)) {
memcpy(p, lbtmp->buf, lbtmp->sz);
lb->sz += lbtmp->sz;
p += lbtmp->sz;
}
logbuf_free(lb->next);
lb->next = NULL;
return lb;
}
/*
* Calculate the total size of the logbuf and all chained buffers.
*/
ssize_t
logbuf_size(logbuf_t *lb)
{
ssize_t sz;
sz = lb->sz;
if (lb->next) {
sz += logbuf_size(lb->next);
}
return sz;
}
/*
* Write content of logbuf using writefunc and free all buffers.
* Returns -1 on errors and sets errno according to write().
* Returns total of bytes written by 1 .. n write() calls on success.
*/
ssize_t
logbuf_write_free(logbuf_t *lb, writefunc_t writefunc)
{
ssize_t rv1, rv2 = 0;
rv1 = writefunc(lb->prio, lb->fh, lb->ctl, lb->buf, lb->sz);
if (lb->buf) {
free(lb->buf);
}
if (lb->next) {
if (rv1 == -1) {
logbuf_free(lb->next);
} else {
lb->next->fh = lb->fh;
rv2 = logbuf_write_free(lb->next, writefunc);
}
}
free(lb);
if (rv1 == -1 || rv2 == -1)
return -1;
else
return rv1 + rv2;
}
/*
* Free logbuf including internal and chained buffers.
*/
void
logbuf_free(logbuf_t *lb)
{
if (lb->buf) {
free(lb->buf);
}
if (lb->next) {
logbuf_free(lb->next);
}
free(lb);
}
/* vim: set noet ft=c: */

@ -0,0 +1,71 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 LOGBUF_H
#define LOGBUF_H
#include "attrib.h"
#include <stdlib.h>
#include <unistd.h>
typedef struct logbuf {
int prio;
unsigned char *buf;
ssize_t sz;
void *fh;
unsigned long ctl;
struct logbuf *next;
} logbuf_t;
typedef ssize_t (*writefunc_t)(int, void *, unsigned long, const void *, size_t);
logbuf_t * logbuf_new(int, void *, size_t, logbuf_t *) MALLOC;
logbuf_t * logbuf_new_alloc(size_t, logbuf_t *) MALLOC;
logbuf_t * logbuf_new_copy(const void *, size_t, logbuf_t *) MALLOC;
logbuf_t * logbuf_new_printf(logbuf_t *, const char *, ...) MALLOC PRINTF(2,3);
logbuf_t * logbuf_new_deepcopy(logbuf_t *, int) MALLOC;
logbuf_t * logbuf_make_contiguous(logbuf_t *) WUNRES;
ssize_t logbuf_size(logbuf_t *) NONNULL(1) WUNRES;
ssize_t logbuf_write_free(logbuf_t *, writefunc_t) NONNULL(1);
void logbuf_free(logbuf_t *) NONNULL(1);
#define logbuf_ctl_clear(x) (x)->ctl = 0
#define logbuf_ctl_set(x, y) (x)->ctl |= (y)
#define logbuf_ctl_unset(x, y) (x)->ctl &= ~(y)
#define logbuf_ctl_isset(x, y) (!!((x)->ctl & (y)))
#define LBFLAG_REOPEN (1 << 0) /* logger */
#define LBFLAG_OPEN (1 << 1) /* logger */
#define LBFLAG_CLOSE (1 << 2) /* logger */
#define LBFLAG_IS_REQ (1 << 3) /* pcap/mirror content log */
#define LBFLAG_IS_RESP (1 << 4) /* pcap/mirror content log */
#endif /* !LOGBUF_H */
/* vim: set noet ft=c: */

@ -0,0 +1,361 @@
/*
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "logger.h"
#include "thrqueue.h"
#include "logbuf.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
/*
* Logger for multithreaded environments. Disk writes are executed in a
* writer thread. Logging threads submit buffers to be logged by adding
* them to the thrqueue. Logging threads may block on the pthread mutex
* of the thrqueue, but not on disk writes.
*/
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;
logger_write_func_t write;
logger_except_func_t except;
thrqueue_t *queue;
};
static void
logger_clear(logger_t *logger)
{
memset(logger, 0, sizeof(logger_t));
}
/*
* Create new logger with a set of specific function callbacks:
*
* reopenfunc: handle SIGHUP for the log by reopening all open files across
* multiple connections
* openfunc: open a new log for a new connection
* closefunc: close a log for a connection
* writefunc: write a single logbuf to the log
* prepfunc: prepare a log buffer before adding it to the logbuffer's queue
* exceptfunc: called after failed callback operations
*
* All callbacks except prepfunc will be executed in the logger's writer
* thread, not in the thread calling logger_submit(). Prepfunc will be called
* in the thread calling logger_submit().
*/
logger_t *
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_except_func_t exceptfunc)
{
logger_t *logger;
logger = malloc(sizeof(logger_t));
if (!logger)
return NULL;
logger_clear(logger);
logger->reopen = reopenfunc;
logger->open = openfunc;
logger->close = closefunc;
logger->write = writefunc;
logger->prep = prepfunc;
logger->except = exceptfunc;
logger->queue = NULL;
return logger;
}
/*
* Free the logger data structures. Caller must call logger_stop()
* or logger_leave() and logger_join() prior to freeing.
*/
void
logger_free(logger_t *logger) {
if (logger->queue) {
thrqueue_free(logger->queue);
}
free(logger);
}
/*
* Submit a buffer to be logged by the logger thread.
* Calls the prep callback from within the calling tread before submission.
* Buffer guaranteed to be freed after logging completes or on failure.
* Returns -1 on error, 0 on success (including logging a NULL logbuf, which
* is a no-op).
*/
int
logger_submit(logger_t *logger, void *fh, unsigned long prepflags,
logbuf_t *lb)
{
if (lb) {
lb->fh = fh;
logbuf_ctl_clear(lb);
}
if (logger->prep)
lb = logger->prep(fh, prepflags, lb);
/* If we got passed lb == NULL and prep callback did not replace it
* with an actual log buffer, stop here. */
if (!lb)
return 0;
if (thrqueue_enqueue(logger->queue, lb)) {
return 0;
} else {
logbuf_free(lb);
return -1;
}
}
/*
* Submit a log reopen event to the logger thread.
*/
int
logger_reopen(logger_t *logger)
{
logbuf_t *lb;
if (!logger->reopen)
return 0;
if (!(lb = logbuf_new(0, NULL, 0, NULL)))
return -1;
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)
{
logbuf_t *lb;
if (!logger->open)
return 0;
if (!(lb = logbuf_new(0, NULL, 0, NULL)))
return -1;
lb->fh = fh;
logbuf_ctl_set(lb, LBFLAG_OPEN);
return thrqueue_enqueue(logger->queue, lb) ? 0 : -1;
}
/*
* Submit a file close event to the logger thread.
* If no close callback is configured, returns successfully.
* Returns 0 on success, -1 on failure.
*/
int
logger_close(logger_t *logger, void *fh, unsigned long ctl)
{
logbuf_t *lb;
if (!logger->close)
return 0;
if (!(lb = logbuf_new(0, NULL, 0, NULL)))
return -1;
lb->fh = fh;
lb->ctl = ctl;
logbuf_ctl_set(lb, LBFLAG_CLOSE);
return thrqueue_enqueue(logger->queue, lb) ? 0 : -1;
}
/*
* Logger thread main function.
*/
static void *
logger_thread(void *arg)
{
logger_t *logger = arg;
logbuf_t *lb;
int e = 0;
while ((lb = thrqueue_dequeue(logger->queue))) {
if (logbuf_ctl_isset(lb, LBFLAG_REOPEN)) {
if (logger->reopen() != 0)
e = 1;
} else if (logbuf_ctl_isset(lb, LBFLAG_OPEN)) {
if (logger->open(lb->fh) != 0)
e = 1;
} else if (logbuf_ctl_isset(lb, LBFLAG_CLOSE)) {
logger->close(lb->fh, lb->ctl);
} else {
if (logbuf_write_free(lb, logger->write) < 0)
e = 1;
}
if (e && logger->except) {
logger->except();
}
}
return NULL;
}
/*
* Start the logger's write thread.
*/
int
logger_start(logger_t *logger) {
int rv;
if (logger->queue) {
thrqueue_free(logger->queue);
}
logger->queue = thrqueue_new(1024);
rv = pthread_create(&logger->thr, NULL, logger_thread, logger);
if (rv)
return -1;
sched_yield();
return 0;
}
/*
* Tell the logger's write thread to write all pending write requests
* and then exit. Don't wait for the logger to exit.
*/
void
logger_leave(logger_t *logger) {
thrqueue_unblock_dequeue(logger->queue);
sched_yield();
}
/*
* Wait for the logger to exit.
*/
int
logger_join(logger_t *logger) {
int rv;
rv = pthread_join(logger->thr, NULL);
if (rv)
return -1;
return 0;
}
/*
* Tell the logger's write thread to write all pending write requests
* and then exit; wait for the logger to exit.
*/
int
logger_stop(logger_t *logger) {
logger_leave(logger);
return logger_join(logger);
}
/*
* Generic print to a logger. These functions should be called by the
* actual worker thread(s) doing network I/O.
*
* _printf(), _print() and _write() copy the input buffers.
* _ncprint() and _ncwrite() will free() the input buffers.
*
* The file descriptor argument is a virtual or real system file descriptor
* used for multiplexing write requests to several files over the same
* logger. This argument is passed to the write handler as-is and is not
* interpreted or used by the logger itself in any way.
*
* All of the functions return 0 on succes, -1 on failure.
*/
int
logger_printf(logger_t *logger, void *fh, unsigned long prepflags,
const char *fmt, ...)
{
va_list ap;
logbuf_t *lb;
if (!(lb = logbuf_new(0, NULL, 0, NULL)))
return -1;
lb->fh = fh;
va_start(ap, fmt);
lb->sz = vasprintf((char**)&lb->buf, fmt, ap);
va_end(ap);
if (lb->sz < 0) {
logbuf_free(lb);
return -1;
}
return logger_submit(logger, fh, prepflags, lb);
}
int
logger_write(logger_t *logger, void *fh, unsigned long prepflags,
const void *buf, size_t sz)
{
logbuf_t *lb;
if (!(lb = logbuf_new_copy(buf, sz, NULL)))
return -1;
lb->fh = fh;
return logger_submit(logger, fh, prepflags, lb);
}
int
logger_print(logger_t *logger, void *fh, unsigned long prepflags,
const char *s)
{
logbuf_t *lb;
if (!(lb = logbuf_new_copy(s, strlen(s), NULL)))
return -1;
lb->fh = fh;
return logger_submit(logger, fh, prepflags, lb);
}
int
logger_write_freebuf(logger_t *logger, int level, void *fh, unsigned long prepflags,
void *buf, size_t sz)
{
logbuf_t *lb;
if (!(lb = logbuf_new(level, buf, sz, NULL)))
return -1;
lb->fh = fh;
return logger_submit(logger, fh, prepflags, lb);
}
int
logger_print_freebuf(logger_t *logger, void *fh, unsigned long prepflags,
char *s)
{
logbuf_t *lb;
if (!(lb = logbuf_new(0, s, strlen(s), NULL)))
return -1;
lb->fh = fh;
return logger_submit(logger, fh, prepflags, lb);
}
/* vim: set noet ft=c: */

@ -0,0 +1,74 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 LOGGER_H
#define LOGGER_H
#include "logbuf.h"
#include "attrib.h"
#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 *, unsigned long);
typedef ssize_t (*logger_write_func_t)(int, void *, unsigned long,
const void *, size_t);
typedef logbuf_t * (*logger_prep_func_t)(void *, unsigned long, logbuf_t *);
typedef void (*logger_except_func_t)(void);
typedef struct logger logger_t;
logger_t * logger_new(logger_reopen_func_t, logger_open_func_t,
logger_close_func_t, logger_write_func_t,
logger_prep_func_t, logger_except_func_t)
NONNULL(4,6) 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 *, unsigned long) NONNULL(1,2) WUNRES;
int logger_submit(logger_t *, void *, unsigned long,
logbuf_t *) NONNULL(1) WUNRES;
int logger_printf(logger_t *, void *, unsigned long,
const char *, ...) PRINTF(4,5) NONNULL(1,4) WUNRES;
int logger_print(logger_t *, void *, unsigned long,
const char *) NONNULL(1,4) WUNRES;
int logger_write(logger_t *, void *, unsigned long,
const void *, size_t) NONNULL(1,4) WUNRES;
int logger_print_freebuf(logger_t *, void *, unsigned long,
char *) NONNULL(1,4) WUNRES;
int logger_write_freebuf(logger_t *, int, void *, unsigned long,
void *, size_t) NONNULL(1,5) WUNRES;
#endif /* !LOGGER_H */
/* vim: set noet ft=c: */

Binary file not shown.

@ -0,0 +1,66 @@
# Sample configuration for lp v0.6.0
#
# Use the -f command line option to start lp with a config file.
# See lp.conf(5) and lp(1) for documentation.
# Drop privileges to user.
# Equivalent to -u command line option.
# (default: nobody, if run as root)
User soner
# Drop privileges to group.
# Equivalent to -m command line option.
# (default: primary group of user)
Group soner
# chroot() to jaildir (impacts sni proxyspecs, see lp(1)).
# Equivalent to -j command line option.
#Chroot /var/run/lp
# Write pid to file.
# Equivalent to -p command line option.
# (default: no pid file)
PidFile /var/run/lp.pid
# Connect log: log one line summary per connection to logfile.
# Equivalent to -l command line option.
#ConnectLog /var/log/lp/connect.log
# Content log: full data to file or named pipe
# (excludes ContentLogDir/ContentLogPathSpec).
# Equivalent to -L command line option.
#ContentLog /var/log/lp/content.log
# Content log: full data to separate files in dir
# (excludes ContentLog/ContentLogPathSpec).
# Equivalent to -S command line option.
#ContentLogDir /var/log/lp/content
# Content log: full data to sep files with % subst
# (excludes ContentLog/ContentLogDir).
# Equivalent to -F command line option.
#ContentLogPathSpec /var/log/lp/%X/%u-%s-%d-%T.log
# Daemon mode: run in background, log error messages to syslog.
# Equivalent to -d command line option.
Daemon yes
# Debug mode: run in foreground, log debug messages on stderr.
# Equivalent to -D command line option.
#Debug yes
# Verbose debug level
#DebugLevel 4
# Log statistics to syslog
# Equivalent to -J command line option.
LogStats yes
# Log statistics every this many ExpiredConnCheckPeriod periods
StatsPeriod 1
# Set open files limit, use 50-10000
#OpenFilesLimit 1024
# Proxy specifications: listenaddr+port
ProxySpec 127.0.0.1 8080

@ -0,0 +1,432 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
*/
/* silence daemon(3) deprecation warning on Mac OS X */
#if __APPLE__
#define daemon xdaemon
#endif /* __APPLE__ */
#include "opts.h"
#include "proxy.h"
#include "privsep.h"
#include "sys.h"
#include "log.h"
#include "build.h"
#include "defaults.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#ifndef __BSD__
#include <getopt.h>
#endif /* !__BSD__ */
#include <event2/event.h>
#if __APPLE__
#undef daemon
extern int daemon(int, int);
#endif /* __APPLE__ */
/*
* Print version information to stderr.
*/
static void
main_version(void)
{
fprintf(stderr, "%s %s (built %s)\n",
PKGLABEL, build_version, build_date);
if (strlen(build_version) < 5) {
/*
* Note to package maintainers: If you break the version
* string in your build, it will be impossible to provide
* proper upstream support to the users of the package,
* because it will be difficult or impossible to identify
* the exact codebase that is being used by the user
* reporting a bug. The version string is provided through
* different means depending on whether the code is a git
* checkout, a tarball downloaded from GitHub or a release.
* See GNUmakefile for the gory details.
*/
fprintf(stderr, "---------------------------------------"
"---------------------------------------\n");
fprintf(stderr, "WARNING: Something is wrong with the "
"version compiled into lp!\n");
fprintf(stderr, "The version should contain a release "
"number and/or a git commit reference.\n");
fprintf(stderr, "If using a package, please report a bug "
"to the distro package maintainer.\n");
fprintf(stderr, "---------------------------------------"
"---------------------------------------\n");
}
fprintf(stderr, "Copyright (c) 2017-2019, Soner Tari <sonertari@gmail.com>\n");
fprintf(stderr, "https://github.com/sonertari/SSLproxy\n");
fprintf(stderr, "Copyright (c) 2009-2018, "
"Daniel Roethlisberger <daniel@roe.ch>\n");
fprintf(stderr, "https://www.roe.ch/SSLsplit\n");
if (build_info[0]) {
fprintf(stderr, "Build info: %s\n", build_info);
}
if (build_features[0]) {
fprintf(stderr, "Features: %s\n", build_features);
}
fprintf(stderr, "compiled against libevent %s\n", LIBEVENT_VERSION);
fprintf(stderr, "rtlinked against libevent %s\n", event_get_version());
fprintf(stderr, "%d CPU cores detected\n", sys_get_cpu_cores());
}
/*
* Print usage to stderr.
*/
static void
main_usage(void)
{
const char *usagefmt =
"Usage: %s [-D] [-f conffile] [-o opt=val] [options...] [proxyspecs...]\n"
" -f conffile use conffile to load configuration from\n"
" -o opt=val override conffile option opt with value val\n"
" -u user drop privileges to user (default if run as root: " DFLT_DROPUSER ")\n"
" -m group when using -u, override group (default: primary group of user)\n"
" -j jaildir chroot() to jaildir (impacts sni proxyspecs, see manual page)\n"
" -p pidfile write pid to pidfile (default: no pid file)\n"
" -l logfile connect log: log one line summary per connection to logfile\n"
" -J enable connection statistics logging\n"
" -L logfile content log: full data to file or named pipe (excludes -S/-F)\n"
" -S logdir content log: full data to separate files in dir (excludes -L/-F)\n"
" -F pathspec content log: full data to sep files with %% subst (excl. -L/-S):\n"
" %%T - initial connection time as an ISO 8601 UTC timestamp\n"
" %%d - destination host and port\n"
" %%D - destination host\n"
" %%p - destination port\n"
" %%s - source host and port\n"
" %%S - source host\n"
" %%q - source port\n"
" %%%% - literal '%%'\n"
" -d daemon mode: run in background, log error messages to syslog\n"
" -D debug mode: run in foreground, log debug messages on stderr\n"
" -V print version information and exit\n"
" -h print usage information and exit\n"
" proxyspec = listenaddr+port\n"
" e.g. 127.0.0.1 8080 # tcp/4; static\n"
" # et al\n"
"Example:\n"
" %s 127.0.0.1 8080\n";
fprintf(stderr, usagefmt, build_pkgname, build_pkgname);
}
/*
* Main entry point.
*/
int
main(int argc, char *argv[])
{
const char *argv0;
int ch;
opts_t *opts;
int pidfd = -1;
int rv = EXIT_FAILURE;
argv0 = argv[0];
opts = opts_new();
while ((ch = getopt(argc, argv,
"u:m:j:p:l:L:S:F:dD::Vhf:o:J")) != -1) {
switch (ch) {
case 'f':
if (opts->conffile)
free(opts->conffile);
opts->conffile = strdup(optarg);
if (!opts->conffile)
oom_die(argv0);
if (opts_load_conffile(opts, argv0) == -1) {
exit(EXIT_FAILURE);
}
#ifdef DEBUG_OPTS
log_dbg_printf("Conf file: %s\n", opts->conffile);
#endif /* DEBUG_OPTS */
break;
case 'o':
if (opts_set_option(opts, argv0, optarg) == -1) {
exit(EXIT_FAILURE);
}
break;
case 'u':
opts_set_user(opts, argv0, optarg);
break;
case 'm':
opts_set_group(opts, argv0, optarg);
break;
case 'p':
opts_set_pidfile(opts, argv0, optarg);
break;
case 'j':
opts_set_jaildir(opts, argv0, optarg);
break;
case 'l':
opts_set_connectlog(opts, argv0, optarg);
break;
case 'J':
opts_set_statslog(opts);
break;
case 'L':
opts_set_contentlog(opts, argv0, optarg);
break;
case 'S':
opts_set_contentlogdir(opts, argv0, optarg);
break;
case 'F':
opts_set_contentlogpathspec(opts, argv0, optarg);
break;
case 'd':
opts_set_daemon(opts);
break;
case 'D':
opts_set_debug(opts);
if (optarg) {
opts_set_debug_level(optarg);
}
break;
case 'V':
main_version();
exit(EXIT_SUCCESS);
case 'h':
main_usage();
exit(EXIT_SUCCESS);
case '?':
exit(EXIT_FAILURE);
default:
main_usage();
exit(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
proxyspec_parse(&argc, &argv, &opts->spec);
/* usage checks before defaults */
if (opts->detach && OPTS_DEBUG(opts)) {
fprintf(stderr, "%s: -d and -D are mutually exclusive.\n",
argv0);
exit(EXIT_FAILURE);
}
if (!opts->spec) {
fprintf(stderr, "%s: no proxyspec specified.\n", argv0);
exit(EXIT_FAILURE);
}
for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) {
if (spec->connect_addrlen)
continue;
}
#ifdef __APPLE__
if (opts->dropuser && !!strcmp(opts->dropuser, "root") &&
nat_used("pf")) {
fprintf(stderr, "%s: cannot use 'pf' proxyspec with -u due "
"to Apple bug\n", argv0);
exit(EXIT_FAILURE);
}
#endif /* __APPLE__ */
/* prevent multiple instances running */
if (opts->pidfile) {
pidfd = sys_pidf_open(opts->pidfile);
if (pidfd == -1) {
fprintf(stderr, "%s: cannot open PID file '%s' "
"- process already running?\n",
argv0, opts->pidfile);
exit(EXIT_FAILURE);
}
}
if (!opts->dropuser && !geteuid() && !getuid() &&
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(DFLT_DROPUSER);
if (!opts->dropuser)
oom_die(argv0);
#ifdef __APPLE__
}
#endif /* __APPLE__ */
}
if (opts->dropuser && sys_isgeteuid(opts->dropuser)) {
if (opts->dropgroup) {
fprintf(stderr, "%s: cannot use -m when -u is "
"current user\n", argv0);
exit(EXIT_FAILURE);
}
free(opts->dropuser);
opts->dropuser = NULL;
}
/* usage checks after defaults */
if (opts->dropgroup && !opts->dropuser) {
fprintf(stderr, "%s: -m depends on -u\n", argv0);
exit(EXIT_FAILURE);
}
/* Warn about options that require per-connection privileged operations
* to be executed through privsep, but only if dropuser is set and is
* not root, because privsep will fastpath in that situation, skipping
* the latency-incurring overhead. */
int privsep_warn = 0;
if (opts->dropuser) {
if (opts->contentlog_isdir) {
log_dbg_printf("| Warning: -F requires a privileged "
"operation for each connection!\n");
privsep_warn = 1;
}
if (opts->contentlog_isspec) {
log_dbg_printf("| Warning: -S requires a privileged "
"operation for each connection!\n");
privsep_warn = 1;
}
}
if (privsep_warn) {
log_dbg_printf("| Privileged operations require communication "
"between parent and child process\n"
"| and will negatively impact latency and "
"performance on each connection.\n");
}
/* debug log, part 1 */
if (OPTS_DEBUG(opts)) {
main_version();
}
/* debug log, part 2 */
if (OPTS_DEBUG(opts)) {
log_dbg_printf("proxyspecs:\n");
for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) {
char *specstr = proxyspec_str(spec);
if (!specstr) {
fprintf(stderr, "%s: out of memory\n", argv0);
exit(EXIT_FAILURE);
}
log_dbg_printf("- %s\n", specstr);
free(specstr);
}
}
/*
* Initialize as much as possible before daemon() in order to be
* able to provide direct feedback to the user when failing.
*/
if (log_preinit(opts) == -1) {
fprintf(stderr, "%s: failed to preinit logging.\n", argv0);
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(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);
}
if (opts->pidfile && (sys_pidf_write(pidfd) == -1)) {
log_err_level_printf(LOG_CRIT, "Failed to write PID to PID file '%s': %s (%i)"
"\n", opts->pidfile, strerror(errno), errno);
return -1;
}
descriptor_table_size = getdtablesize();
/* Fork into parent monitor process and (potentially unprivileged)
* child process doing the actual work. We request 6 privsep client
* sockets: five logger threads, and the child process main thread,
* which will become the main proxy thread. First slot is main thread,
* remaining slots are passed down to log subsystem. */
int clisock[6];
if (privsep_fork(opts, clisock,
sizeof(clisock)/sizeof(clisock[0])) != 0) {
/* parent has exited the monitor loop after waiting for child,
* or an error occurred */
if (opts->pidfile) {
sys_pidf_close(pidfd, opts->pidfile);
}
goto out_parent;
}
/* child */
/* close pidfile in child */
if (opts->pidfile)
close(pidfd);
/* Initialize proxy before dropping privs */
proxy_ctx_t *proxy = proxy_new(opts, clisock[0]);
if (!proxy) {
log_err_level_printf(LOG_CRIT, "Failed to initialize proxy.\n");
exit(EXIT_FAILURE);
}
/* Drop privs, chroot */
if (sys_privdrop(opts->dropuser, opts->dropgroup,
opts->jaildir) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to drop privileges: %s (%i)\n",
strerror(errno), errno);
exit(EXIT_FAILURE);
}
log_dbg_printf("Dropped privs to user %s group %s chroot %s\n",
opts->dropuser ? opts->dropuser : "-",
opts->dropgroup ? opts->dropgroup : "-",
opts->jaildir ? opts->jaildir : "-");
/* Post-privdrop/chroot/detach initialization, thread spawning */
if (log_init(opts, proxy, &clisock[1]) == -1) {
fprintf(stderr, "%s: failed to init log facility: %s\n",
argv0, strerror(errno));
goto out_log_failed;
}
rv = EXIT_SUCCESS;
proxy_run(proxy);
proxy_free(proxy);
log_fini();
out_log_failed:
out_parent:
opts_free(opts);
return rv;
}
/* vim: set noet ft=c: */

@ -0,0 +1,743 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* Copyright (c) 2017-2019, Soner Tari <sonertari@gmail.com>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "opts.h"
#include "sys.h"
#include "log.h"
#include "defaults.h"
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/resource.h>
/*
* Handle out of memory conditions in early stages of main().
* Print error message and exit with failure status code.
* Does not return.
*/
void NORET
oom_die(const char *argv0)
{
fprintf(stderr, "%s: out of memory\n", argv0);
exit(EXIT_FAILURE);
}
opts_t *
opts_new(void)
{
opts_t *opts;
opts = malloc(sizeof(opts_t));
memset(opts, 0, sizeof(opts_t));
opts->stats_period = 1;
return opts;
}
void
opts_free(opts_t *opts)
{
if (opts->spec) {
proxyspec_free(opts->spec);
}
if (opts->dropuser) {
free(opts->dropuser);
}
if (opts->dropgroup) {
free(opts->dropgroup);
}
if (opts->jaildir) {
free(opts->jaildir);
}
if (opts->pidfile) {
free(opts->pidfile);
}
if (opts->connectlog) {
free(opts->connectlog);
}
if (opts->contentlog) {
free(opts->contentlog);
}
if (opts->contentlog_basedir) {
free(opts->contentlog_basedir);
}
memset(opts, 0, sizeof(opts_t));
free(opts);
}
/*
* Parse proxyspecs using a simple state machine.
*/
void
proxyspec_parse(int *argc, char **argv[], proxyspec_t **opts_spec)
{
proxyspec_t *spec = NULL;
int af = AF_UNSPEC;
int state = 0;
char *la, *lp;
while ((*argc)--) {
switch (state) {
default:
case 0:
spec = malloc(sizeof(proxyspec_t));
memset(spec, 0, sizeof(proxyspec_t));
spec->next = *opts_spec;
*opts_spec = spec;
// @todo IPv6?
la = **argv;
state++;
break;
case 1:
lp = **argv;
af = sys_sockaddr_parse(&spec->listen_addr,
&spec->listen_addrlen,
la, lp,
sys_get_af(la),
EVUTIL_AI_PASSIVE);
if (af == -1) {
exit(EXIT_FAILURE);
}
state = 0;
break;
}
(*argv)++;
}
if (state != 0 && state != 4) {
fprintf(stderr, "Incomplete proxyspec!\n");
exit(EXIT_FAILURE);
}
}
/*
* Clear and free a proxy spec.
*/
void
proxyspec_free(proxyspec_t *spec)
{
do {
proxyspec_t *next = spec->next;
memset(spec, 0, sizeof(proxyspec_t));
free(spec);
spec = next;
} while (spec);
}
/*
* Return text representation of proxy spec for display to the user.
* Returned string must be freed by caller.
*/
char *
proxyspec_str(proxyspec_t *spec)
{
char *s;
char *lhbuf, *lpbuf;
char *cbuf = NULL;
if (sys_sockaddr_str((struct sockaddr *)&spec->listen_addr,
spec->listen_addrlen, &lhbuf, &lpbuf) != 0) {
return NULL;
}
if (spec->connect_addrlen) {
char *chbuf, *cpbuf;
if (sys_sockaddr_str((struct sockaddr *)&spec->connect_addr,
spec->connect_addrlen,
&chbuf, &cpbuf) != 0) {
return NULL;
}
if (asprintf(&cbuf, "\nconnect= [%s]:%s", chbuf, cpbuf) < 0) {
return NULL;
}
free(chbuf);
free(cpbuf);
}
if (asprintf(&s, "listen=[%s]:%s %s", lhbuf, lpbuf, "tcp") < 0) {
s = NULL;
}
free(lhbuf);
free(lpbuf);
if (cbuf)
free(cbuf);
return s;
}
void
opts_set_user(opts_t *opts, const char *argv0, const char *optarg)
{
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);
if (!opts->dropuser)
oom_die(argv0);
#ifdef DEBUG_OPTS
log_dbg_printf("User: %s\n", opts->dropuser);
#endif /* DEBUG_OPTS */
}
void
opts_set_group(opts_t *opts, const char *argv0, const char *optarg)
{
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);
if (!opts->dropgroup)
oom_die(argv0);
#ifdef DEBUG_OPTS
log_dbg_printf("Group: %s\n", opts->dropgroup);
#endif /* DEBUG_OPTS */
}
void
opts_set_jaildir(opts_t *opts, const char *argv0, const char *optarg)
{
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 = realpath(optarg, NULL);
if (!opts->jaildir) {
fprintf(stderr, "%s: Failed to realpath '%s': %s (%i)\n",
argv0, optarg, strerror(errno), errno);
exit(EXIT_FAILURE);
}
#ifdef DEBUG_OPTS
log_dbg_printf("Chroot: %s\n", opts->jaildir);
#endif /* DEBUG_OPTS */
}
void
opts_set_pidfile(opts_t *opts, const char *argv0, const char *optarg)
{
if (opts->pidfile)
free(opts->pidfile);
opts->pidfile = strdup(optarg);
if (!opts->pidfile)
oom_die(argv0);
#ifdef DEBUG_OPTS
log_dbg_printf("PidFile: %s\n", opts->pidfile);
#endif /* DEBUG_OPTS */
}
void
opts_set_connectlog(opts_t *opts, const char *argv0, const char *optarg)
{
if (opts->connectlog)
free(opts->connectlog);
if (!(opts->connectlog = sys_realdir(optarg))) {
if (errno == ENOENT) {
fprintf(stderr, "Directory part of '%s' does not "
"exist\n", optarg);
exit(EXIT_FAILURE);
} else {
fprintf(stderr, "Failed to realpath '%s': %s (%i)\n",
optarg, strerror(errno), errno);
oom_die(argv0);
}
}
#ifdef DEBUG_OPTS
log_dbg_printf("ConnectLog: %s\n", opts->connectlog);
#endif /* DEBUG_OPTS */
}
void
opts_set_contentlog(opts_t *opts, const char *argv0, const char *optarg)
{
if (opts->contentlog)
free(opts->contentlog);
if (!(opts->contentlog = sys_realdir(optarg))) {
if (errno == ENOENT) {
fprintf(stderr, "Directory part of '%s' does not "
"exist\n", optarg);
exit(EXIT_FAILURE);
} else {
fprintf(stderr, "Failed to realpath '%s': %s (%i)\n",
optarg, strerror(errno), errno);
oom_die(argv0);
}
}
opts->contentlog_isdir = 0;
opts->contentlog_isspec = 0;
#ifdef DEBUG_OPTS
log_dbg_printf("ContentLog: %s\n", opts->contentlog);
#endif /* DEBUG_OPTS */
}
void
opts_set_contentlogdir(opts_t *opts, const char *argv0, const char *optarg)
{
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 = realpath(optarg, NULL);
if (!opts->contentlog) {
fprintf(stderr, "%s: Failed to realpath '%s': %s (%i)\n",
argv0, optarg, strerror(errno), errno);
exit(EXIT_FAILURE);
}
opts->contentlog_isdir = 1;
opts->contentlog_isspec = 0;
#ifdef DEBUG_OPTS
log_dbg_printf("ContentLogDir: %s\n", opts->contentlog);
#endif /* DEBUG_OPTS */
}
static void
opts_set_logbasedir(const char *argv0, const char *optarg,
char **basedir, char **log)
{
char *lhs, *rhs, *p, *q;
size_t n;
if (*basedir)
free(*basedir);
if (*log)
free(*log);
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);
}
*basedir = realpath(lhs, NULL);
if (!*basedir) {
fprintf(stderr, "%s: Failed to realpath '%s': %s (%i)\n",
argv0, lhs, strerror(errno), errno);
exit(EXIT_FAILURE);
}
/* count '%' in basedir */
for (n = 0, p = *basedir;
*p;
p++) {
if (*p == '%')
n++;
}
free(lhs);
n += strlen(*basedir);
if (!(lhs = malloc(n + 1)))
oom_die(argv0);
/* re-encoding % to %%, copying basedir to lhs */
for (p = *basedir, q = lhs;
*p;
p++, q++) {
*q = *p;
if (*q == '%')
*(++q) = '%';
}
*q = '\0';
/* lhs contains encoded realpathed basedir */
if (asprintf(log, "%s/%s", lhs, rhs) < 0)
oom_die(argv0);
free(lhs);
free(rhs);
}
void
opts_set_contentlogpathspec(opts_t *opts, const char *argv0, const char *optarg)
{
opts_set_logbasedir(argv0, optarg, &opts->contentlog_basedir,
&opts->contentlog);
opts->contentlog_isdir = 0;
opts->contentlog_isspec = 1;
#ifdef DEBUG_OPTS
log_dbg_printf("ContentLogPathSpec: basedir=%s, %s\n",
opts->contentlog_basedir, opts->contentlog);
#endif /* DEBUG_OPTS */
}
void
opts_set_daemon(opts_t *opts)
{
opts->detach = 1;
}
void
opts_unset_daemon(opts_t *opts)
{
opts->detach = 0;
}
void
opts_set_debug(opts_t *opts)
{
log_dbg_mode(LOG_DBG_MODE_ERRLOG);
opts->debug = 1;
}
void
opts_unset_debug(opts_t *opts)
{
log_dbg_mode(LOG_DBG_MODE_NONE);
opts->debug = 0;
}
void
opts_set_debug_level(const char *optarg)
{
// Compare strlen(s2)+1 chars to match exactly
if (strncmp(optarg, "2", 2) == 0) {
log_dbg_mode(LOG_DBG_MODE_FINE);
} else if (strncmp(optarg, "3", 2) == 0) {
log_dbg_mode(LOG_DBG_MODE_FINER);
} else if (strncmp(optarg, "4", 2) == 0) {
log_dbg_mode(LOG_DBG_MODE_FINEST);
} else {
fprintf(stderr, "Invalid DebugLevel '%s', use 2-4\n", optarg);
exit(EXIT_FAILURE);
}
#ifdef DEBUG_OPTS
log_dbg_printf("DebugLevel: %s\n", optarg);
#endif /* DEBUG_OPTS */
}
void
opts_set_statslog(opts_t *opts)
{
opts->statslog = 1;
}
void
opts_unset_statslog(opts_t *opts)
{
opts->statslog = 0;
}
static void
opts_set_open_files_limit(const char *value, int line_num)
{
unsigned int i = atoi(value);
if (i >= 50 && i <= 10000) {
struct rlimit rl;
rl.rlim_cur = i;
rl.rlim_max = i;
if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
fprintf(stderr, "Failed setting OpenFilesLimit\n");
if (errno) {
fprintf(stderr, "%s\n", strerror(errno));
}
exit(EXIT_FAILURE);
}
} else {
fprintf(stderr, "Invalid OpenFilesLimit %s at line %d, use 50-10000\n", value, line_num);
exit(EXIT_FAILURE);
}
#ifdef DEBUG_OPTS
log_dbg_printf("OpenFilesLimit: %u\n", i);
#endif /* DEBUG_OPTS */
}
static int
check_value_yesno(const char *value, const char *name, int line_num)
{
/* Compare strlen(s2)+1 chars to match exactly */
if (!strncmp(value, "yes", 4)) {
return 1;
} else if (!strncmp(value, "no", 3)) {
return 0;
}
fprintf(stderr, "Error in conf: Invalid '%s' value '%s' at line %d, use yes|no\n", name, value, line_num);
return -1;
}
#define MAX_TOKEN 10
static int
set_option(opts_t *opts, const char *argv0,
const char *name, char *value, int line_num)
{
int yes;
int retval = -1;
/* Compare strlen(s2)+1 chars to match exactly */
if (!strncmp(name, "User", 5)) {
opts_set_user(opts, argv0, value);
} else if (!strncmp(name, "Group", 6)) {
opts_set_group(opts, argv0, value);
} else if (!strncmp(name, "Chroot", 7)) {
opts_set_jaildir(opts, argv0, value);
} else if (!strncmp(name, "PidFile", 8)) {
opts_set_pidfile(opts, argv0, value);
} else if (!strncmp(name, "ConnectLog", 11)) {
opts_set_connectlog(opts, argv0, value);
} else if (!strncmp(name, "ContentLog", 11)) {
opts_set_contentlog(opts, argv0, value);
} else if (!strncmp(name, "ContentLogDir", 14)) {
opts_set_contentlogdir(opts, argv0, value);
} else if (!strncmp(name, "ContentLogPathSpec", 19)) {
opts_set_contentlogpathspec(opts, argv0, value);
} else if (!strncmp(name, "Daemon", 7)) {
yes = check_value_yesno(value, "Daemon", line_num);
if (yes == -1) {
goto leave;
}
yes ? opts_set_daemon(opts) : opts_unset_daemon(opts);
#ifdef DEBUG_OPTS
log_dbg_printf("Daemon: %u\n", opts->detach);
#endif /* DEBUG_OPTS */
} else if (!strncmp(name, "Debug", 6)) {
yes = check_value_yesno(value, "Debug", line_num);
if (yes == -1) {
goto leave;
}
yes ? opts_set_debug(opts) : opts_unset_debug(opts);
#ifdef DEBUG_OPTS
log_dbg_printf("Debug: %u\n", opts->debug);
#endif /* DEBUG_OPTS */
} else if (!strncmp(name, "DebugLevel", 11)) {
opts_set_debug_level(value);
} else if (!strncmp(name, "ProxySpec", 10)) {
/* Use MAX_TOKEN instead of computing the actual number of tokens in value */
char **argv = malloc(sizeof(char *) * MAX_TOKEN);
char **save_argv = argv;
int argc = 0;
char *p, *last = NULL;
for ((p = strtok_r(value, " ", &last));
p;
(p = strtok_r(NULL, " ", &last))) {
/* Limit max # token */
if (argc < MAX_TOKEN) {
argv[argc++] = p;
} else {
break;
}
}
proxyspec_parse(&argc, &argv, &opts->spec);
free(save_argv);
} else if (!strncasecmp(name, "LogStats", 9)) {
yes = check_value_yesno(value, "LogStats", line_num);
if (yes == -1) {
goto leave;
}
yes ? opts_set_statslog(opts) : opts_unset_statslog(opts);
#ifdef DEBUG_OPTS
log_dbg_printf("LogStats: %u\n", opts->statslog);
#endif /* DEBUG_OPTS */
} else if (!strncasecmp(name, "StatsPeriod", 12)) {
unsigned int i = atoi(value);
if (i >= 1 && i <= 10) {
opts->stats_period = i;
} else {
fprintf(stderr, "Invalid StatsPeriod %s at line %d, use 1-10\n", value, line_num);
goto leave;
}
#ifdef DEBUG_OPTS
log_dbg_printf("StatsPeriod: %u\n", opts->stats_period);
#endif /* DEBUG_OPTS */
} else if (!strncasecmp(name, "OpenFilesLimit", 15)) {
opts_set_open_files_limit(value, line_num);
} else {
#ifdef DEBUG_OPTS
log_dbg_printf("Skipping option '%s' at line %d\n", name, line_num);
#endif /* DEBUG_OPTS */
}
retval = 0;
leave:
return retval;
}
/*
* Separator param is needed for command line options only.
* Conf file option separator is ' '.
*/
static int
get_name_value(char **name, char **value, const char sep)
{
char *n, *v, *value_end;
int retval = -1;
/* Skip to the end of option name and terminate it with '\0' */
for (n = *name;; n++) {
/* White spaces possible around separator,
* if the command line option is passed between the quotes */
if (*n == ' ' || *n == '\t' || *n == sep) {
*n = '\0';
n++;
break;
}
if (*n == '\0') {
n = NULL;
break;
}
}
/* No option name */
if (n == NULL) {
fprintf(stderr, "Error in option: No option name\n");
goto leave;
}
/* White spaces possible before value and around separator,
* if the command line option is passed between the quotes */
while (*n == ' ' || *n == '\t' || *n == sep) {
n++;
}
*value = n;
/* Find end of value and terminate it with '\0'
* Find first occurrence of trailing white space */
value_end = NULL;
for (v = *value;; v++) {
if (*v == '\0') {
break;
}
if (*v == '\r' || *v == '\n') {
*v = '\0';
break;
}
if (*v == ' ' || *v == '\t') {
if (!value_end) {
value_end = v;
}
} else {
value_end = NULL;
}
}
if (value_end) {
*value_end = '\0';
}
retval = 0;
leave:
return retval;
}
int
opts_set_option(opts_t *opts, const char *argv0, const char *optarg)
{
char *name, *value;
int retval = -1;
char *line = strdup(optarg);
/* White spaces possible before option name,
* if the command line option is passed between the quotes */
for (name = line; *name == ' ' || *name == '\t'; name++);
/* Command line option separator is '=' */
retval = get_name_value(&name, &value, '=');
if (retval == 0) {
/* Line number param is for conf file, pass 0 for command line options */
retval = set_option(opts, argv0, name, value, 0);
}
if (line) {
free(line);
}
return retval;
}
int
opts_load_conffile(opts_t *opts, const char *argv0)
{
int retval, line_num;
char *line, *name, *value;
size_t line_len;
FILE *f;
f = fopen(opts->conffile, "r");
if (!f) {
fprintf(stderr, "Error opening conf file '%s': %s\n", opts->conffile, strerror(errno));
return -1;
}
line = NULL;
line_num = 0;
retval = -1;
while (!feof(f)) {
if (getline(&line, &line_len, f) == -1) {
break;
}
if (line == NULL) {
fprintf(stderr, "Error in conf file: getline() returns NULL line after line %d\n", line_num);
goto leave;
}
line_num++;
/* Skip white space */
for (name = line; *name == ' ' || *name == '\t'; name++);
/* Skip comments and empty lines */
if ((name[0] == '\0') || (name[0] == '#') || (name[0] == ';') ||
(name[0] == '\r') || (name[0] == '\n')) {
continue;
}
retval = get_name_value(&name, &value, ' ');
if (retval == 0) {
retval = set_option(opts, argv0, name, value, line_num);
}
if (retval == -1) {
goto leave;
}
}
leave:
fclose(f);
if (line) {
free(line);
}
return retval;
}
/* vim: set noet ft=c: */

@ -0,0 +1,105 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 OPTS_H
#define OPTS_H
#include "attrib.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sqlite3.h>
/*
* Print helper for logging code.
*/
#define STRORDASH(x) (((x)&&*(x))?(x):"-")
#define STRORNONE(x) (((x)&&*(x))?(x):"")
typedef struct proxyspec {
struct sockaddr_storage listen_addr;
socklen_t listen_addrlen;
/* connect_addr and connect_addrlen are set: static mode;
* natlookup is set: NAT mode; natsocket /may/ be set too;
* sni_port is set, in which case we use SNI lookups */
struct sockaddr_storage connect_addr;
socklen_t connect_addrlen;
struct proxyspec *next;
} proxyspec_t;
typedef struct opts {
unsigned int debug : 1;
unsigned int detach : 1;
unsigned int contentlog_isdir : 1;
unsigned int contentlog_isspec : 1;
char *dropuser;
char *dropgroup;
char *jaildir;
char *pidfile;
char *conffile;
char *connectlog;
char *contentlog;
char *contentlog_basedir; /* static part of logspec for privsep srv */
proxyspec_t *spec;
unsigned int stats_period;
unsigned int statslog: 1;
unsigned int log_stats: 1;
} opts_t;
void NORET oom_die(const char *) NONNULL(1);
opts_t *opts_new(void) MALLOC;
void opts_free(opts_t *) NONNULL(1);
void opts_proto_dbg_dump(opts_t *) NONNULL(1);
#define OPTS_DEBUG(opts) unlikely((opts)->debug)
void proxyspec_parse(int *, char **[], proxyspec_t **);
void proxyspec_free(proxyspec_t *) NONNULL(1);
char *proxyspec_str(proxyspec_t *) NONNULL(1) MALLOC;
void opts_set_user(opts_t *, const char *, const char *) NONNULL(1,2,3);
void opts_set_group(opts_t *, const char *, const char *) NONNULL(1,2,3);
void opts_set_jaildir(opts_t *, const char *, const char *) NONNULL(1,2,3);
void opts_set_pidfile(opts_t *, const char *, const char *) NONNULL(1,2,3);
void opts_set_connectlog(opts_t *, const char *, const char *) NONNULL(1,2,3);
void opts_set_contentlog(opts_t *, const char *, const char *) NONNULL(1,2,3);
void opts_set_contentlogdir(opts_t *, const char *, const char *)
NONNULL(1,2,3);
void opts_set_contentlogpathspec(opts_t *, const char *, const char *)
NONNULL(1,2,3);
void opts_set_daemon(opts_t *) NONNULL(1);
void opts_set_debug(opts_t *) NONNULL(1);
void opts_set_debug_level(const char *) NONNULL(1);
void opts_set_statslog(opts_t *) NONNULL(1);
int opts_set_option(opts_t *, const char *, const char *)
NONNULL(1,2,3);
int opts_load_conffile(opts_t *, const char *) NONNULL(1,2);
#endif /* !OPTS_H */
/* vim: set noet ft=c: */

@ -0,0 +1,896 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "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 */
/* Whether we short-circuit calls to privsep_client_* directly to
* privsep_server_* within the client process, bypassing the privilege
* separation mechanism; this is a performance optimization for use cases
* where the user chooses performance over security, especially with options
* that require privsep operations for each connection passing through.
* In the current implementation, for consistency, we still fork normally, but
* will not actually send any privsep requests to the parent process. */
static int privsep_fastpath;
/* communication with signal handler */
static volatile sig_atomic_t received_sighup;
static volatile sig_atomic_t received_sigint;
static volatile sig_atomic_t received_sigquit;
static volatile sig_atomic_t received_sigterm;
static volatile sig_atomic_t received_sigchld;
static volatile sig_atomic_t received_sigusr1;
/* write end of pipe used for unblocking select */
static volatile sig_atomic_t selfpipe_wrfd;
static void
privsep_server_signal_handler(int sig)
{
int saved_errno;
saved_errno = errno;
#ifdef DEBUG_PRIVSEP_SERVER
log_dbg_printf("privsep_server_signal_handler\n");
#endif /* DEBUG_PRIVSEP_SERVER */
switch (sig) {
case SIGHUP:
received_sighup = 1;
break;
case SIGINT:
received_sigint = 1;
break;
case SIGQUIT:
received_sigquit = 1;
break;
case SIGTERM:
received_sigterm = 1;
break;
case SIGCHLD:
received_sigchld = 1;
break;
case SIGUSR1:
received_sigusr1 = 1;
break;
}
if (selfpipe_wrfd != -1) {
ssize_t n;
#ifdef DEBUG_PRIVSEP_SERVER
log_dbg_printf("writing to selfpipe_wrfd %i\n", selfpipe_wrfd);
#endif /* DEBUG_PRIVSEP_SERVER */
do {
n = write(selfpipe_wrfd, "!", 1);
} while (n == -1 && errno == EINTR);
if (n == -1) {
log_err_level_printf(LOG_CRIT, "Failed to write from signal handler: "
"%s (%i)\n", strerror(errno), errno);
/* ignore error */
}
#ifdef DEBUG_PRIVSEP_SERVER
} else {
log_dbg_printf("selfpipe_wrfd is %i - not writing\n", selfpipe_wrfd);
#endif /* DEBUG_PRIVSEP_SERVER */
}
errno = saved_errno;
}
static int WUNRES
privsep_server_openfile_verify(opts_t *opts, const char *fn, UNUSED int mkpath)
{
/* Prefix must match one of the active log files that use privsep. */
do {
if (opts->contentlog) {
if (strstr(fn, opts->contentlog_isspec
? opts->contentlog_basedir
: opts->contentlog) == fn)
break;
}
if (opts->connectlog) {
if (strstr(fn, opts->connectlog) == fn)
break;
}
return -1;
} while (0);
/* Path must not contain dot-dot to prevent escaping the prefix. */
if (strstr(fn, "/../"))
return -1;
return 0;
}
static int WUNRES
privsep_server_openfile(const char *fn, int mkpath)
{
int fd, tmp;
if (mkpath) {
char *filedir, *fn2;
fn2 = strdup(fn);
if (!fn2) {
tmp = errno;
log_err_level_printf(LOG_CRIT, "Could not duplicate filname: %s (%i)\n",
strerror(errno), errno);
errno = tmp;
return -1;
}
filedir = dirname(fn2);
if (!filedir) {
tmp = errno;
log_err_level_printf(LOG_CRIT, "Could not get dirname: %s (%i)\n",
strerror(errno), errno);
free(fn2);
errno = tmp;
return -1;
}
if (sys_mkpath(filedir, DFLT_DIRMODE) == -1) {
tmp = errno;
log_err_level_printf(LOG_CRIT, "Could not create directory '%s': %s (%i)\n",
filedir, strerror(errno), errno);
free(fn2);
errno = tmp;
return -1;
}
free(fn2);
}
fd = open(fn, O_RDWR|O_CREAT, DFLT_FILEMODE);
if (fd == -1) {
tmp = errno;
log_err_level_printf(LOG_CRIT, "Failed to open '%s': %s (%i)\n",
fn, strerror(errno), errno);
errno = tmp;
return -1;
}
if (lseek(fd, 0, SEEK_END) == -1) {
tmp = errno;
log_err_level_printf(LOG_CRIT, "Failed to seek on '%s': %s (%i)\n",
fn, strerror(errno), errno);
errno = tmp;
return -1;
}
return fd;
}
static int WUNRES
privsep_server_opensock_verify(opts_t *opts, void *arg)
{
/* This check is safe, because modifications of the spec in the child
* process do not affect the copy of the spec here in the parent. */
for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) {
if (spec == arg)
return 0;
}
return 1;
}
static int WUNRES
privsep_server_opensock(const 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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "Error from setsockopt(SO_REUSABLE): %s\n",
strerror(errno));
evutil_closesocket(fd);
return -1;
}
rv = bind(fd, (struct sockaddr *)&spec->listen_addr,
spec->listen_addrlen);
if (rv == -1) {
log_err_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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;
/* fall through */
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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "Sending message failed: %s (%i"
")\n", strerror(errno), errno);
return -1;
}
}
return 0;
}
#define util_max(a,b) ((a) > (b) ? (a) : (b))
/*
* 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;
#ifdef DEBUG_PRIVSEP_SERVER
log_dbg_printf("privsep_server select()\n");
#endif /* DEBUG_PRIVSEP_SERVER */
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);
#ifdef DEBUG_PRIVSEP_SERVER
log_dbg_printf("privsep_server woke up (1)\n");
#endif /* DEBUG_PRIVSEP_SERVER */
} while (rv == -1 && errno == EINTR);
if (rv == -1) {
log_err_level_printf(LOG_CRIT, "select() failed: %s (%i)\n",
strerror(errno), errno);
return -1;
}
#ifdef DEBUG_PRIVSEP_SERVER
log_dbg_printf("privsep_server woke up (2)\n");
#endif /* DEBUG_PRIVSEP_SERVER */
if (FD_ISSET(sigpipe, &readfds)) {
char buf[16];
ssize_t n;
/* first drain the signal pipe, then deal with
* all the individual signal flags */
n = read(sigpipe, buf, sizeof(buf));
if (n == -1) {
log_err_level_printf(LOG_CRIT, "read(sigpipe) failed:"
" %s (%i)\n",
strerror(errno), errno);
return -1;
}
if (received_sigquit) {
if (kill(childpid, SIGQUIT) == -1) {
log_err_level_printf(LOG_CRIT, "kill(%i,SIGQUIT) "
"failed: %s (%i)\n",
childpid,
strerror(errno), errno);
}
received_sigquit = 0;
}
if (received_sigterm) {
if (kill(childpid, SIGTERM) == -1) {
log_err_level_printf(LOG_CRIT, "kill(%i,SIGTERM) "
"failed: %s (%i)\n",
childpid,
strerror(errno), errno);
}
received_sigterm = 0;
}
if (received_sighup) {
if (kill(childpid, SIGHUP) == -1) {
log_err_level_printf(LOG_CRIT, "kill(%i,SIGHUP) "
"failed: %s (%i)\n",
childpid,
strerror(errno), errno);
}
received_sighup = 0;
}
if (received_sigusr1) {
if (kill(childpid, SIGUSR1) == -1) {
log_err_level_printf(LOG_CRIT, "kill(%i,SIGUSR1) "
"failed: %s (%i)\n",
childpid,
strerror(errno), errno);
}
received_sigusr1 = 0;
}
if (received_sigint) {
/* if we don't detach from the TTY, the
* child process receives SIGINT directly */
if (opts->detach) {
if (kill(childpid, SIGINT) == -1) {
log_err_level_printf(LOG_CRIT, "kill(%i,SIGINT"
") failed: "
"%s (%i)\n",
childpid,
strerror(errno),
errno);
}
}
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_level_printf(LOG_CRIT, "Failed to handle "
"privsep req "
"on srvsock %i\n",
srvsock[i]);
return -1;
}
if (rv == 1) {
#ifdef DEBUG_PRIVSEP_SERVER
log_dbg_printf("srveof[%zu]=1\n", i);
#endif /* DEBUG_PRIVSEP_SERVER */
srveof[i] = 1;
}
}
}
/*
* We cannot exit as long as we need the signal handling,
* which is as long as the child process is running.
* The only way out of here is receiving SIGCHLD.
*/
}
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;
if (privsep_fastpath)
return privsep_server_openfile(fn, mkpath);
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:
errno = EINVAL;
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;
if (privsep_fastpath)
return privsep_server_opensock(spec);
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:
errno = EINVAL;
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;
if (!opts->dropuser) {
log_dbg_printf("Privsep fastpath enabled\n");
privsep_fastpath = 1;
} else {
log_dbg_printf("Privsep fastpath disabled\n");
privsep_fastpath = 0;
}
received_sigquit = 0;
received_sighup = 0;
received_sigint = 0;
received_sigchld = 0;
received_sigusr1 = 0;
if (pipe(selfpipev) == -1) {
log_err_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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]);
}
log_dbg_printf("Privsep parent pid %i\n", getpid());
pid = fork();
if (pid == -1) {
log_err_level_printf(LOG_CRIT, "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]);
log_dbg_printf("Privsep child pid %i\n", getpid());
/* 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 */
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_level_printf(LOG_CRIT, "Failed to install SIGHUP handler: %s (%i)\n",
strerror(errno), errno);
return -1;
}
if (signal(SIGINT, privsep_server_signal_handler) == SIG_ERR) {
log_err_level_printf(LOG_CRIT, "Failed to install SIGINT handler: %s (%i)\n",
strerror(errno), errno);
return -1;
}
if (signal(SIGTERM, privsep_server_signal_handler) == SIG_ERR) {
log_err_level_printf(LOG_CRIT, "Failed to install SIGTERM handler: %s (%i)\n",
strerror(errno), errno);
return -1;
}
if (signal(SIGQUIT, privsep_server_signal_handler) == SIG_ERR) {
log_err_level_printf(LOG_CRIT, "Failed to install SIGQUIT handler: %s (%i)\n",
strerror(errno), errno);
return -1;
}
if (signal(SIGUSR1, privsep_server_signal_handler) == SIG_ERR) {
log_err_level_printf(LOG_CRIT, "Failed to install SIGUSR1 handler: %s (%i)\n",
strerror(errno), errno);
return -1;
}
if (signal(SIGCHLD, privsep_server_signal_handler) == SIG_ERR) {
log_err_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "Privsep server failed: %s (%i)\n",
strerror(errno), errno);
/* fall through */
}
#ifdef DEBUG_PRIVSEP_SERVER
log_dbg_printf("privsep_server exited\n");
#endif /* DEBUG_PRIVSEP_SERVER */
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_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "Child proc %lld killed by signal %d\n",
(long long)pid, WTERMSIG(status));
} else {
log_err_level_printf(LOG_CRIT, "Child proc %lld neither exited nor killed\n",
(long long)pid);
}
return 1;
}
/* vim: set noet ft=c: */

@ -0,0 +1,42 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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: */

@ -0,0 +1,552 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* Copyright (c) 2017-2019, Soner Tari <sonertari@gmail.com>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "prototcp.h"
#include "sys.h"
#include <sys/param.h>
#include <string.h>
/*
* Set up a bufferevent structure for either a dst or src connection,
* optionally with or without SSL. Sets all callbacks, enables read
* and write events, but does not call bufferevent_socket_connect().
*
* For dst connections, pass -1 as fd. Pass a pointer to an initialized
* SSL struct as ssl if the connection should use SSL.
*
* Returns pointer to initialized bufferevent structure, as returned
* by bufferevent_socket_new() or bufferevent_openssl_socket_new().
*/
static struct bufferevent * NONNULL(1)
prototcp_bufferevent_setup(pxy_conn_ctx_t *ctx, evutil_socket_t fd)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bufferevent_setup: ENTER, fd=%d\n", fd);
#endif /* DEBUG_PROXY */
// @todo Do we really need to defer callbacks? BEV_OPT_DEFER_CALLBACKS seems responsible for the issue with dst: We get writecb sometimes, no eventcb for CONNECTED event
struct bufferevent *bev = bufferevent_socket_new(ctx->evbase, fd, BEV_OPT_DEFER_CALLBACKS|BEV_OPT_THREADSAFE);
if (!bev) {
log_err_level_printf(LOG_CRIT, "Error creating bufferevent socket\n");
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "prototcp_bufferevent_setup: bufferevent_socket_connect failed, fd=%d\n", fd);
#endif /* DEBUG_PROXY */
return NULL;
}
return bev;
}
/*
* Free bufferenvent and close underlying socket properly.
*/
static void
prototcp_bufferevent_free_and_close_fd(struct bufferevent *bev, UNUSED pxy_conn_ctx_t *ctx)
{
evutil_socket_t fd = bufferevent_getfd(bev);
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINER, "prototcp_bufferevent_free_and_close_fd: in=%zu, out=%zu, fd=%d\n",
evbuffer_get_length(bufferevent_get_input(bev)), evbuffer_get_length(bufferevent_get_output(bev)), fd);
#endif /* DEBUG_PROXY */
bufferevent_free(bev);
evutil_closesocket(fd);
}
static int NONNULL(1)
prototcp_setup_src(pxy_conn_ctx_t *ctx)
{
ctx->src.bev = prototcp_bufferevent_setup(ctx, ctx->fd);
if (!ctx->src.bev) {
log_err_level_printf(LOG_CRIT, "Error creating src bufferevent\n");
pxy_conn_term(ctx, 1);
return -1;
}
ctx->src.free = prototcp_bufferevent_free_and_close_fd;
return 0;
}
static int NONNULL(1)
prototcp_setup_dst(pxy_conn_ctx_t *ctx)
{
ctx->dst.bev = prototcp_bufferevent_setup(ctx, -1);
if (!ctx->dst.bev) {
log_err_level_printf(LOG_CRIT, "Error creating parent dst\n");
pxy_conn_term(ctx, 1);
return -1;
}
ctx->dst.free = prototcp_bufferevent_free_and_close_fd;
return 0;
}
static int NONNULL(1) WUNRES
prototcp_conn_connect(pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_conn_connect: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (prototcp_setup_src(ctx) == -1) {
return -1;
}
if (prototcp_setup_dst(ctx) == -1) {
return -1;
}
// Conn setup is successful, so add the conn to the conn list of its thread now
pxy_thrmgr_add_conn(ctx);
bufferevent_setcb(ctx->src.bev, pxy_bev_readcb, pxy_bev_writecb, pxy_bev_eventcb, ctx);
bufferevent_setcb(ctx->dst.bev, pxy_bev_readcb, pxy_bev_writecb, pxy_bev_eventcb, ctx);
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINER, "prototcp_conn_connect: Enabling src, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
// Now open the gates
bufferevent_enable(ctx->src.bev, EV_READ|EV_WRITE);
return 0;
}
static void
prototcp_fd_readcb(UNUSED evutil_socket_t fd, UNUSED short what, void *arg)
{
pxy_conn_ctx_t *ctx = arg;
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_fd_readcb: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
pxy_conn_connect(ctx);
}
static int
prototcp_parse_sslproxy_line(char *line, pxy_conn_ctx_t *ctx)
{
#define MAX_IPADDR_LEN 45
#define MAX_PORT_LEN 5
// SSLproxy: [127.0.0.1]:34649,[192.168.3.24]:47286,[74.125.206.108]:465,s,soner
if (!strncasecmp(line, "SSLproxy:", 9)) {
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "%s\n", line);
char *ip_start = strchr(line, '[') + 1;
char *ip_end = strchr(ip_start, ']');
char *port_start = strchr(ip_end, ':') + 1;
char *port_end = strchr(port_start, ',');
if (!ip_start || !ip_end || !port_start || !port_end) {
log_err_level_printf(LOG_ERR, "Unable to find sslproxy addr: %s", line);
return -1;
}
int addr_len = ip_end - ip_start;
if (addr_len > MAX_IPADDR_LEN) {
log_err_level_printf(LOG_ERR, "sslproxy addr_len larger than MAX_IPADDR_LEN: %d\n", addr_len);
return -1;
}
char addr[MAX_IPADDR_LEN + 1];
strncpy(addr, ip_start, addr_len);
addr[addr_len] = '\0';
int port_len = port_end - port_start;
if (port_len > MAX_PORT_LEN) {
log_err_level_printf(LOG_ERR, "sslproxy port_len larger than MAX_PORT_LEN: %d\n", port_len);
return -1;
}
char port[MAX_PORT_LEN + 1];
strncpy(port, port_start, port_len);
port[port_len] = '\0';
if (sys_sockaddr_parse(&ctx->dstaddr,
&ctx->dstaddrlen,
addr, port,
sys_get_af(addr),
EVUTIL_AI_PASSIVE) == -1) {
log_err_level_printf(LOG_ERR, "Cannot convert sslproxy addr to sockaddr: [%s]:%s\n", addr, port);
return -1;
}
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("Connecting to [%s]:%s\n", addr, port);
ctx->dsthost_str = strdup(addr);
ctx->dstport_str = strdup(port);
if (!ctx->dsthost_str || !ctx->dstport_str) {
log_err_level_printf(LOG_ERR, "Cannot dup addr or port: [%s]:%s\n", addr, port);
return -1;
}
}
ctx->seen_sslproxy_line = 1;
}
return 0;
}
static void NONNULL(1,2)
prototcp_bev_readcb_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_readcb_src: ENTER, size=%zu, fd=%d\n",
evbuffer_get_length(bufferevent_get_input(bev)), ctx->fd);
#endif /* DEBUG_PROXY */
struct evbuffer *inbuf = bufferevent_get_input(bev);
struct evbuffer *outbuf = bufferevent_get_output(ctx->dst.bev);
if (!ctx->seen_sslproxy_line) {
char *line;
while (!ctx->seen_sslproxy_line && (line = evbuffer_readln(inbuf, NULL, EVBUFFER_EOL_CRLF))) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_readcb_src: %s, fd=%d\n", line, ctx->fd);
#endif /* DEBUG_PROXY */
if (prototcp_parse_sslproxy_line(line, ctx) == -1) {
free(line);
pxy_conn_term(ctx, 1);
return;
}
if (ctx->seen_sslproxy_line) {
/* initiate connection */
if (bufferevent_socket_connect(ctx->dst.bev, (struct sockaddr *)&ctx->dstaddr, ctx->dstaddrlen) == -1) {
log_err_level_printf(LOG_CRIT, "prototcp_bev_readcb_src: bufferevent_socket_connect for dst failed\n");
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "prototcp_bev_readcb_src: bufferevent_socket_connect for dst failed, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
}
}
evbuffer_add_printf(outbuf, "%s\r\n", line);
free(line);
}
if (evbuffer_get_length(inbuf) == 0) {
goto out;
}
} else {
if (ctx->dst.closed) {
pxy_discard_inbuf(bev);
return;
}
}
evbuffer_add_buffer(outbuf, inbuf);
out:
pxy_try_set_watermark(bev, ctx, ctx->dst.bev);
}
static void NONNULL(1)
prototcp_bev_readcb_dst(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_readcb_dst: ENTER, size=%zu, fd=%d\n",
evbuffer_get_length(bufferevent_get_input(bev)), ctx->fd);
#endif /* DEBUG_PROXY */
if (!ctx->dst_connected) {
log_err_level_printf(LOG_CRIT, "prototcp_bev_readcb_dst: readcb called when not connected - aborting.\n");
log_exceptcb();
return;
}
if (ctx->src.closed) {
pxy_discard_inbuf(bev);
return;
}
struct evbuffer *inbuf = bufferevent_get_input(bev);
struct evbuffer *outbuf = bufferevent_get_output(ctx->src.bev);
evbuffer_add_buffer(outbuf, inbuf);
pxy_try_set_watermark(bev, ctx, ctx->src.bev);
}
static int NONNULL(1,2)
prototcp_connect_conn_end(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "prototcp_connect_conn_end: writecb before connected, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
// @attention Sometimes write cb fires but not event cb, especially if the listener cb is not finished yet, so the conn stalls.
// This is a workaround for this error condition, nothing else seems to work.
// @attention Do not try to free the conn here, since the listener cb may not be finished yet, which causes multithreading issues
// XXX: Workaround, should find the real cause: BEV_OPT_DEFER_CALLBACKS?
ctx->protoctx->bev_eventcb(bev, BEV_EVENT_CONNECTED, ctx);
return pxy_bev_eventcb_postexec_logging_and_stats(bev, BEV_EVENT_CONNECTED, ctx);
}
static void NONNULL(1)
prototcp_bev_writecb_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_writecb_src: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (!ctx->src_connected) {
if (prototcp_connect_conn_end(bev, ctx) == -1) {
return;
}
}
if (ctx->dst.closed) {
if (pxy_try_close_conn_end(&ctx->src, ctx) == 1) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_writecb_src: other->closed, terminate conn, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
pxy_conn_term(ctx, 1);
}
return;
}
pxy_try_unset_watermark(bev, ctx, &ctx->dst);
}
static void NONNULL(1)
prototcp_bev_writecb_dst(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_writecb_dst: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (!ctx->dst_connected) {
if (prototcp_connect_conn_end(bev, ctx) == -1) {
return;
}
}
if (ctx->src.closed) {
if (pxy_try_close_conn_end(&ctx->dst, ctx) == 1) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_writecb_dst: other->closed, terminate conn, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
pxy_conn_term(ctx, 0);
}
return;
}
pxy_try_unset_watermark(bev, ctx, &ctx->src);
}
static void NONNULL(1,2)
prototcp_bev_eventcb_connected_src(UNUSED struct bufferevent *bev, UNUSED pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_eventcb_connected_src: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
ctx->src_connected = 1;
}
static void NONNULL(1,2)
prototcp_bev_eventcb_connected_dst(UNUSED struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_eventcb_connected_dst: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
ctx->dst_connected = 1;
}
static void NONNULL(1,2)
prototcp_bev_eventcb_eof_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_eventcb_eof_src: ENTER, fd=%d\n", ctx->fd);
pxy_log_dbg_evbuf_info(ctx, &ctx->src, &ctx->dst);
#endif /* DEBUG_PROXY */
if (!ctx->src_connected) {
log_err_level_printf(LOG_WARNING, "EOF on connection before connection establishment\n");
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "prototcp_bev_eventcb_eof_src: EOF on connection before connection establishment, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
ctx->dst.closed = 1;
} else if (!ctx->dst.closed) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_eventcb_eof_src: !other->closed, terminate conn, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (pxy_try_consume_last_input(bev, ctx) == -1) {
return;
}
pxy_try_close_conn_end(&ctx->dst, ctx);
}
pxy_try_disconnect(ctx, &ctx->src, &ctx->dst, 1);
}
static void NONNULL(1,2)
prototcp_bev_eventcb_eof_dst(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_eventcb_eof_dst: ENTER, fd=%d\n", ctx->fd);
pxy_log_dbg_evbuf_info(ctx, &ctx->dst, &ctx->src);
#endif /* DEBUG_PROXY */
if (!ctx->dst_connected) {
log_err_level_printf(LOG_WARNING, "EOF on connection before connection establishment\n");
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "prototcp_bev_eventcb_eof_dst: EOF on connection before connection establishment, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
ctx->src.closed = 1;
} else if (!ctx->src.closed) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "prototcp_bev_eventcb_eof_dst: !other->closed, terminate conn, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (pxy_try_consume_last_input(bev, ctx) == -1) {
return;
}
pxy_try_close_conn_end(&ctx->src, ctx);
}
pxy_try_disconnect(ctx, &ctx->dst, &ctx->src, 0);
}
static void NONNULL(1,2)
prototcp_bev_eventcb_error_src(UNUSED struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "prototcp_bev_eventcb_error_src: BEV_EVENT_ERROR, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (!ctx->src_connected) {
ctx->dst.closed = 1;
} else if (!ctx->dst.closed) {
pxy_try_close_conn_end(&ctx->dst, ctx);
}
pxy_try_disconnect(ctx, &ctx->src, &ctx->dst, 1);
}
static void NONNULL(1,2)
prototcp_bev_eventcb_error_dst(UNUSED struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "prototcp_bev_eventcb_error_dst: BEV_EVENT_ERROR, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (!ctx->dst_connected) {
ctx->src.closed = 1;
} else if (!ctx->src.closed) {
pxy_try_close_conn_end(&ctx->src, ctx);
}
pxy_try_disconnect(ctx, &ctx->dst, &ctx->src, 0);
}
static void NONNULL(1,3)
prototcp_bev_eventcb_src(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx)
{
if (events & BEV_EVENT_CONNECTED) {
prototcp_bev_eventcb_connected_src(bev, ctx);
} else if (events & BEV_EVENT_EOF) {
prototcp_bev_eventcb_eof_src(bev, ctx);
} else if (events & BEV_EVENT_ERROR) {
prototcp_bev_eventcb_error_src(bev, ctx);
}
}
static void NONNULL(1)
prototcp_bev_eventcb_dst(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx)
{
if (events & BEV_EVENT_CONNECTED) {
prototcp_bev_eventcb_connected_dst(bev, ctx);
} else if (events & BEV_EVENT_EOF) {
prototcp_bev_eventcb_eof_dst(bev, ctx);
} else if (events & BEV_EVENT_ERROR) {
prototcp_bev_eventcb_error_dst(bev, ctx);
}
}
static void NONNULL(1)
prototcp_bev_readcb(struct bufferevent *bev, void *arg)
{
pxy_conn_ctx_t *ctx = arg;
if (bev == ctx->src.bev) {
prototcp_bev_readcb_src(bev, ctx);
} else if (bev == ctx->dst.bev) {
prototcp_bev_readcb_dst(bev, ctx);
} else {
log_err_printf("prototcp_bev_readcb: UNKWN conn end\n");
}
}
static void NONNULL(1)
prototcp_bev_writecb(struct bufferevent *bev, void *arg)
{
pxy_conn_ctx_t *ctx = arg;
if (bev == ctx->src.bev) {
prototcp_bev_writecb_src(bev, ctx);
} else if (bev == ctx->dst.bev) {
prototcp_bev_writecb_dst(bev, ctx);
} else {
log_err_printf("prototcp_bev_writecb: UNKWN conn end\n");
}
}
static void NONNULL(1)
prototcp_bev_eventcb(struct bufferevent *bev, short events, void *arg)
{
pxy_conn_ctx_t *ctx = arg;
if (bev == ctx->src.bev) {
prototcp_bev_eventcb_src(bev, events, ctx);
} else if (bev == ctx->dst.bev) {
prototcp_bev_eventcb_dst(bev, events, ctx);
} else {
log_err_printf("prototcp_bev_eventcb: UNKWN conn end\n");
}
}
protocol_t
prototcp_setup(pxy_conn_ctx_t *ctx)
{
ctx->protoctx->proto = PROTO_TCP;
ctx->protoctx->connectcb = prototcp_conn_connect;
ctx->protoctx->fd_readcb = prototcp_fd_readcb;
ctx->protoctx->bev_readcb = prototcp_bev_readcb;
ctx->protoctx->bev_writecb = prototcp_bev_writecb;
ctx->protoctx->bev_eventcb = prototcp_bev_eventcb;
return PROTO_TCP;
}
/* vim: set noet ft=c: */

@ -0,0 +1,38 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* Copyright (c) 2017-2019, Soner Tari <sonertari@gmail.com>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 PROTOTCP_H
#define PROTOTCP_H
#include "pxyconn.h"
protocol_t prototcp_setup(pxy_conn_ctx_t *) NONNULL(1);
#endif /* PROTOTCP_H */

@ -0,0 +1,383 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "proxy.h"
#include "privsep.h"
#include "pxythrmgr.h"
#include "pxyconn.h"
#include "opts.h"
#include "log.h"
#include "attrib.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/bufferevent_ssl.h>
#include <event2/buffer.h>
#include <event2/thread.h>
/*
* Proxy engine, built around libevent 2.x.
*/
static int signals[] = { SIGTERM, SIGQUIT, SIGHUP, SIGINT, SIGPIPE, SIGUSR1 };
struct proxy_ctx {
pxy_thrmgr_ctx_t *thrmgr;
struct event_base *evbase;
struct event *sev[sizeof(signals)/sizeof(int)];
struct proxy_listener_ctx *lctx;
opts_t *opts;
};
static proxy_listener_ctx_t *
proxy_listener_ctx_new(pxy_thrmgr_ctx_t *thrmgr, proxyspec_t *spec,
opts_t *opts) MALLOC;
static proxy_listener_ctx_t *
proxy_listener_ctx_new(pxy_thrmgr_ctx_t *thrmgr, proxyspec_t *spec,
opts_t *opts)
{
proxy_listener_ctx_t *ctx = malloc(sizeof(proxy_listener_ctx_t));
if (!ctx)
return NULL;
memset(ctx, 0, sizeof(proxy_listener_ctx_t));
ctx->thrmgr = thrmgr;
ctx->spec = spec;
ctx->opts = opts;
return ctx;
}
static void
proxy_listener_ctx_free(proxy_listener_ctx_t *ctx) NONNULL(1);
static void
proxy_listener_ctx_free(proxy_listener_ctx_t *ctx)
{
if (ctx->evcl) {
evconnlistener_free(ctx->evcl);
}
if (ctx->next) {
proxy_listener_ctx_free(ctx->next);
}
free(ctx);
}
/*
* Callback for accept events on the socket listener bufferevent.
*/
static void
proxy_listener_acceptcb(UNUSED struct evconnlistener *listener,
evutil_socket_t fd,
struct sockaddr *peeraddr, int peeraddrlen,
void *arg)
{
proxy_listener_ctx_t *lctx = arg;
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "proxy_listener_acceptcb: ENTER, fd=%d\n", fd);
#endif /* DEBUG_PROXY */
pxy_conn_setup(fd, peeraddr, peeraddrlen, lctx->thrmgr, lctx->opts);
}
/*
* Callback for error events on the socket listener bufferevent.
*/
void
proxy_listener_errorcb(struct evconnlistener *listener, UNUSED void *arg)
{
struct event_base *evbase = evconnlistener_get_base(listener);
int err = EVUTIL_SOCKET_ERROR();
log_err_level_printf(LOG_CRIT, "Error %d on listener: %s\n", err,
evutil_socket_error_to_string(err));
/* Do not break the event loop if out of fds:
* Too many open files (24) */
if (err == 24) {
return;
}
event_base_loopbreak(evbase);
}
/*
* Dump a description of an evbase to debugging code.
*/
static void
proxy_debug_base(const struct event_base *ev_base)
{
log_dbg_printf("Using libevent backend '%s'\n",
event_base_get_method(ev_base));
enum event_method_feature f;
f = event_base_get_features(ev_base);
log_dbg_printf("Event base supports: edge %s, O(1) %s, anyfd %s\n",
((f & EV_FEATURE_ET) ? "yes" : "no"),
((f & EV_FEATURE_O1) ? "yes" : "no"),
((f & EV_FEATURE_FDS) ? "yes" : "no"));
}
/*
* Set up the listener for a single proxyspec and add it to evbase.
* Returns the proxy_listener_ctx_t pointer if successful, NULL otherwise.
*/
static proxy_listener_ctx_t *
proxy_listener_setup(struct event_base *evbase, pxy_thrmgr_ctx_t *thrmgr,
proxyspec_t *spec, opts_t *opts, evutil_socket_t clisock)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "proxy_listener_setup: ENTER\n");
#endif /* DEBUG_PROXY */
proxy_listener_ctx_t *lctx;
int fd;
if ((fd = privsep_client_opensock(clisock, spec)) == -1) {
log_err_level_printf(LOG_CRIT, "Error opening socket: %s (%i)\n",
strerror(errno), errno);
return NULL;
}
lctx = proxy_listener_ctx_new(thrmgr, spec, opts);
if (!lctx) {
log_err_level_printf(LOG_CRIT, "Error creating listener context\n");
evutil_closesocket(fd);
return NULL;
}
// @todo Should we enable threadsafe event structs?
// @attention Do not pass NULL as user-supplied pointer
lctx->evcl = evconnlistener_new(evbase, proxy_listener_acceptcb,
lctx, LEV_OPT_CLOSE_ON_FREE, 1024, fd);
if (!lctx->evcl) {
log_err_level_printf(LOG_CRIT, "Error creating evconnlistener: %s\n",
strerror(errno));
proxy_listener_ctx_free(lctx);
evutil_closesocket(fd);
return NULL;
}
evconnlistener_set_error_cb(lctx->evcl, proxy_listener_errorcb);
return lctx;
}
/*
* Signal handler for SIGTERM, SIGQUIT, SIGINT, SIGHUP, SIGPIPE and SIGUSR1.
*/
static void
proxy_signal_cb(evutil_socket_t fd, UNUSED short what, void *arg)
{
proxy_ctx_t *ctx = arg;
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("Received signal %i\n", fd);
}
switch(fd) {
case SIGTERM:
case SIGQUIT:
case SIGINT:
proxy_loopbreak(ctx);
break;
case SIGHUP:
case SIGUSR1:
if (log_reopen() == -1) {
log_err_level_printf(LOG_WARNING, "Failed to reopen logs\n");
} else {
log_dbg_printf("Reopened log files\n");
}
break;
case SIGPIPE:
log_err_level_printf(LOG_WARNING, "Received SIGPIPE; ignoring.\n");
break;
default:
log_err_level_printf(LOG_WARNING, "Received unexpected signal %i\n", fd);
break;
}
}
/*
* 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, int clisock)
{
proxy_listener_ctx_t *head;
proxy_ctx_t *ctx;
/* adds locking, only required if accessed from separate threads */
evthread_use_pthreads();
#ifndef PURIFY
if (OPTS_DEBUG(opts)) {
event_enable_debug_mode();
}
#endif /* PURIFY */
ctx = malloc(sizeof(proxy_ctx_t));
if (!ctx) {
log_err_level_printf(LOG_CRIT, "Error allocating memory\n");
goto leave0;
}
memset(ctx, 0, sizeof(proxy_ctx_t));
ctx->opts = opts;
ctx->evbase = event_base_new();
if (!ctx->evbase) {
log_err_level_printf(LOG_CRIT, "Error getting event base\n");
goto leave1;
}
if (OPTS_DEBUG(opts)) {
proxy_debug_base(ctx->evbase);
}
ctx->thrmgr = pxy_thrmgr_new(opts);
if (!ctx->thrmgr) {
log_err_level_printf(LOG_CRIT, "Error creating thread manager\n");
goto leave1b;
}
head = ctx->lctx = NULL;
for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) {
head = proxy_listener_setup(ctx->evbase, ctx->thrmgr,
spec, opts, clisock);
if (!head)
goto leave2;
head->next = ctx->lctx;
ctx->lctx = head;
char *specstr = proxyspec_str(spec);
if (!specstr) {
fprintf(stderr, "out of memory\n");
exit(EXIT_FAILURE);
}
log_dbg_printf("proxy_listener_setup: %s\n", specstr);
free(specstr);
}
for (size_t i = 0; i < (sizeof(signals) / sizeof(int)); i++) {
ctx->sev[i] = evsignal_new(ctx->evbase, signals[i],
proxy_signal_cb, ctx);
if (!ctx->sev[i])
goto leave3;
evsignal_add(ctx->sev[i], NULL);
}
privsep_client_close(clisock);
return ctx;
leave3:
for (size_t i = 0; i < (sizeof(ctx->sev) / sizeof(ctx->sev[0])); i++) {
if (ctx->sev[i]) {
event_free(ctx->sev[i]);
}
}
leave2:
if (ctx->lctx) {
proxy_listener_ctx_free(ctx->lctx);
}
pxy_thrmgr_free(ctx->thrmgr);
leave1b:
event_base_free(ctx->evbase);
leave1:
free(ctx);
leave0:
return NULL;
}
/*
* Run the event loop. Returns when the event loop is canceled by a signal
* or on failure.
*/
void
proxy_run(proxy_ctx_t *ctx)
{
if (ctx->opts->detach) {
event_reinit(ctx->evbase);
}
#ifndef PURIFY
if (OPTS_DEBUG(ctx->opts)) {
event_base_dump_events(ctx->evbase, stderr);
}
#endif /* PURIFY */
if (pxy_thrmgr_run(ctx->thrmgr) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to start thread manager\n");
return;
}
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("Starting main event loop.\n");
}
event_base_dispatch(ctx->evbase);
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("Main event loop stopped.\n");
}
}
/*
* Break the loop of the proxy, causing the proxy_run to return.
*/
void
proxy_loopbreak(proxy_ctx_t *ctx)
{
event_base_loopbreak(ctx->evbase);
}
/*
* Free the proxy data structures.
*/
void
proxy_free(proxy_ctx_t *ctx)
{
if (ctx->lctx) {
proxy_listener_ctx_free(ctx->lctx);
}
for (size_t i = 0; i < (sizeof(ctx->sev) / sizeof(ctx->sev[0])); i++) {
if (ctx->sev[i]) {
event_free(ctx->sev[i]);
}
}
if (ctx->thrmgr) {
pxy_thrmgr_free(ctx->thrmgr);
}
if (ctx->evbase) {
event_base_free(ctx->evbase);
}
free(ctx);
}
/* vim: set noet ft=c: */

@ -0,0 +1,59 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 PROXY_H
#define PROXY_H
#include "opts.h"
#include "attrib.h"
#include "pxythrmgr.h"
#include <sys/syslog.h>
typedef struct proxy_ctx proxy_ctx_t;
/*
* Listener context.
*/
typedef struct proxy_listener_ctx {
pxy_thrmgr_ctx_t *thrmgr;
proxyspec_t *spec;
opts_t *opts;
struct evconnlistener *evcl;
struct proxy_listener_ctx *next;
} proxy_listener_ctx_t;
proxy_ctx_t * proxy_new(opts_t *, int) NONNULL(1) MALLOC;
void proxy_run(proxy_ctx_t *) NONNULL(1);
void proxy_loopbreak(proxy_ctx_t *) NONNULL(1);
void proxy_free(proxy_ctx_t *) NONNULL(1);
void proxy_listener_errorcb(struct evconnlistener *, UNUSED void *);
#endif /* !PROXY_H */
/* vim: set noet ft=c: */

@ -0,0 +1,796 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* Copyright (c) 2017-2019, Soner Tari <sonertari@gmail.com>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "pxyconn.h"
#include "prototcp.h"
#include "privsep.h"
#include "sys.h"
#include "log.h"
#include "attrib.h"
#include <string.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <assert.h>
#include <event2/listener.h>
#ifdef __linux__
#include <glob.h>
#endif /* __linux__ */
#include <net/if_arp.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/route.h>
#include <netinet/if_ether.h>
#ifdef __OpenBSD__
#include <net/if_dl.h>
#endif /* __OpenBSD__ */
/*
* Maximum size of data to buffer per connection direction before
* temporarily stopping to read data from the other end.
*/
#define OUTBUF_LIMIT (128*1024)
int descriptor_table_size = 0;
// @attention The order of names should match the order in protocol enum
char *protocol_names[] = {
// ERROR = -1
"TCP", // = 0
};
static protocol_t NONNULL(1)
pxy_setup_proto(pxy_conn_ctx_t *ctx)
{
ctx->protoctx = malloc(sizeof(proto_ctx_t));
if (!ctx->protoctx) {
return PROTO_ERROR;
}
memset(ctx->protoctx, 0, sizeof(proto_ctx_t));
// Default to tcp
protocol_t proto = prototcp_setup(ctx);
if (proto == PROTO_ERROR) {
free(ctx->protoctx);
}
return proto;
}
static pxy_conn_ctx_t * MALLOC NONNULL(2,3)
pxy_conn_ctx_new(evutil_socket_t fd,
pxy_thrmgr_ctx_t *thrmgr, opts_t *opts)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_new: ENTER, fd=%d\n", fd);
#endif /* DEBUG_PROXY */
pxy_conn_ctx_t *ctx = malloc(sizeof(pxy_conn_ctx_t));
if (!ctx) {
return NULL;
}
memset(ctx, 0, sizeof(pxy_conn_ctx_t));
ctx->id = thrmgr->conn_count++;
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_new: id=%llu, fd=%d\n", ctx->id, fd);
#endif /* DEBUG_PROXY */
ctx->fd = fd;
ctx->thrmgr = thrmgr;
ctx->proto = pxy_setup_proto(ctx);
if (ctx->proto == PROTO_ERROR) {
free(ctx);
return NULL;
}
ctx->opts = opts;
ctx->ctime = time(NULL);
ctx->atime = ctx->ctime;
ctx->next = NULL;
pxy_thrmgr_attach(ctx);
return ctx;
}
void
pxy_conn_ctx_free(pxy_conn_ctx_t *ctx, int by_requestor)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_free: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (WANT_CONTENT_LOG(ctx)) {
if (log_content_close(&ctx->logctx, by_requestor) == -1) {
log_err_level_printf(LOG_WARNING, "Content log close failed\n");
}
}
if (ctx->thr_locked) {
pxy_thrmgr_detach_unlocked(ctx);
} else {
pxy_thrmgr_detach(ctx);
}
if (ctx->srchost_str) {
free(ctx->srchost_str);
}
if (ctx->srcport_str) {
free(ctx->srcport_str);
}
if (ctx->dsthost_str) {
free(ctx->dsthost_str);
}
if (ctx->dstport_str) {
free(ctx->dstport_str);
}
// If the proto doesn't have special args, proto_free() callback is NULL
if (ctx->protoctx->proto_free) {
ctx->protoctx->proto_free(ctx);
}
free(ctx->protoctx);
free(ctx);
}
void
pxy_conn_free(pxy_conn_ctx_t *ctx, int by_requestor)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_free: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
// We always assign NULL to bevs after freeing them
if (ctx->src.bev) {
ctx->src.free(ctx->src.bev, ctx);
ctx->src.bev = NULL;
} else if (!ctx->src.closed) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_conn_free: evutil_closesocket on NULL src->bev, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
// @attention early in the conn setup, src fd may be open, although src.bev is NULL
evutil_closesocket(ctx->fd);
}
if (ctx->dst.bev) {
ctx->dst.free(ctx->dst.bev, ctx);
ctx->dst.bev = NULL;
}
pxy_conn_ctx_free(ctx, by_requestor);
}
void
pxy_conn_term(pxy_conn_ctx_t *ctx, int by_requestor)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_term: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
ctx->term = 1;
ctx->term_requestor = by_requestor;
}
void
pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx)
{
char *msg;
int rv;
/*
* The following ifdef's within asprintf arguments list generates
* warnings with -Wembedded-directive on some compilers.
* Not fixing the code in order to avoid more code duplication.
*/
rv = asprintf(&msg, "CONN: tcp %s %s %s %s\n",
STRORDASH(ctx->srchost_str),
STRORDASH(ctx->srcport_str),
STRORDASH(ctx->dsthost_str),
STRORDASH(ctx->dstport_str));
if ((rv < 0) || !msg) {
ctx->enomem = 1;
goto out;
}
if (!ctx->opts->detach) {
log_err_printf("%s", msg);
} else if (ctx->opts->statslog) {
if (log_conn(msg) == -1) {
log_err_level_printf(LOG_WARNING, "Conn logging failed\n");
}
}
if (ctx->opts->connectlog) {
if (log_connect_print_free(msg) == -1) {
free(msg);
log_err_level_printf(LOG_WARNING, "Connection logging failed\n");
}
} else {
free(msg);
}
out:
return;
}
int
pxy_log_content_inbuf(pxy_conn_ctx_t *ctx, struct evbuffer *inbuf, int req)
{
size_t sz = evbuffer_get_length(inbuf);
unsigned char *buf = malloc(sz);
if (!buf) {
ctx->enomem = 1;
return -1;
}
if (evbuffer_copyout(inbuf, buf, sz) == -1) {
free(buf);
return -1;
}
logbuf_t *lb = logbuf_new_alloc(sz, NULL);
if (!lb) {
free(buf);
ctx->enomem = 1;
return -1;
}
memcpy(lb->buf, buf, lb->sz);
free(buf);
if (log_content_submit(&ctx->logctx, lb, req) == -1) {
logbuf_free(lb);
log_err_level_printf(LOG_WARNING, "Content log submission failed\n");
return -1;
}
return 0;
}
static int
pxy_prepare_logging(pxy_conn_ctx_t *ctx)
{
/* prepare logging, part 2 */
if (WANT_CONTENT_LOG(ctx)) {
if (log_content_open(&ctx->logctx, ctx->opts,
STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str),
STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str),
NULL, NULL, NULL
) == -1) {
if (errno == ENOMEM)
ctx->enomem = 1;
pxy_conn_term(ctx, 1);
return -1;
}
}
return 0;
}
static void NONNULL(1)
pxy_log_dbg_connect_type(pxy_conn_ctx_t *ctx)
{
if (OPTS_DEBUG(ctx->opts)) {
/* for TCP, we get only a dst connect event,
* since src was already connected from the
* beginning; mirror SSL debug output anyway
* in order not to confuse anyone who might be
* looking closely at the output */
log_dbg_printf("%s connected to [%s]:%s\n",
protocol_names[ctx->proto],
STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str));
log_dbg_printf("%s connected from [%s]:%s\n",
protocol_names[ctx->proto],
STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str));
}
}
void
pxy_log_connect(pxy_conn_ctx_t *ctx)
{
/* log connection if we don't analyze any headers */
if (WANT_CONNECT_LOG(ctx)) {
pxy_log_connect_nonhttp(ctx);
}
pxy_log_dbg_connect_type(ctx);
}
static void
pxy_log_dbg_disconnect(pxy_conn_ctx_t *ctx)
{
/* we only get a single disconnect event here for both connections */
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("%s disconnected to [%s]:%s\n",
protocol_names[ctx->proto],
STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str));
log_dbg_printf("%s disconnected from [%s]:%s\n",
protocol_names[ctx->proto],
STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str));
}
}
#ifdef DEBUG_PROXY
void
pxy_log_dbg_evbuf_info(pxy_conn_ctx_t *ctx, pxy_conn_desc_t *this, pxy_conn_desc_t *other)
{
// Use ctx->conn, because this function is used by child conns too
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("evbuffer size at EOF: i:%zu o:%zu i:%zu o:%zu\n",
evbuffer_get_length(bufferevent_get_input(this->bev)),
evbuffer_get_length(bufferevent_get_output(this->bev)),
other->closed ? 0 : evbuffer_get_length(bufferevent_get_input(other->bev)),
other->closed ? 0 : evbuffer_get_length(bufferevent_get_output(other->bev)));
}
}
#endif /* DEBUG_PROXY */
#ifdef DEBUG_PROXY
char *bev_names[] = {
"src",
"dst",
"NULL",
"UNKWN"
};
static char *
pxy_get_event_name(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
// XXX: Used by watermark functions only, remove
if (bev == ctx->src.bev) {
return bev_names[0];
} else if (bev == ctx->dst.bev) {
return bev_names[1];
} else if (bev == NULL) {
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_get_event_name: event_name=NULL\n");
return bev_names[2];
} else {
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_get_event_name: event_name=UNKWN\n");
return bev_names[3];
}
}
#endif /* DEBUG_PROXY */
void
pxy_try_set_watermark(struct bufferevent *bev, pxy_conn_ctx_t *ctx, struct bufferevent *other)
{
if (evbuffer_get_length(bufferevent_get_output(other)) >= OUTBUF_LIMIT) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_try_set_watermark: %s, fd=%d\n", pxy_get_event_name(bev, ctx), ctx->fd);
#endif /* DEBUG_PROXY */
/* temporarily disable data source;
* set an appropriate watermark. */
bufferevent_setwatermark(other, EV_WRITE, OUTBUF_LIMIT/2, OUTBUF_LIMIT);
bufferevent_disable(bev, EV_READ);
ctx->thr->set_watermarks++;
}
}
void
pxy_try_unset_watermark(struct bufferevent *bev, pxy_conn_ctx_t *ctx, pxy_conn_desc_t *other)
{
if (other->bev && !(bufferevent_get_enabled(other->bev) & EV_READ)) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_try_unset_watermark: %s, fd=%d\n", pxy_get_event_name(bev, ctx), ctx->fd);
#endif /* DEBUG_PROXY */
/* data source temporarily disabled;
* re-enable and reset watermark to 0. */
bufferevent_setwatermark(bev, EV_WRITE, 0, 0);
bufferevent_enable(other->bev, EV_READ);
ctx->thr->unset_watermarks++;
}
}
void
pxy_discard_inbuf(struct bufferevent *bev)
{
struct evbuffer *inbuf = bufferevent_get_input(bev);
size_t inbuf_size = evbuffer_get_length(inbuf);
log_dbg_printf("Warning: Drained %zu bytes (conn closed)\n", inbuf_size);
evbuffer_drain(inbuf, inbuf_size);
}
#if defined(__APPLE__) || defined(__FreeBSD__)
#define getdtablecount() 0
/*
* Copied from:
* opensmtpd-201801101641p1/openbsd-compat/imsg.c
*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
static int
available_fds(unsigned int n)
{
unsigned int i;
int ret, fds[256];
if (n > (sizeof(fds)/sizeof(fds[0])))
return -1;
ret = 0;
for (i = 0; i < n; i++) {
fds[i] = -1;
if ((fds[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
ret = -1;
break;
}
}
for (i = 0; i < n && fds[i] >= 0; i++)
close(fds[i]);
return ret;
}
#endif /* __APPLE__ || __FreeBSD__ */
#ifdef __linux__
/*
* Copied from:
* https://github.com/tmux/tmux/blob/master/compat/getdtablecount.c
*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
int
getdtablecount()
{
char path[PATH_MAX];
glob_t g;
int n = 0;
if (snprintf(path, sizeof path, "/proc/%ld/fd/*", (long)getpid()) < 0) {
log_err_level_printf(LOG_CRIT, "snprintf overflow\n");
return 0;
}
if (glob(path, 0, NULL, &g) == 0)
n = g.gl_pathc;
globfree(&g);
return n;
}
#endif /* __linux__ */
/*
* Check if we are out of file descriptors to close the conn, or else libevent will crash us
* @attention We cannot guess the number of children in a connection at conn setup time. So, FD_RESERVE is just a ball park figure.
* But what if a connection passes the check below, but eventually tries to create more children than FD_RESERVE allows for? This will crash us the same.
* Beware, this applies to all current conns, not just the last connection setup.
* For example, 20x conns pass the check below before creating any children, at which point we reach the last FD_RESERVE fds,
* then they all start creating children, which crashes us again.
* So, no matter how large an FD_RESERVE we choose, there will always be a risk of running out of fds, if we check the number of fds during parent conn setup only.
* If we are left with less than FD_RESERVE fds, we should not create more children than FD_RESERVE allows for either.
* Therefore, we check if we are out of fds in pxy_listener_acceptcb_child() and close the conn there too.
* @attention These checks are expected to slow us further down, but it is critical to avoid a crash in case we run out of fds.
*/
static int
check_fd_usage(
#ifdef DEBUG_PROXY
evutil_socket_t fd
#endif /* DEBUG_PROXY */
)
{
int dtable_count = getdtablecount();
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINER, "check_fd_usage: descriptor_table_size=%d, dtablecount=%d, reserve=%d, fd=%d\n",
descriptor_table_size, dtable_count, FD_RESERVE, fd);
#endif /* DEBUG_PROXY */
if (dtable_count + FD_RESERVE >= descriptor_table_size) {
goto out;
}
#if defined(__APPLE__) || defined(__FreeBSD__)
if (available_fds(FD_RESERVE) == -1) {
goto out;
}
#endif /* __APPLE__ || __FreeBSD__ */
return 0;
out:
errno = EMFILE;
log_err_level_printf(LOG_CRIT, "Out of file descriptors\n");
return -1;
}
int
pxy_try_close_conn_end(pxy_conn_desc_t *conn_end, pxy_conn_ctx_t *ctx)
{
/* if the other end is still open and doesn't have data
* to send, close it, otherwise its writecb will close
* it after writing what's left in the output buffer */
if (evbuffer_get_length(bufferevent_get_output(conn_end->bev)) == 0) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_try_close_conn_end: evbuffer_get_length(outbuf) == 0, terminate conn, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
conn_end->free(conn_end->bev, ctx);
conn_end->bev = NULL;
conn_end->closed = 1;
return 1;
}
return 0;
}
void
pxy_try_disconnect(pxy_conn_ctx_t *ctx, pxy_conn_desc_t *this, pxy_conn_desc_t *other, int is_requestor)
{
// @attention srvdst should never reach here unless in passthrough mode, its bev may be NULL
this->closed = 1;
this->free(this->bev, ctx);
this->bev = NULL;
if (other->closed) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_try_disconnect: other->closed, terminate conn, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
// Uses only ctx to log disconnect, never any of the bevs
pxy_log_dbg_disconnect(ctx);
pxy_conn_term(ctx, is_requestor);
}
}
int
pxy_try_consume_last_input(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
/* if there is data pending in the closed connection,
* handle it here, otherwise it will be lost. */
if (evbuffer_get_length(bufferevent_get_input(bev))) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_try_consume_last_input: evbuffer_get_length(inbuf) > 0, terminate conn, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (pxy_bev_readcb_preexec_logging_and_stats(bev, ctx) == -1) {
return -1;
}
ctx->protoctx->bev_readcb(bev, ctx);
}
return 0;
}
int
pxy_bev_readcb_preexec_logging_and_stats(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
{
if (bev == ctx->src.bev || bev == ctx->dst.bev) {
struct evbuffer *inbuf = bufferevent_get_input(bev);
size_t inbuf_size = evbuffer_get_length(inbuf);
if (bev == ctx->src.bev) {
ctx->thr->intif_in_bytes += inbuf_size;
} else {
ctx->thr->intif_out_bytes += inbuf_size;
}
if (WANT_CONTENT_LOG(ctx)) {
// HTTP content logging at this point may record certain header lines twice, if we have not seen all headers yet
return pxy_log_content_inbuf(ctx, inbuf, (bev == ctx->src.bev));
}
}
return 0;
}
/*
* Callback for read events on the up- and downstream connection bufferevents.
* Called when there is data ready in the input evbuffer.
*/
void
pxy_bev_readcb(struct bufferevent *bev, void *arg)
{
pxy_conn_ctx_t *ctx = arg;
if (pxy_bev_readcb_preexec_logging_and_stats(bev, ctx) == -1) {
goto out;
}
ctx->atime = time(NULL);
ctx->protoctx->bev_readcb(bev, ctx);
out:
if (ctx->term || ctx->enomem) {
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : (bev == ctx->src.bev));
}
}
/*
* Callback for write events on the up- and downstream connection bufferevents.
* Called when either all data from the output evbuffer has been written,
* or if the outbuf is only half full again after having been full.
*/
void
pxy_bev_writecb(struct bufferevent *bev, void *arg)
{
pxy_conn_ctx_t *ctx = arg;
ctx->atime = time(NULL);
ctx->protoctx->bev_writecb(bev, ctx);
if (ctx->term || ctx->enomem) {
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : (bev == ctx->src.bev));
}
}
int
pxy_bev_eventcb_postexec_logging_and_stats(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx)
{
if (ctx->term || ctx->enomem) {
return -1;
}
if (events & BEV_EVENT_CONNECTED) {
if (bev == ctx->dst.bev) {
pxy_log_connect(ctx);
ctx->dst_fd = bufferevent_getfd(ctx->dst.bev);
ctx->thr->max_fd = MAX(ctx->thr->max_fd, ctx->dst_fd);
}
if (ctx->src_connected && ctx->dst_connected) {
if (pxy_prepare_logging(ctx) == -1) {
return -1;
}
}
}
return 0;
}
/*
* Callback for meta events on the up- and downstream connection bufferevents.
* Called when EOF has been reached, a connection has been made, and on errors.
*/
void
pxy_bev_eventcb(struct bufferevent *bev, short events, void *arg)
{
pxy_conn_ctx_t *ctx = arg;
ctx->atime = time(NULL);
if (events & BEV_EVENT_ERROR) {
log_err_printf("BEV_EVENT_ERROR\n");
ctx->thr->errors++;
}
ctx->protoctx->bev_eventcb(bev, events, arg);
pxy_bev_eventcb_postexec_logging_and_stats(bev, events, ctx);
// Logging functions may set term or enomem too
// EOF eventcb may call readcb possibly causing enomem
if (ctx->term || ctx->enomem) {
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : (bev == ctx->src.bev));
}
}
/*
* Complete the connection.
*/
void
pxy_conn_connect(pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_connect: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
if (ctx->protoctx->connectcb(ctx) == -1) {
// @attention Do not try to close conns or do anything else with conn ctx on the thrmgr thread after setting event callbacks and/or socket connect.
// The return value of -1 from connectcb indicates that there was a fatal error before event callbacks were set, so we can terminate the connection.
// Otherwise, it is up to the event callbacks to terminate the connection. This is necessary to avoid multithreading issues.
if (ctx->term || ctx->enomem) {
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : 1);
}
}
// @attention Do not do anything with the conn ctx after this point on the thrmgr thread
}
/*
* Callback for accept events on the socket listener bufferevent.
* Called when a new incoming connection has been accepted.
* Initiates the connection to the server. The incoming connection
* from the client is not being activated until we have a successful
* connection to the server, because we need the server's certificate
* in order to set up the SSL session to the client.
* For consistency, plain TCP works the same way, even if we could
* start reading from the client while waiting on the connection to
* the server to connect.
*/
void
pxy_conn_setup(evutil_socket_t fd,
struct sockaddr *peeraddr, int peeraddrlen,
pxy_thrmgr_ctx_t *thrmgr, opts_t *opts)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_setup: ENTER, fd=%d\n", fd);
char *host, *port;
if (sys_sockaddr_str(peeraddr, peeraddrlen, &host, &port) == 0) {
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_setup: peer addr=[%s]:%s, fd=%d\n", host, port, fd);
free(host);
free(port);
}
#endif /* DEBUG_PROXY */
if (check_fd_usage(
#ifdef DEBUG_PROXY
fd
#endif /* DEBUG_PROXY */
) == -1) {
evutil_closesocket(fd);
return;
}
/* create per connection state and attach to thread */
pxy_conn_ctx_t *ctx = pxy_conn_ctx_new(fd, thrmgr, opts);
if (!ctx) {
log_err_level_printf(LOG_CRIT, "Error allocating memory\n");
evutil_closesocket(fd);
return;
}
ctx->af = peeraddr->sa_family;
if (sys_sockaddr_str(peeraddr, peeraddrlen, &ctx->srchost_str, &ctx->srcport_str) != 0) {
log_err_level_printf(LOG_CRIT, "Aborting connection setup (out of memory)!\n");
goto out;
}
ctx->protoctx->fd_readcb(ctx->fd, 0, ctx);
return;
out:
evutil_closesocket(fd);
pxy_conn_ctx_free(ctx, 1);
}
/* vim: set noet ft=c: */

@ -0,0 +1,198 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* Copyright (c) 2017-2019, Soner Tari <sonertari@gmail.com>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 PXYCONN_H
#define PXYCONN_H
#if defined(__FreeBSD__) || defined(__DragonFly__)
#include <netinet/in.h>
#endif
#include "proxy.h"
#include "opts.h"
#include "attrib.h"
#include "pxythrmgr.h"
#include "log.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#define WANT_CONNECT_LOG(ctx) ((ctx)->opts->connectlog||!(ctx)->opts->detach||(ctx)->opts->statslog)
#define WANT_CONTENT_LOG(ctx) ((ctx)->opts->contentlog)
typedef void (*fd_readcb_func_t)(evutil_socket_t, short, void *);
typedef int (*connect_func_t)(pxy_conn_ctx_t *);
typedef void (*callback_func_t)(struct bufferevent *, void *);
typedef void (*eventcb_func_t)(struct bufferevent *, short, void *);
typedef void (*bev_free_func_t)(struct bufferevent *, pxy_conn_ctx_t *);
typedef void (*proto_free_func_t)(pxy_conn_ctx_t *);
/*
* Proxy connection context state, describes a proxy connection
* with source and destination socket bufferevents, SSL context and
* other session state. One of these exists per handled proxy
* connection.
*/
/* single socket bufferevent descriptor */
typedef struct pxy_conn_desc {
struct bufferevent *bev;
unsigned int closed : 1;
bev_free_func_t free;
} pxy_conn_desc_t;
typedef enum protocol {
PROTO_ERROR = -1,
PROTO_TCP = 0,
} protocol_t;
typedef struct proto_ctx proto_ctx_t;
struct proto_ctx {
protocol_t proto;
connect_func_t connectcb;
fd_readcb_func_t fd_readcb;
callback_func_t bev_readcb;
callback_func_t bev_writecb;
eventcb_func_t bev_eventcb;
proto_free_func_t proto_free;
// For protocol specific fields, if any
void *arg;
};
/* connection state consisting of two connection descriptors,
* connection-wide state and the specs and options */
struct pxy_conn_ctx {
protocol_t proto;
/* per-connection state */
struct pxy_conn_desc src;
struct pxy_conn_desc dst;
/* store fd and fd event while connected is 0 */
evutil_socket_t fd;
// For protocol specific fields, never NULL
proto_ctx_t *protoctx;
/* log strings from socket */
char *srchost_str;
char *srcport_str;
char *dsthost_str;
char *dstport_str;
/* content log context */
log_content_ctx_t logctx;
/* status flags */
unsigned int src_connected : 1; /* 0 until src connected */
unsigned int dst_connected : 1; /* 0 until dst connected */
unsigned int enomem : 1; /* 1 if out of memory */
unsigned int term : 1; /* 0 until term requested */
unsigned int term_requestor : 1; /* 1 client, 0 server side */
unsigned int seen_sslproxy_line : 1; /* 1 if seen sslproxy line */
/* original source and destination address, and family */
struct sockaddr_storage srcaddr;
socklen_t srcaddrlen;
struct sockaddr_storage dstaddr;
socklen_t dstaddrlen;
int af;
// Thread that the conn is attached to
pxy_thr_ctx_t *thr;
unsigned int thr_locked : 1; /* 1 to prevent double locking */
unsigned int in_thr_conns : 1; /* 1 to prevent adding twice */
// Unique id of the conn
long long unsigned int id;
pxy_thrmgr_ctx_t *thrmgr;
opts_t *opts;
struct event_base *evbase;
evutil_socket_t dst_fd;
// Conn create time
time_t ctime;
// Conn last access time, used to determine expired conns
// Updated on entry to callback functions, parent or child
time_t atime;
// Per-thread conn list, used to determine idle and expired conns, and to close them
pxy_conn_ctx_t *next;
};
void pxy_log_connect(pxy_conn_ctx_t *) NONNULL(1);
int pxy_log_content_inbuf(pxy_conn_ctx_t *, struct evbuffer *, int) NONNULL(1);
void pxy_log_connect_nonhttp(pxy_conn_ctx_t *) NONNULL(1);
void pxy_log_dbg_evbuf_info(pxy_conn_ctx_t *, pxy_conn_desc_t *, pxy_conn_desc_t *) NONNULL(1,2,3);
void pxy_try_set_watermark(struct bufferevent *, pxy_conn_ctx_t *, struct bufferevent *) NONNULL(1,2,3);
void pxy_try_unset_watermark(struct bufferevent *, pxy_conn_ctx_t *, pxy_conn_desc_t *) NONNULL(1,2,3);
int pxy_try_close_conn_end(pxy_conn_desc_t *, pxy_conn_ctx_t *) NONNULL(1,2);
void pxy_try_disconnect(pxy_conn_ctx_t *, pxy_conn_desc_t *, pxy_conn_desc_t *, int) NONNULL(1,2,3);
int pxy_try_consume_last_input(struct bufferevent *, pxy_conn_ctx_t *) NONNULL(1,2);
void pxy_discard_inbuf(struct bufferevent *) NONNULL(1);
void pxy_conn_ctx_free(pxy_conn_ctx_t *, int) NONNULL(1);
void pxy_conn_free(pxy_conn_ctx_t *, int) NONNULL(1);
void pxy_conn_term(pxy_conn_ctx_t *, int) NONNULL(1);
int pxy_bev_readcb_preexec_logging_and_stats(struct bufferevent *, pxy_conn_ctx_t *) NONNULL(1,2);
int pxy_bev_eventcb_postexec_logging_and_stats(struct bufferevent *, short , pxy_conn_ctx_t *) NONNULL(1,3);
void pxy_bev_readcb(struct bufferevent *, void *);
void pxy_bev_writecb(struct bufferevent *, void *);
void pxy_bev_eventcb(struct bufferevent *, short, void *);
void pxy_conn_connect(pxy_conn_ctx_t *) NONNULL(1);
void pxy_conn_setup(evutil_socket_t, struct sockaddr *, int,
pxy_thrmgr_ctx_t *, opts_t *)
NONNULL(2,4,5);
#endif /* !PXYCONN_H */
/* vim: set noet ft=c: */

@ -0,0 +1,438 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* Copyright (c) 2017-2019, Soner Tari <sonertari@gmail.com>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "pxythrmgr.h"
#include "sys.h"
#include "log.h"
#include "pxyconn.h"
#include <string.h>
#include <event2/bufferevent.h>
#include <pthread.h>
#include <assert.h>
#include <sys/param.h>
/*
* Proxy thread manager: manages the connection handling worker threads
* and the per-thread resources (i.e. event bases). The load is shared
* across num_cpu * 2 connection handling threads, using the number of
* currently assigned connections as the sole metric.
*
* The attach and detach functions are thread-safe.
*/
static void
pxy_thrmgr_print_thr_info(pxy_thr_ctx_t *tctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_print_thr_info: thr=%d, load=%lu\n", tctx->thridx, tctx->load);
#endif /* DEBUG_PROXY */
unsigned int idx = 1;
evutil_socket_t max_fd = 0;
time_t max_atime = 0;
time_t max_ctime = 0;
char *smsg = NULL;
if (tctx->conns) {
time_t now = time(NULL);
pxy_conn_ctx_t *ctx = tctx->conns;
while (ctx) {
time_t atime = now - ctx->atime;
time_t ctime = now - ctx->ctime;
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_print_thr_info: CONN: thr=%d, id=%u, fd=%d, dst=%d, p=%d-%d, at=%lld ct=%lld, src_addr=%s:%s, dst_addr=%s:%s\n",
tctx->thridx, idx, ctx->fd, ctx->dst_fd, ctx->src.closed, ctx->dst.closed, (long long)atime, (long long)ctime,
STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str), STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str));
#endif /* DEBUG_PROXY */
max_fd = MAX(max_fd, MAX(ctx->fd, ctx->dst_fd));
max_atime = MAX(max_atime, atime);
max_ctime = MAX(max_ctime, ctime);
idx++;
ctx = ctx->next;
}
}
if (asprintf(&smsg, "STATS: thr=%d, mld=%zu, mfd=%d, mat=%lld, mct=%lld, iib=%llu, iob=%llu, eib=%llu, eob=%llu, swm=%zu, uwm=%zu, err=%zu, si=%u\n",
tctx->thridx, tctx->max_load, tctx->max_fd, (long long)max_atime, (long long)max_ctime, tctx->intif_in_bytes, tctx->intif_out_bytes, tctx->extif_in_bytes, tctx->extif_out_bytes,
tctx->set_watermarks, tctx->unset_watermarks, tctx->errors, tctx->stats_id) < 0) {
return;
}
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_print_thr_info: %s", smsg);
#endif /* DEBUG_PROXY */
if (log_stats(smsg) == -1) {
log_err_level_printf(LOG_WARNING, "Stats logging failed\n");
}
free(smsg);
smsg = NULL;
tctx->stats_id++;
tctx->errors = 0;
tctx->set_watermarks = 0;
tctx->unset_watermarks = 0;
tctx->intif_in_bytes = 0;
tctx->intif_out_bytes = 0;
tctx->extif_in_bytes = 0;
tctx->extif_out_bytes = 0;
// Reset these stats with the current values (do not reset to 0 directly, there may be active conns)
tctx->max_fd = max_fd;
tctx->max_load = tctx->load;
}
/*
* Recurring timer event to prevent the event loops from exiting when
* they run out of events.
*/
static void
pxy_thrmgr_timer_cb(UNUSED evutil_socket_t fd, UNUSED short what, UNUSED void *arg)
{
pxy_thr_ctx_t *ctx = arg;
pthread_mutex_lock(&ctx->mutex);
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_timer_cb: thr=%d, load=%lu, to=%u\n", ctx->thridx, ctx->load, ctx->timeout_count);
#endif /* DEBUG_PROXY */
// @attention Print thread info only if stats logging is enabled, if disabled debug logs are not printed either
if (ctx->thrmgr->opts->statslog) {
ctx->timeout_count++;
if (ctx->timeout_count >= ctx->thrmgr->opts->stats_period) {
ctx->timeout_count = 0;
pxy_thrmgr_print_thr_info(ctx);
}
}
pthread_mutex_unlock(&ctx->mutex);
}
/*
* Thread entry point; runs the event loop of the event base.
* Does not exit until the libevent loop is broken explicitly.
*/
static void *
pxy_thrmgr_thr(void *arg)
{
pxy_thr_ctx_t *ctx = arg;
struct timeval timer_delay = {10, 0};
struct event *ev;
ev = event_new(ctx->evbase, -1, EV_PERSIST, pxy_thrmgr_timer_cb, ctx);
if (!ev)
return NULL;
evtimer_add(ev, &timer_delay);
ctx->running = 1;
event_base_dispatch(ctx->evbase);
event_free(ev);
return NULL;
}
/*
* Create new thread manager but do not start any threads yet.
* This gets called before forking to background.
*/
pxy_thrmgr_ctx_t *
pxy_thrmgr_new(opts_t *opts)
{
pxy_thrmgr_ctx_t *ctx;
if (!(ctx = malloc(sizeof(pxy_thrmgr_ctx_t))))
return NULL;
memset(ctx, 0, sizeof(pxy_thrmgr_ctx_t));
ctx->opts = opts;
ctx->num_thr = 2 * sys_get_cpu_cores();
return ctx;
}
/*
* Start the thread manager and associated threads.
* This must be called after forking.
*
* Returns -1 on failure, 0 on success.
*/
int
pxy_thrmgr_run(pxy_thrmgr_ctx_t *ctx)
{
int idx = -1;
if (!(ctx->thr = malloc(ctx->num_thr * sizeof(pxy_thr_ctx_t*)))) {
log_dbg_printf("Failed to allocate memory\n");
goto leave;
}
memset(ctx->thr, 0, ctx->num_thr * sizeof(pxy_thr_ctx_t*));
for (idx = 0; idx < ctx->num_thr; idx++) {
if (!(ctx->thr[idx] = malloc(sizeof(pxy_thr_ctx_t)))) {
log_dbg_printf("Failed to allocate memory\n");
goto leave;
}
memset(ctx->thr[idx], 0, sizeof(pxy_thr_ctx_t));
ctx->thr[idx]->evbase = event_base_new();
if (!ctx->thr[idx]->evbase) {
log_dbg_printf("Failed to create evbase %d\n", idx);
goto leave;
}
ctx->thr[idx]->load = 0;
ctx->thr[idx]->running = 0;
ctx->thr[idx]->conns = NULL;
ctx->thr[idx]->thridx = idx;
ctx->thr[idx]->timeout_count = 0;
ctx->thr[idx]->thrmgr = ctx;
if (pthread_mutex_init(&ctx->thr[idx]->mutex, NULL)) {
log_dbg_printf("Failed to initialize thr mutex\n");
goto leave;
}
}
log_dbg_printf("Initialized %d connection handling threads\n",
ctx->num_thr);
for (idx = 0; idx < ctx->num_thr; idx++) {
if (pthread_create(&ctx->thr[idx]->thr, NULL,
pxy_thrmgr_thr, ctx->thr[idx]))
goto leave_thr;
while (!ctx->thr[idx]->running) {
sched_yield();
}
}
log_dbg_printf("Started %d connection handling threads\n",
ctx->num_thr);
return 0;
leave_thr:
idx--;
while (idx >= 0) {
pthread_cancel(ctx->thr[idx]->thr);
pthread_join(ctx->thr[idx]->thr, NULL);
idx--;
}
idx = ctx->num_thr - 1;
leave:
while (idx >= 0) {
if (ctx->thr[idx]) {
if (ctx->thr[idx]->evbase) {
event_base_free(ctx->thr[idx]->evbase);
}
pthread_mutex_destroy(&ctx->thr[idx]->mutex);
free(ctx->thr[idx]);
}
idx--;
}
if (ctx->thr) {
free(ctx->thr);
ctx->thr = NULL;
}
return -1;
}
/*
* Destroy the event manager and stop all threads.
*/
void
pxy_thrmgr_free(pxy_thrmgr_ctx_t *ctx)
{
if (ctx->thr) {
for (int idx = 0; idx < ctx->num_thr; idx++) {
event_base_loopbreak(ctx->thr[idx]->evbase);
sched_yield();
}
for (int idx = 0; idx < ctx->num_thr; idx++) {
pthread_join(ctx->thr[idx]->thr, NULL);
}
for (int idx = 0; idx < ctx->num_thr; idx++) {
if (ctx->thr[idx]->evbase) {
event_base_free(ctx->thr[idx]->evbase);
}
pthread_mutex_destroy(&ctx->thr[idx]->mutex);
free(ctx->thr[idx]);
}
free(ctx->thr);
}
free(ctx);
}
void
pxy_thrmgr_add_conn(pxy_conn_ctx_t *ctx)
{
pthread_mutex_lock(&ctx->thr->mutex);
if (!ctx->in_thr_conns) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_add_conn: Adding conn, id=%llu, fd=%d\n", ctx->id, ctx->fd);
#endif /* DEBUG_PROXY */
ctx->in_thr_conns = 1;
// Always keep thr load and conns list in sync
ctx->thr->load++;
ctx->next = ctx->thr->conns;
ctx->thr->conns = ctx;
} else {
// Do not add conns twice
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_add_conn: Will not add conn twice, id=%llu, fd=%d\n", ctx->id, ctx->fd);
#endif /* DEBUG_PROXY */
}
pthread_mutex_unlock(&ctx->thr->mutex);
}
static void NONNULL(1)
pxy_thrmgr_remove_conn_unlocked(pxy_conn_ctx_t *ctx)
{
assert(ctx != NULL);
if (ctx->in_thr_conns) {
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_remove_conn_unlocked: Removing conn, id=%llu, fd=%d\n", ctx->id, ctx->fd);
#endif /* DEBUG_PROXY */
// Thr conns list cannot be empty, if the in_thr_conns flag of a conn is set
assert(ctx->thr->conns != NULL);
// Shouldn't need to reset the in_thr_conns flag, because the conn ctx will be freed next, but just in case
ctx->in_thr_conns = 0;
// We increment thr load in pxy_thrmgr_add_conn() only
ctx->thr->load--;
// @attention We may get multiple conns with the same fd combinations, so fds cannot uniquely define a conn; hence the need for unique ids.
if (ctx->id == ctx->thr->conns->id) {
ctx->thr->conns = ctx->thr->conns->next;
return;
} else {
pxy_conn_ctx_t *current = ctx->thr->conns->next;
pxy_conn_ctx_t *previous = ctx->thr->conns;
while (current != NULL && previous != NULL) {
if (ctx->id == current->id) {
previous->next = current->next;
return;
}
previous = current;
current = current->next;
}
// This should never happen
log_err_level_printf(LOG_CRIT, "Cannot find conn in thr conns\n");
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_thrmgr_remove_conn_unlocked: Cannot find conn in thr conns, id=%llu, fd=%d\n", ctx->id, ctx->fd);
#endif /* DEBUG_PROXY */
assert(0);
}
} else {
// This can happen if we are closing the conn after a fatal error before setting its event callback
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_remove_conn_unlocked: Conn not in thr conns, id=%llu, fd=%d\n", ctx->id, ctx->fd);
#endif /* DEBUG_PROXY */
}
}
/*
* Attach a new connection to a thread. Chooses the thread with the fewest
* currently active connections, returns the appropriate event bases.
* No need to be so accurate about balancing thread loads, so uses
* thread-level mutexes, instead of a thrmgr level mutex.
* Returns the index of the chosen thread (for passing to _detach later).
* This function cannot fail.
*/
void
pxy_thrmgr_attach(pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_attach: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
int thridx = 0;
size_t minload;
pxy_thrmgr_ctx_t *tmctx = ctx->thrmgr;
pthread_mutex_lock(&tmctx->thr[0]->mutex);
minload = tmctx->thr[0]->load;
pthread_mutex_unlock(&tmctx->thr[0]->mutex);
#ifdef DEBUG_THREAD
log_dbg_printf("===> Proxy connection handler thread status:\n"
"thr[0]: %zu\n", minload);
#endif /* DEBUG_THREAD */
for (int idx = 1; idx < tmctx->num_thr; idx++) {
pthread_mutex_lock(&tmctx->thr[idx]->mutex);
#ifdef DEBUG_THREAD
log_dbg_printf("thr[%d]: %zu\n", idx, tmctx->thr[idx]->load);
#endif /* DEBUG_THREAD */
if (minload > tmctx->thr[idx]->load) {
minload = tmctx->thr[idx]->load;
thridx = idx;
}
pthread_mutex_unlock(&tmctx->thr[idx]->mutex);
}
// Defer adding the conn to the conn list of its thread until after a successful conn setup while returning from pxy_conn_connect()
// otherwise pxy_thrmgr_timer_cb() may try to access the conn ctx while it is being freed on failure (signal 6 crash)
ctx->thr = tmctx->thr[thridx];
ctx->evbase = ctx->thr->evbase;
#ifdef DEBUG_THREAD
log_dbg_printf("thridx: %d\n", thridx);
#endif /* DEBUG_THREAD */
}
/*
* Detach a connection from a thread by index.
* This function cannot fail.
*/
void
pxy_thrmgr_detach_unlocked(pxy_conn_ctx_t *ctx)
{
#ifdef DEBUG_PROXY
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_thrmgr_detach_unlocked: ENTER, fd=%d\n", ctx->fd);
#endif /* DEBUG_PROXY */
pxy_thrmgr_remove_conn_unlocked(ctx);
}
void
pxy_thrmgr_detach(pxy_conn_ctx_t *ctx)
{
pthread_mutex_lock(&ctx->thr->mutex);
pxy_thrmgr_detach_unlocked(ctx);
pthread_mutex_unlock(&ctx->thr->mutex);
}
/* vim: set noet ft=c: */

@ -0,0 +1,100 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 PXYTHRMGR_H
#define PXYTHRMGR_H
#include "opts.h"
#include "attrib.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <event2/event.h>
#include <event2/dns.h>
#include <pthread.h>
extern int descriptor_table_size;
#define FD_RESERVE 10
typedef struct pxy_conn_ctx pxy_conn_ctx_t;
typedef struct pxy_thrmgr_ctx pxy_thrmgr_ctx_t;
typedef struct pxy_thr_ctx {
pthread_t thr;
int thridx;
pxy_thrmgr_ctx_t *thrmgr;
size_t load;
struct event_base *evbase;
int running;
// Per-thread locking is necessary during connection setup and termination
// to prevent multithreading issues between thrmgr thread and conn handling threads
pthread_mutex_t mutex;
// Statistics
evutil_socket_t max_fd;
size_t max_load;
size_t errors;
size_t set_watermarks;
size_t unset_watermarks;
long long unsigned int intif_in_bytes;
long long unsigned int intif_out_bytes;
long long unsigned int extif_in_bytes;
long long unsigned int extif_out_bytes;
// Each stats has an id, incremented on each stats print
unsigned short stats_id;
// Used to print statistics, compared against stats_period
unsigned int timeout_count;
// List of active connections on the thread
pxy_conn_ctx_t *conns;
} pxy_thr_ctx_t;
struct pxy_thrmgr_ctx {
int num_thr;
opts_t *opts;
pxy_thr_ctx_t **thr;
// Provides unique conn id, always goes up, never down
// There is no risk of collision if/when it rolls back to 0
long long unsigned int conn_count;
};
pxy_thrmgr_ctx_t * pxy_thrmgr_new(opts_t *) MALLOC;
int pxy_thrmgr_run(pxy_thrmgr_ctx_t *) NONNULL(1) WUNRES;
void pxy_thrmgr_free(pxy_thrmgr_ctx_t *) NONNULL(1);
void pxy_thrmgr_add_conn(pxy_conn_ctx_t *) NONNULL(1);
void pxy_thrmgr_attach(pxy_conn_ctx_t *) NONNULL(1);
void pxy_thrmgr_detach_unlocked(pxy_conn_ctx_t *) NONNULL(1);
void pxy_thrmgr_detach(pxy_conn_ctx_t *) NONNULL(1);
#endif /* !PXYTHRMGR_H */
/* vim: set noet ft=c: */

@ -0,0 +1,676 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "sys.h"
#include "log.h"
#include "defaults.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/un.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <fts.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#ifndef _SC_NPROCESSORS_ONLN
#include <sys/sysctl.h>
#endif /* !_SC_NPROCESSORS_ONLN */
#if HAVE_DARWIN_LIBPROC
#include <libproc.h>
#endif
#include <event2/util.h>
/*
* Permanently drop from root privileges to an unprivileged user account.
* Sets the real, effective and stored user and group ID and the list of
* ancillary groups. This is only safe if the effective user ID is 0.
* If username is unset and the effective uid != uid, drop privs to uid.
* This is to support setuid bit configurations.
* If groupname is set, it will be used instead of the user's default primary
* group.
* If jaildir is set, also chroot to jaildir after reading system files
* but before dropping privileges.
* Returns 0 on success, -1 on failure.
*/
int
sys_privdrop(const char *username, const char *groupname, const char *jaildir)
{
struct passwd *pw = NULL;
struct group *gr = NULL;
int ret = -1;
if (groupname) {
errno = 0;
if (!(gr = getgrnam(groupname))) {
log_err_level_printf(LOG_CRIT, "Failed to getgrnam group '%s': %s\n",
groupname, strerror(errno));
goto error;
}
}
if (username) {
errno = 0;
if (!(pw = getpwnam(username))) {
log_err_level_printf(LOG_CRIT, "Failed to getpwnam user '%s': %s\n",
username, strerror(errno));
goto error;
}
if (gr != NULL) {
pw->pw_gid = gr->gr_gid;
}
if (initgroups(username, pw->pw_gid) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to initgroups user '%s': %s\n",
username, strerror(errno));
goto error;
}
}
if (jaildir) {
if (chroot(jaildir) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to chroot to '%s': %s\n",
jaildir, strerror(errno));
goto error;
}
if (chdir("/") == -1) {
log_err_level_printf(LOG_CRIT, "Failed to chdir to '/': %s\n",
strerror(errno));
goto error;
}
}
if (username) {
if (setgid(pw->pw_gid) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to setgid to %i: %s\n",
pw->pw_gid, strerror(errno));
goto error;
}
if (setuid(pw->pw_uid) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to setuid to %i: %s\n",
pw->pw_uid, strerror(errno));
goto error;
}
} else if (getuid() != geteuid()) {
if (setuid(getuid()) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to setuid(getuid()): %s\n",
strerror(errno));
goto error;
}
}
ret = 0;
error:
if (pw) {
endpwent();
}
if (gr) {
endgrent();
}
return ret;
}
/*
* If the user exists and on successful lookup, return 0 and if uid != NULL,
* write the uid of *username* to the value pointed to by uid.
* Return -1 on failure or if the user does not exist.
*/
int
sys_uid(const char *username, uid_t *uid)
{
struct passwd *pw;
int rv;
errno = 0;
if (!(pw = getpwnam(username))) {
if (errno != 0 && errno != ENOENT) {
log_err_level_printf(LOG_CRIT, "Failed to load user '%s': %s (%i)\n",
username, strerror(errno), errno);
}
rv = -1;
} else {
if (uid)
*uid = pw->pw_uid;
rv = 0;
}
endpwent();
return rv;
}
/*
* Returns 1 if username can be loaded from user database, 0 otherwise.
*/
int
sys_isuser(const char *username)
{
return sys_uid(username, NULL) == 0;
}
/*
* If the group exists and on successful lookup, return 0 and if gid != NULL,
* write the gid of *groupname* to the value pointed to by gid.
* Return -1 on failure or if the group does not exist.
*/
int
sys_gid(const char *groupname, gid_t *gid)
{
struct group *gr;
int rv;
errno = 0;
if (!(gr = getgrnam(groupname))) {
if (errno != 0 && errno != ENOENT) {
log_err_level_printf(LOG_CRIT, "Failed to load group '%s': %s (%i)\n",
groupname, strerror(errno), errno);
}
rv = -1;
} else {
if (gid)
*gid = gr->gr_gid;
rv = 0;
}
endgrent();
return rv;
}
/*
* Returns 1 if groupname can be loaded from group database, 0 otherwise.
*/
int
sys_isgroup(const char *groupname)
{
return sys_gid(groupname, NULL) == 0;
}
/*
* Returns 1 if username is equivalent to the current effective UID.
* Returns 0 otherwise.
*/
int
sys_isgeteuid(const char *username)
{
uid_t uid;
if (sys_uid(username, &uid) == -1)
return 0;
if (uid == geteuid())
return 1;
return 0;
}
/*
* Open and lock process ID file fn.
* Returns open file descriptor on success or -1 on errors.
*/
int
sys_pidf_open(const char *fn)
{
int fd;
if ((fd = open(fn, O_RDWR|O_CREAT, DFLT_PIDFMODE)) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to open '%s': %s\n", fn,
strerror(errno));
return -1;
}
if (flock(fd, LOCK_EX|LOCK_NB) == -1) {
log_err_level_printf(LOG_CRIT, "Failed to lock '%s': %s\n", fn,
strerror(errno));
close(fd);
return -1;
}
return fd;
}
/*
* Write process ID to open process ID file descriptor fd.
* Returns 0 on success, -1 on errors.
*/
int
sys_pidf_write(int fd)
{
char pidbuf[4*sizeof(pid_t)];
int rv;
ssize_t n;
rv = snprintf(pidbuf, sizeof(pidbuf), "%d\n", getpid());
if (rv == -1 || rv >= (int)sizeof(pidbuf))
return -1;
n = write(fd, pidbuf, strlen(pidbuf));
if (n < (ssize_t)strlen(pidbuf))
return -1;
rv = fsync(fd);
if (rv == -1)
return -1;
rv = fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
if (rv == -1)
return -1;
return 0;
}
/*
* Close and remove open process ID file before quitting.
*/
void
sys_pidf_close(int fd, const char *fn)
{
unlink(fn);
close(fd);
}
/*
* Determine address family of addr
*/
int
sys_get_af(const char *addr)
{
if (strstr(addr, ":"))
return AF_INET6;
else if (!strpbrk(addr, "abcdefghijklmnopqrstu"
"vwxyzABCDEFGHIJKLMNOP"
"QRSTUVWXYZ-"))
return AF_INET;
else
return AF_UNSPEC;
}
/*
* Parse an ascii host/IP and port tuple into a sockaddr_storage.
* On success, returns address family and fills in addr, addrlen.
* Returns -1 on error.
*/
int
sys_sockaddr_parse(struct sockaddr_storage *addr, socklen_t *addrlen,
char *naddr, char *nport, int af, int flags)
{
struct evutil_addrinfo hints;
struct evutil_addrinfo *ai;
int rv;
memset(&hints, 0, sizeof(hints));
hints.ai_family = af;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = EVUTIL_AI_ADDRCONFIG | flags;
rv = evutil_getaddrinfo(naddr, nport, &hints, &ai);
if (rv != 0) {
log_err_level_printf(LOG_CRIT, "Cannot resolve address '%s' port '%s': %s\n",
naddr, nport, gai_strerror(rv));
return -1;
}
memcpy(addr, ai->ai_addr, ai->ai_addrlen);
*addrlen = ai->ai_addrlen;
af = ai->ai_family;
freeaddrinfo(ai);
return af;
}
/*
* Converts an IPv4/IPv6 sockaddr into printable string representations of the
* host and the service (port) part. Writes allocated buffers to *host and
* *serv which must both be freed by the caller. Neither *host nor *port are
* freed by this function before newly allocating.
* Returns 0 on success, -1 otherwise. When -1 is returned, pointers in *host
* and *serv are invalid and must not be used nor freed by the caller.
*/
int
sys_sockaddr_str(struct sockaddr *addr, socklen_t addrlen,
char **host, char **serv)
{
char tmphost[INET6_ADDRSTRLEN];
int rv;
size_t hostsz;
*serv = malloc(6); /* max decimal digits of short plus terminator */
if (!*serv) {
log_err_level_printf(LOG_CRIT, "Cannot allocate memory\n");
return -1;
}
rv = getnameinfo(addr, addrlen,
tmphost, sizeof(tmphost),
*serv, 6,
NI_NUMERICHOST | NI_NUMERICSERV);
if (rv != 0) {
log_err_level_printf(LOG_CRIT, "Cannot get nameinfo for socket address: %s\n",
gai_strerror(rv));
free(*serv);
return -1;
}
hostsz = strlen(tmphost) + 1; /* including terminator */
*host = malloc(hostsz);
if (!*host) {
log_err_level_printf(LOG_CRIT, "Cannot allocate memory\n");
free(*serv);
return -1;
}
memcpy(*host, tmphost, hostsz);
return 0;
}
/*
* Sanitizes a valid IPv4 or IPv6 address for use in a filename, i.e. removes
* characters that are invalid on NTFS and replaces them with more innocent
* characters. The function assumes that the input is a valid IPv4 or IPv6
* address; it is not a generic filename sanitizer.
*
* Returns a copy of string s that must be freed by the caller.
*
* Invalid NTFS characters are < > : " / \ | ? * according to
* https://msdn.microsoft.com/en-gb/library/windows/desktop/aa365247.aspx
*/
char *
sys_ip46str_sanitize(const char *s)
{
char *copy, *p;
copy = strdup(s);
if (!copy)
return NULL;
p = copy;
while (*p) {
switch (*p) {
case ':':
case '%':
*p = '_';
break;
}
p++;
}
return copy;
}
/*
* Returns 1 if path points to an existing directory node in the filesystem.
* Returns 0 if path is NULL, does not exist, or points to a file of some kind.
*/
int
sys_isdir(const char *path)
{
struct stat s;
if (stat(path, &s) == -1) {
if (errno != ENOENT) {
log_err_level_printf(LOG_CRIT, "Error stating file: %s (%i)\n",
strerror(errno), errno);
}
return 0;
}
if (s.st_mode & S_IFDIR)
return 1;
return 0;
}
/*
* Create directory including parent directories with mode_t.
* Mode of existing parent directories is not changed.
* Returns 0 on success, -1 and sets errno on error.
*/
int
sys_mkpath(const char *path, mode_t mode)
{
char parent[strlen(path)+1];
char *p;
memcpy(parent, path, sizeof(parent));
p = parent;
do {
/* skip leading '/' characters */
while (*p == '/') p++;
p = strchr(p, '/');
if (p) {
/* overwrite '/' to terminate the string at the next
* parent directory */
*p = '\0';
}
struct stat sbuf;
if (stat(parent, &sbuf) == -1) {
if (errno == ENOENT) {
if (mkdir(parent, mode) != 0)
return -1;
} else {
return -1;
}
} else if (!S_ISDIR(sbuf.st_mode)) {
errno = ENOTDIR;
return -1;
}
if (p) {
/* replace the overwritten slash */
*p = '/';
p++;
}
} while (p);
return 0;
}
/*
* Return realpath(dirname(path)) + / + basename(path) in a newly allocated
* string. Returns NULL on failure and sets errno to ENOENT if the directory
* part does not exist.
*/
char *
sys_realdir(const char *path)
{
char *sep, *udir, *rdir, *p;
int rerrno, rv;
if (path[0] == '\0') {
errno = EINVAL;
return NULL;
}
udir = strdup(path);
if (!udir)
return NULL;
sep = strrchr(udir, '/');
if (!sep) {
free(udir);
rv = asprintf(&udir, "./%s", path);
if (rv == -1)
return NULL;
sep = udir + 1;
} else if (sep == udir) {
return udir;
}
*sep = '\0';
rdir = realpath(udir, NULL);
if (!rdir) {
rerrno = errno;
free(udir);
errno = rerrno;
return NULL;
}
rv = asprintf(&p, "%s/%s", rdir, sep + 1);
rerrno = errno;
free(rdir);
free(udir);
errno = rerrno;
if (rv == -1)
return NULL;
return p;
}
/*
* Portably get the number of CPU cores online in the system.
*/
uint32_t
sys_get_cpu_cores(void)
{
#ifdef _SC_NPROCESSORS_ONLN
return sysconf(_SC_NPROCESSORS_ONLN);
#else /* !_SC_NPROCESSORS_ONLN */
int mib[2];
uint32_t n;
size_t len = sizeof(n);
mib[0] = CTL_HW;
mib[1] = HW_AVAILCPU;
sysctl(mib, sizeof(mib)/sizeof(int), &n, &len, NULL, 0);
if (n < 1) {
mib[1] = HW_NCPU;
sysctl(mib, sizeof(mib)/sizeof(int), &n, &len, NULL, 0);
if (n < 1) {
n = 1;
}
}
return n;
#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;
}
/* vim: set noet ft=c: */

@ -0,0 +1,68 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 SYS_H
#define SYS_H
#include "attrib.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <stdint.h>
int sys_privdrop(const char *, const char *, const char *) WUNRES;
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_uid(const char *, uid_t *) NONNULL(1) WUNRES;
int sys_gid(const char *, gid_t *) NONNULL(1) WUNRES;
int sys_isuser(const char *) NONNULL(1) WUNRES;
int sys_isgroup(const char *) NONNULL(1) WUNRES;
int sys_isgeteuid(const char *) NONNULL(1) WUNRES;
int sys_get_af(const char *);
int sys_sockaddr_parse(struct sockaddr_storage *, socklen_t *,
char *, char *, int, int) NONNULL(1,2,3,4) WUNRES;
int sys_sockaddr_str(struct sockaddr *, socklen_t,
char **, char **) NONNULL(1,3,4);
char * sys_ip46str_sanitize(const char *) NONNULL(1) MALLOC;
int sys_isdir(const char *) NONNULL(1) WUNRES;
int sys_mkpath(const char *, mode_t) NONNULL(1) WUNRES;
char * sys_realdir(const char *) NONNULL(1) MALLOC;
uint32_t sys_get_cpu_cores(void) WUNRES;
ssize_t sys_sendmsgfd(int, void *, size_t, int) NONNULL(2) WUNRES;
ssize_t sys_recvmsgfd(int, void *, size_t, int *) NONNULL(2) WUNRES;
#endif /* !SYS_H */
/* vim: set noet ft=c: */

@ -0,0 +1,229 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "thrqueue.h"
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
/*
* Thread-safe, bounded-size queue based on pthreads mutex and conds.
* Both enqueue and dequeue are available in a blocking and non-blocking
* version.
*/
struct thrqueue {
void **data;
size_t sz, n;
size_t in, out;
unsigned int block_enqueue : 1;
unsigned int block_dequeue : 1;
pthread_mutex_t mutex;
pthread_cond_t notempty;
pthread_cond_t notfull;
};
/*
* Create a new thread-safe queue of size sz.
*/
thrqueue_t *
thrqueue_new(size_t sz)
{
thrqueue_t *queue;
if (!(queue = malloc(sizeof(thrqueue_t))))
goto out0;
if (!(queue->data = malloc(sz * sizeof(void*))))
goto out1;
if (pthread_mutex_init(&queue->mutex, NULL))
goto out2;
if (pthread_cond_init(&queue->notempty, NULL))
goto out3;
if (pthread_cond_init(&queue->notfull, NULL))
goto out4;
queue->sz = sz;
queue->n = 0;
queue->in = 0;
queue->out = 0;
queue->block_enqueue = 1;
queue->block_dequeue = 1;
return queue;
out4:
pthread_cond_destroy(&queue->notempty);
out3:
pthread_mutex_destroy(&queue->mutex);
out2:
free(queue->data);
out1:
free(queue);
out0:
return NULL;
}
/*
* Free all resources associated with queue.
* The caller must ensure that there are no threads still
* using the queue when it is free'd.
*/
void
thrqueue_free(thrqueue_t *queue)
{
free(queue->data);
pthread_mutex_destroy(&queue->mutex);
pthread_cond_destroy(&queue->notempty);
pthread_cond_destroy(&queue->notfull);
free(queue);
}
/*
* Enqueue an item into the queue. Will block if the queue is full.
* If enqueue has been switched to non-blocking mode, never blocks
* but instead returns NULL if queue is full.
* Returns enqueued item on success.
*/
void *
thrqueue_enqueue(thrqueue_t *queue, void *item)
{
pthread_mutex_lock(&queue->mutex);
while (queue->n == queue->sz) {
if (!queue->block_enqueue) {
pthread_mutex_unlock(&queue->mutex);
return NULL;
}
pthread_cond_wait(&queue->notfull, &queue->mutex);
}
queue->data[queue->in++] = item;
queue->in %= queue->sz;
queue->n++;
pthread_mutex_unlock(&queue->mutex);
pthread_cond_broadcast(&queue->notempty);
return item;
}
/*
* Non-blocking enqueue. Never blocks.
* Returns NULL if the queue is full.
* Returns the enqueued item on success.
*/
void *
thrqueue_enqueue_nb(thrqueue_t *queue, void *item)
{
pthread_mutex_lock(&queue->mutex);
if (queue->n == queue->sz) {
pthread_mutex_unlock(&queue->mutex);
return NULL;
}
queue->data[queue->in++] = item;
queue->in %= queue->sz;
queue->n++;
pthread_mutex_unlock(&queue->mutex);
pthread_cond_signal(&queue->notempty);
return item;
}
/*
* Dequeue an item from the queue. Will block if the queue is empty.
* If dequeue has been switched to non-blocking mode, never blocks
* but instead returns NULL if queue is empty.
* Returns dequeued item on success.
*/
void *
thrqueue_dequeue(thrqueue_t *queue)
{
void *item;
pthread_mutex_lock(&queue->mutex);
while (queue->n == 0) {
if (!queue->block_dequeue) {
pthread_mutex_unlock(&queue->mutex);
return NULL;
}
pthread_cond_wait(&queue->notempty, &queue->mutex);
}
item = queue->data[queue->out++];
queue->out %= queue->sz;
queue->n--;
pthread_mutex_unlock(&queue->mutex);
pthread_cond_signal(&queue->notfull);
return item;
}
/*
* Non-blocking dequeue. Never blocks.
* Returns NULL if the queue is empty.
* Returns the dequeued item on success.
*/
void *
thrqueue_dequeue_nb(thrqueue_t *queue)
{
void *item;
pthread_mutex_lock(&queue->mutex);
if (queue->n == 0) {
pthread_mutex_unlock(&queue->mutex);
return NULL;
}
item = queue->data[queue->out++];
queue->out %= queue->sz;
queue->n--;
pthread_mutex_unlock(&queue->mutex);
pthread_cond_signal(&queue->notfull);
return item;
}
/*
* Permanently make all enqueue operations on queue non-blocking and wake
* up all threads currently waiting for the queue to become not full.
* This is to allow threads to finish their work on the queue on application
* shutdown, but not be blocked forever.
*/
void
thrqueue_unblock_enqueue(thrqueue_t *queue)
{
queue->block_enqueue = 0;
pthread_cond_broadcast(&queue->notfull);
sched_yield();
}
/*
* Permanently make all dequeue operations on queue non-blocking and wake
* up all threads currently waiting for the queue to become not empty.
* This is to allow threads to finish their work on the queue on application
* shutdown, but not be blocked forever.
*/
void
thrqueue_unblock_dequeue(thrqueue_t *queue)
{
queue->block_dequeue = 0;
pthread_cond_broadcast(&queue->notempty);
sched_yield();
}
/* vim: set noet ft=c: */

@ -0,0 +1,50 @@
/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
* All rights reserved.
*
* 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,
* 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 THRQUEUE_H
#define THRQUEUE_H
#include "attrib.h"
#include <unistd.h>
typedef struct thrqueue thrqueue_t;
thrqueue_t * thrqueue_new(size_t) MALLOC;
void thrqueue_free(thrqueue_t *) NONNULL(1);
void * thrqueue_enqueue(thrqueue_t *, void *) NONNULL(1) WUNRES;
void * thrqueue_enqueue_nb(thrqueue_t *, void *) NONNULL(1) WUNRES;
void * thrqueue_dequeue(thrqueue_t *) NONNULL(1) WUNRES;
void * thrqueue_dequeue_nb(thrqueue_t *) NONNULL(1) WUNRES;
void thrqueue_unblock_enqueue(thrqueue_t *) NONNULL(1);
void thrqueue_unblock_dequeue(thrqueue_t *) NONNULL(1);
#endif /* !THRQUEUE_H */
/* vim: set noet ft=c: */

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIElDCCA3ygAwIBAgIBATANBgkqhkiG9w0BAQsFADCBkjELMAkGA1UEBhMCVFIx
EDAOBgNVBAgMB0FudGFseWExDjAMBgNVBAcMBVNlcmlrMRIwEAYDVQQKDAlDb21p
eFdhbGwxETAPBgNVBAsMCFNTTHByb3h5MRYwFAYDVQQDDA1jb21peHdhbGwub3Jn
MSIwIAYJKoZIhvcNAQkBFhNzb25lcnRhcmlAZ21haWwuY29tMB4XDTE5MDQyNzEy
MTY0MloXDTIwMDQyNjEyMTY0MlowgZIxCzAJBgNVBAYTAlRSMRAwDgYDVQQIDAdB
bnRhbHlhMQ4wDAYDVQQHDAVTZXJpazESMBAGA1UECgwJQ29taXhXYWxsMREwDwYD
VQQLDAhTU0xwcm94eTEWMBQGA1UEAwwNY29taXh3YWxsLm9yZzEiMCAGCSqGSIb3
DQEJARYTc29uZXJ0YXJpQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBANU+3/V8KIBopview+Kg3q4c2FRyR9SIe87sXhxGrHhAdQqmfhGv
7BcdLeROxjGHNmJkXJTD9yH0RY9C3cYdySwPx6sRrrQlHvKHLLPSs6xnPMDMnjwf
BLxZRI5njZ+UI2FiqAnAxqy5DQn21N803gBlAG1YbWguDF7m8h0bkGmFKjXXBllU
h3qZf/mYPV9TdIj7daUkz/4ZxkKfrwRYkCdet+b5jBFTaWYkakMaE62XjRS2TpYB
3UDrrnuwbR79NU54C4GlNV2i0sARRpK31baiuap40Nhz5wSfHwvMP+x+I+IsBhEW
KePs/HWD2eOetsCy1rDEhi1Tpc+AKvGmqpECAwEAAaOB8jCB7zAMBgNVHRMEBTAD
AQH/MB0GA1UdDgQWBBTtGNULL6e88kyjlAnOpyX6jX4M0TCBvwYDVR0jBIG3MIG0
gBTtGNULL6e88kyjlAnOpyX6jX4M0aGBmKSBlTCBkjELMAkGA1UEBhMCVFIxEDAO
BgNVBAgMB0FudGFseWExDjAMBgNVBAcMBVNlcmlrMRIwEAYDVQQKDAlDb21peFdh
bGwxETAPBgNVBAsMCFNTTHByb3h5MRYwFAYDVQQDDA1jb21peHdhbGwub3JnMSIw
IAYJKoZIhvcNAQkBFhNzb25lcnRhcmlAZ21haWwuY29tggEBMA0GCSqGSIb3DQEB
CwUAA4IBAQAe3v0HBHuJbLbDOUDXcBxg8LXRfwatDcrAwktn1mYctMjJAI7zRhDy
Y+Sm9Ke4VhBUWwdqgYEv7mQDkoUsaZ0+do+kgbIiCOwNwifPh8UuqTK1Kq7/BNYq
UghtJFZKreB3CJDAyCzpK+fiXUnPpo8hdEnAUQDfGhDilmmb0VizdiINxRZqUkhZ
LAmaHC9G56uO9zQKF7j1ngXeQ17pxn2XHkv53DRpcBFxNq4mX75O6dagKAmhllUw
E2UCw5dOfHgoMJy2Cesvu+vr87MRfRH3MKw4dvOYFPEyMaBB6nZnScRiKZArSdlh
frqk1iL+PKJAKeaY9AqR6+bAPXbsr8hb
-----END CERTIFICATE-----

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA1T7f9XwogGim+J7D4qDerhzYVHJH1Ih7zuxeHEaseEB1CqZ+
Ea/sFx0t5E7GMYc2YmRclMP3IfRFj0Ldxh3JLA/HqxGutCUe8ocss9KzrGc8wMye
PB8EvFlEjmeNn5QjYWKoCcDGrLkNCfbU3zTeAGUAbVhtaC4MXubyHRuQaYUqNdcG
WVSHepl/+Zg9X1N0iPt1pSTP/hnGQp+vBFiQJ1635vmMEVNpZiRqQxoTrZeNFLZO
lgHdQOuue7BtHv01TngLgaU1XaLSwBFGkrfVtqK5qnjQ2HPnBJ8fC8w/7H4j4iwG
ERYp4+z8dYPZ4562wLLWsMSGLVOlz4Aq8aaqkQIDAQABAoIBAQDK9qohM8g01+a8
UhhtVuI7ZBb+4Tq+7hyxCUZVsWqsleUQewEZApE6OrqYR+XZ9DDuG0oHCjHpLl8a
iPEehPEZr/dCOnVG3uICwmfoy83ZdPVdKCQfYwKV5n0a8TLZxRybr1Py3Hn8obDo
X/eibuYyB/ANIxIXC2UY2sVssPSViL2exoqi5IV8fwoHR4fMPME1tAxk5lKUf8qD
GFzMomREREl678tPU1RuqvU/pd98Cup8mEwK3yGEbKBHBhjsWPiqlHNCPy0nPt87
fe+4nWMUrWvwhKofHtzFDL/Ojjgm8fejISTXdcTiut3QH94fDi5ksanaRMUJRa/9
h/ReMooBAoGBAO1IWZryMdGnV8+dexSXrr8P8MpcKag6chPw4eTwb34MIWFCqQbE
fK66fh7vw5cSMZaMz679ejFFQvX3lGKFEkrauAbkGausv0R40QtxaOgewUq8sWI2
Ryhh0jnVUTGj9VZOefa6Jm+sabyyCxNeME9iLtoUgtnNdr9JnTaXhoOxAoGBAOYR
H/0RLNAcnFd5/UILplg1u9d4Q+ssI6TL/XGr/59/Us3GKfD9mlSaJOORYPB1Dm3Y
ujodFjq7nUaVjvwIt0DvYok5Nf60ZdRbqOeSrL6To5cujig6m8TDgO3y+xeFXERu
PAO0xxqVJv7TJYTn0jeaNH0ReYECt8g5zi4ZkqzhAoGBAN/9zmiQfSr2l9QrS0bT
MWi3eYztl31xPsNIP1ZJClaoyNHxhXIYBcND1U8K804nJJyD7IG6UqE0StO6lV6z
U+NJuTL4HKuM9TmD+4Kb2nZUog8VNTYLGv0p6rQhhPYhWrXQex3H+ASvc1lnkUEF
RwGJI8VGW4eTh6dnrHseayLhAoGAM4NOGEnKMObXErcbv7gADOwrHPmuq298o5+R
JeyPFdVaYvyl03HVJnBFJjcc73omOu419GEi4w+zDWTbWQ9SiNWQyIzozBj0W/IS
BZfP2fQQwv8HkXZd/laP/bdUBVY07JWKFoJZulf/HTuFwRZu+UgrzH0nKX3ETK60
vZm4P8ECgYAU1tgyXZPbKh3a7VZQbiVOW2AHPrpw+c/Jo8/ROqnV+/PzbtAEn9gW
cKtEWRcYt0CxLqetW4ZpuMVHRPmjeQ5erEk9Wi+SMRGb0OAgjj5T+lgzzHXwCpYJ
MOGenMH+/M4Y6/QeQ42vUaCtqMojpxiO8InMTYL0xHCvy76MgELb8Q==
-----END RSA PRIVATE KEY-----

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIErzCCA5egAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmzELMAkGA1UEBhMCTlox
EzARBgNVBAgMCldlbGxpbmd0b24xEzARBgNVBAcMCkxvd2VyIEh1dHQxFDASBgNV
BAoMC0NvbWl4V2FsbE5aMQ4wDAYDVQQLDAVVVE1GVzEYMBYGA1UEAwwPY29taXh3
YWxsbnoub3JnMSIwIAYJKoZIhvcNAQkBFhNzb25lcnRhcmlAZ21haWwuY29tMB4X
DTE5MDcxNjIwMDk1NloXDTIwMDcxNTIwMDk1NlowgZsxCzAJBgNVBAYTAk5aMRMw
EQYDVQQIDApXZWxsaW5ndG9uMRMwEQYDVQQHDApMb3dlciBIdXR0MRQwEgYDVQQK
DAtDb21peFdhbGxOWjEOMAwGA1UECwwFVVRNRlcxGDAWBgNVBAMMD2NvbWl4d2Fs
bG56Lm9yZzEiMCAGCSqGSIb3DQEJARYTc29uZXJ0YXJpQGdtYWlsLmNvbTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALvy41PXpMn8O3FC0EcU5gbEizo0
l7JN/aSfxHCBjn3r+rhZurbM25d50F2P5dlGY3oQiMfkYz6CIEfU+qmYaVG42VJi
QY1Bu7oMvsGwUIOfGlZRpAtHX7yd0V9OXF0WaliS2T2DBeXdsSGYl/BRmBs0sBCH
5kQzq67npwCxOBjJ55dwp51In56JA1gLV3hFZf9Nf0YjuKBr8p6HXPjug8LRPjMl
naR2NyE4Nu0Ud6L2RtSrmfzEKym3IcwHE6Ml9PiT2DFLwBsvRYjXgq5dL1Cl2jcV
vRE4LtloeRuDjjdqNnAHRCYKowkVIt81wYlFMn1SSGONk3E7RClvM1T+wy8CAwEA
AaOB+zCB+DAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBSmCS1uoDAm9AKxVw/E7wL3
sAwBYzCByAYDVR0jBIHAMIG9gBSmCS1uoDAm9AKxVw/E7wL3sAwBY6GBoaSBnjCB
mzELMAkGA1UEBhMCTloxEzARBgNVBAgMCldlbGxpbmd0b24xEzARBgNVBAcMCkxv
d2VyIEh1dHQxFDASBgNVBAoMC0NvbWl4V2FsbE5aMQ4wDAYDVQQLDAVVVE1GVzEY
MBYGA1UEAwwPY29taXh3YWxsbnoub3JnMSIwIAYJKoZIhvcNAQkBFhNzb25lcnRh
cmlAZ21haWwuY29tggEAMA0GCSqGSIb3DQEBCwUAA4IBAQA709412mgZaAI1k4Em
N2ZwCaMJAFimQKWVNJtFUV+doa6oa0zwXEBHq8VnRC1mFX5BLmZ/XkXOA6Df/onQ
qg9nNjFo0SnxSbh/Z/6WBmh2RmJOpXjY6QM7t/aEsmy+/e1LEcnZhXxSH+8PWYOl
UqVzZxLfTXfIpxaH3F65CW//ziXzWng1AeQRqF9+YhJnsuDXxP4WWfAoaVk+95Uz
1Z5lPTjkGpJoDwwTJK5rMzfl/QPDJKzhUtnDkwP4PfsRIzzIJ5PEvJ8utBkoaz70
/vuOaXX+oVgNg+1rLZv65UfJdbsBLiWam3dBpwxv/7jfqBcR3BpiSmGX+y6kEKBR
OkNT
-----END CERTIFICATE-----

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAu/LjU9ekyfw7cULQRxTmBsSLOjSXsk39pJ/EcIGOfev6uFm6
tszbl3nQXY/l2UZjehCIx+RjPoIgR9T6qZhpUbjZUmJBjUG7ugy+wbBQg58aVlGk
C0dfvJ3RX05cXRZqWJLZPYMF5d2xIZiX8FGYGzSwEIfmRDOrruenALE4GMnnl3Cn
nUifnokDWAtXeEVl/01/RiO4oGvynodc+O6DwtE+MyWdpHY3ITg27RR3ovZG1KuZ
/MQrKbchzAcToyX0+JPYMUvAGy9FiNeCrl0vUKXaNxW9ETgu2Wh5G4OON2o2cAdE
JgqjCRUi3zXBiUUyfVJIY42TcTtEKW8zVP7DLwIDAQABAoIBAQCbIdo9hAsCpW0z
CWD8quVKxMPeoTZs+C7ZyP3Nl0JT8YmazIVvCRxfpS41a+beNlk5kZg01fCM0b9v
Sv4zKhgUWi0W8P8GZzFaWdbB7JQocBS/Ftf+b0U4XFNLBVT7iNryjRM+0Hz3xMrF
3jvbIp4YJp8EpgeMV9HnplGUIrMY0XOx5zlOGPEIiHLwqox61NXf5AKJKebXlDXy
s+IB+fyh4OaJM+xXKZXQNMSO8AkXsBJyxzMUi1AZnxOvOYrcT/9sEdG8OVHdqi4e
WxTzbKVVi5VbiZfH7i2+fjx3j+Q+oWri5+NdrDM5FW/h3yc904uMzMBpiRYsoBbI
XNnDc6aBAoGBAOo17oTaSZNw+w6m71oELVnpD0nIwdeU41pg9if45RRqyxFVEDVE
Y+5TrRNnjjt0nuGBV0hXcwXLPAPHVmhI8mJqSX9Ucl7oagXEiBnGzFQ9bBQbgoAL
JrrEHVmK7knye+3c+hWuZVj6e3MbHrG+iByAYr/nK6aR8GxkyXJsgnuhAoGBAM1v
KXirV5GDky7ZWPVMLulMr67Q7YkDdHcBKY/WIqMqVrhyjK2sQ9uC8AXLG7c8ozNM
hpQqwyFGmFuiUu/exhK2MGKyVjkBWBI/QUNMNYsTctCvLWrFNGZYpYJyVzcLTzeG
wyKvfcJZ5pqnH0zpjY461du6rv2gbK7btyZOKUzPAoGAGTg/e9zJiW/kCNre8TF7
9K+M7uQIQ3+Lz8KbHwjFGiK5xR8ExMedfx9RhsJi5XIUXbIAxRBtmVUeHEYNvuMD
/qb1TRm5yxB5wi88fJIUlImcprmjnsgUno3Znze/mwTaZW2jHEyQKzmlq2pjLr6W
h4MnnwR0hdY3LmTX8FNQ5aECgYBhlYtSmbmIEsMofvAl3WFvSxEs7mvXKL/7A59V
hvd/IE5/YVJ38Rtuu1z+s4Nf4Dr51EEdQs7cEKew54OUE/Ns0gRb7bDNdVj7mfaL
XDkW7k8c2Amv7Ss97p/4Pg41xHaFvssUv93yIzhKgFZ693pZdJM/xAb5zWmzCgWO
+ZPeuwKBgQDRRyGy3qQH6SxiKMNQ8FxjjagxKe/NR37EJTBmJoDaHfkcHU7fmov5
qP6zAEdtpY2smZEsfkhKYSvLeiqPwdAYmppLWAnP2WJ+USbEFiENT4vNzX/3QmHK
dBB/CetyXlqzdnSS21CsPA+CSChNJme8z+GLI8uq5RPVY09FrH0q5A==
-----END RSA PRIVATE KEY-----

@ -0,0 +1,49 @@
{
"comment": "Tests for CACert/CAKey",
"configs": {
"1": {
"proto": {
"proto": "ssl"
},
"client": {
"ip": "127.0.0.1",
"port": "8457"
},
"server": {
"ip": "127.0.0.1",
"port": "9457",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Forges certs using the global CA cert/key pair",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n",
"assert": {
"peer_certificate": {
"==": [
"TR, Antalya, Serik, ComixWall, SSLproxy, comixwall.org, sonertari@gmail.com"
]
}
}
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
}
}
}
}
}

@ -0,0 +1,50 @@
{
"comment": "Tests for CACert/CAKey",
"configs": {
"1": {
"proto": {
"proto": "ssl"
},
"client": {
"ip": "127.0.0.1",
"port": "8458"
},
"server": {
"ip": "127.0.0.1",
"port": "9458",
"crt": "server2.crt",
"key": "server2.key",
"comment": "We need a crt/key pair different from the other tests to avoid cache HIT"
}
}
},
"tests": {
"1": {
"comment": "Forges certs using the proxyspec CA cert/key pair",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n",
"assert": {
"peer_certificate": {
"==": [
"NZ, Wellington, Lower Hutt, ComixWallNZ, UTMFW, comixwallnz.org, sonertari@gmail.com"
]
}
}
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
}
}
}
}
}

@ -0,0 +1,340 @@
{
"comment": "Tests for HTTP request headers: SSLproxy, Connection, Upgrade, Keep-Alive, Accept-Encoding, Via, X-Forwarded-For, and Referer",
"configs": {
"1": {
"proto": {
"proto": "tcp"
},
"client": {
"ip": "127.0.0.1",
"port": "8180"
},
"server": {
"ip": "127.0.0.1",
"port": "9180"
}
},
"2": {
"proto": {
"proto": "ssl",
"tcp_nodelay": "yes",
"ip_ttl": "15",
"connect_timeout": "1000",
"read_timeout": "50",
"write_timeout": "50",
"verify_peer": "no",
"ciphers": "MEDIUM:HIGH",
"no_ssl2": "yes",
"no_ssl3": "yes",
"no_tls10": "yes",
"no_tls11": "yes",
"no_tls12": "yes",
"no_tls13": "yes",
"min_proto_version": "ssl3",
"max_proto_version": "tls13",
"ecdhcurve": "prime256v1",
"use_sni": "no",
"verify_hostname": "no",
"compression": "no"
},
"client": {
"ip": "127.0.0.1",
"port": "8446",
"ciphers": "MEDIUM",
"use_sni": "yes",
"verify_hostname": "yes",
"no_tls10": "no",
"max_proto_version": "tls11"
},
"server": {
"ip": "127.0.0.1",
"port": "9446",
"crt": "server.crt",
"key": "server.key",
"ciphers": "HIGH",
"no_tls12": "no",
"min_proto_version": "tls12",
"compression": "yes"
}
}
},
"tests": {
"1": {
"comment": "Removes any extra SSLproxy line",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nSSLproxy: sslproxy\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": "",
"comment": "To obtain server crt, SSLproxy srvdst connects/disconnects to the server without sending any data, so we should have this as the second state in all tests"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"6": {
"testend": "server",
"cmd": "timeout",
"payload": "",
"comment": "Just a sample timeout command"
}
}
},
"2": {
"comment": "Removes all extra SSLproxy lines",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nSSLproxy: sslproxy\r\nSSLproxy: sslproxy\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"3": {
"comment": "Changes Connection header to close",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: Keep-Alive\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"4": {
"comment": "Suppresses upgrading to SSL/TLS, WebSockets or HTTP/2",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nUpgrade: websocket\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"5": {
"comment": "Removes Keep-Alive",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nKeep-Alive: keep-alive\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"6": {
"comment": "Does not remove Accept-Encoding by default (it's a config option)",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nAccept-Encoding: encoding\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nAccept-Encoding: encoding\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"7": {
"comment": "Removes Via",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nVia: via\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"8": {
"comment": "Removes X-Forwarded-For",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nX-Forwarded-For: x-forwarded-for\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"9": {
"comment": "Removes Referer",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nReferer: referer\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
}
}
}

@ -0,0 +1,236 @@
{
"comment": "Tests for HTTP response headers: Public-Key-Pins, Public-Key-Pins-Report-Only, Strict-Transport-Security, Expect-CT, Alternate-Protocol, Upgrade, OCSP request",
"configs": {
"1": {
"proto": {
"proto": "tcp"
},
"client": {
"ip": "127.0.0.1",
"port": "8181"
},
"server": {
"ip": "127.0.0.1",
"port": "9181"
}
},
"2": {
"proto": {
"proto": "ssl",
"crt": "server.crt",
"key": "server.key"
},
"client": {
"ip": "127.0.0.1",
"port": "8447"
},
"server": {
"ip": "127.0.0.1",
"port": "9447"
}
}
},
"tests": {
"1": {
"comment": "Removes Public-Key-Pins",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nPublic-Key-Pins: public-key-pins\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"2": {
"comment": "Removes Public-Key-Pins-Report-Only",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nPublic-Key-Pins-Report-Only: public-key-pins-report-only\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"3": {
"comment": "Removes Strict-Transport-Security",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nStrict-Transport-Security: strict-transport-security\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"4": {
"comment": "Removes Expect-CT",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nExpect-CT: expect-ct\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"5": {
"comment": "Removes Alternate-Protocol",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nAlternate-Protocol: alternate-protocol\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"6": {
"comment": "Removes Upgrade",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
},
"4": {
"testend": "server",
"cmd": "send",
"payload": "HTTP/1.1 302 Found\r\nUpgrade: upgrade\r\nLocation: sslproxy\r\n\r\n"
},
"5": {
"testend": "client",
"cmd": "recv",
"payload": "HTTP/1.1 302 Found\r\nLocation: sslproxy\r\n\r\n"
}
}
},
"7": {
"comment": "Does not deny OCSP request",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "POST / HTTP/1.1\r\nHost: comixwall.org\r\nContent-Type: application/ocsp-request\r\n\r\n",
"comment": "It is easier to send a dummy POST ocsp request than a valid GET one"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "POST / HTTP/1.1\r\nHost: comixwall.org\r\nContent-Type: application/ocsp-request\r\nConnection: close\r\n\r\n"
}
}
}
}
}

@ -0,0 +1,101 @@
{
"comment": "Tests for HTTP response headers: Deny OCSP request, Remove Accept-Encoding, and Do not remove Referer",
"configs": {
"1": {
"proto": {
"proto": "tcp"
},
"client": {
"ip": "127.0.0.1",
"port": "8186"
},
"server": {
"ip": "127.0.0.1",
"port": "9186"
}
},
"2": {
"proto": {
"proto": "ssl",
"crt": "server.crt",
"key": "server.key"
},
"client": {
"ip": "127.0.0.1",
"port": "8448"
},
"server": {
"ip": "127.0.0.1",
"port": "9448"
}
}
},
"tests": {
"1": {
"comment": "Denies OCSP request",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "POST / HTTP/1.1\r\nHost: comixwall.org\r\nContent-Type: application/ocsp-request\r\n\r\n",
"comment": "It is easier to send a dummy POST ocsp request than a valid GET one"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "client",
"cmd": "recv",
"payload_file": "payload_ocsp_denied_response.bin"
},
"4": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
},
"2": {
"comment": "Remove Accept-Encoding",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nAccept-Encoding: encoding\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
}
}
},
"3": {
"comment": "Does not remove Referer",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nReferer: referer\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nReferer: referer\r\nConnection: close\r\n\r\n"
}
}
}
}
}

@ -0,0 +1,55 @@
{
"comment": "Tests for Passthrough",
"configs": {
"1": {
"comment": "Passthrough should behave similar to direct connection, so test direct connection first",
"proto": {
"proto": "ssl",
"verify_peer": "no"
},
"client": {
"ip": "127.0.0.1",
"port": "9454"
},
"server": {
"ip": "127.0.0.1",
"port": "9454",
"crt": "server.crt",
"key": "server.key"
}
},
"2": {
"proto": {
"proto": "ssl",
"verify_peer": "no"
},
"client": {
"ip": "127.0.0.1",
"port": "8454"
},
"server": {
"ip": "127.0.0.1",
"port": "9454",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Passes conn through if ssl handshake fails",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
}
}
}
}
}

@ -0,0 +1,7 @@
HTTP/1.0 200 OK
Content-Type: application/ocsp-response
Content-Length: 5
Connection: close
0


@ -0,0 +1,117 @@
{
"comment": "Tests for HTTP GET method validation",
"configs": {
"1": {
"proto": {
"proto": "tcp"
},
"client": {
"ip": "127.0.0.1",
"port": "8184"
},
"server": {
"ip": "127.0.0.1",
"port": "9184"
}
},
"2": {
"proto": {
"proto": "ssl"
},
"client": {
"ip": "127.0.0.1",
"port": "8444"
},
"server": {
"ip": "127.0.0.1",
"port": "9444",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Validates GET",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": "",
"comment": "To obtain server crt, SSLproxy srvdst connects/disconnects to the server without sending any data, so we should have this as the second state in all tests"
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
}
}
},
"2": {
"comment": "Does not validate GE as a method",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GE / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "timeout",
"payload": "",
"comment": "SSLproxy should not validate method GE, so not connect to server"
}
}
},
"3": {
"comment": "Does not validate GE1 as a method",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GE1 / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
},
"4": {
"comment": "Does not validate GET1 as a method",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET1 / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
}
}
}

@ -0,0 +1,116 @@
{
"comment": "Tests for HTTP POST method validation",
"configs": {
"1": {
"proto": {
"proto": "tcp"
},
"client": {
"ip": "127.0.0.1",
"port": "8185"
},
"server": {
"ip": "127.0.0.1",
"port": "9185"
}
},
"2": {
"proto": {
"proto": "ssl"
},
"client": {
"ip": "127.0.0.1",
"port": "8445"
},
"server": {
"ip": "127.0.0.1",
"port": "9445",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Validates POST",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "POST / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "POST / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
}
}
},
"2": {
"comment": "Does not validate POS as a method",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "POS / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "timeout",
"payload": "",
"comment": "SSLproxy should not validate method POS, so not connect to server"
}
}
},
"3": {
"comment": "Does not validate POS1 as a method",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "POS1 / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
},
"4": {
"comment": "Does not validate POST1 as a method",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "POST1 / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
}
}
}

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDlDCCAnwCAQEwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlRSMRAwDgYD
VQQIDAdBbnRhbHlhMQ4wDAYDVQQHDAVTZXJpazESMBAGA1UECgwJQ29taXhXYWxs
MQ4wDAYDVQQLDAVVVE1GVzEWMBQGA1UEAwwNY29taXh3YWxsLm9yZzEiMCAGCSqG
SIb3DQEJARYTc29uZXJ0YXJpQGdtYWlsLmNvbTAeFw0xOTA0MjcxMjIyNDlaFw0y
MDA0MjYxMjIyNDlaMIGPMQswCQYDVQQGEwJUUjEQMA4GA1UECAwHQW50YWx5YTEO
MAwGA1UEBwwFU2VyaWsxEjAQBgNVBAoMCUNvbWl4V2FsbDEOMAwGA1UECwwFVVRN
RlcxFjAUBgNVBAMMDWNvbWl4d2FsbC5vcmcxIjAgBgkqhkiG9w0BCQEWE3NvbmVy
dGFyaUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDl
KmwGjqh9BCtLRPM2VZBI0N7WM0ihygo21bp3f/Hb4/WH1+yxy5evqM96rlQz1z+l
CP72AU5+qPf3niXXvYBqyXl0kd/ZlQ50qkUdvO85ttFcwZqCaZeEhJNeh+R0cUfj
A0JBfJJf20sTTpRj0+GNHOtfoGoT9AI60TPZDygh62qRGWwxhfESc9g2UIu4Zbzc
llBa+mi+sqkI+HRoJyT0f/QBK1yGRNYbZ3uChuKW4fBSfXQMzftK80kMpzRtLSUP
TfKoUWcl/PMedyeRH4xOwmj79UrQkN8Nw/fX9N7EheUesxmUMri5KbaHtmWsCgd7
vdAlZWR9Mkp6AmJihe69AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAELYz1kevbF2
8evTEUKf2MzWx4NXnpVeYvHKYQaZhs9Q8fbjRpPUfOv43QQoBQf8GObxXTikjne/
Egz+abXY99jvckc2SuU72EHq1wtnjcy6pkCiZ1X1NUlcjKvNWjFpzbqnZtMG+8/b
qcTtz72HhEfu1yMk0v9yja0n7MIhTHDCXT25DhKE/kDEyUGyIIUEqOrmvloQcrpO
LmXlLfh92tMh+3hcE/NdfwzUB+KcKFffYxbGcKDq3QjqxALzZQNjWKIcLIemjAbS
EXrmlT7pO2aSXGY5W5cq/k4M9teZd19lRxd7NlIXxQ0nqfLLKAfModpto9buvVnY
NLTngehTe2k=
-----END CERTIFICATE-----

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlKmwGjqh9BCtL
RPM2VZBI0N7WM0ihygo21bp3f/Hb4/WH1+yxy5evqM96rlQz1z+lCP72AU5+qPf3
niXXvYBqyXl0kd/ZlQ50qkUdvO85ttFcwZqCaZeEhJNeh+R0cUfjA0JBfJJf20sT
TpRj0+GNHOtfoGoT9AI60TPZDygh62qRGWwxhfESc9g2UIu4ZbzcllBa+mi+sqkI
+HRoJyT0f/QBK1yGRNYbZ3uChuKW4fBSfXQMzftK80kMpzRtLSUPTfKoUWcl/PMe
dyeRH4xOwmj79UrQkN8Nw/fX9N7EheUesxmUMri5KbaHtmWsCgd7vdAlZWR9Mkp6
AmJihe69AgMBAAECggEBAKo0JsV8ARHRHeKqlET/qckbMNZAgqp/Z7SwauzEXzrl
BYBeU9L9lmyF4YuJnMpgDUoi81J9R/J0xq7ni82/Vnh5qTZ1/Z8ZwQHPUMQ0hi0E
Zl0RAhPoHvvyh43HOltto2mtCvGedwyw0ALH8xhtzvDC0OJWP1MQgPN0W6DUTAdb
7JvWSUsVtYGvAfodF4O+OpeExUhK5UmJLs4shx49QGwx2wiIGiBeppXUSXbeXFTS
7fH+/Jn1u1djJtUolmFqSVnQ+TlzdzAzJv3oI2Dh6Z/Zmmt253KmMwOIFSPb37Oj
jIaNRjaUk8+/o218eAIxvP2PukqnleBhiKEajiC6YgECgYEA/5OjKcftaSPpwOJT
l+KT3zfR87dRX64hK/QQl4tzk4jAfC5GGS3mZz+DLp7Tb9q2MJ05JMDY9nCw9dgX
Y10qhmwx9QllV7eZAYj1PNyaHSJBwZW+nOHW3/8Wb5fm/08M2Ix1nqrFaWak+9MH
ZJJJxmx1Yg8vrPk0gbs1HhEpXj0CgYEA5YuWKCaF5NeBg86poVO5q8EpFc5D+o/2
sWO3NkXO3bCiPDpSB1lagoFk+YGsJFEavcMzA4fL56QyBRIII0Q7eC9xiMFaAmZ2
df+6+WkG0p0y/3IysCIffPvit3wl0NyGELBV2RjyT+TWTnj1bhJB+cG9fnJYHED5
aIbo1Hx8WoECgYEA7U10+09SRgR9f4XmHinYKTOYMtXsrY1t6XTFjyCBYORh7Su+
FJUE0KstL7VzFvGZpsvlChYxnakG9k39cVpJKOT/AckGmwqy+7TiF++w/QoYyhkz
ElGzhOpCN71wU3BfuhW5BGkFwZb364aG37/g1mdQGouFNfNx6F8ds9w1b/UCgYB5
RmtZ4SiChD2AJvLGMlb5YH9VbvnJasA6bmWAvhcSV10Plvx5t4KzSqvPiuwo79TC
B1xvdBKN2tk2hpVYFJea4u4IA6eLdxkFNMxTM2MQaU9lWK9hEVYzkVzx9hyAodIP
BSsGHUEAZvW21f9NXUYQL4TvRng4zTc4O3bNtMt6gQKBgCREU9pT8BFyMtd2Hye3
xW9LnsUA+eIM85CrXaIgZiqqnhqogTy0UqMZzABs4B+CZLeUv5nutjBvicNzJmir
TzFLGcIN35J2g3sZvA/wuL0U09nKywkOoYhq5lMEptmaPCLeh7XiuU9gM1wpvbNM
EfnCgF6NRIF84AnhWU0i1iVx
-----END PRIVATE KEY-----

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDrDCCApQCAQAwDQYJKoZIhvcNAQELBQAwgZsxCzAJBgNVBAYTAk5aMRMwEQYD
VQQIDApXZWxsaW5ndG9uMRMwEQYDVQQHDApMb3dlciBIdXR0MRQwEgYDVQQKDAtD
b21peFdhbGxOWjEOMAwGA1UECwwFVVRNRlcxGDAWBgNVBAMMD2NvbWl4d2FsbG56
Lm9yZzEiMCAGCSqGSIb3DQEJARYTc29uZXJ0YXJpQGdtYWlsLmNvbTAeFw0xOTA3
MTYyMDE2MzFaFw0yMDA3MTUyMDE2MzFaMIGbMQswCQYDVQQGEwJOWjETMBEGA1UE
CAwKV2VsbGluZ3RvbjETMBEGA1UEBwwKTG93ZXIgSHV0dDEUMBIGA1UECgwLQ29t
aXhXYWxsTloxDjAMBgNVBAsMBVVUTUZXMRgwFgYDVQQDDA9jb21peHdhbGxuei5v
cmcxIjAgBgkqhkiG9w0BCQEWE3NvbmVydGFyaUBnbWFpbC5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrsqnVqGCBiBCpU0isyxlLiN9OGs/Re3ZZ
ZEr9wVdEyn7x3UVmdYcYBrLwhCTVW1NRsJ1fVh4dQwDSC0fMENdsyNmhMt9yDO7P
QGMvVu/ILzsBJJLUfyzzqffTsZfujQ7ftQu87zEgoy+91dehpjpH3/7NhwkjHkc4
/a08EhhBbbYIgrjwaXbZLAvJOjQoPM5UHqK34i1uzCleFnI17hDzkZjNfbnk6TYv
5PLWLaGpRhQ39upg1hrvbTIHtF9eoBryApNyR5emYrQUGih26HszfefCg8vswI+e
Cm2E5saSrrJWN7PF44yTsCgGkTV/Co1rNap0ATwyDsP4jGIGTaLrAgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAJ9j+EQj/+JvOG890TtxS7RZG/FjKJjJ9I/WzKadpzaG
fHlqDwz6xK3Z7LusxawXf+/NN3SmXRydRloHEYIGBk0x9DfIpzD/bXoD81+37vCD
2W1D7WrUAjnvdHr4P6bGIJLf8chZEKBV1UKV187iCvdXunV1TxZYV1Yp7ZayLlJD
/cyEP5CY/ZFVXN17i/XfqiUt8tiX+P8gzI0snbfKXADcbR3ki4iZ7jtM4vP1FLsJ
NvJJEvtc3+Ujifiyl8w0g4LF/tFStcQmXOfU1j6egj0a7RBItyfovEbRZWCAa5fV
EKLkrXZROnLubRVZ4gPuwvSPZf3gvFsHaiYYsCrG5Jw=
-----END CERTIFICATE-----

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCrsqnVqGCBiBCp
U0isyxlLiN9OGs/Re3ZZZEr9wVdEyn7x3UVmdYcYBrLwhCTVW1NRsJ1fVh4dQwDS
C0fMENdsyNmhMt9yDO7PQGMvVu/ILzsBJJLUfyzzqffTsZfujQ7ftQu87zEgoy+9
1dehpjpH3/7NhwkjHkc4/a08EhhBbbYIgrjwaXbZLAvJOjQoPM5UHqK34i1uzCle
FnI17hDzkZjNfbnk6TYv5PLWLaGpRhQ39upg1hrvbTIHtF9eoBryApNyR5emYrQU
Gih26HszfefCg8vswI+eCm2E5saSrrJWN7PF44yTsCgGkTV/Co1rNap0ATwyDsP4
jGIGTaLrAgMBAAECggEAbflU8OYVUkJNsbcHkK3R+OClb0ibHek/h8hGhDxnYkd1
ggOhZRwYOJpp2MY7NXF2b5d5ZUwqo8F78A2LojiTUzles/J6znlcKOFr5jGACi++
UmATsAn5R+TxOOXCk+hDw6QZ7Z0jUW1UsfobUI3BPLJLaWd5wcWuBuHtKNHKOXU3
wh1dNYALEFxe3YSsspV5uSrVJPXP0FvFfODRWDldh2s2VNq9p8hBSYl8HSULA2Df
yPkGy0SpRL6PhdnG36YgtrjasWAq605aCGhai2jMgguRzeEtJMadcDvh6yUX94xW
/DfZ/NJqdv08128Rn7QFydXe+jgIPgxi7ErCaJqx0QKBgQDivkQU/kn0pAbYu3/h
ciSf3hkGUJRxKsKbjDIu3ix2RrBNJY7tLQj0RmKDr4lljk2RE6Xl/EZHAYisNRFI
tHytZFcxM6pnBoJpmdGepN0V3PlroMbUveBtr0FbkRDFcaeqRXwEzpZIYIO/X1iD
9A46NpMZpqDkAIYjCZBsSeIGMwKBgQDB2ibu6pusFxUwdNCcsQs1O40soUuYn+io
wNtBDKqPJnsutjP6lR1aQ9lgrNiMI/kShTY6P2BDAyvxChVgp/LxKV/46iZKTw/u
UtK9D+soxiCK1S09gAARym4KtQuAHaQRDq9c8r/0Wtcm1uBUtKWt9Zu5Y0LTF28e
VA7Ri3qIaQKBgEXDTKc45gBDR2f/qITw3ZvidcifmkyHX4EETZxl90Ac17mkyKjo
pkyiK0VhCOEaO3tblDuCtwy5yBdT7JF2FrYynTEiOFeihRWAoiIxj2RerM8UlJh4
I0kcFvvZUlOteGzHHDVOaqayK1cCOvW+bXIzwGawAeik0KCPHMR0pvpJAoGAKE3t
LluFBmC+PRbskMeWpvi8v3zBtPf6bau7amjxxVWg4vNrFzyNn8jfl2QYmfqYvKsJ
vU6T+xrbtf/8td31ewK5O42jbGvHyitaOYjnwdB/z53HDDRiz1AhVQSTYY3IIOvG
tjKainmgpiii97mfgO3B9OeYaz9CETI06ohvb5ECgYBgNq1Qy1MjRP7takJnRB0p
3cRPs9eGvom0N0yLmsPZNbPsTQVgA6X2pQ8pIOHEXG8WE3AQ9BcRgI0GivERlr3+
7nzcgMsF/6UsDOYQyAlqduBaIE+fmar1Mbbzkgm7PZ/FnhRQ967qH22+fvJaSDoY
vwMYU2YZfUECNzIkk6FRiQ==
-----END PRIVATE KEY-----

@ -0,0 +1,41 @@
{
"comment": "Tests for ssl connection on tcp proxyspec",
"configs": {
"1": {
"proto": {
},
"client": {
"proto": "ssl",
"ip": "127.0.0.1",
"port": "8183"
},
"server": {
"proto": "tcp",
"ip": "127.0.0.1",
"port": "9183"
}
}
},
"tests": {
"1": {
"comment": "Does not accept ssl connection on tcp proxyspec if validating HTTP only",
"states": {
"1": {
"testend": "client",
"cmd": "sslconnectfail",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
}
}
}

@ -0,0 +1,213 @@
{
"comment": "Tests for SSL configuration",
"configs": {
"1": {
"proto": {
"proto": "ssl",
"tcp_nodelay": "yes",
"ip_ttl": "15",
"connect_timeout": "1000",
"read_timeout": "50",
"write_timeout": "50",
"verify_peer": "no",
"ciphers": "MEDIUM:HIGH",
"no_ssl2": "yes",
"no_ssl3": "yes",
"no_tls10": "yes",
"no_tls11": "yes",
"no_tls12": "yes",
"no_tls13": "yes",
"min_proto_version": "ssl3",
"max_proto_version": "tls13",
"ecdhcurve": "prime256v1",
"use_sni": "no",
"verify_hostname": "no",
"compression": "no"
},
"client": {
"ip": "127.0.0.1",
"port": "8443",
"crt": "server.crt",
"key": "server.key",
"ciphers": "MEDIUM",
"use_sni": "yes",
"sni_servername": "comixwall.org",
"verify_hostname": "yes",
"no_tls10": "no",
"max_proto_version": "tls11"
},
"server": {
"ip": "127.0.0.1",
"port": "9443",
"crt": "server.crt",
"key": "server.key",
"ciphers": "HIGH",
"no_tls12": "no",
"min_proto_version": "tls12",
"compression": "yes"
}
}
},
"tests": {
"1": {
"comment": "Configures ssl cert, proto, ciphers correctly",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n",
"assert": {
"current_cipher_name": {
"match": [
"^DHE-\\w+-\\w+-\\w+",
"\\w+-\\w+-SEED-\\w+",
"\\w+-\\w+-\\w+-SHA$"
],
"!match": [
"ECDHE-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+",
"[A-Z0-9]+-[A-Z0-9]+-AES256-[A-Z0-9]+-[A-Z0-9]+",
"[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-SHA384"
]
},
"current_cipher_version": {
"==": [
"SSLv3",
"TLSv1"
],
"!match": [
"^TLSv1\\.[1-3]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1"
],
"!=": [
"SSLv3"
],
"!match": [
"^TLSv1\\.[1-3]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
},
"peer_certificate": {
"==": [
"TR, Antalya, Serik, ComixWall, SSLproxy, comixwall.org, sonertari@gmail.com"
]
},
"peer_certificate_not_before": {
">=": [
"-2"
],
"<=": [
"0"
]
},
"peer_certificate_not_after": {
">=": [
"363"
],
"<=": [
"365"
]
}
}
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": "",
"assert": {
"current_cipher_name": {
"match": [
"ECDHE-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+",
"[A-Z0-9]+-[A-Z0-9]+-AES256-[A-Z0-9]+-[A-Z0-9]+",
"[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-SHA384"
],
"!match": [
"^DHE-\\w+-\\w+-\\w+",
"\\w+-\\w+-SEED-\\w+",
"\\w+-\\w+-\\w+-SHA$"
]
},
"current_cipher_version": {
"==": [
"TLSv1.2"
],
"!match": [
"^(SSLv3|TLSv1|TLSv1\\.[13]?)$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1.2"
],
"!match": [
"^(SSLv3|TLSv1|TLSv1\\.[13]?)$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
},
"sni_servername": {
"==": [
"comixwall.org"
]
}
}
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n",
"assert": {
"current_cipher_name": {
"match": [
"ECDHE-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+",
"[A-Z0-9]+-[A-Z0-9]+-AES256-[A-Z0-9]+-[A-Z0-9]+",
"[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+-SHA384"
],
"!match": [
"^DHE-\\w+-\\w+-\\w+",
"\\w+-\\w+-SEED-\\w+",
"\\w+-\\w+-\\w+-SHA$"
]
},
"current_cipher_version": {
"==": [
"TLSv1.2"
],
"!match": [
"^(SSLv3|TLSv1|TLSv1\\.[13]?)$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1.2"
],
"!match": [
"^(SSLv3|TLSv1|TLSv1\\.[13]?)$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
},
"sni_servername": {
"==": [
"comixwall.org"
]
}
}
}
}
}
}
}

@ -0,0 +1,144 @@
{
"comment": "Tests for SSL configuration: tls10 only",
"configs": {
"1": {
"proto": {
"proto": "ssl",
"no_ssl2": "yes",
"no_ssl3": "yes",
"no_tls10": "no",
"no_tls11": "yes",
"no_tls12": "yes",
"no_tls13": "yes"
},
"client": {
"ip": "127.0.0.1",
"port": "8449"
},
"server": {
"ip": "127.0.0.1",
"port": "9449",
"crt": "server.crt",
"key": "server.key"
}
},
"2": {
"proto": {
"proto": "ssl",
"no_ssl2": "no",
"no_ssl3": "no",
"no_tls10": "no",
"no_tls11": "no",
"no_tls12": "no",
"no_tls13": "no"
},
"client": {
"ip": "127.0.0.1",
"port": "8449"
},
"server": {
"ip": "127.0.0.1",
"port": "9449",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Configures tls10 only",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.0"
],
"!match": [
"SSLv3",
"^TLSv1\\.[1-3]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[1-3]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": "",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.0"
],
"!match": [
"SSLv3",
"^TLSv1\\.[1-3]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[1-3]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.0"
],
"!match": [
"SSLv3",
"^TLSv1\\.[1-3]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[1-3]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
}
}
}
}
}

@ -0,0 +1,150 @@
{
"comment": "Tests for SSL configuration: tls10/tls11 only",
"configs": {
"1": {
"proto": {
"proto": "ssl",
"no_ssl2": "yes",
"no_ssl3": "yes",
"no_tls10": "yes",
"no_tls11": "no",
"no_tls12": "yes",
"no_tls13": "yes"
},
"client": {
"ip": "127.0.0.1",
"port": "8450"
},
"server": {
"ip": "127.0.0.1",
"port": "9450",
"crt": "server.crt",
"key": "server.key"
}
},
"2": {
"proto": {
"proto": "ssl",
"no_ssl2": "no",
"no_ssl3": "no",
"no_tls10": "no",
"no_tls11": "no",
"no_tls12": "no",
"no_tls13": "no"
},
"client": {
"ip": "127.0.0.1",
"port": "8450"
},
"server": {
"ip": "127.0.0.1",
"port": "9450",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Configures tls10/tls11 only",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.0",
"TLSv1.1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[23]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1.0",
"TLSv1.1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[23]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": "",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.0",
"TLSv1.1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[23]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1.0",
"TLSv1.1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[23]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.0",
"TLSv1.1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[23]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1.0",
"TLSv1.1"
],
"!match": [
"SSLv3",
"^TLSv1\\.[23]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
}
}
}
}
}

@ -0,0 +1,150 @@
{
"comment": "Tests for SSL configuration: tls12 only",
"configs": {
"1": {
"proto": {
"proto": "ssl",
"no_ssl2": "yes",
"no_ssl3": "yes",
"no_tls10": "yes",
"no_tls11": "yes",
"no_tls12": "no",
"no_tls13": "yes"
},
"client": {
"ip": "127.0.0.1",
"port": "8451"
},
"server": {
"ip": "127.0.0.1",
"port": "9451",
"crt": "server.crt",
"key": "server.key"
}
},
"2": {
"proto": {
"proto": "ssl",
"no_ssl2": "no",
"no_ssl3": "no",
"no_tls10": "no",
"no_tls11": "no",
"no_tls12": "no",
"no_tls13": "no"
},
"client": {
"ip": "127.0.0.1",
"port": "8451"
},
"server": {
"ip": "127.0.0.1",
"port": "9451",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Configures tls12 only",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.2"
],
"!match": [
"SSLv3",
"^TLSv1\\.[013]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1.2"
],
"!=": [
"SSLv3"
],
"!match": [
"^TLSv1\\.[013]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": "",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.2"
],
"!match": [
"SSLv3",
"^TLSv1\\.[013]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1.2"
],
"!=": [
"SSLv3"
],
"!match": [
"^TLSv1\\.[013]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n",
"assert": {
"current_cipher_version": {
"==": [
"TLSv1.2"
],
"!match": [
"SSLv3",
"^TLSv1\\.[013]?$"
]
},
"ssl_proto_version": {
"==": [
"TLSv1.2"
],
"!=": [
"SSLv3"
],
"!match": [
"^TLSv1\\.[013]?$"
]
},
"ssl_state": {
"==": [
"SSLOK "
]
}
}
}
}
}
}
}

@ -0,0 +1,64 @@
{
"comment": "Tests for SSL configuration: Rejects unsupported SSL/TLS proto",
"configs": {
"1": {
"proto": {
"proto": "ssl",
"no_ssl2": "yes",
"no_ssl3": "yes",
"no_tls10": "yes",
"no_tls11": "yes",
"no_tls12": "no",
"no_tls13": "yes"
},
"client": {
"ip": "127.0.0.1",
"port": "8452"
},
"server": {
"ip": "127.0.0.1",
"port": "9452",
"crt": "server.crt",
"key": "server.key"
}
},
"2": {
"proto": {
"proto": "ssl",
"no_ssl2": "yes",
"no_ssl3": "yes",
"no_tls10": "no",
"no_tls11": "yes",
"no_tls12": "yes",
"no_tls13": "yes"
},
"client": {
"ip": "127.0.0.1",
"port": "8453"
},
"server": {
"ip": "127.0.0.1",
"port": "9453",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Rejects tls10 over tls12 and tls12 over tls10 proxyspecs",
"states": {
"1": {
"testend": "client",
"cmd": "sslconnectfail",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
}
}
}

@ -0,0 +1,248 @@
# TestProxy test configuration for sslproxy v0.6.0
# Global options
#User _sslproxy
#Group _sslproxy
#Chroot /var/run/sslproxy
PidFile /var/run/sslproxy.pid
#Daemon yes
Debug yes
DebugLevel 4
#OpenFilesLimit 1024
#LeafCerts /etc/sslproxy/leaf.key
#LeafKeyRSABits 1024
#OpenSSLEngine cloudhsm
#TargetCertDir /etc/sslproxy/target
#WriteGenCertsDir /var/log/sslproxy
#WriteAllCertsDir /var/log/sslproxy
#ConnectLog /var/log/sslproxy/connect.log
#ContentLog /var/log/sslproxy/content.log
#ContentLogDir /var/log/sslproxy/content
#ContentLogPathSpec /var/log/sslproxy/%X/%u-%s-%d-%T.log
#LogProcInfo yes
#PcapLog /var/log/sslproxy/content.pcap
#PcapLogDir /var/log/sslproxy/pcap
#PcapLogPathSpec /var/log/sslproxy/%X/%u-%s-%d-%T.pcap
#MirrorIf lo
#MirrorTarget 192.0.2.1
#MasterKeyLog /var/log/sslproxy/masterkeys.log
LogStats yes
StatsPeriod 1
ConnIdleTimeout 120
ExpiredConnCheckPeriod 10
SSLShutdownRetryDelay 100
#UserDBPath /var/db/users.db
# Default ProxySpec options (cloned to each proxyspec)
CACert ca.crt
CAKey ca.key
#ClientCert /etc/sslproxy/client.crt
#ClientKey /etc/sslproxy/client.key
#CAChain /etc/sslproxy/chain.crt
#CRL http://example.com/example.crl
#DenyOCSP yes
#Passthrough yes
#DHGroupParams /etc/sslproxy/dh.pem
#ECDHCurve prime256v1
#SSLCompression no
#ForceSSLProto tls12
#DisableSSLProto tls10
#Ciphers MEDIUM:HIGH
#NATEngine netfilter
#RemoveHTTPAcceptEncoding no
#RemoveHTTPReferer yes
VerifyPeer no
#AllowWrongHost no
#UserAuth no
#UserTimeout 300
#UserAuthURL https://192.168.0.1/userdblogin.php
#ValidateProto no
#MaxHTTPHeaderSize 8192
#PassSite example.com
#PassSite example.com 192.168.0.1
#PassSite example.com soner
#PassSite *.google.com * android
# Tests for tcp connection over ssl proxyspec
ProxySpec https 127.0.0.1 8441 up:8080 127.0.0.1 9441
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8442
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9442
ValidateProto yes
}
# Tests for ssl connection on tcp proxyspec
ProxySpec {
Proto http
Addr 127.0.0.1
Port 8183
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9183
ValidateProto yes
}
# Tests for HTTP GET method validation
ProxySpec {
Proto http
Addr 127.0.0.1
Port 8184
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9184
ValidateProto yes
}
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8444
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9444
ValidateProto yes
}
# Tests for HTTP POST method validation
ProxySpec {
Proto http
Addr 127.0.0.1
Port 8185
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9185
ValidateProto yes
}
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8445
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9445
ValidateProto yes
}
# Tests for SSL configuration
ProxySpec https 127.0.0.1 8443 up:8080 127.0.0.1 9443
# Tests for SSL configuration: tls10 only
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8449
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9449
ForceSSLProto tls10
}
# Tests for SSL configuration: tls11 only
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8450
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9450
ForceSSLProto tls11
}
# Tests for SSL configuration: tls12 only
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8451
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9451
ForceSSLProto tls12
}
# Tests for SSL configuration: Rejects unsupported SSL/TLS proto
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8452
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9452
ForceSSLProto tls10
}
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8453
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9453
ForceSSLProto tls12
}
# Tests for HTTP request headers: SSLproxy, Connection, Upgrade, Keep-Alive, Accept-Encoding, Via, X-Forwarded-For, and Referer
ProxySpec http 127.0.0.1 8180 up:8080 127.0.0.1 9180
ProxySpec https 127.0.0.1 8446 up:8080 127.0.0.1 9446
# Tests for HTTP response headers: Public-Key-Pins, Public-Key-Pins-Report-Only, Strict-Transport-Security, Expect-CT, Alternate-Protocol, Upgrade, OCSP request
ProxySpec http 127.0.0.1 8181 up:8080 127.0.0.1 9181
ProxySpec https 127.0.0.1 8447 up:8080 127.0.0.1 9447
# Tests for HTTP response headers: Deny OCSP request, remove Accept-Encoding, and do not remove Referer
ProxySpec {
Proto http
Addr 127.0.0.1
Port 8186
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9186
DenyOCSP yes
RemoveHTTPAcceptEncoding yes
RemoveHTTPReferer no
}
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8448
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9448
DenyOCSP yes
RemoveHTTPAcceptEncoding yes
RemoveHTTPReferer no
}
# Tests for Passthrough
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8454
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9454
Passthrough yes
VerifyPeer yes
}
# Tests for VerifyPeer
ProxySpec https 127.0.0.1 8455 up:8080 127.0.0.1 9455
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8456
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9456
VerifyPeer yes
}
# Tests for CACert/CAKey
ProxySpec https 127.0.0.1 8457 up:8080 127.0.0.1 9457
ProxySpec {
Proto https
Addr 127.0.0.1
Port 8458
DivertPort 8080
TargetAddr 127.0.0.1
TargetPort 9458
CACert ca2.crt
CAKey ca2.key
}

@ -0,0 +1,57 @@
{
"comment": "Tests for tcp connection over ssl proxyspec",
"configs": {
"1": {
"comment": "Test should pass without proto validation enabled",
"proto": {
"comment": "Test config should always have the proto key"
},
"client": {
"proto": "tcp",
"ip": "127.0.0.1",
"port": "8441"
},
"server": {
"proto": "ssl",
"ip": "127.0.0.1",
"port": "9441",
"crt": "server.crt",
"key": "server.key"
}
},
"2": {
"comment": "Test should pass with proto validation enabled",
"proto": {
},
"client": {
"proto": "tcp",
"ip": "127.0.0.1",
"port": "8442"
},
"server": {
"proto": "ssl",
"ip": "127.0.0.1",
"port": "9442",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Does not pass tcp connection over ssl proxyspec",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
}
}
}

@ -0,0 +1,42 @@
{
"comment": "SSLproxy tests",
"testharnesses": {
"1": {
"comment": "HTTP tests",
"testsets": {
"1": "http_testset_1.json",
"2": "http_testset_2.json",
"3": "http_testset_3.json"
}
},
"2": {
"comment": "SSL config tests",
"testsets": {
"1": "ssl_testset_1.json",
"2": "ssl_testset_2.json",
"3": "ssl_testset_3.json",
"4": "ssl_testset_4.json",
"5": "ssl_testset_5.json"
}
},
"3": {
"comment": "Protocol validation tests",
"testsets": {
"1": "tcp_ssl_testends_testset_1.json",
"2": "ssl_tcp_testends_testset_1.json",
"3": "proto_validate_testset_1.json",
"4": "proto_validate_testset_2.json"
}
},
"4": {
"comment": "Various option tests",
"testsets": {
"1": "passthrough_testset_1.json",
"2": "verifypeer_testset_1.json",
"3": "verifypeer_testset_2.json",
"4": "ca_testset_1.json",
"5": "ca_testset_2.json"
}
}
}
}

@ -0,0 +1,37 @@
{
"comment": "Tests for UserAuth",
"configs": {
"1": {
"proto": {
"proto": "ssl"
},
"client": {
"ip": "127.0.0.1",
"port": "8458"
},
"server": {
"ip": "127.0.0.1",
"port": "9458",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Rejects IP with user auth enabled",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
}
}
}

@ -0,0 +1,42 @@
{
"comment": "Tests for VerifyPeer",
"configs": {
"1": {
"proto": {
"proto": "ssl"
},
"client": {
"ip": "127.0.0.1",
"port": "8455"
},
"server": {
"ip": "127.0.0.1",
"port": "9455",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Accepts peer without verification",
"states": {
"1": {
"testend": "client",
"cmd": "send",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "recv",
"payload": ""
},
"3": {
"testend": "server",
"cmd": "recv",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\nConnection: close\r\n\r\n"
}
}
}
}
}

@ -0,0 +1,37 @@
{
"comment": "Tests for VerifyPeer",
"configs": {
"1": {
"proto": {
"proto": "ssl"
},
"client": {
"ip": "127.0.0.1",
"port": "8456"
},
"server": {
"ip": "127.0.0.1",
"port": "9456",
"crt": "server.crt",
"key": "server.key"
}
}
},
"tests": {
"1": {
"comment": "Rejects peer with verification",
"states": {
"1": {
"testend": "client",
"cmd": "sslconnectfail",
"payload": "GET / HTTP/1.1\r\nHost: comixwall.org\r\n\r\n"
},
"2": {
"testend": "server",
"cmd": "timeout",
"payload": ""
}
}
}
}
}

@ -1531,7 +1531,9 @@ protossl_bev_eventcb_error_srvdst(UNUSED struct bufferevent *bev, pxy_conn_ctx_t
/* the callout to the original destination failed,
* e.g. because it asked for client cert auth, so
* close the accepted socket and clean up */
if (ctx->spec->opts->passthrough && ctx->sslctx->have_sslerr) {
// Passite is and can only be set in protossl_srcssl_create() after srvdst obtains the orig cert
// So the passsite condition here will most probably never used
if ((ctx->spec->opts->passthrough || ctx->passsite) && ctx->sslctx->have_sslerr) {
/* ssl callout failed, fall back to plain TCP passthrough of SSL connection */
log_err_level_printf(LOG_WARNING, "SSL srvdst connection failed; falling back to passthrough\n");
ctx->sslctx->have_sslerr = 0;

Loading…
Cancel
Save