pax_global_header00006660000000000000000000000064134021211020014474gustar00rootroot0000000000000052 comment=fc5f90d7222c0c7be27c7514f53a5e5756c39c46 sniproxy-0.6.0/000077500000000000000000000000001340212110200133725ustar00rootroot00000000000000sniproxy-0.6.0/.gitignore000066400000000000000000000004411340212110200153610ustar00rootroot00000000000000a.out *.o *.cdbs-orig .deps .dirstamp *.swp *.swo *.gcov *.gcda *.gcno .vimrc depcomp compile config.guess config.sub core autoscan.log aclocal.m4 autom4te.cache configure gmon.out install-sh missing Makefile Makefile.in config.status config.log INSTALL sniproxy-*.tar.gz tags test-driver sniproxy-0.6.0/.travis.yml000066400000000000000000000053671340212110200155160ustar00rootroot00000000000000language: c compiler: - clang - gcc install: - sudo apt-get update - DEBIAN_FRONTEND=noninteractive sudo apt-get install -y apache2-utils cdbs dh-autoreconf devscripts libev-dev libpcre3-dev libudns-dev lintian rpm valgrind - mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} - ./autogen.sh script: - echo "Running unit and functional tests" - ./configure - make all check - bash -c "cd tests && sudo ./transparent_proxy_test" - echo "Checking for memory leaks" - bash -c "cd tests && ./bad_dns_request_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./bad_request_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./connection_reset_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./functional_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./hostname_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./slow_client_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./wildcard_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./bind_source_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./reload_test valgrind --leak-check=full --error-exitcode=1" - bash -c "cd tests && ./proxy_header_test valgrind --leak-check=full --error-exitcode=1" - echo "Testing Debian package build" - dpkg-buildpackage -us -uc - echo "Testing RPM package build" - make dist - rpmbuild --define "_sourcedir `pwd`" -ba --nodeps redhat/sniproxy.spec - echo "Testing Debian package installation, configuration, and usage" - lintian --suppress-tags debian-changelog-file-contains-invalid-email-address ../sniproxy_*_`dpkg --print-architecture`.deb - sudo dpkg -i ../sniproxy_*_`dpkg --print-architecture`.deb - sudo perl -pi -e 's/ENABLED=0/ENABLED=1/' /etc/default/sniproxy - echo -e "user daemon\n\npidfile /var/run/sniproxy.pid\n\nerror_log {\n syslog daemon\n priority notice\n}\n\nlisten 80 {\n proto http\n}\n\ntable {\n localhost *\n}" | sudo tee /etc/sniproxy.conf - sudo service sniproxy start - sleep 1; test -e /var/run/sniproxy.pid && ps -up `cat /var/run/sniproxy.pid` - echo "Testing config reload functionality" - echo -e "user daemon\n\npidfile /var/run/sniproxy.pid\n\nerror_log {\n syslog daemon\n priority notice\n}\n\nlisten 80 {\n proto http\n}\n\nlisten 81 {\n proto http\n}\n\ntable {\n localhost *\n}" | sudo tee /etc/sniproxy.conf - sudo kill -HUP `cat /var/run/sniproxy.pid` - sudo netstat -lptn | egrep ':::81.+/sniproxy' - sudo service sniproxy stop - sudo apt-get remove sniproxy - echo "Rebuilding without DNS and rerunning unit tests" - ./configure --disable-dns - make clean all check sniproxy-0.6.0/ARCHITECTURE.md000066400000000000000000000044031340212110200155770ustar00rootroot00000000000000 +---------------+ |Config: | | config_file | | username | +---------------+ | \----------\ v | +-----------+ v |Listener: |+ +-------+ +------------+ | socket || |Table: |+ |Backend: |+ | protocol ||--has one-->| name ||--has many->| pattern* ||+ +-----------+| +-------+| | address ||| +-----------+ +-------+ | port ||| ^ +------------+|| | +-------------+ +------------+| | |Connection: |+ +------------+ | | state ||+ ^ \-references--| listener ||| | | client ||| | | socket ||| | | buffer ||| | | server |||--selected from---/ | socket ||| | buffer ||| +-------------+|| +-------------+| +-------------+ Listeners are listening service ports, each has an associated address, port, protocol and socket. When an incoming connection is accepted on the socket, a new connection object is created. The first packet is inspected and the hostname is extracted from the TLS Client Hello or HTTP Request (depending on protocol selected). The listener's table is consulted for backend matching the requested hostname - this match may be simple matching strings or regular expressions. A second server connection is established to the address and port specified by the backend, and the initial packet is forwarded to over this second socket. From this point on, when a packet is received from either the client or server, its contents are buffered and sent through the other socket. When either the client or server closes the socket, the buffer to the other socket is sent and the connection is closed. After both sockets have been closed the connection is removed. sniproxy-0.6.0/AUTHORS000066400000000000000000000000471340212110200144430ustar00rootroot00000000000000Dustin Lundquist sniproxy-0.6.0/COPYING000066400000000000000000000024711340212110200144310ustar00rootroot00000000000000Copyright (c) 2011 - 2013, Dustin Lundquist 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 HOLDERS 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. sniproxy-0.6.0/ChangeLog000066400000000000000000000027711340212110200151530ustar00rootroot000000000000002018-12-05 Dustin Lundquist 0.6.0 Release * PROXY v1 protocol support * SO_REUSEPORT support on Linux 3.9 and later * Listener ipv6_only directive to accept only IPv6 connections * TCP keepalive 2017-04-26 Dustin Lundquist 0.5.0 Release * Transparent proxy support * Use accept4() on Linix * Run as group specified in config 2015-04-07 Dustin Lundquist 0.4.0 release * Improve DNS resolver: Support for AAAA records Configuration options * Global access log * Man page for sniproxy.conf * Reject IP literals as hostnames for wildcard backends 2014-09-26 Dustin Lundquist 0.3.6 release * Improve logging: Fix negative connection duration in access log Include log rotate script Reopen log files on SIGHUP Share file handle to same log file between listeners Avoid unnecessary reconnection to syslog socket Cache timestamp string for current second * Man page * Packaging improvements: passes lintian and rpm-lint 2014-08-13 Dustin Lundquist 0.3.5 release * Configuration reloading on SIGHUP * SSL 2.0 connection handling: do not treat as an error, use fallback address if configured. * Fix buffer_coalesce error * Spawn privileged child to bind sockets to privileged ports on reload * Add -V flag to return sniproxy version * Use libev for timestamps to improve portability * Include several for BSD compatibility * Large file support (for log files) sniproxy-0.6.0/Makefile.am000066400000000000000000000001111340212110200154170ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 SUBDIRS = src \ man \ tests sniproxy-0.6.0/NEWS000066400000000000000000000000001340212110200140570ustar00rootroot00000000000000sniproxy-0.6.0/README000077700000000000000000000000001340212110200155242README.mdustar00rootroot00000000000000sniproxy-0.6.0/README.md000066400000000000000000000103521340212110200146520ustar00rootroot00000000000000SNI Proxy ========= Proxies incoming HTTP and TLS connections based on the hostname contained in the initial request of the TCP session. This enables HTTPS name-based virtual hosting to separate backend servers without installing the private key on the proxy machine. Features -------- + Name-based proxying of HTTPS without decrypting traffic. No keys or certificates required. + Supports both TLS and HTTP protocols. + Supports IPv4, IPv6 and Unix domain sockets for both back end servers and listeners. + Supports multiple listening sockets per instance. + Supports HAProxy proxy protocol to propagate original source address to backend servers. Usage ----- Usage: sniproxy [-c ] [-f] [-n ] [-V] -c configuration file, defaults to /etc/sniproxy.conf -f run in foreground, do not drop privileges -n specify file descriptor limit -V print the version of SNIProxy and exit Installation ------------ For Debian or Fedora based Linux distributions see building packages below. **Prerequisites** + Autotools (autoconf, automake, gettext and libtool) + libev4, libpcre and libudns development headers + Perl and cURL for test suite **Install** ./autogen.sh && ./configure && make check && sudo make install **Building Debian/Ubuntu package** This is the preferred installation method on recent Debian based distributions: 1. Install required packages sudo apt-get install autotools-dev cdbs debhelper dh-autoreconf dpkg-dev gettext libev-dev libpcre3-dev libudns-dev pkg-config fakeroot devscripts 2. Build a Debian package ./autogen.sh && dpkg-buildpackage 3. Install the resulting package sudo dpkg -i ../sniproxy__.deb **Building Fedora/RedHat package** This is the preferred installation method for modern Fedora based distributions. 1. Install required packages sudo yum install autoconf automake curl gettext-devel libev-devel pcre-devel perl pkgconfig rpm-build udns-devel 2. Build a distribution tarball: ./autogen.sh && ./configure && make dist 3. Build a RPM package rpmbuild --define "_sourcedir `pwd`" -ba redhat/sniproxy.spec 4. Install resulting RPM sudo yum install ../sniproxy-..rpm I've used Scientific Linux 6 a fair amount, but I prefer Debian based distributions. RPM builds are tested in Travis-CI on Ubuntu, but not natively. This build process may not follow the current Fedora packaging standards, and may not even work. ***Building on OS X with Homebrew*** 1. install dependencies. brew install libev pcre udns autoconf automake gettext libtool 2. Read the warning about gettext and force link it so autogen.sh works. We need the GNU gettext for the macro `AC_LIB_HAVE_LINKFLAGS` which isn't present in the default OS X package. brew link --force gettext 3. Make it so ./autogen.sh && ./configure && make OS X support is a best effort, and isn't a primary target platform. Configuration Syntax -------------------- user daemon pidfile /tmp/sniproxy.pid error_log { syslog daemon priority notice } listener 127.0.0.1:443 { protocol tls table TableName # Specify a server to use if the initial client request doesn't contain # a hostname fallback 192.0.2.5:443 } table TableName { # Match exact request hostnames example.com 192.0.2.10:4343 # If port is not specified the listener port will be used example.net [2001:DB8::1:10] # Or use regular expression to match .*\\.com [2001:DB8::1:11]:443 # Combining regular expression and wildcard will resolve the hostname # client requested and proxy to it .*\\.edu *:443 } DNS Resolution -------------- Using hostnames or wildcard entries in the configuration requires sniproxy to be built with [UDNS](http://www.corpit.ru/mjt/udns.html). SNIProxy will still build without UDNS, but these features will be unavailable. UDNS uses a single UDP socket for all queries, so it is recommended you use a local caching DNS resolver (with a single socket each DNS query is protected by spoofing by a single 16 bit query ID, which makes it relatively easy to spoof). sniproxy-0.6.0/TODO000066400000000000000000000004001340212110200140540ustar00rootroot00000000000000* ALPN support * Improved table backend lookup, currently this is a linear search ** Considering splitting hostname at label boundaries and search backwards from TLD * HTTP or DNS interface for backend servers to determine remote IP and port of connection sniproxy-0.6.0/autogen.sh000077500000000000000000000001341340212110200153710ustar00rootroot00000000000000#!/bin/sh ./setver.sh autoreconf --install automake --add-missing --copy > /dev/null 2>&1 sniproxy-0.6.0/config.rpath000066400000000000000000000000001340212110200156650ustar00rootroot00000000000000sniproxy-0.6.0/configure.ac000066400000000000000000000051541340212110200156650ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.60]) AC_INIT([sniproxy], [0.6.0]) AC_CONFIG_SRCDIR([src/sniproxy.c]) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([subdir-objects]) AM_SILENT_RULES([yes]) AC_GNU_SOURCE # Checks for programs. AC_PROG_CC_C99 # Required by automake < 1.14 AM_PROG_CC_C_O # Checks for libraries. PKG_CHECK_MODULES([LIBEV], [libev], HAVE_LIBEV=yes; AC_DEFINE(HAVE_LIBEV, 1), [AC_LIB_HAVE_LINKFLAGS(ev,, [#include ], [ev_run(0,0);]) if test x$ac_cv_libev = xyes; then AC_SUBST([LIBEV_LIBS], [$LIBEV]) else AC_MSG_ERROR([[*** *** libev4 was not found. ***]]) fi ]) PKG_CHECK_MODULES([LIBPCRE], [libpcre], HAVE_LIBPCRE=yes; AC_DEFINE(HAVE_LIBPCRE, 1), [AC_LIB_HAVE_LINKFLAGS(pcre,, [#include ], [pcre_exec(0,0,0,0,0,0,0,0);]) if test x$ac_cv_libpcre = xyes; then AC_SUBST([LIBPCRE_LIBS], [$LIBPCRE]) else AC_MSG_ERROR([[*** *** libpcre was not found. ***]]) fi ]) AC_ARG_ENABLE([dns], [AS_HELP_STRING([--disable-dns], [Disable DNS resolution])], [dns="$withval"], [dns=yes]) AM_CONDITIONAL([DNS_ENABLED], [test "x$dns" = "xyes"]) AS_IF([test "x$dns" = "xyes"], [PKG_CHECK_MODULES([LIBUDNS], [libudns], HAVE_LIBUDNS=yes; AC_DEFINE(HAVE_LIBUDNS, 1), [AC_LIB_HAVE_LINKFLAGS(udns,, [#include ], [dns_init(0, 0);]) AS_IF([test x$ac_cv_libudns = xyes], [AC_SUBST([LIBUDNS_LIBS], [$LIBUDNS])]) ]) ]) AC_ARG_ENABLE([rfc3339-timestamps], [AS_HELP_STRING([--enable-rfc3339-timestamps], [Enable RFC3339 timestamps])], [rfc3339_timestamps=${enableval}], [rfc3339_timestamps=no]) AS_IF([test "x$rfc3339_timestamps" = "xyes"], [AC_DEFINE([RFC3339_TIMESTAMP], 1, [RFC3339 timestamps enabled])]) # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h syslog.h unistd.h],, AC_MSG_ERROR([required header(s) not found])) # Checks for typedefs, structures, and compiler characteristics. AC_C_INLINE AC_TYPE_PID_T AC_TYPE_UID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT16_T AC_TYPE_UINT8_T # Checks for library functions. AC_FUNC_FORK AC_FUNC_MALLOC AC_FUNC_REALLOC AC_FUNC_STRTOD AC_CHECK_FUNCS([atexit daemon memset socket strcasecmp strchr strdup strerror strncasecmp strrchr strspn strtoul],, AC_MSG_ERROR([required functions(s) not found])) AC_CHECK_FUNCS([accept4]) # Enable large file support (so we can log more than 2GB) AC_SYS_LARGEFILE AC_CONFIG_FILES([Makefile src/Makefile man/Makefile tests/Makefile]) AC_OUTPUT sniproxy-0.6.0/debian/000077500000000000000000000000001340212110200146145ustar00rootroot00000000000000sniproxy-0.6.0/debian/.gitignore000066400000000000000000000005001340212110200165770ustar00rootroot00000000000000autoreconf.after autoreconf.before files sniproxy.debhelper.log sniproxy.postinst.debhelper sniproxy.postrm.debhelper sniproxy.prerm.debhelper sniproxy.substvars sniproxy sniproxy-dbg.debhelper.log sniproxy-dbg.substvars sniproxy-dbg stamp-autotools stamp-autotools-files stamp-makefile-build stamp-makefile-install tmp sniproxy-0.6.0/debian/changelog000066400000000000000000000064501340212110200164730ustar00rootroot00000000000000sniproxy (0.6.0) unstable; urgency=medium * PROXY v1 protocol support * SO_REUSEPORT support on Linux 3.9 and later * Listener ipv6_only directive to accept only IPv6 connections * TCP keepalive -- Dustin Lundquist Wed, 05 Dec 2018 20:12:24 -0800 sniproxy (0.5.0) unstable; urgency=medium * Transparent proxy support * Use accept4() on Linix * Run as group specified in config -- Dustin Lundquist Wed, 26 Apr 2017 07:17:13 -0700 sniproxy (0.4.0) unstable; urgency=medium * Improve DNS resolver: Support for AAAA records Configuration options * Global access log * Man page for sniproxy.conf * Reject IP literals as hostnames for wildcard backends -- Dustin Lundquist Tue, 07 Apr 2015 09:14:41 -0700 sniproxy (0.3.6) unstable; urgency=medium * Improve logging: Fix negative connection duration in access log Include log rotate script Reopen log files on SIGHUP Share file handle to same log file between listeners Avoid unnecessary reconnection to syslog socket Cache timestamp string for current second * Man page * Packaging improvements: passes lintian and rpm-lint -- Dustin Lundquist Fri, 26 Sep 2014 19:52:38 -0700 sniproxy (0.3.5) unstable; urgency=medium * Configuration reloading on SIGHUP * SSL 2.0 connection handling: do not treat as an error, use fallback address if configured. * Fix buffer_coalesce error * Spawn privileged child to bind sockets to privileged ports on reload * Add -V flag to return sniproxy version * Use libev for timestamps to improve portability * Include several for BSD compatibility -- Dustin Lundquist Wed, 13 Aug 2014 18:25:53 -0700 sniproxy (0.3.4) unstable; urgency=medium * Add source address specification configuration option. * Line buffer log files. * Fix segfault when no hostname included in TLS extensions. * Fix erroneously report of invalid TLS client handshake. -- Dustin Lundquist Sun, 18 May 2014 14:38:33 -0700 sniproxy (0.3.3) unstable; urgency=medium * Fix format argument segfault in buffer full warning. * Add sniproxy-dbg package. * File descriptor limit: raise limit and improve handling when limit is reached. -- Dustin Lundquist Tue, 22 Apr 2014 17:35:59 -0700 sniproxy (0.3.2-1) unstable; urgency=high * Fix use after free when client closes connection before DNS response is received. * Fix two DNS query memory leaks. -- Dustin Lundquist Fri, 11 Apr 2014 16:32:06 -0700 sniproxy (0.3.1-1) unstable; urgency=high * Fix bug when client completely fills the buffer before the DNS query is answered. * Fix handling of invalid hostnames in client requests. -- Dustin Lundquist Wed, 09 Apr 2014 21:08:55 -0700 sniproxy (0.3-1) unstable; urgency=medium * Nonblocking connect and DNS resolution -- Dustin Lundquist Tue, 08 Apr 2014 17:03:37 -0700 sniproxy (0.2) unstable; urgency=low * Moving pidfile -- Dustin Lundquist Thu, 30 Jan 2014 13:51:02 -0800 sniproxy (0.1-1) unstable; urgency=low * Initial release -- Andreas Loibl Tue, 18 Jun 2013 17:55:43 +0200 sniproxy-0.6.0/debian/compat000066400000000000000000000000021340212110200160120ustar00rootroot000000000000008 sniproxy-0.6.0/debian/control000066400000000000000000000023601340212110200162200ustar00rootroot00000000000000Source: sniproxy Section: web Priority: optional Maintainer: Dustin Lundquist Build-Depends: cdbs, debhelper (>= 8.0.0), dh-autoreconf, autotools-dev, gettext, pkg-config, libev-dev (>= 4.0), libpcre3-dev, libudns-dev Standards-Version: 3.9.5 Vcs-Git: https://github.com/dlundquist/sniproxy.git Vcs-Browser: https://github.com/dlundquist/sniproxy Package: sniproxy Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Recommends: logrotate Description: Transparent TLS and HTTP layer 4 proxy with SNI support Proxies incoming HTTP and TLS connections based on the hostname contained in the initial request of the TCP session. This enables HTTPS name-based virtual hosting to separate backend servers without installing the private key on the proxy machine. Package: sniproxy-dbg Architecture: any Section: debug Priority: extra Depends: sniproxy (= ${binary:Version}), ${misc:Depends} Description: debugging symbols for sniproxy Proxies incoming HTTP and TLS connections based on the hostname contained in the initial request. This enables HTTPS name based virtual hosting to separate backend servers without the installing the private key on the proxy machine. . This package contains the debugging symbols for sniproxy. sniproxy-0.6.0/debian/copyright000066400000000000000000000031251340212110200165500ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: HTTPS-SNI-Proxy Source: Files: * Copyright: 2011 - 2013 Dustin Lundquist License: BSD-2 Files: debian/* Copyright: 2013 Andreas Loibl License: BSD-2 License: BSD-2 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 HOLDERS 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 HOLDERS 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. sniproxy-0.6.0/debian/docs000066400000000000000000000000331340212110200154630ustar00rootroot00000000000000NEWS README README.md TODO sniproxy-0.6.0/debian/init.d000066400000000000000000000073001340212110200157240ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: sniproxy # Required-Start: $network $local_fs $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: HTTPS SNI Proxy # Description: Proxies incoming HTTP and TLS connections based on the # hostname contained in the initial request. ### END INIT INFO # Author: Andreas Loibl # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="HTTPS SNI proxy" # Introduce a short description here NAME=sniproxy # Introduce the short server's name here DAEMON=/usr/sbin/sniproxy # Introduce the server's location here DAEMON_ARGS="" # Arguments to run the daemon with PIDFILE="/var/run/sniproxy.pid" SCRIPTNAME=/etc/init.d/$NAME ENABLED=1 # Exit if the package is not installed [ -x $DAEMON ] || exit 0 # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet \ --pidfile $PIDFILE \ --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet \ --pidfile $PIDFILE \ --exec $DAEMON -- $DAEMON_ARGS \ || return 2 # Add code here, if necessary, that waits for the process to be ready # to handle requests from services started subsequently which depend # on this one. As a last resort, sleep for some time. } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet \ --pidfile $PIDFILE \ --retry=TERM/30/KILL/5 --name "$(basename $DAEMON)" RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks # and if the daemon is only ever run from this initscript. # If the above conditions are not satisfied then add some other code # that waits for the process to drop all resources that could be # needed by services started subsequently. A last resort is to # sleep for some time. start-stop-daemon --stop --quiet --oknodo \ --pidfile $PIDFILE \ --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 return "$RETVAL" } case "$1" in start) test "$ENABLED" != "0" || exit 0 [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; restart|force-reload) test "$ENABLED" != "0" || exit 1 log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 exit 3 ;; esac : sniproxy-0.6.0/debian/logrotate.conf000066400000000000000000000002111340212110200174550ustar00rootroot00000000000000/var/log/sniproxy/*.log { daily copytruncate missingok rotate 90 compress notifempty create 640 daemon adm } sniproxy-0.6.0/debian/rules000077500000000000000000000006761340212110200157050ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/autotools.mk include /usr/share/cdbs/1/rules/autoreconf.mk CDBS_BUILD_DEPENDS += , dh-autoreconf install/sniproxy:: install -D -m 644 debian/sniproxy.conf debian/sniproxy/etc/sniproxy.conf install -D -m 644 debian/logrotate.conf debian/sniproxy/etc/logrotate.d/sniproxy.conf install -d -m 750 -o daemon -g adm debian/sniproxy/var/log/sniproxy sniproxy-0.6.0/debian/sniproxy.conf000066400000000000000000000034621340212110200173630ustar00rootroot00000000000000# sniproxy example configuration file # lines that start with # are comments # lines with only white space are ignored user daemon # PID file pidfile /var/run/sniproxy.pid error_log { # Log to the daemon syslog facility syslog daemon # Alternatively we could log to file #filename /var/log/sniproxy/sniproxy.log # Control the verbosity of the log priority notice } # blocks are delimited with {...} listen 80 { proto http table http_hosts # Fallback backend server to use if we can not parse the client request fallback localhost:8080 access_log { filename /var/log/sniproxy/http_access.log priority notice } } listen 443 { proto tls table https_hosts access_log { filename /var/log/sniproxy/https_access.log priority notice } } # named tables are defined with the table directive table http_hosts { example.com 192.0.2.10:8001 example.net 192.0.2.10:8002 example.org 192.0.2.10:8003 # pattern: # valid Perl-compatible Regular Expression that matches the # hostname # # target: # - a DNS name # - an IP address (with optional port) # - '*' to use the hostname that the client requested # # pattern target #.*\.itunes\.apple\.com$ *:443 #.* 127.0.0.1:4443 } # named tables are defined with the table directive table https_hosts { # When proxying to local sockets you should use different tables since the # local socket server most likely will not autodetect which protocol is # being used example.org unix:/var/run/server.sock } # if no table specified the default 'default' table is defined table { # if no port is specified default HTTP (80) and HTTPS (443) ports are # assumed based on the protocol of the listen block using this table example.com 192.0.2.10 example.net 192.0.2.20 } sniproxy-0.6.0/debian/sniproxy.default000066400000000000000000000005711340212110200200600ustar00rootroot00000000000000# Defaults for sniproxy initscript # This file has two functions: # 1) to completely disable starting sniproxy, # 2) to select an alternative config file # by setting DAEMON_ARGS to -c # Additional options that are passed to the Daemon. #DAEMON_ARGS="-c /etc/sniproxy.conf" # Whether or not to run the sniproxy daemon; set to 0 to disable, 1 to enable. ENABLED=0 sniproxy-0.6.0/debian/sniproxy.install000066400000000000000000000000231340212110200200720ustar00rootroot00000000000000/usr/sbin/sniproxy sniproxy-0.6.0/debian/sniproxy.manpages000066400000000000000000000000431340212110200202210ustar00rootroot00000000000000man/sniproxy.8 man/sniproxy.conf.5 sniproxy-0.6.0/debian/source/000077500000000000000000000000001340212110200161145ustar00rootroot00000000000000sniproxy-0.6.0/debian/source/format000066400000000000000000000000151340212110200173230ustar00rootroot000000000000003.0 (native) sniproxy-0.6.0/m4/000077500000000000000000000000001340212110200137125ustar00rootroot00000000000000sniproxy-0.6.0/m4/lib-link.m4000066400000000000000000001004431340212110200156570ustar00rootroot00000000000000# lib-link.m4 serial 26 (gettext-0.18.2) dnl Copyright (C) 2001-2014 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible. AC_PREREQ([2.54]) dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and dnl the libraries corresponding to explicit and implicit dependencies. dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and dnl augments the CPPFLAGS variable. dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_LINKFLAGS], [ AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) AC_REQUIRE([AC_LIB_RPATH]) pushdef([Name],[m4_translit([$1],[./+-], [____])]) pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [ AC_LIB_LINKFLAGS_BODY([$1], [$2]) ac_cv_lib[]Name[]_libs="$LIB[]NAME" ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME" ac_cv_lib[]Name[]_cppflags="$INC[]NAME" ac_cv_lib[]Name[]_prefix="$LIB[]NAME[]_PREFIX" ]) LIB[]NAME="$ac_cv_lib[]Name[]_libs" LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs" INC[]NAME="$ac_cv_lib[]Name[]_cppflags" LIB[]NAME[]_PREFIX="$ac_cv_lib[]Name[]_prefix" AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) AC_SUBST([LIB]NAME) AC_SUBST([LTLIB]NAME) AC_SUBST([LIB]NAME[_PREFIX]) dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the dnl results of this search when this library appears as a dependency. HAVE_LIB[]NAME=yes popdef([NAME]) popdef([Name]) ]) dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode, [missing-message]) dnl searches for libname and the libraries corresponding to explicit and dnl implicit dependencies, together with the specified include files and dnl the ability to compile and link the specified testcode. The missing-message dnl defaults to 'no' and may contain additional hints for the user. dnl If found, it sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME} dnl and LTLIB${NAME} variables and augments the CPPFLAGS variable, and dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty. dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_HAVE_LINKFLAGS], [ AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) AC_REQUIRE([AC_LIB_RPATH]) pushdef([Name],[m4_translit([$1],[./+-], [____])]) pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME dnl accordingly. AC_LIB_LINKFLAGS_BODY([$1], [$2]) dnl Add $INC[]NAME to CPPFLAGS before performing the following checks, dnl because if the user has installed lib[]Name and not disabled its use dnl via --without-lib[]Name-prefix, he wants to use it. ac_save_CPPFLAGS="$CPPFLAGS" AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [ ac_save_LIBS="$LIBS" dnl If $LIB[]NAME contains some -l options, add it to the end of LIBS, dnl because these -l options might require -L options that are present in dnl LIBS. -l options benefit only from the -L options listed before it. dnl Otherwise, add it to the front of LIBS, because it may be a static dnl library that depends on another static library that is present in LIBS. dnl Static libraries benefit only from the static libraries listed after dnl it. case " $LIB[]NAME" in *" -l"*) LIBS="$LIBS $LIB[]NAME" ;; *) LIBS="$LIB[]NAME $LIBS" ;; esac AC_LINK_IFELSE( [AC_LANG_PROGRAM([[$3]], [[$4]])], [ac_cv_lib[]Name=yes], [ac_cv_lib[]Name='m4_if([$5], [], [no], [[$5]])']) LIBS="$ac_save_LIBS" ]) if test "$ac_cv_lib[]Name" = yes; then HAVE_LIB[]NAME=yes AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the lib][$1 library.]) AC_MSG_CHECKING([how to link with lib[]$1]) AC_MSG_RESULT([$LIB[]NAME]) else HAVE_LIB[]NAME=no dnl If $LIB[]NAME didn't lead to a usable library, we don't need dnl $INC[]NAME either. CPPFLAGS="$ac_save_CPPFLAGS" LIB[]NAME= LTLIB[]NAME= LIB[]NAME[]_PREFIX= fi AC_SUBST([HAVE_LIB]NAME) AC_SUBST([LIB]NAME) AC_SUBST([LTLIB]NAME) AC_SUBST([LIB]NAME[_PREFIX]) popdef([NAME]) popdef([Name]) ]) dnl Determine the platform dependent parameters needed to use rpath: dnl acl_libext, dnl acl_shlibext, dnl acl_libname_spec, dnl acl_library_names_spec, dnl acl_hardcode_libdir_flag_spec, dnl acl_hardcode_libdir_separator, dnl acl_hardcode_direct, dnl acl_hardcode_minus_L. AC_DEFUN([AC_LIB_RPATH], [ dnl Tell automake >= 1.10 to complain if config.rpath is missing. m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])]) AC_REQUIRE([AC_PROG_CC]) dnl we use $CC, $GCC, $LDFLAGS AC_REQUIRE([AC_LIB_PROG_LD]) dnl we use $LD, $with_gnu_ld AC_REQUIRE([AC_CANONICAL_HOST]) dnl we use $host AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir AC_CACHE_CHECK([for shared library run path origin], [acl_cv_rpath], [ CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \ ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh . ./conftest.sh rm -f ./conftest.sh acl_cv_rpath=done ]) wl="$acl_cv_wl" acl_libext="$acl_cv_libext" acl_shlibext="$acl_cv_shlibext" acl_libname_spec="$acl_cv_libname_spec" acl_library_names_spec="$acl_cv_library_names_spec" acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" acl_hardcode_direct="$acl_cv_hardcode_direct" acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" dnl Determine whether the user wants rpath handling at all. AC_ARG_ENABLE([rpath], [ --disable-rpath do not hardcode runtime library paths], :, enable_rpath=yes) ]) dnl AC_LIB_FROMPACKAGE(name, package) dnl declares that libname comes from the given package. The configure file dnl will then not have a --with-libname-prefix option but a dnl --with-package-prefix option. Several libraries can come from the same dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar dnl macro call that searches for libname. AC_DEFUN([AC_LIB_FROMPACKAGE], [ pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) define([acl_frompackage_]NAME, [$2]) popdef([NAME]) pushdef([PACK],[$2]) pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) define([acl_libsinpackage_]PACKUP, m4_ifdef([acl_libsinpackage_]PACKUP, [m4_defn([acl_libsinpackage_]PACKUP)[, ]],)[lib$1]) popdef([PACKUP]) popdef([PACK]) ]) dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and dnl the libraries corresponding to explicit and implicit dependencies. dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_LINKFLAGS_BODY], [ AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, lib[$1])]) pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, [acl_libsinpackage_]PACKUP, lib[$1])]) dnl Autoconf >= 2.61 supports dots in --with options. pushdef([P_A_C_K],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[m4_translit(PACK,[.],[_])],PACK)]) dnl By default, look in $includedir and $libdir. use_additional=yes AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) AC_ARG_WITH(P_A_C_K[-prefix], [[ --with-]]P_A_C_K[[-prefix[=DIR] search for ]PACKLIBS[ in DIR/include and DIR/lib --without-]]P_A_C_K[[-prefix don't search for ]PACKLIBS[ in includedir and libdir]], [ if test "X$withval" = "Xno"; then use_additional=no else if test "X$withval" = "X"; then AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) else additional_includedir="$withval/include" additional_libdir="$withval/$acl_libdirstem" if test "$acl_libdirstem2" != "$acl_libdirstem" \ && ! test -d "$withval/$acl_libdirstem"; then additional_libdir="$withval/$acl_libdirstem2" fi fi fi ]) dnl Search the library and its dependencies in $additional_libdir and dnl $LDFLAGS. Using breadth-first-seach. LIB[]NAME= LTLIB[]NAME= INC[]NAME= LIB[]NAME[]_PREFIX= dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been dnl computed. So it has to be reset here. HAVE_LIB[]NAME= rpathdirs= ltrpathdirs= names_already_handled= names_next_round='$1 $2' while test -n "$names_next_round"; do names_this_round="$names_next_round" names_next_round= for name in $names_this_round; do already_handled= for n in $names_already_handled; do if test "$n" = "$name"; then already_handled=yes break fi done if test -z "$already_handled"; then names_already_handled="$names_already_handled $name" dnl See if it was already located by an earlier AC_LIB_LINKFLAGS dnl or AC_LIB_HAVE_LINKFLAGS call. uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'` eval value=\"\$HAVE_LIB$uppername\" if test -n "$value"; then if test "$value" = yes; then eval value=\"\$LIB$uppername\" test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" eval value=\"\$LTLIB$uppername\" test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" else dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined dnl that this library doesn't exist. So just drop it. : fi else dnl Search the library lib$name in $additional_libdir and $LDFLAGS dnl and the already constructed $LIBNAME/$LTLIBNAME. found_dir= found_la= found_so= found_a= eval libname=\"$acl_libname_spec\" # typically: libname=lib$name if test -n "$acl_shlibext"; then shrext=".$acl_shlibext" # typically: shrext=.so else shrext= fi if test $use_additional = yes; then dir="$additional_libdir" dnl The same code as in the loop below: dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext"; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi fi if test "X$found_dir" = "X"; then for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) case "$x" in -L*) dir=`echo "X$x" | sed -e 's/^X-L//'` dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext"; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi ;; esac if test "X$found_dir" != "X"; then break fi done fi if test "X$found_dir" != "X"; then dnl Found the library. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" if test "X$found_so" != "X"; then dnl Linking with a shared library. We attempt to hardcode its dnl directory into the executable's runpath, unless it's the dnl standard /usr/lib. if test "$enable_rpath" = no \ || test "X$found_dir" = "X/usr/$acl_libdirstem" \ || test "X$found_dir" = "X/usr/$acl_libdirstem2"; then dnl No hardcoding is needed. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl Use an explicit option to hardcode DIR into the resulting dnl binary. dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $found_dir" fi dnl The hardcoding into $LIBNAME is system dependent. if test "$acl_hardcode_direct" = yes; then dnl Using DIR/libNAME.so during linking hardcodes DIR into the dnl resulting binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode DIR into the resulting dnl binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $found_dir" fi else dnl Rely on "-L$found_dir". dnl But don't add it if it's already contained in the LDFLAGS dnl or the already constructed $LIBNAME haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" fi if test "$acl_hardcode_minus_L" != no; then dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH dnl here, because this doesn't fit in flags passed to the dnl compiler. So give up. No hardcoding. This affects only dnl very old systems. dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" fi fi fi fi else if test "X$found_a" != "X"; then dnl Linking with a static library. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" else dnl We shouldn't come here, but anyway it's good to have a dnl fallback. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" fi fi dnl Assume the include files are nearby. additional_includedir= case "$found_dir" in */$acl_libdirstem | */$acl_libdirstem/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` if test "$name" = '$1'; then LIB[]NAME[]_PREFIX="$basedir" fi additional_includedir="$basedir/include" ;; */$acl_libdirstem2 | */$acl_libdirstem2/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'` if test "$name" = '$1'; then LIB[]NAME[]_PREFIX="$basedir" fi additional_includedir="$basedir/include" ;; esac if test "X$additional_includedir" != "X"; then dnl Potentially add $additional_includedir to $INCNAME. dnl But don't add it dnl 1. if it's the standard /usr/include, dnl 2. if it's /usr/local/include and we are using GCC on Linux, dnl 3. if it's already present in $CPPFLAGS or the already dnl constructed $INCNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_includedir" != "X/usr/include"; then haveit= if test "X$additional_includedir" = "X/usr/local/include"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then for x in $CPPFLAGS $INC[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-I$additional_includedir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_includedir"; then dnl Really add $additional_includedir to $INCNAME. INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" fi fi fi fi fi dnl Look for dependencies. if test -n "$found_la"; then dnl Read the .la file. It defines the variables dnl dlname, library_names, old_library, dependency_libs, current, dnl age, revision, installed, dlopen, dlpreopen, libdir. save_libdir="$libdir" case "$found_la" in */* | *\\*) . "$found_la" ;; *) . "./$found_la" ;; esac libdir="$save_libdir" dnl We use only dependency_libs. for dep in $dependency_libs; do case "$dep" in -L*) additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME. dnl But don't add it dnl 1. if it's the standard /usr/lib, dnl 2. if it's /usr/local/lib and we are using GCC on Linux, dnl 3. if it's already present in $LDFLAGS or the already dnl constructed $LIBNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_libdir" != "X/usr/$acl_libdirstem" \ && test "X$additional_libdir" != "X/usr/$acl_libdirstem2"; then haveit= if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem" \ || test "X$additional_libdir" = "X/usr/local/$acl_libdirstem2"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LIBNAME. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir" fi fi haveit= for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LTLIBNAME. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir" fi fi fi fi ;; -R*) dir=`echo "X$dep" | sed -e 's/^X-R//'` if test "$enable_rpath" != no; then dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $dir" fi dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $dir" fi fi ;; -l*) dnl Handle this in the next round. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` ;; *.la) dnl Handle this in the next round. Throw away the .la's dnl directory; it is already contained in a preceding -L dnl option. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` ;; *) dnl Most likely an immediate library name. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" ;; esac done fi else dnl Didn't find the library; assume it is in the system directories dnl known to the linker and runtime loader. (All the system dnl directories known to the linker should also be known to the dnl runtime loader, otherwise the system is severely misconfigured.) LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" fi fi fi done done if test "X$rpathdirs" != "X"; then if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user must dnl pass all path elements in one option. We can arrange that for a dnl single library, but not when more than one $LIBNAMEs are used. alldirs= for found_dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" done dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl. acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" else dnl The -rpath options are cumulative. for found_dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$found_dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" done fi fi if test "X$ltrpathdirs" != "X"; then dnl When using libtool, the option that works for both libraries and dnl executables is -R. The -R options are cumulative. for found_dir in $ltrpathdirs; do LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" done fi popdef([P_A_C_K]) popdef([PACKLIBS]) popdef([PACKUP]) popdef([PACK]) popdef([NAME]) ]) dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, dnl unless already present in VAR. dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes dnl contains two or three consecutive elements that belong together. AC_DEFUN([AC_LIB_APPENDTOVAR], [ for element in [$2]; do haveit= for x in $[$1]; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X$element"; then haveit=yes break fi done if test -z "$haveit"; then [$1]="${[$1]}${[$1]:+ }$element" fi done ]) dnl For those cases where a variable contains several -L and -l options dnl referring to unknown libraries and directories, this macro determines the dnl necessary additional linker options for the runtime path. dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL]) dnl sets LDADDVAR to linker options needed together with LIBSVALUE. dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed, dnl otherwise linking without libtool is assumed. AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS], [ AC_REQUIRE([AC_LIB_RPATH]) AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) $1= if test "$enable_rpath" != no; then if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode directories into the resulting dnl binary. rpathdirs= next= for opt in $2; do if test -n "$next"; then dir="$next" dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem" \ && test "X$dir" != "X/usr/$acl_libdirstem2"; then rpathdirs="$rpathdirs $dir" fi next= else case $opt in -L) next=yes ;; -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'` dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem" \ && test "X$dir" != "X/usr/$acl_libdirstem2"; then rpathdirs="$rpathdirs $dir" fi next= ;; *) next= ;; esac fi done if test "X$rpathdirs" != "X"; then if test -n ""$3""; then dnl libtool is used for linking. Use -R options. for dir in $rpathdirs; do $1="${$1}${$1:+ }-R$dir" done else dnl The linker is used for linking directly. if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user dnl must pass all path elements in one option. alldirs= for dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir" done acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="$flag" else dnl The -rpath options are cumulative. for dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="${$1}${$1:+ }$flag" done fi fi fi fi fi AC_SUBST([$1]) ]) sniproxy-0.6.0/man/000077500000000000000000000000001340212110200141455ustar00rootroot00000000000000sniproxy-0.6.0/man/Makefile.am000066400000000000000000000000531340212110200161770ustar00rootroot00000000000000dist_man_MANS = sniproxy.8 sniproxy.conf.5 sniproxy-0.6.0/man/sniproxy.8000066400000000000000000000016171340212110200161360ustar00rootroot00000000000000.TH SNIPROXY 8 "14 September 2014" "SNIProxy manual" "sniproxy" .SH NAME SNIProxy \- transparent session based TLS and HTTP proxy .SH SYNOPSIS \fBsniproxy\fR [ -\fBc\fR \fIconfig\fR ] [ -\fBf\fR ] [ -\fBn\fR \fIfile-descriptor-limit\fR ] [ -\fBV\fR ] .SH DESCRIPTION Proxies incoming HTTP and TLS connections based on the hostname contained in the initial request of the TCP session. This enables HTTPS name-based virtual hosting to separate backend servers without installing the private key on the proxy machine\&. .SH OPTIONS .TP -c \fIconfig\fR Specify configuration file to use. The default is /etc/sniproxy\&.conf\&. .TP -f Do not daemonize, and run in foreground\&. .TP -n \fIfile-descriptor-limit\fR Specify the maximum file descriptor resource limit\&. SNIProxy will attempt to set the maximum file descriptor limit to the value specified\&. .TP -V Print the version of SNIProxy and exit\&. sniproxy-0.6.0/man/sniproxy.conf.5000066400000000000000000000143541340212110200170610ustar00rootroot00000000000000.TH SNIPROXY.CONF 5 "22 March 2015" "SNIProxy manual" "sniproxy" .SH NAME sniproxy.conf - sniproxy configuration file .SH SYNOPSIS /etc/sniproxy.conf .SH DESCRIPTION /etc/sniproxy.conf is the configuration file for sniproxy. Statements are separated by either a new line or semi-colon. Lines starting with \&# are comments. The configuration is broken down into stanzas delimited by curly braces. Characters may be escaped using \&\\. Configuration directives may may be shorted as long as they are unambiguous e.g. user daemon instead of username daemon. .SS USERNAME .PP .nf username daemon .fi .PP Specify the user sniproxy will run as. When sniproxy is launched as super user, it will drop permissions to this user. .SS PIDFILE .PP .nf pidfile /var/run/sniproxy.pid .fi .PP Specify the path to the pid file, the directory much be writeable by the user sniproxy runs as. .SS ERROR_LOG .PP .nf error_log { syslog daemon priority notice } .fi .PP Specify how error messages should be handled. Messages can be either logged to syslog using the syslog directive specifies that logs should to a given syslog facility. Alternatively the filename directive may be specified to log to file, these two options are mutually exclusive. The priority directive indicates what severity of messages should be logged. Accepted priorities the standard syslog priorities, in increasing verbosity: emergency, alert, critical, error, warning, notice, info, and debug. .SS ACCESS_LOG .PP .nf access_log { filename /var/log/sniproxy/access.log } .fi .PP Specify how connections should be logged, may be overridden in a specific listener. Connections are logged after both the client and server have sockets have been closed. The syslog and priority directive may be used here as in error_log. .SS RESOLVER .PP .nf resolver { nameserver 127.0.0.1 mode ipv6_first } .fi .PP Specify how DNS queries should be resolved, this is only required if using hostnames as addresses in the configuration or using wildcard backends. If not specified the IPv4 only queries will be preformed using the system default name servers. Four modes are supported: ipv4_only: query for any A records, use the first A record returned (following CNAME records). ipv6_only: query for any AAAA records, use the first AAAA record returned (following CNAME records). ipv4_first: query for both A and AAAA records, wait for both queries to complete, use the first A record if any, otherwise use the first AAAA record. ipv6_first: query for both A and AAAA records, wait for both queries to complete, use the first AAAA record if any, otherwise use the first A record. It is strongly recommended to use a local name server, since a single socket is reused for all DNS queries and thus the UDP port number is predictable leaving the query only protected from spoofed replies by the 16 bit query ID. Additionally since no internal DNS caching is performed a local resolver can improve performance. .SS LISTENER .PP .nf listener 192.0.2.10:80 { protocol http reuseport yes table http_hosts fallback 192.0.2.100:80 bad_requests log source 192.0.2.10 access_log { filename /var/log/sniproxy/http_access.log } } listener [::]:80 { protocol http ipv6_v6only yes table http_hosts fallback unix:/var/run/http_fallback_unix.sock } .fi .PP Define a listening address to accept new connections from clients on. Addresses may be specified as an IPv4 literal followed by a TCP port number, and IPv6 literal followed by a TCP port number, a bare TCP port number or a unix socket path prefixed with 'unix:'. Protocol defines how the client request should be parsed to obtain the requested hostname, two protocols are supported http and tls. Reuseport directive controls if the port is opened in SO_REUSEPORT mode, which allows to run several sniproxy instances on the same ip:port pair. This enables us to evenly load-balance incoming connections between these instances without the use of any external load-balancing proxy. Requires Linux kernel 3.9+. Setting reuseport to "yes" enables this functionality. The ipv6_v6only directive controls if listening on the IPv6 any address '::' will accepting connections to any IPv4 address as well as an IPv6 address. This is useful if the user wants different configurations for IPv4 and IPv6 or wishes to handle IPv4 traffic with another server/proxy entirely. This is only applicable to IPv6 listeners, and is ignored on other listeners. Table specifies the name of the table used to lookup which server to forward the connection to based on the hostname extracted from the initial client request. If no table directive is specified the default, unnamed, table will be used. The fallback directive specifies a server to be used if the client request can not be parsed, a server can not be found in the table for the hostname specified or the hostname can not be resolved.. This should be an IP address and port or unix socket path. The bad_requests directive allows logging the contents of the client request if it is not parsable, this is useful for debugging. The source directive allows specifying a specified address to bind to before connecting to the backend server. In most cases it is better to omit this option and allow the operating system to select the outgoing address automatically. Do not include a port number in this address, doing so will limit the proxy to one simultaneous to each server at time. The access log configuration may be overridden on each listener. .SS TABLE .PP .nf table http_hosts { ^example\\.com$ 192.0.2.101 ^example\\.net$ 192.0.2.102 ^example\\.org$ 192.0.2.103 proxy_protocol } .fi .PP Tables define how to map each hostname to a backend server. Each request's hostname is matched against entries in the table in order, until a match is found and that server is used. The server address may be either IP, an IP and port, a unix socket path, a hostname or '*'. If no port is specified, the port of the listener which connection was received on will be used. The optional proxy_protocol option will prepend a HAProxy PROXY v1 protocol header to the proxied connection allowing supporting webservers to obtain the source and destination IP and port of the original incoming TCP connection. .SH "SEE ALSO" .PP \fBsniproxy\fR(8) sniproxy-0.6.0/redhat/000077500000000000000000000000001340212110200146415ustar00rootroot00000000000000sniproxy-0.6.0/redhat/sniproxy.init000066400000000000000000000037341340212110200174300ustar00rootroot00000000000000#!/bin/sh # # sniproxy This shell script takes care of starting and stopping # sniproxy (DNS server). # # chkconfig: - 14 86 # description: sniproxy is a tiny little proxy server \ # capable of proxying https connections. ### BEGIN INIT INFO # Provides: sniproxy # Required-Start: $network $local_fs # Required-Stop: $network $local_fs # Short-Description: sniproxy proxy server # Description: sniproxy is a tiny little proxy server # that is capable of proxying https connections. ### END INIT INFO # Source function library. . /etc/rc.d/init.d/functions exec="/usr/sbin/sniproxy" prog="sniproxy" config="/etc/sniproxy.conf" pidfile="/var/tmp/sniproxy.pid" [ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog lockfile=/var/lock/subsys/$prog start() { [ -x $exec ] || exit 5 [ -f $config ] || exit 6 echo -n $"Starting $prog: " daemon $exec -c $config retval=$? echo [ $retval -eq 0 ] && touch $lockfile return $retval } stop() { echo -n $"Stopping $prog: " # stop it here, often "killproc $prog" killproc -p $pidfile $prog retval=$? echo [ $retval -eq 0 ] && rm -f $lockfile return $retval } restart() { stop start } reload() { kill -HUP `cat $pidfile` } force_reload() { restart } rh_status() { # run checks to determine if the service is running or use generic status status -p $pidfile $prog } rh_status_q() { rh_status -p $pidfile >/dev/null 2>&1 } case "$1" in start) rh_status_q && exit 0 $1 ;; stop) rh_status_q || exit 0 $1 ;; restart) $1 ;; reload) rh_status_q || exit 7 $1 ;; force-reload) force_reload ;; status) rh_status ;; condrestart|try-restart) rh_status_q || exit 0 restart ;; *) echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" exit 2 esac exit $? sniproxy-0.6.0/redhat/sniproxy.spec000066400000000000000000000046751340212110200174240ustar00rootroot00000000000000Name: sniproxy Version: 0.6.0 Release: 1%{?dist} Summary: Transparent TLS and HTTP layer 4 proxy with SNI support Group: System Environment/Daemons License: BSD URL: https://github.com/dlundquist/sniproxy Source0: %{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: autoconf, automake, curl, libev-devel, pcre-devel, perl, gettext-devel, udns-devel %description Proxies incoming HTTP and TLS connections based on the hostname contained in the initial request of the TCP session. This enables HTTPS name-based virtual hosting to separate backend servers without installing the private key on the proxy machine. %prep %setup -q %build %configure CFLAGS="-I/usr/include/libev" make %{?_smp_mflags} %install rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_sbindir}/sniproxy %doc %{_mandir}/man8/sniproxy.8.gz %{_mandir}/man5/sniproxy.conf.5.gz %changelog * Wed Dec 5 2018 Dustin Lundquist 0.6.0-1 - PROXY v1 protocol support - SO_REUSEPORT support on Linux 3.9 and later - Listener ipv6_only directive to accept only IPv6 connections - TCP keepalive * Wed Apr 26 2017 Dustin Lundquist 0.5.0-1 - Transparent proxy support - Use accept4() on Linix - Run as group specified in config * Tue Apr 7 2015 Dustin Lundquist 0.4.0-1 - Improve DNS resolver: Support for AAAA records Configuration options - Global access log - Man page for sniproxy.conf - Reject IP literals as hostnames for wildcard backends * Fri Sep 26 2014 Dustin Lundquist 0.3.6-1 - Improve logging: Fix negative connection duration in access log Include log rotate script Reopen log files on SIGHUP Share file handle to same log file between listeners Avoid unnecessary reconnection to syslog socket Cache timestamp string for current second - Man page - Packaging improvements: passes lintian and rpm-lint * Wed Aug 13 2014 Dustin Lundquist 0.3.5-1 - Configuration reloading on SIGHUP - SSL 2.0 connection handling: do not treat as an error, use fallback address if configured. - Fix buffer_coalesce error - Spawn privileged child to bind sockets to privileged ports on reload - Add -V flag to return sniproxy version - Use libev for timestamps to improve portability - Include several for BSD compatibility - Large file support (for log files) sniproxy-0.6.0/setver.sh000077500000000000000000000017641340212110200152510ustar00rootroot00000000000000#!/bin/sh VERSION=0.6.0 SOURCE_DIR=$(dirname $0) GIT_DIR=${SOURCE_DIR}/.git cd ${SOURCE_DIR} if [ -d ${GIT_DIR} ]; then GIT_VERSION=$(git describe --tags) if [ "x" != "x${GIT_VERSION}" ]; then if echo ${GIT_VERSION} | grep -q '-'; then VER=$(echo ${GIT_VERSION} | cut -d- -f1) REV=$(echo ${GIT_VERSION} | cut -d- -f2) REF=$(echo ${GIT_VERSION} | cut -d- -f3) VERSION=${VER}+git.${REV}.${REF} else # Release version (e.g. 0.3.5) VERSION=${GIT_VERSION} DEBIAN_VERSION=${VERSION} SPEC_VERSION=${VERSION} fi fi fi # Update Autoconf with new version sed -i "s/^\(AC_INIT(\[sniproxy\], \[\)[^]]*\(.\+\)$/\1${VERSION}\2/" ${SOURCE_DIR}/configure.ac # Update redhat/sniproxy.spec with new version sed -i "s/^Version:\s\+[^ ]\+/Version: ${VERSION}/" ${SOURCE_DIR}/redhat/sniproxy.spec # Update debian/changelog with new version debchange --newversion ${VERSION} "New git revision" sniproxy-0.6.0/sniproxy.conf000066400000000000000000000104111340212110200161310ustar00rootroot00000000000000# sniproxy example configuration file # lines that start with # are comments # lines with only white space are ignored user nobody group nogroup # PID file, needs to be placed in directory writable by user pidfile /var/run/sniproxy.pid # The DNS resolver is required for tables configured using wildcard or hostname # targets. If no resolver is specified, the nameserver and search domain are # loaded from /etc/resolv.conf. resolver { # Specify name server # # NOTE: it is strongly recommended to use a local caching DNS server, since # uDNS and thus SNIProxy only uses single socket to each name server so # each DNS query is only protected by the 16 bit query ID and lacks # additional source port randomization. Additionally no caching is # preformed within SNIProxy, so a local resolver can improve performance. nameserver 127.0.0.1 # DNS search domain search example.com # Specify which type of address to lookup in DNS: # # * ipv4_only query for IPv4 addresses (default) # * ipv6_only query for IPv6 addresses # * ipv4_first query for both IPv4 and IPv6, use IPv4 is present # * ipv6_first query for both IPv4 and IPv6, use IPv6 is present mode ipv6_first } error_log { # Log to the daemon syslog facility syslog daemon # Alternatively we could log to file #filename /var/log/sniproxy.log # Control the verbosity of the log priority notice } # Global access log for all listeners access_log { # Same options as error_log filename /tmp/sniproxy-access.log } # blocks are delimited with {...} listen 80 { proto http table http_hosts # Enable SO_REUSEPORT to allow multiple processess to bind to this ip:port pair reuseport no # Fallback server to use if we can not parse the client request fallback localhost:8080 # Specify the source address for outgoing connections. # # Use "source client" to enable transparent proxy support. This requires # running sniproxy as root ("user root"). # # Do not include a port in this address, otherwise you will be limited # to a single connection to each backend server. # # NOTE: binding to a specific address prevents the operating system from # selecting and source address and port optimally and may significantly # reduce the maximum number of simultaneous connections possible. source 192.0.2.10 # Log the content of bad requests #bad_requests log # Override global access log for this listener access_log { # Same options as error_log filename /tmp/sniproxy.log } } listen [::]:443 { proto tls # controls if this listener will accept IPv4 connections as well on # supported operating systems such as Linux or FreeBSD, but not OpenBSD. ipv6_v6only on table https_hosts } listen 0.0.0.0 443 { # This listener will only accept IPv4 connections since it is bound to the # IPv4 any address. proto tls table https_hosts } listen 192.0.2.10:80 { protocol http # this will use default table } listen [2001:0db8::10]:80 { protocol http # this will use default table } listen unix:/var/run/proxy.sock { protocol http # this will use default table } # named tables are defined with the table directive table http_hosts { example.com 192.0.2.10:8001 example.net 192.0.2.10:8002 example.org 192.0.2.10:8003 proxy_protocol # Each table entry is composed of three parts: # # pattern: # valid Perl-compatible Regular Expression that matches the # hostname # # target: # - a DNS name # - an IP address and TCP port # - an IP address (will connect to the same port as the listener received the # connection) # - '*' to use the hostname that the client requested # # pattern target #.*\.itunes\.apple\.com$ *:443 #.* 127.0.0.1:4443 } # named tables are defined with the table directive table https_hosts { # When proxying to local sockets you should use different tables since the # local socket server most likely will not detect which protocol is being # used example.org unix:/var/run/server.sock } # if no table specified the default 'default' table is defined table { # If no port is specified the port of the incoming listener is used example.com 192.0.2.10 example.net 192.0.2.20 } sniproxy-0.6.0/src/000077500000000000000000000000001340212110200141615ustar00rootroot00000000000000sniproxy-0.6.0/src/.gitignore000066400000000000000000000000111340212110200161410ustar00rootroot00000000000000sniproxy sniproxy-0.6.0/src/Makefile.am000066400000000000000000000020751340212110200162210ustar00rootroot00000000000000AM_CPPFLAGS = $(LIBEV_CFLAGS) $(LIBPCRE_CFLAGS) $(LIBUDNS_CFLAGS) sbin_PROGRAMS = sniproxy sniproxy_SOURCES = sniproxy.c \ address.c \ address.h \ backend.c \ backend.h \ binder.c \ binder.h \ buffer.c \ buffer.h \ cfg_parser.c \ cfg_parser.h \ cfg_tokenizer.c \ cfg_tokenizer.h \ config.c \ config.h \ connection.c \ connection.h \ http.c \ http.h \ listener.c \ listener.h \ logger.c \ logger.h \ protocol.h \ resolv.c \ resolv.h \ table.c \ table.h \ tls.c \ tls.h sniproxy_LDADD = $(LIBEV_LIBS) $(LIBPCRE_LIBS) $(LIBUDNS_LIBS) sniproxy-0.6.0/src/address.c000066400000000000000000000332361340212110200157610ustar00rootroot00000000000000/* * Copyright (c) 2013, Dustin Lundquist * 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 HOLDERS 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 #include #include #include #include /* tolower */ #include #include #include #include /* inet_pton */ #include #include #include "address.h" struct Address { enum { HOSTNAME, SOCKADDR, WILDCARD, } type; size_t len; /* length of data */ uint16_t port; /* for hostname and wildcard */ char data[]; }; static const char valid_label_bytes[] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) static int valid_hostname(const char *); struct Address * new_address(const char *hostname_or_ip) { struct sockaddr_storage sa; char ip_buf[ADDRESS_BUFFER_SIZE]; char *port; size_t len; if (hostname_or_ip == NULL) return NULL; /* IPv6 address */ /* we need to test for raw IPv6 address for IPv4 port combinations since a * colon would give false positives */ memset(&sa, 0, sizeof(sa)); if (inet_pton(AF_INET6, hostname_or_ip, &((struct sockaddr_in6 *)&sa)->sin6_addr) == 1) { ((struct sockaddr_in6 *)&sa)->sin6_family = AF_INET6; return new_address_sa( (struct sockaddr *)&sa, sizeof(struct sockaddr_in6)); } /* Unix socket */ memset(&sa, 0, sizeof(sa)); if (strncmp("unix:", hostname_or_ip, 5) == 0) { /* XXX: only supporting pathname unix sockets */ ((struct sockaddr_un *)&sa)->sun_family = AF_UNIX; strncpy(((struct sockaddr_un *)&sa)->sun_path, hostname_or_ip + 5, sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)); return new_address_sa( (struct sockaddr *)&sa, offsetof(struct sockaddr_un, sun_path) + strlen(((struct sockaddr_un *)&sa)->sun_path) + 1); } /* Trailing port */ if ((port = strrchr(hostname_or_ip, ':')) != NULL && is_numeric(port + 1)) { len = (size_t)(port - hostname_or_ip); int port_num = atoi(port + 1); if (len < sizeof(ip_buf) && port_num >= 0 && port_num <= 65535) { strncpy(ip_buf, hostname_or_ip, len); ip_buf[len] = '\0'; struct Address *addr = new_address(ip_buf); if (addr != NULL) address_set_port(addr, (uint16_t) port_num); return addr; } } /* Wildcard */ if (strcmp("*", hostname_or_ip) == 0) { struct Address *addr = malloc(sizeof(struct Address)); if (addr != NULL) { addr->type = WILDCARD; addr->len = 0; address_set_port(addr, 0); } return addr; } /* IPv4 address */ memset(&sa, 0, sizeof(sa)); if (inet_pton(AF_INET, hostname_or_ip, &((struct sockaddr_in *)&sa)->sin_addr) == 1) { ((struct sockaddr_in *)&sa)->sin_family = AF_INET; return new_address_sa( (struct sockaddr *)&sa, sizeof(struct sockaddr_in)); } /* [IPv6 address] */ memset(&sa, 0, sizeof(sa)); if (hostname_or_ip[0] == '[' && (port = strchr(hostname_or_ip, ']')) != NULL) { len = (size_t)(port - hostname_or_ip - 1); /* inet_pton() will not parse the IP correctly unless it is in a * separate string. */ strncpy(ip_buf, hostname_or_ip + 1, len); ip_buf[len] = '\0'; if (inet_pton(AF_INET6, ip_buf, &((struct sockaddr_in6 *)&sa)->sin6_addr) == 1) { ((struct sockaddr_in6 *)&sa)->sin6_family = AF_INET6; return new_address_sa( (struct sockaddr *)&sa, sizeof(struct sockaddr_in6)); } } /* hostname */ if (valid_hostname(hostname_or_ip)) { len = strlen(hostname_or_ip); struct Address *addr = malloc( offsetof(struct Address, data) + len + 1); if (addr != NULL) { addr->type = HOSTNAME; addr->port = 0; addr->len = len; memcpy(addr->data, hostname_or_ip, len); addr->data[addr->len] = '\0'; /* Store address in lower case */ for (char *c = addr->data; *c != '\0'; c++) *c = tolower(*c); } return addr; } return NULL; } struct Address * new_address_sa(const struct sockaddr *sa, socklen_t sa_len) { struct Address *addr = NULL; addr = malloc(offsetof(struct Address, data) + sa_len); if (addr != NULL) { addr->type = SOCKADDR; addr->len = sa_len; memcpy(addr->data, sa, sa_len); addr->port = address_port(addr); } return addr; } struct Address * copy_address(const struct Address *addr) { size_t len = address_len(addr); struct Address *new_addr = malloc(len); if (new_addr != NULL) memcpy(new_addr, addr, len); return new_addr; } size_t address_len(const struct Address *addr) { switch (addr->type) { case HOSTNAME: /* include trailing null byte */ return offsetof(struct Address, data) + addr->len + 1; case SOCKADDR: return offsetof(struct Address, data) + addr->len; case WILDCARD: return sizeof(struct Address); default: assert(0); return 0; } } int address_compare(const struct Address *addr_1, const struct Address *addr_2) { if (addr_1 == NULL && addr_2 == NULL) return 0; if (addr_1 == NULL && addr_2 != NULL) return -1; if (addr_1 != NULL && addr_2 == NULL) return 1; if (addr_1->type < addr_2->type) return -1; if (addr_1->type > addr_2->type) return 1; size_t addr1_len = addr_1->len; size_t addr2_len = addr_2->len; int result = memcmp(addr_1->data, addr_2->data, MIN(addr1_len, addr2_len)); if (result == 0) { /* they match, find a tie breaker */ if (addr1_len < addr2_len) return -1; if (addr1_len > addr2_len) return 1; if (addr_1->port < addr_2->port) return -1; if (addr_1->port > addr_2->port) return 1; } return result; } int address_is_hostname(const struct Address *addr) { return addr != NULL && addr->type == HOSTNAME; } int address_is_sockaddr(const struct Address *addr) { return addr != NULL && addr->type == SOCKADDR; } int address_is_wildcard(const struct Address *addr) { return addr != NULL && addr->type == WILDCARD; } const char * address_hostname(const struct Address *addr) { if (addr->type != HOSTNAME) return NULL; return addr->data; } const struct sockaddr * address_sa(const struct Address *addr) { if (addr->type != SOCKADDR) return NULL; return (struct sockaddr *)addr->data; } socklen_t address_sa_len(const struct Address *addr) { if (addr->type != SOCKADDR) return 0; return addr->len; } uint16_t address_port(const struct Address *addr) { switch (addr->type) { case HOSTNAME: return addr->port; case SOCKADDR: switch (address_sa(addr)->sa_family) { case AF_INET: return ntohs(((struct sockaddr_in *)addr->data) ->sin_port); case AF_INET6: return ntohs(((struct sockaddr_in6 *)addr->data) ->sin6_port); case AF_UNIX: case AF_UNSPEC: return 0; default: assert(0); return 0; } case WILDCARD: return addr->port; default: /* invalid Address type */ assert(0); return 0; } } void address_set_port(struct Address *addr, uint16_t port) { switch (addr->type) { case SOCKADDR: switch (address_sa(addr)->sa_family) { case AF_INET: (((struct sockaddr_in *)addr->data) ->sin_port) = htons(port); break; case AF_INET6: (((struct sockaddr_in6 *)addr->data) ->sin6_port) = htons(port); break; case AF_UNIX: case AF_UNSPEC: /* no op */ break; default: assert(0); } /* fall through */ case HOSTNAME: case WILDCARD: addr->port = port; break; default: /* invalid Address type */ assert(0); } } int address_set_port_str(struct Address *addr, const char* str) { int port = atoi(str); if (port < 0 || port > 65535) { return 0; } address_set_port(addr, (uint16_t) port); return 1; } const char * display_address(const struct Address *addr, char *buffer, size_t buffer_len) { if (addr == NULL || buffer == NULL) return NULL; switch (addr->type) { case HOSTNAME: if (addr->port != 0) snprintf(buffer, buffer_len, "%s:%" PRIu16, addr->data, addr->port); else snprintf(buffer, buffer_len, "%s", addr->data); return buffer; case SOCKADDR: return display_sockaddr(addr->data, buffer, buffer_len); case WILDCARD: if (addr->port != 0) snprintf(buffer, buffer_len, "*:%" PRIu16, addr->port); else snprintf(buffer, buffer_len, "*"); return buffer; default: assert(0); return NULL; } } const char * display_sockaddr(const void *sa, char *buffer, size_t buffer_len) { char ip[INET6_ADDRSTRLEN]; if (sa == NULL || buffer == NULL) return NULL; switch (((const struct sockaddr *)sa)->sa_family) { case AF_INET: inet_ntop(AF_INET, &((const struct sockaddr_in *)sa)->sin_addr, ip, sizeof(ip)); if (((struct sockaddr_in *)sa)->sin_port != 0) snprintf(buffer, buffer_len, "%s:%" PRIu16, ip, ntohs(((struct sockaddr_in *)sa)->sin_port)); else snprintf(buffer, buffer_len, "%s", ip); break; case AF_INET6: inet_ntop(AF_INET6, &((const struct sockaddr_in6 *)sa)->sin6_addr, ip, sizeof(ip)); if (((struct sockaddr_in6 *)sa)->sin6_port != 0) snprintf(buffer, buffer_len, "[%s]:%" PRIu16, ip, ntohs(((struct sockaddr_in6 *)sa)->sin6_port)); else snprintf(buffer, buffer_len, "[%s]", ip); break; case AF_UNIX: snprintf(buffer, buffer_len, "unix:%s", ((struct sockaddr_un *)sa)->sun_path); break; case AF_UNSPEC: snprintf(buffer, buffer_len, "NONE"); break; default: /* unexpected AF */ assert(0); } return buffer; } int is_numeric(const char *s) { char *p; if (s == NULL || *s == '\0') return 0; int n = strtod(s, &p); (void)n; /* unused */ return *p == '\0'; /* entire string was numeric */ } static int valid_hostname(const char *hostname) { if (hostname == NULL) return 0; size_t hostname_len = strlen(hostname); if (hostname_len < 1 || hostname_len > 255) return 0; if (hostname[0] == '.') return 0; const char *hostname_end = hostname + hostname_len; for (const char *label = hostname; label < hostname_end;) { size_t label_len = (size_t)(hostname_end - label); char *next_dot = strchr(label, '.'); if (next_dot != NULL) label_len = (size_t)(next_dot - label); assert(label + label_len <= hostname_end); if (label_len > 63 || label_len < 1) return 0; if (label[0] == '-' || label[label_len - 1] == '-') return 0; if (strspn(label, valid_label_bytes) < label_len) return 0; label += label_len + 1; } return 1; } sniproxy-0.6.0/src/address.h000066400000000000000000000051031340212110200157560ustar00rootroot00000000000000/* * Copyright (c) 2013, Dustin Lundquist * 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 HOLDERS 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 ADDRESS_H #define ADDRESS_H #include #include #include #include /* * Define size of address buffers for display_address() calls to * be large enough for the maximum domain name and a 5 digit port */ #define ADDRESS_BUFFER_SIZE 262 struct Address; struct Address *new_address(const char *); struct Address *new_address_sa(const struct sockaddr *, socklen_t); struct Address *copy_address(const struct Address *); size_t address_len(const struct Address *); int address_compare(const struct Address *, const struct Address *); int address_is_hostname(const struct Address *); int address_is_sockaddr(const struct Address *); int address_is_wildcard(const struct Address *); const char *address_hostname(const struct Address *); const struct sockaddr *address_sa(const struct Address *); socklen_t address_sa_len(const struct Address *); uint16_t address_port(const struct Address *); void address_set_port(struct Address *, uint16_t); int address_set_port_str(struct Address *addr, const char* str); const char *display_address(const struct Address *, char *, size_t); const char *display_sockaddr(const void *, char *, size_t); int is_numeric(const char *); #endif sniproxy-0.6.0/src/backend.c000066400000000000000000000120761340212110200157220ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * Copyright (c) 2011 Manuel Kasper * 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 HOLDERS 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 #include #include #include #include #include "backend.h" #include "address.h" #include "logger.h" static void free_backend(struct Backend *); static char *backend_config_options(const struct Backend *); struct Backend * new_backend() { struct Backend *backend; backend = calloc(1, sizeof(struct Backend)); if (backend == NULL) { err("malloc"); return NULL; } return backend; } int accept_backend_arg(struct Backend *backend, const char *arg) { if (backend->pattern == NULL) { backend->pattern = strdup(arg); if (backend->pattern == NULL) { err("strdup failed"); return -1; } } else if (backend->address == NULL) { backend->address = new_address(arg); if (backend->address == NULL) { err("invalid address: %s", arg); return -1; } #ifndef HAVE_LIBUDNS if (!address_is_sockaddr(backend->address)) { err("Only socket address backends are permitted when compiled without libudns"); return -1; } #endif } else if (address_port(backend->address) == 0 && is_numeric(arg)) { if (!address_set_port_str(backend->address, arg)) { err("Invalid port: %s", arg); return -1; } } else if (backend->use_proxy_header == 0 && strcasecmp(arg, "proxy_protocol") == 0) { backend->use_proxy_header = 1; } else { err("Unexpected table backend argument: %s", arg); return -1; } return 1; } void add_backend(struct Backend_head *backends, struct Backend *backend) { STAILQ_INSERT_TAIL(backends, backend, entries); } int init_backend(struct Backend *backend) { if (backend->pattern_re == NULL) { const char *reerr; int reerroffset; backend->pattern_re = pcre_compile(backend->pattern, 0, &reerr, &reerroffset, NULL); if (backend->pattern_re == NULL) { err("Regex compilation of \"%s\" failed: %s, offset %d", backend->pattern, reerr, reerroffset); return 0; } char address[ADDRESS_BUFFER_SIZE]; debug("Parsed %s %s", backend->pattern, display_address(backend->address, address, sizeof(address))); } return 1; } struct Backend * lookup_backend(const struct Backend_head *head, const char *name, size_t name_len) { struct Backend *iter; if (name == NULL) { name = ""; name_len = 0; } STAILQ_FOREACH(iter, head, entries) { assert(iter->pattern_re != NULL); if (pcre_exec(iter->pattern_re, NULL, name, name_len, 0, 0, NULL, 0) >= 0) return iter; } return NULL; } void print_backend_config(FILE *file, const struct Backend *backend) { char address[ADDRESS_BUFFER_SIZE]; fprintf(file, "\t%s %s%s\n", backend->pattern, display_address(backend->address, address, sizeof(address)), backend_config_options(backend)); } static char * backend_config_options(const struct Backend *backend) { if (backend->use_proxy_header) return " proxy_protocol"; else return ""; } void remove_backend(struct Backend_head *head, struct Backend *backend) { STAILQ_REMOVE(head, backend, Backend, entries); free_backend(backend); } static void free_backend(struct Backend *backend) { if (backend == NULL) return; free(backend->pattern); free(backend->address); if (backend->pattern_re != NULL) pcre_free(backend->pattern_re); free(backend); } sniproxy-0.6.0/src/backend.h000066400000000000000000000041701340212110200157230ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * Copyright (c) 2011 Manuel Kasper * 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 HOLDERS 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 BACKEND_H #define BACKEND_H #include #include #include "address.h" STAILQ_HEAD(Backend_head, Backend); struct Backend { char *pattern; struct Address *address; int use_proxy_header; /* Runtime fields */ pcre *pattern_re; STAILQ_ENTRY(Backend) entries; }; void add_backend(struct Backend_head *, struct Backend *); int init_backend(struct Backend *); struct Backend *lookup_backend(const struct Backend_head *, const char *, size_t); void print_backend_config(FILE *, const struct Backend *); void remove_backend(struct Backend_head *, struct Backend *); struct Backend *new_backend(); int accept_backend_arg(struct Backend *, const char *); #endif sniproxy-0.6.0/src/binder.c000066400000000000000000000160541340212110200155760ustar00rootroot00000000000000/* * Copyright (c) 2012, Dustin Lundquist * 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 HOLDERS 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 #include #include #include /* memcpy() */ #include /* errno */ #include #include #include #include "binder.h" #include "logger.h" /* * binder is a child process we spawn before dropping privileges that is * responsible for creating new bound sockets to low ports */ static void binder_main(int); static int parse_ancillary_data(struct msghdr *); struct binder_request { size_t address_len; struct sockaddr address[]; }; static int binder_sock = -1; /* socket to binder */ static pid_t binder_pid = -1; void start_binder() { int sockets[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) { err("sockpair: %s", strerror(errno)); return; } pid_t pid = fork(); if (pid == -1) { /* error case */ err("fork: %s", strerror(errno)); close(sockets[0]); close(sockets[1]); } else if (pid == 0) { /* child */ /* don't leak file descriptors to the child process */ for (int i = 0; i < sockets[1]; i++) close(i); binder_main(sockets[1]); exit(0); } else { /* parent */ close(sockets[1]); binder_sock = sockets[0]; binder_pid = pid; } } int bind_socket(const struct sockaddr *addr, size_t addr_len) { struct binder_request *request; struct msghdr msg; struct iovec iov[1]; char control_buf[64]; char data_buf[256]; if (binder_pid <= 0) { err("%s: Binder not started", __func__); return -1; } size_t request_len = sizeof(request) + addr_len; if (request_len > sizeof(data_buf)) fatal("bind_socket: request length %zu exceeds buffer", request_len); request = (struct binder_request *)data_buf; request->address_len = addr_len; memcpy(&request->address, addr, addr_len); if (send(binder_sock, request, request_len, 0) < 0) { err("send: %s", strerror(errno)); return -1; } memset(&msg, 0, sizeof(msg)); iov[0].iov_base = data_buf; iov[0].iov_len = sizeof(data_buf); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control_buf; msg.msg_controllen = sizeof(control_buf); int len = recvmsg(binder_sock, &msg, 0); if (len < 0) { err("recvmsg: %s", strerror(errno)); return -1; } int fd = parse_ancillary_data(&msg); if (fd < 0) err("binder returned: %.*s", len, data_buf); return fd; } void stop_binder() { close(binder_sock); int status; if (waitpid(binder_pid, &status, 0) < 0) err("waitpid: %s", strerror(errno)); } static void binder_main(int sockfd) { for (;;) { char buffer[256]; int len = recv(sockfd, buffer, sizeof(buffer), 0); if (len < 0) { memset(buffer, 0, sizeof(buffer)); snprintf(buffer, sizeof(buffer), "recv(): %s", strerror(errno)); goto error; } else if (len == 0) { /* socket was closed */ close(sockfd); break; } else if (len < (int)sizeof(struct binder_request)) { memset(buffer, 0, sizeof(buffer)); strncpy(buffer, "Incomplete error:", sizeof(buffer)); goto error; } struct binder_request *req = (struct binder_request *)buffer; int fd = socket(req->address[0].sa_family, SOCK_STREAM, 0); if (fd < 0) { memset(buffer, 0, sizeof(buffer)); snprintf(buffer, sizeof(buffer), "socket(): %s", strerror(errno)); goto error; } /* set SO_REUSEADDR on server socket to facilitate restart */ int on = 1; int result = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (result < 0) { memset(buffer, 0, sizeof(buffer)); snprintf(buffer, sizeof(buffer), "setsockopt SO_REUSEADDR failed: %s", strerror(errno)); goto error; } if (bind(fd, req->address, req->address_len) < 0) { memset(buffer, 0, sizeof(buffer)); snprintf(buffer, sizeof(buffer), "bind(): %s", strerror(errno)); goto error; } struct msghdr msg; struct iovec iov[1]; struct cmsghdr *cmsg; char control_data[64]; memset(&msg, 0, sizeof(msg)); memset(&iov, 0, sizeof(iov)); memset(&control_data, 0, sizeof(control_data)); iov[0].iov_base = buffer; iov[0].iov_len = sizeof(buffer); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control_data; msg.msg_controllen = sizeof(control_data); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); int *fdptr = (int *)CMSG_DATA(cmsg); memcpy(fdptr, &fd, sizeof(fd)); msg.msg_controllen = cmsg->cmsg_len; if (sendmsg(sockfd, &msg, 0) < 0) { memset(buffer, 0, sizeof(buffer)); snprintf(buffer, sizeof(buffer), "send: %s", strerror(errno)); goto error; } close(fd); continue; error: if (send(sockfd, buffer, strlen(buffer), 0) < 0) { err("send: %s", strerror(errno)); close(sockfd); break; } } } static int parse_ancillary_data(struct msghdr *m) { struct cmsghdr *cmsg; int fd = -1; int *fdptr; for (cmsg = CMSG_FIRSTHDR(m); cmsg != NULL; cmsg = CMSG_NXTHDR(m, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { fdptr = (int *)CMSG_DATA(cmsg); memcpy(&fd, fdptr, sizeof(fd)); } } return fd; } sniproxy-0.6.0/src/binder.h000066400000000000000000000030251340212110200155750ustar00rootroot00000000000000/* * Copyright (c) 2012, Dustin Lundquist * 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 HOLDERS 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 BINDER_H #define BINDER_H #include void start_binder(); int bind_socket(const struct sockaddr *, size_t); void stop_binder(); #endif sniproxy-0.6.0/src/buffer.c000066400000000000000000000245341340212110200156060ustar00rootroot00000000000000/* * Copyright (c) 2012, Dustin Lundquist * 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 HOLDERS 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 #include /* malloc */ #include /* memcpy */ #include #include #include #include #include #include #include #include #include "buffer.h" #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define NOT_POWER_OF_2(x) (x == 0 || (x & (x - 1))) static const size_t BUFFER_MAX_SIZE = 1024 * 1024 * 1024; static size_t setup_write_iov(const struct Buffer *, struct iovec *, size_t); static size_t setup_read_iov(const struct Buffer *, struct iovec *, size_t); static inline void advance_write_position(struct Buffer *, size_t); static inline void advance_read_position(struct Buffer *, size_t); struct Buffer * new_buffer(size_t size, struct ev_loop *loop) { if (NOT_POWER_OF_2(size)) return NULL; struct Buffer *buf = malloc(sizeof(struct Buffer)); if (buf == NULL) return NULL; buf->size_mask = size - 1; buf->len = 0; buf->head = 0; buf->tx_bytes = 0; buf->rx_bytes = 0; buf->last_recv = ev_now(loop); buf->last_send = ev_now(loop); buf->buffer = malloc(size); if (buf->buffer == NULL) { free(buf); buf = NULL; } return buf; } ssize_t buffer_resize(struct Buffer *buf, size_t new_size) { if (NOT_POWER_OF_2(new_size)) return -4; if (new_size > BUFFER_MAX_SIZE) return -3; if (new_size < buf->len) return -1; /* new_size too small to hold existing data */ char *new_buffer = malloc(new_size); if (new_buffer == NULL) return -2; buffer_peek(buf, new_buffer, new_size); free(buf->buffer); buf->buffer = new_buffer; buf->size_mask = new_size - 1; buf->head = 0; return (ssize_t)buf->len; } void free_buffer(struct Buffer *buf) { if (buf == NULL) return; free(buf->buffer); free(buf); } ssize_t buffer_recv(struct Buffer *buffer, int sockfd, int flags, struct ev_loop *loop) { /* coalesce when reading into an empty buffer */ if (buffer->len == 0) buffer->head = 0; struct iovec iov[2]; struct msghdr msg = { .msg_iov = iov, .msg_iovlen = setup_write_iov(buffer, iov, 0) }; ssize_t bytes = recvmsg(sockfd, &msg, flags); buffer->last_recv = ev_now(loop); if (bytes > 0) advance_write_position(buffer, (size_t)bytes); return bytes; } ssize_t buffer_send(struct Buffer *buffer, int sockfd, int flags, struct ev_loop *loop) { struct iovec iov[2]; struct msghdr msg = { .msg_iov = iov, .msg_iovlen = setup_read_iov(buffer, iov, 0) }; ssize_t bytes = sendmsg(sockfd, &msg, flags); buffer->last_send = ev_now(loop); if (bytes > 0) advance_read_position(buffer, (size_t)bytes); return bytes; } /* * Read data from file into buffer */ ssize_t buffer_read(struct Buffer *buffer, int fd) { /* coalesce when reading into an empty buffer */ if (buffer->len == 0) buffer->head = 0; struct iovec iov[2]; size_t iov_len = setup_write_iov(buffer, iov, 0); ssize_t bytes = readv(fd, iov, iov_len); if (bytes > 0) advance_write_position(buffer, (size_t)bytes); return bytes; } /* * Write data to file from buffer */ ssize_t buffer_write(struct Buffer *buffer, int fd) { struct iovec iov[2]; size_t iov_len = setup_read_iov(buffer, iov, 0); ssize_t bytes = writev(fd, iov, iov_len); if (bytes > 0) advance_read_position(buffer, (size_t)bytes); return bytes; } /* * Coalesce a buffer into a single continuous region, optionally returning a * pointer to that region. * * Returns the size of the buffer contents */ size_t buffer_coalesce(struct Buffer *buffer, const void **dst) { size_t buffer_tail = (buffer->head + buffer->len) & buffer->size_mask; if (buffer_tail <= buffer->head) { /* buffer not wrapped */ if (dst != NULL) *dst = &buffer->buffer[buffer->head]; return buffer->len; } else { /* buffer wrapped */ size_t len = buffer->len; char *temp = malloc(len); if (temp != NULL) { buffer_pop(buffer, temp, len); assert(buffer->len == 0); buffer_push(buffer, temp, len); assert(buffer->head == 0); assert(buffer->len == len); free(temp); } if (dst != NULL) *dst = buffer->buffer; return buffer->len; } } size_t buffer_peek(const struct Buffer *src, void *dst, size_t len) { struct iovec iov[2]; size_t bytes_copied = 0; size_t iov_len = setup_read_iov(src, iov, len); for (size_t i = 0; i < iov_len; i++) { if (dst != NULL) memcpy((char *)dst + bytes_copied, iov[i].iov_base, iov[i].iov_len); bytes_copied += iov[i].iov_len; } return bytes_copied; } size_t buffer_pop(struct Buffer *src, void *dst, size_t len) { size_t bytes = buffer_peek(src, dst, len); if (bytes > 0) advance_read_position(src, bytes); return bytes; } size_t buffer_push(struct Buffer *dst, const void *src, size_t len) { struct iovec iov[2]; size_t bytes_appended = 0; /* coalesce when reading into an empty buffer */ if (dst->len == 0) dst->head = 0; if (buffer_size(dst) - dst->len < len) return 0; /* insufficient room */ size_t iov_len = setup_write_iov(dst, iov, len); for (size_t i = 0; i < iov_len; i++) { memcpy(iov[i].iov_base, (char *)src + bytes_appended, iov[i].iov_len); bytes_appended += iov[i].iov_len; } if (bytes_appended > 0) advance_write_position(dst, bytes_appended); return bytes_appended; } /* * Setup a struct iovec iov[2] for a write to a buffer. * struct iovec *iov MUST be at least length 2. * returns the number of entries setup */ static size_t setup_write_iov(const struct Buffer *buffer, struct iovec *iov, size_t len) { size_t room = buffer_size(buffer) - buffer->len; if (room == 0) /* trivial case: no room */ return 0; size_t write_len = room; /* Allow caller to specify maximum length */ if (len != 0) write_len = MIN(room, len); size_t start = (buffer->head + buffer->len) & buffer->size_mask; if (start + write_len <= buffer_size(buffer)) { iov[0].iov_base = buffer->buffer + start; iov[0].iov_len = write_len; /* assert iov are within bounds, non-zero length and non-overlapping */ assert(iov[0].iov_len > 0); assert((char *)iov[0].iov_base >= buffer->buffer); assert((char *)iov[0].iov_base + iov[0].iov_len <= buffer->buffer + buffer_size(buffer)); return 1; } else { iov[0].iov_base = buffer->buffer + start; iov[0].iov_len = buffer_size(buffer) - start; iov[1].iov_base = buffer->buffer; iov[1].iov_len = write_len - iov[0].iov_len; /* assert iov are within bounds, non-zero length and non-overlapping */ assert(iov[0].iov_len > 0); assert((char *)iov[0].iov_base >= buffer->buffer); assert((char *)iov[0].iov_base + iov[0].iov_len <= buffer->buffer + buffer_size(buffer)); assert(iov[1].iov_len > 0); assert((char *)iov[1].iov_base >= buffer->buffer); assert((char *)iov[1].iov_base + iov[1].iov_len <= (char *)iov[0].iov_base); return 2; } } static size_t setup_read_iov(const struct Buffer *buffer, struct iovec *iov, size_t len) { if (buffer->len == 0) return 0; size_t read_len = buffer->len; if (len != 0) read_len = MIN(len, buffer->len); if (buffer->head + read_len <= buffer_size(buffer)) { iov[0].iov_base = buffer->buffer + buffer->head; iov[0].iov_len = read_len; /* assert iov are within bounds, non-zero length and non-overlapping */ assert(iov[0].iov_len > 0); assert((char *)iov[0].iov_base >= buffer->buffer); assert((char *)iov[0].iov_base + iov[0].iov_len <= buffer->buffer + buffer_size(buffer)); return 1; } else { iov[0].iov_base = buffer->buffer + buffer->head; iov[0].iov_len = buffer_size(buffer) - buffer->head; iov[1].iov_base = buffer->buffer; iov[1].iov_len = read_len - iov[0].iov_len; /* assert iov are within bounds, non-zero length and non-overlapping */ assert(iov[0].iov_len > 0); assert((char *)iov[0].iov_base >= buffer->buffer); assert((char *)iov[0].iov_base + iov[0].iov_len <= buffer->buffer + buffer_size(buffer)); assert(iov[1].iov_len > 0); assert((char *)iov[1].iov_base >= buffer->buffer); assert((char *)iov[1].iov_base + iov[1].iov_len <= (char *)iov[0].iov_base); return 2; } } static inline void advance_write_position(struct Buffer *buffer, size_t offset) { buffer->len += offset; buffer->rx_bytes += offset; } static inline void advance_read_position(struct Buffer *buffer, size_t offset) { buffer->head = (buffer->head + offset) & buffer->size_mask; buffer->len -= offset; buffer->tx_bytes += offset; } sniproxy-0.6.0/src/buffer.h000066400000000000000000000051351340212110200156070ustar00rootroot00000000000000/* * Copyright (c) 2012, Dustin Lundquist * 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 HOLDERS 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 BUFFER_H #define BUFFER_H #include #include #include struct Buffer { char *buffer; size_t size_mask; /* bit mask for buffer size */ size_t head; /* index of first byte of content */ size_t len; /* size of content */ ev_tstamp last_recv; ev_tstamp last_send; size_t tx_bytes; size_t rx_bytes; }; struct Buffer *new_buffer(size_t, struct ev_loop *); void free_buffer(struct Buffer *); ssize_t buffer_recv(struct Buffer *, int, int, struct ev_loop *); ssize_t buffer_send(struct Buffer *, int, int, struct ev_loop *); ssize_t buffer_read(struct Buffer *, int); ssize_t buffer_write(struct Buffer *, int); ssize_t buffer_resize(struct Buffer *, size_t); size_t buffer_peek(const struct Buffer *, void *, size_t); size_t buffer_coalesce(struct Buffer *, const void **); size_t buffer_pop(struct Buffer *, void *, size_t); size_t buffer_push(struct Buffer *, const void *, size_t); static inline size_t buffer_size(const struct Buffer *b) { return b->size_mask + 1; } static inline size_t buffer_len(const struct Buffer *b) { return b->len; } static inline size_t buffer_room(const struct Buffer *b) { return buffer_size(b) - b->len; } #endif sniproxy-0.6.0/src/cfg_parser.c000066400000000000000000000111341340212110200164400ustar00rootroot00000000000000/* * Copyright (c) 2012, Dustin Lundquist * 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 HOLDERS 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 #include #include "cfg_parser.h" #include "cfg_tokenizer.h" #include "logger.h" static const struct Keyword *find_keyword(const struct Keyword *, const char *); int parse_config(void *context, FILE *cfg, const struct Keyword *grammar) { char buffer[256]; const struct Keyword *keyword = NULL; void *sub_context = NULL; int result; for (;;) { switch (next_token(cfg, buffer, sizeof(buffer))) { case TOKEN_ERROR: err("%s: tokenizer error", __func__); return -1; case TOKEN_WORD: if (keyword && sub_context && keyword->parse_arg) { result = keyword->parse_arg(sub_context, buffer); if (result <= 0) return result; } else if ((keyword = find_keyword(grammar, buffer))) { if (keyword->create) { sub_context = keyword->create(); if (sub_context == NULL) { err("failed to create subcontext"); return -1; } } else { sub_context = context; } /* Special case for wildcard grammars i.e. tables */ if (keyword->keyword == NULL && keyword->parse_arg) { result = keyword->parse_arg(sub_context, buffer); if (result <= 0) return result; } } else { err("%s: unknown keyword %s", __func__, buffer); return -1; } break; case TOKEN_OBRACE: if (keyword && sub_context && keyword->block_grammar) { result = parse_config(sub_context, cfg, keyword->block_grammar); if (result > 0 && keyword->finalize) result = keyword->finalize(context, sub_context); if (result <= 0) return result; keyword = NULL; sub_context = NULL; } else { err("%s: block without context", __func__); return -1; } break; case TOKEN_EOL: if (keyword && sub_context && keyword->finalize) { result = keyword->finalize(context, sub_context); if (result <= 0) return result; } keyword = NULL; sub_context = NULL; break; case TOKEN_CBRACE: /* fall through */ case TOKEN_END: return 1; } } } static const struct Keyword * find_keyword(const struct Keyword *grammar, const char *word) { for (; grammar->keyword; grammar++) if (strncmp(grammar->keyword, word, strlen(word)) == 0) return grammar; /* Special case for wildcard grammars i.e. tables */ if (grammar->keyword == NULL && grammar->create) return grammar; return NULL; } sniproxy-0.6.0/src/cfg_parser.h000066400000000000000000000033211340212110200164440ustar00rootroot00000000000000/* * Copyright (c) 2012, Dustin Lundquist * 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 HOLDERS 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 CFG_PARSER #define CFG_PARSER #include struct Keyword { const char *const keyword; void *(*const create)(); int (*const parse_arg)(void *, const char *); const struct Keyword *const block_grammar; int (*const finalize)(void *, void *); }; int parse_config(void *, FILE *, const struct Keyword *); #endif sniproxy-0.6.0/src/cfg_tokenizer.c000066400000000000000000000101471340212110200171610ustar00rootroot00000000000000/* * Copyright (c) 2012, Dustin Lundquist * 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 HOLDERS 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 #include "cfg_tokenizer.h" static void chomp_line(FILE *); static int next_word(FILE *, char *, int); /* * next_token() returns the next token based on the current position of * configuration file advancing the position to immediately after the token. */ enum Token next_token(FILE *config, char *buffer, size_t buffer_len) { int ch; int token_len; while ((ch = getc(config)) != EOF) { switch (ch) { case ' ': /* fall through */ case '\t': /* no op */ break; case '#': /* comment */ chomp_line(config); /* fall through */ case ';': /* fall through */ case '\n': /* fall through */ case '\r': return TOKEN_EOL; case '{': return TOKEN_OBRACE; case '}': return TOKEN_CBRACE; default: /* Rewind one byte, so next_word() can fetch from * the beginning of the word */ fseek(config, -1, SEEK_CUR); token_len = next_word(config, buffer, buffer_len); if (token_len <= 0) return TOKEN_ERROR; return TOKEN_WORD; } } return TOKEN_END; } static void chomp_line(FILE *file) { int ch; while ((ch = getc(file)) != EOF) if (ch == '\n' || ch == '\r') return; } static int next_word(FILE *file, char *buffer, int buffer_len) { int ch; int len = 0; int quoted = 0; int escaped = 0; while ((ch = getc(file)) != EOF && len < buffer_len) { if (escaped) { escaped = 0; buffer[len] = (char)ch; len++; continue; } switch (ch) { case '\\': escaped = 1; break; case '\"': quoted = 1 - quoted; /* toggle quoted flag */ break; /* separators */ case ' ': case '\t': case ';': case '\n': case '\r': case '#': case '{': case '}': if (quoted == 0) { /* rewind the file one character, so we don't eat * part of the next token */ fseek(file, -1, SEEK_CUR); buffer[len] = '\0'; len++; return len; } /* fall through */ default: buffer[len] = (char)ch; len++; } } /* We reached the end of the file, or filled our buffer */ return -1; } sniproxy-0.6.0/src/cfg_tokenizer.h000066400000000000000000000031421340212110200171630ustar00rootroot00000000000000/* * Copyright (c) 2012, Dustin Lundquist * 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 HOLDERS 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 CFG_TOKENIZER #define CFG_TOKENIZER #include enum Token { TOKEN_ERROR, TOKEN_EOL, TOKEN_OBRACE, TOKEN_CBRACE, TOKEN_WORD, TOKEN_END, }; enum Token next_token(FILE *, char *, size_t); #endif sniproxy-0.6.0/src/config.c000066400000000000000000000456011340212110200156000ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 #include #include #include #include #include "cfg_parser.h" #include "config.h" #include "logger.h" #include "connection.h" struct LoggerBuilder { const char *filename; const char *syslog_facility; int priority; }; static int accept_username(struct Config *, const char *); static int accept_groupname(struct Config *, const char *); static int accept_pidfile(struct Config *, const char *); static int end_listener_stanza(struct Config *, struct Listener *); static int end_table_stanza(struct Config *, struct Table *); static int end_backend(struct Table *, struct Backend *); static struct LoggerBuilder *new_logger_builder(); static int accept_logger_filename(struct LoggerBuilder *, const char *); static int accept_logger_syslog_facility(struct LoggerBuilder *, const char *); static int accept_logger_priority(struct LoggerBuilder *, const char *); static int end_error_logger_stanza(struct Config *, struct LoggerBuilder *); static int end_global_access_logger_stanza(struct Config *, struct LoggerBuilder *); static int end_listener_access_logger_stanza(struct Listener *, struct LoggerBuilder *); static struct ResolverConfig *new_resolver_config(); static int accept_resolver_nameserver(struct ResolverConfig *, const char *); static int accept_resolver_search(struct ResolverConfig *, const char *); static int accept_resolver_mode(struct ResolverConfig *, const char *); static int end_resolver_stanza(struct Config *, struct ResolverConfig *); static inline size_t string_vector_len(char **); static int append_to_string_vector(char ***, const char *) __attribute__((nonnull(1))); static void free_string_vector(char **); static void print_resolver_config(FILE *, struct ResolverConfig *); static const struct Keyword logger_stanza_grammar[] = { { .keyword="filename", .parse_arg=(int(*)(void *, const char *))accept_logger_filename, }, { .keyword="syslog", .parse_arg=(int(*)(void *, const char *))accept_logger_syslog_facility, }, { .keyword="priority", .parse_arg=(int(*)(void *, const char *))accept_logger_priority, }, { NULL }, }; static const struct Keyword resolver_stanza_grammar[] = { { .keyword="nameserver", .parse_arg=(int(*)(void *, const char *))accept_resolver_nameserver, }, { .keyword="search", .parse_arg=(int(*)(void *, const char *))accept_resolver_search, }, { .keyword="mode", .parse_arg=(int(*)(void *, const char *))accept_resolver_mode, }, { NULL }, }; static const struct Keyword listener_stanza_grammar[] = { { .keyword="protocol", .parse_arg=(int(*)(void *, const char *))accept_listener_protocol, }, { .keyword="reuseport", .parse_arg=(int(*)(void *, const char *))accept_listener_reuseport, }, { .keyword="ipv6_v6only", .parse_arg=(int(*)(void *, const char *))accept_listener_ipv6_v6only, }, { .keyword="table", .parse_arg=(int(*)(void *, const char *))accept_listener_table_name, }, { .keyword="fallback", .parse_arg= (int(*)(void *, const char *))accept_listener_fallback_address, }, { .keyword="source", .parse_arg=(int(*)(void *, const char *))accept_listener_source_address, }, { .keyword="access_log", .create=(void *(*)())new_logger_builder, .parse_arg=(int(*)(void *, const char *))accept_logger_filename, .block_grammar=logger_stanza_grammar, .finalize=(int(*)(void *, void *))end_listener_access_logger_stanza, }, { .keyword="bad_requests", .parse_arg= (int(*)(void *, const char *))accept_listener_bad_request_action, }, { NULL }, }; static struct Keyword table_stanza_grammar[] = { { .create=(void *(*)())new_backend, .parse_arg=(int(*)(void *, const char *))accept_backend_arg, .finalize=(int(*)(void *, void *))end_backend, }, { NULL }, }; static struct Keyword global_grammar[] = { { .keyword="username", .parse_arg=(int(*)(void *, const char *))accept_username, }, { .keyword="groupname", .parse_arg= (int(*)(void *, const char *))accept_groupname, }, { .keyword="pidfile", .parse_arg=(int(*)(void *, const char *))accept_pidfile, }, { .keyword="resolver", .create=(void *(*)())new_resolver_config, .block_grammar=resolver_stanza_grammar, .finalize=(int(*)(void *, void *))end_resolver_stanza, }, { .keyword="error_log", .create=(void *(*)())new_logger_builder, .block_grammar=logger_stanza_grammar, .finalize=(int(*)(void *, void *))end_error_logger_stanza, }, { .keyword="access_log", .create=(void *(*)())new_logger_builder, .block_grammar=logger_stanza_grammar, .finalize=(int(*)(void *, void *))end_global_access_logger_stanza, }, { .keyword="listener", .create=(void *(*)())new_listener, .parse_arg=(int(*)(void *, const char *))accept_listener_arg, .block_grammar=listener_stanza_grammar, .finalize=(int(*)(void *, void *))end_listener_stanza, }, { .keyword="table", .create=(void *(*)())new_table, .parse_arg=(int(*)(void *, const char *))accept_table_arg, .block_grammar=table_stanza_grammar, .finalize=(int(*)(void *, void *))end_table_stanza, }, { NULL }, }; static const char *const resolver_mode_names[] = { "DEFAULT", "ipv4_only", "ipv6_only", "ipv4_first", "ipv6_first", }; struct Config * init_config(const char *filename, struct ev_loop *loop) { struct Config *config = calloc(1, sizeof(struct Config)); if (config == NULL) { err("%s: malloc", __func__); return NULL; } SLIST_INIT(&config->listeners); SLIST_INIT(&config->tables); config->filename = strdup(filename); if (config->filename == NULL) { err("%s: strdup", __func__); free_config(config, loop); return NULL; } FILE *file = fopen(config->filename, "r"); if (file == NULL) { err("%s: unable to open configuration file: %s", __func__, config->filename); free_config(config, loop); return NULL; } if (parse_config(config, file, global_grammar) <= 0) { intmax_t whence = ftell(file); char line[256]; err("error parsing %s at %jd near:", filename, whence); fseek(file, -20, SEEK_CUR); for (int i = 0; i < 5; i++) err(" %jd\t%s", ftell(file), fgets(line, sizeof(line), file)); free_config(config, loop); config = NULL; } fclose(file); /* Listeners without access logger defined used global access log */ if (config != NULL && config->access_log != NULL) { struct Listener *listener; SLIST_FOREACH(listener, &config->listeners, entries) { if (listener->access_log == NULL) { listener->access_log = logger_ref_get(config->access_log); } } } return(config); } void free_config(struct Config *config, struct ev_loop *loop) { free(config->filename); free(config->user); free(config->group); free(config->pidfile); free_string_vector(config->resolver.nameservers); config->resolver.nameservers = NULL; free_string_vector(config->resolver.search); config->resolver.search = NULL; logger_ref_put(config->access_log); free_listeners(&config->listeners, loop); free_tables(&config->tables); free(config); } void reload_config(struct Config *config, struct ev_loop *loop) { notice("reloading configuration from %s", config->filename); struct Config *new_config = init_config(config->filename, loop); if (new_config == NULL) { err("failed to reload %s", config->filename); return; } /* update access_log */ logger_ref_put(config->access_log); config->access_log = logger_ref_get(new_config->access_log); reload_tables(&config->tables, &new_config->tables); listeners_reload(&config->listeners, &new_config->listeners, &config->tables, loop); free_config(new_config, loop); } void print_config(FILE *file, struct Config *config) { struct Listener *listener = NULL; struct Table *table = NULL; if (config->filename) fprintf(file, "# Config loaded from %s\n\n", config->filename); if (config->user) fprintf(file, "username %s\n\n", config->user); if (config->pidfile) fprintf(file, "pidfile %s\n\n", config->pidfile); print_resolver_config(file, &config->resolver); SLIST_FOREACH(listener, &config->listeners, entries) { print_listener_config(file, listener); } SLIST_FOREACH(table, &config->tables, entries) { print_table_config(file, table); } } static int accept_username(struct Config *config, const char *username) { if (config->user != NULL) { err("Duplicate username: %s", username); return 0; } config->user = strdup(username); if (config->user == NULL) { err("%s: strdup", __func__); return -1; } return 1; } static int accept_groupname(struct Config *config, const char *groupname) { if (config->group != NULL) { err("Duplicate groupname: %s", groupname); return 0; } config->group = strdup(groupname); if (config->group == NULL) { err("%s: strdup", __func__); return -1; } return 1; } static int accept_pidfile(struct Config *config, const char *pidfile) { if (config->pidfile != NULL) { err("Duplicate pidfile: %s", pidfile); return 0; } config->pidfile = strdup(pidfile); if (config->pidfile == NULL) { err("%s: strdup", __func__); return -1; } return 1; } static int end_listener_stanza(struct Config *config, struct Listener *listener) { listener->accept_cb = &accept_connection; if (valid_listener(listener) <= 0) { err("Invalid listener"); print_listener_config(stderr, listener); /* free listener */ listener_ref_get(listener); assert(listener->reference_count == 1); listener_ref_put(listener); return -1; } add_listener(&config->listeners, listener); return 1; } static int end_table_stanza(struct Config *config, struct Table *table) { /* TODO check table */ add_table(&config->tables, table); return 1; } static int end_backend(struct Table *table, struct Backend *backend) { /* TODO check backend */ table->use_proxy_header = table->use_proxy_header || backend->use_proxy_header; add_backend(&table->backends, backend); return 1; } static struct LoggerBuilder * new_logger_builder() { struct LoggerBuilder *lb = malloc(sizeof(struct LoggerBuilder)); if (lb == NULL) { err("%s: malloc", __func__); return NULL; } lb->filename = NULL; lb->syslog_facility = NULL; lb->priority = LOG_NOTICE; return lb; } static int accept_logger_filename(struct LoggerBuilder *lb, const char *filename) { lb->filename = strdup(filename); if (lb->filename == NULL) { err("%s: strdup", __func__); return -1; } return 1; } static int accept_logger_syslog_facility(struct LoggerBuilder *lb, const char *facility) { lb->syslog_facility = strdup(facility); if (lb->syslog_facility == NULL) { err("%s: strdup", __func__); return -1; } return 1; } static int accept_logger_priority(struct LoggerBuilder *lb, const char *priority) { const struct { const char *name; int priority; } priorities[] = { { "emergency", LOG_EMERG }, { "alert", LOG_ALERT }, { "critical", LOG_CRIT }, { "error", LOG_ERR }, { "warning", LOG_WARNING }, { "notice", LOG_NOTICE }, { "info", LOG_INFO }, { "debug", LOG_DEBUG }, }; for (size_t i = 0; i < sizeof(priorities) / sizeof(priorities[0]); i++) if (strncasecmp(priorities[i].name, priority, strlen(priority)) == 0) { lb->priority = priorities[i].priority; return 1; } return -1; } static int end_error_logger_stanza(struct Config *config __attribute__ ((unused)), struct LoggerBuilder *lb) { struct Logger *logger = NULL; if (lb->filename != NULL && lb->syslog_facility == NULL) logger = new_file_logger(lb->filename); else if (lb->syslog_facility != NULL && lb->filename == NULL) logger = new_syslog_logger(lb->syslog_facility); else err("Logger can not be both file logger and syslog logger"); if (logger == NULL) { free((char *)lb->filename); free((char *)lb->syslog_facility); free(lb); return -1; } set_logger_priority(logger, lb->priority); set_default_logger(logger); free((char *)lb->filename); free((char *)lb->syslog_facility); free(lb); return 1; } static int __attribute__((unused)) end_global_access_logger_stanza(struct Config *config, struct LoggerBuilder *lb) { struct Logger *logger = NULL; if (lb->filename != NULL && lb->syslog_facility == NULL) logger = new_file_logger(lb->filename); else if (lb->syslog_facility != NULL && lb->filename == NULL) logger = new_syslog_logger(lb->syslog_facility); else err("Logger can not be both file logger and syslog logger"); if (logger == NULL) { free((char *)lb->filename); free((char *)lb->syslog_facility); free(lb); return -1; } set_logger_priority(logger, lb->priority); logger_ref_put(config->access_log); config->access_log = logger_ref_get(logger); free((char *)lb->filename); free((char *)lb->syslog_facility); free(lb); return 1; } static int end_listener_access_logger_stanza(struct Listener *listener, struct LoggerBuilder *lb) { struct Logger *logger = NULL; if (lb->filename != NULL && lb->syslog_facility == NULL) logger = new_file_logger(lb->filename); else if (lb->syslog_facility != NULL && lb->filename == NULL) logger = new_syslog_logger(lb->syslog_facility); else err("Logger can not be both file logger and syslog logger"); if (logger == NULL) { free((char *)lb->filename); free((char *)lb->syslog_facility); free(lb); return -1; } set_logger_priority(logger, lb->priority); logger_ref_put(listener->access_log); listener->access_log = logger_ref_get(logger); free((char *)lb->filename); free((char *)lb->syslog_facility); free(lb); return 1; } static struct ResolverConfig * new_resolver_config() { struct ResolverConfig *resolver = malloc(sizeof(struct ResolverConfig)); if (resolver != NULL) { resolver->nameservers = NULL; resolver->search = NULL; resolver->mode = 0; } return resolver; } static size_t string_vector_len(char **vector) { size_t len = 0; while (vector != NULL && *vector++ != NULL) len++; return len; } static int append_to_string_vector(char ***vector_ptr, const char *string) { char **vector = *vector_ptr; size_t len = string_vector_len(vector); vector = realloc(vector, (len + 2) * sizeof(char *)); if (vector == NULL) { err("%s: realloc", __func__); return -errno; } *vector_ptr = vector; vector[len] = strdup(string); if (vector[len] == NULL) { err("%s: strdup", __func__); return -errno; } vector[len + 1] = NULL; return len + 1; } static void free_string_vector(char **vector) { for (int i = 0; vector != NULL && vector[i] != NULL; i++) { free(vector[i]); vector[i] = NULL; } free(vector); } static int accept_resolver_nameserver(struct ResolverConfig *resolver, const char *nameserver) { /* Validate address is a valid IP */ struct Address *ns_address = new_address(nameserver); if (!address_is_sockaddr(ns_address)) { free(ns_address); return -1; } free(ns_address); return append_to_string_vector(&resolver->nameservers, nameserver); } static int accept_resolver_search(struct ResolverConfig *resolver, const char *search) { struct Address *search_address = new_address(search); if (!address_is_hostname(search_address)) { free(search_address); return -1; } free(search_address); return append_to_string_vector(&resolver->search, search); } static int accept_resolver_mode(struct ResolverConfig *resolver, const char *mode) { for (size_t i = 0; i < sizeof(resolver_mode_names) / sizeof(resolver_mode_names[0]); i++) if (strncasecmp(resolver_mode_names[i], mode, strlen(mode)) == 0) { resolver->mode = i; return 1; } return -1; } static int end_resolver_stanza(struct Config *config, struct ResolverConfig *resolver) { config->resolver = *resolver; free(resolver); return 1; } static void print_resolver_config(FILE *file, struct ResolverConfig *resolver) { fprintf(file, "resolver {\n"); for (int i = 0; resolver->nameservers != NULL && resolver->nameservers[i] != NULL; i++) fprintf(file, "\tnameserver %s\n", resolver->nameservers[i]); for (int i = 0; resolver->search != NULL && resolver->search[i] != NULL; i++) fprintf(file, "\tsearch %s\n", resolver->search[i]); fprintf(file, "\tmode %s\n", resolver_mode_names[resolver->mode]); fprintf(file, "}\n\n"); } sniproxy-0.6.0/src/config.h000066400000000000000000000037511340212110200156050ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 CONFIG_H #define CONFIG_H #include #include "table.h" #include "listener.h" struct Config { char *filename; char *user; char *group; char *pidfile; struct ResolverConfig { char **nameservers; char **search; int mode; } resolver; struct Logger *access_log; struct Listener_head listeners; struct Table_head tables; }; struct Config *init_config(const char *, struct ev_loop *); void reload_config(struct Config *, struct ev_loop *); void free_config(struct Config *, struct ev_loop *); void print_config(FILE *, struct Config *); #endif sniproxy-0.6.0/src/connection.c000066400000000000000000000772201340212110200164740ustar00rootroot00000000000000/* * Copyright (c) 2011-2014, Dustin Lundquist * 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 HOLDERS 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 #include #include #include #include #include #include #include #include #include #include /* getaddrinfo */ #include /* close */ #include #include #include #include #include "connection.h" #include "resolv.h" #include "address.h" #include "protocol.h" #include "logger.h" #define IS_TEMPORARY_SOCKERR(_errno) (_errno == EAGAIN || \ _errno == EWOULDBLOCK || \ _errno == EINTR) struct resolv_cb_data { struct Connection *connection; const struct Address *address; struct ev_loop *loop; int cb_free_addr; }; static TAILQ_HEAD(ConnectionHead, Connection) connections; static inline int client_socket_open(const struct Connection *); static inline int server_socket_open(const struct Connection *); static void reactivate_watcher(struct ev_loop *, struct ev_io *, const struct Buffer *, const struct Buffer *); static void connection_cb(struct ev_loop *, struct ev_io *, int); static void resolv_cb(struct Address *, void *); static void reactivate_watchers(struct Connection *, struct ev_loop *); static void insert_proxy_v1_header(struct Connection *); static void parse_client_request(struct Connection *); static void resolve_server_address(struct Connection *, struct ev_loop *); static void initiate_server_connect(struct Connection *, struct ev_loop *); static void close_connection(struct Connection *, struct ev_loop *); static void close_client_socket(struct Connection *, struct ev_loop *); static void abort_connection(struct Connection *); static void close_server_socket(struct Connection *, struct ev_loop *); static struct Connection *new_connection(struct ev_loop *); static void log_connection(struct Connection *); static void log_bad_request(struct Connection *, const char *, size_t, int); static void free_connection(struct Connection *); static void print_connection(FILE *, const struct Connection *); static void free_resolv_cb_data(struct resolv_cb_data *); void init_connections() { TAILQ_INIT(&connections); } /** * Accept a new incoming connection * * Returns 1 on success or 0 on error; */ int accept_connection(struct Listener *listener, struct ev_loop *loop) { struct Connection *con = new_connection(loop); if (con == NULL) { err("new_connection failed"); return 0; } con->listener = listener_ref_get(listener); #ifdef HAVE_ACCEPT4 int sockfd = accept4(listener->watcher.fd, (struct sockaddr *)&con->client.addr, &con->client.addr_len, SOCK_NONBLOCK); #else int sockfd = accept(listener->watcher.fd, (struct sockaddr *)&con->client.addr, &con->client.addr_len); #endif if (sockfd < 0) { int saved_errno = errno; warn("accept failed: %s", strerror(errno)); free_connection(con); errno = saved_errno; return 0; } #ifndef HAVE_ACCEPT4 int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); #endif if (getsockname(sockfd, (struct sockaddr *)&con->client.local_addr, &con->client.local_addr_len) != 0) { int saved_errno = errno; warn("getsockname failed: %s", strerror(errno)); free_connection(con); errno = saved_errno; return 0; } /* Avoiding type-punned pointer warning */ struct ev_io *client_watcher = &con->client.watcher; ev_io_init(client_watcher, connection_cb, sockfd, EV_READ); con->client.watcher.data = con; con->state = ACCEPTED; con->established_timestamp = ev_now(loop); TAILQ_INSERT_HEAD(&connections, con, entries); ev_io_start(loop, client_watcher); if (con->listener->table->use_proxy_header || con->listener->fallback_use_proxy_header) insert_proxy_v1_header(con); return 1; } /* * Close and free all connections */ void free_connections(struct ev_loop *loop) { struct Connection *iter; while ((iter = TAILQ_FIRST(&connections)) != NULL) { TAILQ_REMOVE(&connections, iter, entries); close_connection(iter, loop); free_connection(iter); } } /* dumps a list of all connections for debugging */ void print_connections() { char filename[] = "/tmp/sniproxy-connections-XXXXXX"; int fd = mkstemp(filename); if (fd < 0) { warn("mkstemp failed: %s", strerror(errno)); return; } FILE *temp = fdopen(fd, "w"); if (temp == NULL) { warn("fdopen failed: %s", strerror(errno)); return; } fprintf(temp, "Running connections:\n"); struct Connection *iter; TAILQ_FOREACH(iter, &connections, entries) print_connection(temp, iter); if (fclose(temp) < 0) warn("fclose failed: %s", strerror(errno)); notice("Dumped connections to %s", filename); } /* * Test is client socket is open * * Returns true iff the client socket is opened based on connection state. */ static inline int client_socket_open(const struct Connection *con) { return con->state == ACCEPTED || con->state == PARSED || con->state == RESOLVING || con->state == RESOLVED || con->state == CONNECTED || con->state == SERVER_CLOSED; } /* * Test is server socket is open * * Returns true iff the server socket is opened based on connection state. */ static inline int server_socket_open(const struct Connection *con) { return con->state == CONNECTED || con->state == CLIENT_CLOSED; } /* * Main client callback: this is used by both the client and server watchers * * The logic is almost the same except for: * + input buffer * + output buffer * + how to close the socket * */ static void connection_cb(struct ev_loop *loop, struct ev_io *w, int revents) { struct Connection *con = (struct Connection *)w->data; int is_client = &con->client.watcher == w; const char *socket_name = is_client ? "client" : "server"; struct Buffer *input_buffer = is_client ? con->client.buffer : con->server.buffer; struct Buffer *output_buffer = is_client ? con->server.buffer : con->client.buffer; void (*close_socket)(struct Connection *, struct ev_loop *) = is_client ? close_client_socket : close_server_socket; /* Receive first in case the socket was closed */ if (revents & EV_READ && buffer_room(input_buffer)) { ssize_t bytes_received = buffer_recv(input_buffer, w->fd, 0, loop); if (bytes_received < 0 && !IS_TEMPORARY_SOCKERR(errno)) { warn("recv(%s): %s, closing connection", socket_name, strerror(errno)); close_socket(con, loop); revents = 0; /* Clear revents so we don't try to send */ } else if (bytes_received == 0) { /* peer closed socket */ close_socket(con, loop); revents = 0; } } /* Transmit */ if (revents & EV_WRITE && buffer_len(output_buffer)) { ssize_t bytes_transmitted = buffer_send(output_buffer, w->fd, 0, loop); if (bytes_transmitted < 0 && !IS_TEMPORARY_SOCKERR(errno)) { warn("send(%s): %s, closing connection", socket_name, strerror(errno)); close_socket(con, loop); } } /* Handle any state specific logic */ if (is_client && con->state == ACCEPTED) parse_client_request(con); if (is_client && con->state == PARSED) resolve_server_address(con, loop); if (is_client && con->state == RESOLVED) initiate_server_connect(con, loop); /* Close other socket if we have flushed corresponding buffer */ if (con->state == SERVER_CLOSED && buffer_len(con->server.buffer) == 0) close_client_socket(con, loop); if (con->state == CLIENT_CLOSED && buffer_len(con->client.buffer) == 0) close_server_socket(con, loop); if (con->state == CLOSED) { TAILQ_REMOVE(&connections, con, entries); if (con->listener->access_log) log_connection(con); free_connection(con); return; } reactivate_watchers(con, loop); } static void reactivate_watchers(struct Connection *con, struct ev_loop *loop) { struct ev_io *client_watcher = &con->client.watcher; struct ev_io *server_watcher = &con->server.watcher; /* Reactivate watchers */ if (client_socket_open(con)) reactivate_watcher(loop, client_watcher, con->client.buffer, con->server.buffer); if (server_socket_open(con)) reactivate_watcher(loop, server_watcher, con->server.buffer, con->client.buffer); /* Neither watcher is active when the corresponding socket is closed */ assert(client_socket_open(con) || !ev_is_active(client_watcher)); assert(server_socket_open(con) || !ev_is_active(server_watcher)); /* At least one watcher is still active for this connection, * or DNS callback active */ assert((ev_is_active(client_watcher) && con->client.watcher.events) || (ev_is_active(server_watcher) && con->server.watcher.events) || con->state == RESOLVING); /* Move to head of queue, so we can find inactive connections */ TAILQ_REMOVE(&connections, con, entries); TAILQ_INSERT_HEAD(&connections, con, entries); } static void reactivate_watcher(struct ev_loop *loop, struct ev_io *w, const struct Buffer *input_buffer, const struct Buffer *output_buffer) { int events = 0; if (buffer_room(input_buffer)) events |= EV_READ; if (buffer_len(output_buffer)) events |= EV_WRITE; if (ev_is_active(w)) { if (events == 0) ev_io_stop(loop, w); else if (events != w->events) { ev_io_stop(loop, w); ev_io_set(w, w->fd, events); ev_io_start(loop, w); } } else if (events != 0) { ev_io_set(w, w->fd, events); ev_io_start(loop, w); } } static void insert_proxy_v1_header(struct Connection *con) { char buf[INET6_ADDRSTRLEN] = { '\0' }; size_t buf_len; con->header_len += buffer_push(con->client.buffer, "PROXY ", 6); switch (con->client.addr.ss_family) { case AF_INET: con->header_len += buffer_push(con->client.buffer, "TCP4 ", 5); inet_ntop(AF_INET, &((const struct sockaddr_in *)&con->client.addr)-> sin_addr, buf, sizeof(buf)); buf_len = strlen(buf); con->header_len += buffer_push(con->client.buffer, buf, buf_len); con->header_len += buffer_push(con->client.buffer, " ", 1); inet_ntop(AF_INET, &((const struct sockaddr_in *)&con->client.local_addr)-> sin_addr, buf, sizeof(buf)); buf_len = strlen(buf); con->header_len += buffer_push(con->client.buffer, buf, buf_len); buf_len = snprintf(buf, sizeof(buf), " %" PRIu16, ntohs(((const struct sockaddr_in *)&con-> client.addr)->sin_port)); con->header_len += buffer_push(con->client.buffer, buf, buf_len); buf_len = snprintf(buf, sizeof(buf), " %" PRIu16, ntohs(((const struct sockaddr_in *)&con-> client.local_addr)->sin_port)); con->header_len += buffer_push(con->client.buffer, buf, buf_len); break; case AF_INET6: con->header_len += buffer_push(con->client.buffer, "TCP6 ", 5); inet_ntop(AF_INET6, &((const struct sockaddr_in6 *)&con->client.addr)-> sin6_addr, buf, sizeof(buf)); buf_len = strlen(buf); con->header_len += buffer_push(con->client.buffer, buf, buf_len); con->header_len += buffer_push(con->client.buffer, " ", 1); inet_ntop(AF_INET6, &((const struct sockaddr_in6 *)&con-> client.local_addr)->sin6_addr, buf, sizeof(buf)); buf_len = strlen(buf); con->header_len += buffer_push(con->client.buffer, buf, buf_len); buf_len = snprintf(buf, sizeof(buf), " %" PRIu16, ntohs(((const struct sockaddr_in6 *)&con-> client.addr)->sin6_port)); con->header_len += buffer_push(con->client.buffer, buf, buf_len); buf_len = snprintf(buf, sizeof(buf), " %" PRIu16, ntohs(((const struct sockaddr_in6 *)&con-> client.local_addr)->sin6_port)); con->header_len += buffer_push(con->client.buffer, buf, buf_len); break; default: con->header_len += buffer_push(con->client.buffer, "UNKNOWN", 7); } con->header_len += buffer_push(con->client.buffer, "\r\n", 2); } static void parse_client_request(struct Connection *con) { const char *payload; size_t payload_len = buffer_coalesce(con->client.buffer, (const void **)&payload); char *hostname = NULL; /* Avoid payload_len underflow and empty request */ if (payload_len <= con->header_len) return; payload += con->header_len; payload_len -= con->header_len; int result = con->listener->protocol->parse_packet(payload, payload_len, &hostname); if (result < 0) { char client[INET6_ADDRSTRLEN + 8]; if (result == -1) { /* incomplete request */ if (buffer_room(con->client.buffer) > 0) return; /* give client a chance to send more data */ warn("Request from %s exceeded %zu byte buffer size", display_sockaddr(&con->client.addr, client, sizeof(client)), buffer_size(con->client.buffer)); } else if (result == -2) { warn("Request from %s did not include a hostname", display_sockaddr(&con->client.addr, client, sizeof(client))); } else { warn("Unable to parse request from %s: parse_packet returned %d", display_sockaddr(&con->client.addr, client, sizeof(client)), result); if (con->listener->log_bad_requests) log_bad_request(con, payload, payload_len, result); } if (con->listener->fallback_address == NULL) { abort_connection(con); return; } } con->hostname = hostname; con->hostname_len = (size_t)result; con->state = PARSED; } static void abort_connection(struct Connection *con) { assert(client_socket_open(con)); buffer_push(con->server.buffer, con->listener->protocol->abort_message, con->listener->protocol->abort_message_len); con->state = SERVER_CLOSED; } static void resolve_server_address(struct Connection *con, struct ev_loop *loop) { struct LookupResult result = listener_lookup_server_address(con->listener, con->hostname, con->hostname_len); if (result.address == NULL) { abort_connection(con); return; } else if (address_is_hostname(result.address)) { #ifndef HAVE_LIBUDNS warn("DNS lookups not supported unless sniproxy compiled with libudns"); if (result.caller_free_address) free((void *)result.address); abort_connection(con); return; #else struct resolv_cb_data *cb_data = malloc(sizeof(struct resolv_cb_data)); if (cb_data == NULL) { err("%s: malloc", __func__); if (result.caller_free_address) free((void *)result.address); abort_connection(con); return; } cb_data->connection = con; cb_data->address = result.address; cb_data->cb_free_addr = result.caller_free_address; cb_data->loop = loop; con->use_proxy_header = result.use_proxy_header; int resolv_mode = RESOLV_MODE_DEFAULT; if (con->listener->transparent_proxy) { char listener_address[ADDRESS_BUFFER_SIZE]; switch (con->client.addr.ss_family) { case AF_INET: resolv_mode = RESOLV_MODE_IPV4_ONLY; break; case AF_INET6: resolv_mode = RESOLV_MODE_IPV6_ONLY; break; default: warn("attempt to use transparent proxy with hostname %s " "on non-IP listener %s, falling back to " "non-transparent mode", address_hostname(result.address), display_sockaddr(con->listener->address, listener_address, sizeof(listener_address)) ); } } con->query_handle = resolv_query(address_hostname(result.address), resolv_mode, resolv_cb, (void (*)(void *))free_resolv_cb_data, cb_data); con->state = RESOLVING; #endif } else if (address_is_sockaddr(result.address)) { con->server.addr_len = address_sa_len(result.address); assert(con->server.addr_len <= sizeof(con->server.addr)); memcpy(&con->server.addr, address_sa(result.address), con->server.addr_len); con->use_proxy_header = result.use_proxy_header; if (result.caller_free_address) free((void *)result.address); con->state = RESOLVED; } else { /* invalid address type */ assert(0); } } static void resolv_cb(struct Address *result, void *data) { struct resolv_cb_data *cb_data = (struct resolv_cb_data *)data; struct Connection *con = cb_data->connection; struct ev_loop *loop = cb_data->loop; if (con->state != RESOLVING) { info("resolv_cb() called for connection not in RESOLVING state"); return; } if (result == NULL) { notice("unable to resolve %s, closing connection", address_hostname(cb_data->address)); abort_connection(con); } else { assert(address_is_sockaddr(result)); /* copy port from server_address */ address_set_port(result, address_port(cb_data->address)); con->server.addr_len = address_sa_len(result); assert(con->server.addr_len <= sizeof(con->server.addr)); memcpy(&con->server.addr, address_sa(result), con->server.addr_len); con->state = RESOLVED; initiate_server_connect(con, loop); } con->query_handle = NULL; reactivate_watchers(con, loop); } static void free_resolv_cb_data(struct resolv_cb_data *cb_data) { if (cb_data->cb_free_addr) free((void *)cb_data->address); free(cb_data); } static void initiate_server_connect(struct Connection *con, struct ev_loop *loop) { #ifdef HAVE_ACCEPT4 int sockfd = socket(con->server.addr.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0); #else int sockfd = socket(con->server.addr.ss_family, SOCK_STREAM, 0); #endif if (sockfd < 0) { char client[INET6_ADDRSTRLEN + 8]; warn("socket failed: %s, closing connection from %s", strerror(errno), display_sockaddr(&con->client.addr, client, sizeof(client))); abort_connection(con); return; } #ifndef HAVE_ACCEPT4 int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); #endif if (con->listener->transparent_proxy && con->client.addr.ss_family == con->server.addr.ss_family) { int on = 1; #ifdef IP_TRANSPARENT int result = setsockopt(sockfd, SOL_IP, IP_TRANSPARENT, &on, sizeof(on)); #else int result = -EPERM; /* XXX error: not implemented would be better, but this shouldn't be * reached since it is prohibited in the configuration parser. */ #endif if (result < 0) { err("setsockopt IP_TRANSPARENT failed: %s", strerror(errno)); close(sockfd); abort_connection(con); return; } result = bind(sockfd, (struct sockaddr *)&con->client.addr, con->client.addr_len); if (result < 0) { err("bind failed: %s", strerror(errno)); close(sockfd); abort_connection(con); return; } } else if (con->listener->source_address) { int on = 1; int result = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (result < 0) { err("setsockopt SO_REUSEADDR failed: %s", strerror(errno)); close(sockfd); abort_connection(con); return; } int tries = 5; do { result = bind(sockfd, address_sa(con->listener->source_address), address_sa_len(con->listener->source_address)); } while (tries-- > 0 && result < 0 && errno == EADDRINUSE && address_port(con->listener->source_address) == 0); if (result < 0) { err("bind failed: %s", strerror(errno)); close(sockfd); abort_connection(con); return; } } int result = connect(sockfd, (struct sockaddr *)&con->server.addr, con->server.addr_len); /* TODO retry connect in EADDRNOTAVAIL case */ if (result < 0 && errno != EINPROGRESS) { close(sockfd); char server[INET6_ADDRSTRLEN + 8]; warn("Failed to open connection to %s: %s", display_sockaddr(&con->server.addr, server, sizeof(server)), strerror(errno)); abort_connection(con); return; } if (getsockname(sockfd, (struct sockaddr *)&con->server.local_addr, &con->server.local_addr_len) != 0) { close(sockfd); warn("getsockname failed: %s", strerror(errno)); abort_connection(con); return; } if (con->header_len && !con->use_proxy_header) { /* If we prepended the PROXY header and this backend isn't configured * to receive it, consume it now */ buffer_pop(con->client.buffer, NULL, con->header_len); } struct ev_io *server_watcher = &con->server.watcher; ev_io_init(server_watcher, connection_cb, sockfd, EV_WRITE); con->server.watcher.data = con; con->state = CONNECTED; ev_io_start(loop, server_watcher); } /* Close client socket. * Caller must ensure that it has not been closed before. */ static void close_client_socket(struct Connection *con, struct ev_loop *loop) { assert(con->state != CLOSED && con->state != CLIENT_CLOSED); ev_io_stop(loop, &con->client.watcher); if (close(con->client.watcher.fd) < 0) warn("close failed: %s", strerror(errno)); if (con->state == RESOLVING) { resolv_cancel(con->query_handle); con->state = PARSED; } /* next state depends on previous state */ if (con->state == SERVER_CLOSED || con->state == ACCEPTED || con->state == PARSED || con->state == RESOLVING || con->state == RESOLVED) con->state = CLOSED; else con->state = CLIENT_CLOSED; } /* Close server socket. * Caller must ensure that it has not been closed before. */ static void close_server_socket(struct Connection *con, struct ev_loop *loop) { assert(con->state != CLOSED && con->state != SERVER_CLOSED); ev_io_stop(loop, &con->server.watcher); if (close(con->server.watcher.fd) < 0) warn("close failed: %s", strerror(errno)); /* next state depends on previous state */ if (con->state == CLIENT_CLOSED) con->state = CLOSED; else con->state = SERVER_CLOSED; } static void close_connection(struct Connection *con, struct ev_loop *loop) { assert(con->state != NEW); /* only used during initialization */ if (con->state == CONNECTED || con->state == CLIENT_CLOSED) close_server_socket(con, loop); assert(con->state == ACCEPTED || con->state == PARSED || con->state == RESOLVING || con->state == RESOLVED || con->state == SERVER_CLOSED || con->state == CLOSED); if (con->state == ACCEPTED || con->state == PARSED || con->state == RESOLVING || con->state == RESOLVED || con->state == SERVER_CLOSED) close_client_socket(con, loop); assert(con->state == CLOSED); } /* * Allocate and initialize a new connection */ static struct Connection * new_connection(struct ev_loop *loop) { struct Connection *con = calloc(1, sizeof(struct Connection)); if (con == NULL) return NULL; con->state = NEW; con->client.addr_len = sizeof(con->client.addr); con->client.local_addr = (struct sockaddr_storage){.ss_family = AF_UNSPEC}; con->client.local_addr_len = sizeof(con->client.local_addr); con->server.addr_len = sizeof(con->server.addr); con->server.local_addr = (struct sockaddr_storage){.ss_family = AF_UNSPEC}; con->server.local_addr_len = sizeof(con->server.local_addr); con->hostname = NULL; con->hostname_len = 0; con->header_len = 0; con->query_handle = NULL; con->use_proxy_header = 0; con->client.buffer = new_buffer(4096, loop); if (con->client.buffer == NULL) { free_connection(con); return NULL; } con->server.buffer = new_buffer(4096, loop); if (con->server.buffer == NULL) { free_connection(con); return NULL; } return con; } static void log_connection(struct Connection *con) { ev_tstamp duration; char client_address[ADDRESS_BUFFER_SIZE]; char listener_address[ADDRESS_BUFFER_SIZE]; char server_address[ADDRESS_BUFFER_SIZE]; if (con->client.buffer->last_recv > con->server.buffer->last_recv) duration = con->client.buffer->last_recv - con->established_timestamp; else duration = con->server.buffer->last_recv - con->established_timestamp; display_sockaddr(&con->client.addr, client_address, sizeof(client_address)); display_sockaddr(&con->client.local_addr, listener_address, sizeof(listener_address)); display_sockaddr(&con->server.addr, server_address, sizeof(server_address)); log_msg(con->listener->access_log, LOG_NOTICE, "%s -> %s -> %s [%.*s] %zu/%zu bytes tx %zu/%zu bytes rx %1.3f seconds", client_address, listener_address, server_address, (int)con->hostname_len, con->hostname, con->server.buffer->tx_bytes, con->server.buffer->rx_bytes, con->client.buffer->tx_bytes, con->client.buffer->rx_bytes, duration); } static void log_bad_request(struct Connection *con __attribute__((unused)), const char *req, size_t req_len, int parse_result) { size_t message_len = 64 + 6 * req_len; char *message = malloc(message_len); if (message == NULL) { err("log_bad_request: unable to allocate message buffer"); return; } char *message_pos = message; char *message_end = message + message_len; message_pos += snprintf(message_pos, (size_t)(message_end - message_pos), "parse_packet({"); for (size_t i = 0; i < req_len; i++) message_pos += snprintf(message_pos, (size_t)(message_end - message_pos), "0x%02hhx, ", (unsigned char)req[i]); message_pos -= 2;/* Delete the trailing ', ' */ snprintf(message_pos, (size_t)(message_end - message_pos), "}, %zu, ...) = %d", req_len, parse_result); debug("%s", message); free(message); } /* * Free a connection and associated data * * Requires that no watchers remain active */ static void free_connection(struct Connection *con) { if (con == NULL) return; listener_ref_put(con->listener); free_buffer(con->client.buffer); free_buffer(con->server.buffer); free((void *)con->hostname); /* cast away const'ness */ free(con); } static void print_connection(FILE *file, const struct Connection *con) { char client[INET6_ADDRSTRLEN + 8]; char server[INET6_ADDRSTRLEN + 8]; switch (con->state) { case NEW: fprintf(file, "NEW -\t-\n"); break; case ACCEPTED: fprintf(file, "ACCEPTED %s %zu/%zu\t-\n", display_sockaddr(&con->client.addr, client, sizeof(client)), buffer_len(con->client.buffer), buffer_size(con->client.buffer)); break; case PARSED: fprintf(file, "PARSED %s %zu/%zu\t-\n", display_sockaddr(&con->client.addr, client, sizeof(client)), buffer_len(con->client.buffer), buffer_size(con->client.buffer)); break; case RESOLVING: fprintf(file, "RESOLVING %s %zu/%zu\t-\n", display_sockaddr(&con->client.addr, client, sizeof(client)), buffer_len(con->client.buffer), buffer_size(con->client.buffer)); break; case RESOLVED: fprintf(file, "RESOLVED %s %zu/%zu\t-\n", display_sockaddr(&con->client.addr, client, sizeof(client)), buffer_len(con->client.buffer), buffer_size(con->client.buffer)); break; case CONNECTED: fprintf(file, "CONNECTED %s %zu/%zu\t%s %zu/%zu\n", display_sockaddr(&con->client.addr, client, sizeof(client)), buffer_len(con->client.buffer), buffer_size(con->client.buffer), display_sockaddr(&con->server.addr, server, sizeof(server)), buffer_len(con->server.buffer), buffer_size(con->server.buffer)); break; case SERVER_CLOSED: fprintf(file, "SERVER_CLOSED %s %zu/%zu\t-\n", display_sockaddr(&con->client.addr, client, sizeof(client)), buffer_len(con->client.buffer), buffer_size(con->client.buffer)); break; case CLIENT_CLOSED: fprintf(file, "CLIENT_CLOSED -\t%s %zu/%zu\n", display_sockaddr(&con->server.addr, server, sizeof(server)), buffer_len(con->server.buffer), buffer_size(con->server.buffer)); break; case CLOSED: fprintf(file, "CLOSED -\t-\n"); break; } } sniproxy-0.6.0/src/connection.h000066400000000000000000000052631340212110200164770ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 CONNECTION_H #define CONNECTION_H #include #include #include #include "listener.h" #include "buffer.h" struct Connection { enum State { NEW, /* Before successful accept */ ACCEPTED, /* Newly accepted client connection */ PARSED, /* Parsed initial request and extracted hostname */ RESOLVING, /* DNS query in progress */ RESOLVED, /* Server socket address resolved */ CONNECTED, /* Connected to server */ SERVER_CLOSED, /* Client closed socket */ CLIENT_CLOSED, /* Server closed socket */ CLOSED /* Both sockets closed */ } state; struct { struct sockaddr_storage addr, local_addr; socklen_t addr_len, local_addr_len; struct ev_io watcher; struct Buffer *buffer; } client, server; struct Listener *listener; const char *hostname; /* Requested hostname */ size_t hostname_len; size_t header_len; struct ResolvQuery *query_handle; ev_tstamp established_timestamp; int use_proxy_header; TAILQ_ENTRY(Connection) entries; }; void init_connections(); int accept_connection(struct Listener *, struct ev_loop *); void free_connections(struct ev_loop *); void print_connections(); #endif sniproxy-0.6.0/src/http.c000066400000000000000000000115111340212110200153030ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 #include /* malloc() */ #include /* strncpy() */ #include /* strncasecmp() */ #include /* isblank(), isdigit() */ #include "http.h" #include "protocol.h" #define SERVER_NAME_LEN 256 static int parse_http_header(const char *, size_t, char **); static int get_header(const char *, const char *, size_t, char **); static size_t next_header(const char **, size_t *); static const char http_503[] = "HTTP/1.1 503 Service Temporarily Unavailable\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n\r\n" "Backend not available"; const struct Protocol *const http_protocol = &(struct Protocol){ .name = "http", .default_port = 80, .parse_packet = &parse_http_header, .abort_message = http_503, .abort_message_len = sizeof(http_503) - 1, }; /* * Parses a HTTP request for the Host: header * * Returns: * >=0 - length of the hostname and updates *hostname * caller is responsible for freeing *hostname * -1 - Incomplete request * -2 - No Host header included in this request * -3 - Invalid hostname pointer * -4 - malloc failure * < -4 - Invalid HTTP request * */ static int parse_http_header(const char* data, size_t data_len, char **hostname) { int result, i; if (hostname == NULL) return -3; result = get_header("Host:", data, data_len, hostname); if (result < 0) return result; /* * if the user specifies the port in the request, it is included here. * Host: example.com:80 * Host: [2001:db8::1]:8080 * so we trim off port portion */ for (i = result - 1; i >= 0; i--) if ((*hostname)[i] == ':') { (*hostname)[i] = '\0'; result = i; break; } else if (!isdigit((*hostname)[i])) { break; } return result; } static int get_header(const char *header, const char *data, size_t data_len, char **value) { size_t len, header_len; header_len = strlen(header); /* loop through headers stopping at first blank line */ while ((len = next_header(&data, &data_len)) != 0) if (len > header_len && strncasecmp(header, data, header_len) == 0) { /* Eat leading whitespace */ while (header_len < len && isblank(data[header_len])) header_len++; *value = malloc(len - header_len + 1); if (*value == NULL) return -4; strncpy(*value, data + header_len, len - header_len); (*value)[len - header_len] = '\0'; return len - header_len; } /* If there is no data left after reading all the headers then we do not * have a complete HTTP request, there must be a blank line */ if (data_len == 0) return -1; return -2; } static size_t next_header(const char **data, size_t *len) { size_t header_len; /* perhaps we can optimize this to reuse the value of header_len, rather * than scanning twice. * Walk our data stream until the end of the header */ while (*len > 2 && (*data)[0] != '\r' && (*data)[1] != '\n') { (*len)--; (*data)++; } /* advanced past the pair */ *data += 2; *len -= 2; /* Find the length of the next header */ header_len = 0; while (*len > header_len + 1 && (*data)[header_len] != '\r' && (*data)[header_len + 1] != '\n') header_len++; return header_len; } sniproxy-0.6.0/src/http.h000066400000000000000000000027741340212110200153230ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 HTTP_H #define HTTP_H #include #include "protocol.h" const struct Protocol *const http_protocol; #endif sniproxy-0.6.0/src/listener.c000066400000000000000000000641071340212110200161620ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 #include #include #include #include /* offsetof */ #include /* strcasecmp() */ #include #include #include #include #include #include #include #include #include #include #include #include "address.h" #include "listener.h" #include "logger.h" #include "binder.h" #include "protocol.h" #include "tls.h" #include "http.h" static void close_listener(struct ev_loop *, struct Listener *); static void accept_cb(struct ev_loop *, struct ev_io *, int); static void backoff_timer_cb(struct ev_loop *, struct ev_timer *, int); static int init_listener(struct Listener *, const struct Table_head *, struct ev_loop *); static void listener_update(struct Listener *, struct Listener *, const struct Table_head *); static void free_listener(struct Listener *); static int parse_boolean(const char *); static int parse_boolean(const char *boolean) { const char *boolean_true[] = { "yes", "true", "on", }; const char *boolean_false[] = { "no", "false", "off", }; for (size_t i = 0; i < sizeof(boolean_true) / sizeof(boolean_true[0]); i++) if (strcasecmp(boolean, boolean_true[i]) == 0) return 1; for (size_t i = 0; i < sizeof(boolean_false) / sizeof(boolean_false[0]); i++) if (strcasecmp(boolean, boolean_false[i]) == 0) return 0; err("Unable to parse '%s' as a boolean value", boolean); return -1; } /* * Initialize each listener. */ void init_listeners(struct Listener_head *listeners, const struct Table_head *tables, struct ev_loop *loop) { struct Listener *iter; char address[ADDRESS_BUFFER_SIZE]; SLIST_FOREACH(iter, listeners, entries) { if (init_listener(iter, tables, loop) < 0) { err("Failed to initialize listener %s", display_address(iter->address, address, sizeof(address))); exit(1); } } } void listeners_reload(struct Listener_head *existing_listeners, struct Listener_head *new_listeners, const struct Table_head *tables, struct ev_loop *loop) { struct Listener *iter_existing = SLIST_FIRST(existing_listeners); struct Listener *iter_new = SLIST_FIRST(new_listeners); while (iter_existing != NULL || iter_new != NULL) { int compare_result; char address[ADDRESS_BUFFER_SIZE]; if (iter_existing == NULL) compare_result = 1; else if (iter_new == NULL) compare_result = -1; else compare_result = address_compare(iter_existing->address, iter_new->address); if (compare_result > 0) { struct Listener *new_listener = iter_new; iter_new = SLIST_NEXT(iter_new, entries); notice("Listener %s added.", display_address(new_listener->address, address, sizeof(address))); SLIST_REMOVE(new_listeners, new_listener, Listener, entries); add_listener(existing_listeners, new_listener); init_listener(new_listener, tables, loop); /* -1 for removing from new_listeners */ listener_ref_put(new_listener); } else if (compare_result == 0) { notice ("Listener %s updated.", display_address(iter_existing->address, address, sizeof(address))); listener_update(iter_existing, iter_new, tables); iter_existing = SLIST_NEXT(iter_existing, entries); iter_new = SLIST_NEXT(iter_new, entries); } else { struct Listener *removed_listener = iter_existing; iter_existing = SLIST_NEXT(iter_existing, entries); notice("Listener %s removed.", display_address(removed_listener->address, address, sizeof(address))); SLIST_REMOVE(existing_listeners, removed_listener, Listener, entries); close_listener(loop, removed_listener); /* -1 for removing from existing_listeners */ listener_ref_put(removed_listener); } } } /* * Copy contents of new_listener into existing listener */ static void listener_update(struct Listener *existing_listener, struct Listener *new_listener, const struct Table_head *tables) { assert(existing_listener != NULL); assert(new_listener != NULL); assert(address_compare(existing_listener->address, new_listener->address) == 0); free(existing_listener->fallback_address); existing_listener->fallback_address = new_listener->fallback_address; new_listener->fallback_address = NULL; free(existing_listener->source_address); existing_listener->source_address = new_listener->source_address; new_listener->source_address = NULL; existing_listener->protocol = new_listener->protocol; free(existing_listener->table_name); existing_listener->table_name = new_listener->table_name; new_listener->table_name = NULL; logger_ref_put(existing_listener->access_log); existing_listener->access_log = logger_ref_get(new_listener->access_log); existing_listener->log_bad_requests = new_listener->log_bad_requests; struct Table *new_table = table_lookup(tables, existing_listener->table_name); if (new_table != NULL) { init_table(new_table); table_ref_put(existing_listener->table); existing_listener->table = table_ref_get(new_table); table_ref_put(new_listener->table); new_listener->table = NULL; } } struct Listener * new_listener() { struct Listener *listener = calloc(1, sizeof(struct Listener)); if (listener == NULL) { err("calloc"); return NULL; } listener->address = NULL; listener->fallback_address = NULL; listener->source_address = NULL; listener->protocol = tls_protocol; listener->table_name = NULL; listener->access_log = NULL; listener->log_bad_requests = 0; listener->reuseport = 0; listener->ipv6_v6only = 0; listener->transparent_proxy = 0; listener->fallback_use_proxy_header = 0; listener->reference_count = 0; /* Initializes sock fd to negative sentinel value to indicate watchers * are not active */ ev_io_init(&listener->watcher, accept_cb, -1, EV_READ); ev_timer_init(&listener->backoff_timer, backoff_timer_cb, 0.0, 0.0); listener->table = NULL; return listener; } int accept_listener_arg(struct Listener *listener, const char *arg) { if (listener->address == NULL && !is_numeric(arg)) { listener->address = new_address(arg); if (listener->address == NULL || !address_is_sockaddr(listener->address)) { err("Invalid listener argument %s", arg); return -1; } } else if (listener->address == NULL && is_numeric(arg)) { listener->address = new_address("[::]"); if (listener->address == NULL || !address_is_sockaddr(listener->address)) { err("Unable to initialize default address"); return -1; } if (!address_set_port_str(listener->address, arg)) { err("Invalid port %s", arg); return -1; } } else if (address_port(listener->address) == 0 && is_numeric(arg)) { if (!address_set_port_str(listener->address, arg)) { err("Invalid port %s", arg); return -1; } } else { err("Invalid listener argument %s", arg); } return 1; } int accept_listener_table_name(struct Listener *listener, const char *table_name) { if (listener->table_name != NULL) { err("Duplicate table: %s", table_name); return 0; } listener->table_name = strdup(table_name); if (listener->table_name == NULL) { err("%s: strdup", __func__); return -1; } return 1; } int accept_listener_protocol(struct Listener *listener, const char *protocol) { if (strncasecmp(protocol, http_protocol->name, strlen(protocol)) == 0) listener->protocol = http_protocol; else listener->protocol = tls_protocol; if (address_port(listener->address) == 0) address_set_port(listener->address, listener->protocol->default_port); return 1; } int accept_listener_reuseport(struct Listener *listener, const char *reuseport) { listener->reuseport = parse_boolean(reuseport); if (listener->reuseport == -1) { return 0; } #ifndef SO_REUSEPORT if (listener->reuseport == 1) { err("Reuseport not supported in this build"); return 0; } #endif return 1; } int accept_listener_ipv6_v6only(struct Listener *listener, const char *ipv6_v6only) { listener->ipv6_v6only = parse_boolean(ipv6_v6only); if (listener->ipv6_v6only == -1) { return 0; } #ifndef IPV6_V6ONLY if (listener->ipv6_v6only == 1) { err("IPV6_V6ONLY not supported in this build"); return 0; } #endif return 1; } int accept_listener_fallback_address(struct Listener *listener, const char *fallback) { if (listener->fallback_address == NULL) { struct Address *fallback_address = new_address(fallback); if (fallback_address == NULL) { err("Unable to parse fallback address: %s", fallback); return 0; } else if (address_is_sockaddr(fallback_address)) { listener->fallback_address = fallback_address; return 1; } else if (address_is_hostname(fallback_address)) { #ifndef HAVE_LIBUDNS err("Only fallback socket addresses permitted when compiled without libudns"); free(fallback_address); return 0; #else warn("Using hostname as fallback address is strongly discouraged"); listener->fallback_address = fallback_address; return 1; #endif } else if (address_is_wildcard(fallback_address)) { /* The wildcard functionality requires successfully parsing the * hostname from the client's request, if we couldn't find the * hostname and are using a fallback address it doesn't make * much sense to configure it as a wildcard. */ err("Wildcard address prohibited as fallback address"); free(fallback_address); return 0; } else { fatal("Unexpected fallback address type"); return 0; } } else if (strcasecmp("proxy", fallback) == 0 && listener->fallback_use_proxy_header == 0) { listener->fallback_use_proxy_header = 1; return 1; } else { err("Unexpected fallback argument: %s", fallback); return 0; } } int accept_listener_source_address(struct Listener *listener, const char *source) { if (listener->source_address != NULL || listener->transparent_proxy) { err("Duplicate source address: %s", source); return 0; } if (strcasecmp("client", source) == 0) { #ifdef IP_TRANSPARENT listener->transparent_proxy = 1; return 1; #else err("Transparent proxy not supported on this platform."); return 0; #endif } listener->source_address = new_address(source); if (listener->source_address == NULL) { err("Unable to parse source address: %s", source); return 0; } if (!address_is_sockaddr(listener->source_address)) { err("Only source socket addresses permitted"); free(listener->source_address); listener->source_address = NULL; return 0; } if (address_port(listener->source_address) != 0) { char address[ADDRESS_BUFFER_SIZE]; err("Source address on listener %s set to non zero port, " "this prevents multiple connection to each backend server.", display_address(listener->address, address, sizeof(address))); } return 1; } int accept_listener_bad_request_action(struct Listener *listener, const char *action) { if (strncmp("log", action, strlen(action)) == 0) { listener->log_bad_requests = 1; } return 1; } /* * Insert an additional listener in to the sorted list of listeners */ void add_listener(struct Listener_head *listeners, struct Listener *listener) { assert(listeners != NULL); assert(listener != NULL); assert(listener->address != NULL); listener_ref_get(listener); if (SLIST_FIRST(listeners) == NULL || address_compare(listener->address, SLIST_FIRST(listeners)->address) < 0) { SLIST_INSERT_HEAD(listeners, listener, entries); return; } struct Listener *iter; SLIST_FOREACH(iter, listeners, entries) { if (SLIST_NEXT(iter, entries) == NULL || address_compare(listener->address, SLIST_NEXT(iter, entries)->address) < 0) { SLIST_INSERT_AFTER(iter, listener, entries); return; } } } void remove_listener(struct Listener_head *listeners, struct Listener *listener, struct ev_loop *loop) { SLIST_REMOVE(listeners, listener, Listener, entries); close_listener(loop, listener); listener_ref_put(listener); } int valid_listener(const struct Listener *listener) { if (listener->accept_cb == NULL) { err("No accept callback not specified"); return 0; } if (listener->address == NULL) { err("No address specified"); return 0; } if (!address_is_sockaddr(listener->address)) { err("Address not specified as IP/socket"); return 0; } switch (address_sa(listener->address)->sa_family) { case AF_UNIX: break; case AF_INET: /* fall through */ case AF_INET6: if (address_port(listener->address) == 0) { err("No port specified"); return 0; } break; default: err("Invalid address family"); return 0; } if (listener->protocol != tls_protocol && listener->protocol != http_protocol) { err("Invalid protocol"); return 0; } return 1; } static int init_listener(struct Listener *listener, const struct Table_head *tables, struct ev_loop *loop) { char address[ADDRESS_BUFFER_SIZE]; struct Table *table = table_lookup(tables, listener->table_name); if (table == NULL) { err("Table \"%s\" not defined", listener->table_name); return -1; } init_table(table); listener->table = table_ref_get(table); /* If no port was specified on the fallback address, inherit the address * from the listening address */ if (listener->fallback_address && address_port(listener->fallback_address) == 0) address_set_port(listener->fallback_address, address_port(listener->address)); #ifdef HAVE_ACCEPT4 int sockfd = socket(address_sa(listener->address)->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0); #else int sockfd = socket(address_sa(listener->address)->sa_family, SOCK_STREAM, 0); #endif if (sockfd < 0) { err("socket failed: %s", strerror(errno)); return sockfd; } /* set SO_REUSEADDR on server socket to facilitate restart */ int on = 1; int result = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (result < 0) { err("setsockopt SO_REUSEADDR failed: %s", strerror(errno)); close(sockfd); return result; } /* set SO_KEEPALIVE on the server socket so that abandoned client connections * do not linger behind forever */ result = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); if (result < 0) { err("setsockopt SO_KEEPALIVE failed: %s", strerror(errno)); close(sockfd); return result; } if (listener->reuseport == 1) { #ifdef SO_REUSEPORT /* set SO_REUSEPORT on server socket to allow binding of multiple * processes on the same ip:port */ result = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); #else result = -ENOSYS; #endif if (result < 0) { err("setsockopt SO_REUSEPORT failed: %s", strerror(errno)); close(sockfd); return result; } } if (listener->ipv6_v6only == 1 && address_sa(listener->address)->sa_family == AF_INET6) { #ifdef IPV6_V6ONLY /* set IPV6_V6ONLY on server socket to only accept IPv6 connections on * IPv6 listeners */ result = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); #else result = -ENOSYS; #endif if (result < 0) { err("setsockopt IPV6_V6ONLY failed: %s", strerror(errno)); close(sockfd); return result; } } result = bind(sockfd, address_sa(listener->address), address_sa_len(listener->address)); if (result < 0 && errno == EACCES) { /* Retry using binder module */ close(sockfd); sockfd = bind_socket(address_sa(listener->address), address_sa_len(listener->address)); if (sockfd < 0) { err("binder failed to bind to %s", display_address(listener->address, address, sizeof(address))); return sockfd; } } else if (result < 0) { err("bind %s failed: %s", display_address(listener->address, address, sizeof(address)), strerror(errno)); close(sockfd); return result; } result = listen(sockfd, SOMAXCONN); if (result < 0) { err("listen failed: %s", strerror(errno)); close(sockfd); return result; } #ifndef HAVE_ACCEPT4 int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); #endif ev_io_init(&listener->watcher, accept_cb, sockfd, EV_READ); listener->watcher.data = listener; listener->backoff_timer.data = listener; listener_ref_get(listener); ev_io_start(loop, &listener->watcher); return sockfd; } /* * Allocate a new server address trying: * 1. lookup name in table for hostname or socket address * 2. lookup name in table for a wildcard address, then create a new * address based on the request hostname (if valid) * 3. use the fallback address */ struct LookupResult listener_lookup_server_address(const struct Listener *listener, const char *name, size_t name_len) { struct LookupResult table_result = table_lookup_server_address(listener->table, name, name_len); if (table_result.address == NULL) { /* No match in table, use fallback address if present */ return (struct LookupResult){ .address = listener->fallback_address, .use_proxy_header = listener->fallback_use_proxy_header }; } else if (address_is_wildcard(table_result.address)) { /* Wildcard table entry, create a new address from hostname */ struct Address *new_addr = new_address(name); if (new_addr == NULL) { warn("Invalid hostname %.*s in client request", (int)name_len, name); return (struct LookupResult){ .address = listener->fallback_address, .use_proxy_header = listener->fallback_use_proxy_header }; } else if (address_is_sockaddr(new_addr)) { warn("Refusing to proxy to socket address literal %.*s in request", (int)name_len, name); free(new_addr); return (struct LookupResult){ .address = listener->fallback_address, .use_proxy_header = listener->fallback_use_proxy_header }; } /* We created a valid new_addr, use the port from wildcard address if * present otherwise the listener */ address_set_port(new_addr, address_port(table_result.address) != 0 ? address_port(table_result.address) : address_port(listener->address)); return (struct LookupResult){ .address = new_addr, .caller_free_address = 1, .use_proxy_header = table_result.use_proxy_header }; } else if (address_port(table_result.address) == 0) { /* If the server port isn't specified return a new address using the * port from the listen, this allows sharing table across listeners */ struct Address *new_addr = copy_address(table_result.address); address_set_port(new_addr, address_port(listener->address)); return (struct LookupResult){ .address = new_addr, .caller_free_address = 1, .use_proxy_header = table_result.use_proxy_header }; } else { return table_result; } } void print_listener_config(FILE *file, const struct Listener *listener) { char address[ADDRESS_BUFFER_SIZE]; fprintf(file, "listener %s {\n", display_address(listener->address, address, sizeof(address))); fprintf(file, "\tprotocol %s\n", listener->protocol->name); if (listener->table_name) fprintf(file, "\ttable %s\n", listener->table_name); if (listener->fallback_address && !listener->fallback_use_proxy_header) fprintf(file, "\tfallback %s\n", display_address(listener->fallback_address, address, sizeof(address))); if (listener->fallback_address && listener->fallback_use_proxy_header) fprintf(file, "\tfallback %s proxy\n", display_address(listener->fallback_address, address, sizeof(address))); if (listener->source_address) fprintf(file, "\tsource %s\n", display_address(listener->source_address, address, sizeof(address))); if (listener->reuseport) fprintf(file, "\treuseport on\n"); fprintf(file, "}\n\n"); } static void close_listener(struct ev_loop *loop, struct Listener *listener) { if (listener->watcher.fd < 0) return; ev_timer_stop(loop, &listener->backoff_timer); ev_io_stop(loop, &listener->watcher); close(listener->watcher.fd); listener_ref_put(listener); } static void free_listener(struct Listener *listener) { if (listener == NULL) return; free(listener->address); free(listener->fallback_address); free(listener->source_address); free(listener->table_name); table_ref_put(listener->table); listener->table = NULL; logger_ref_put(listener->access_log); listener->access_log = NULL; free(listener); } void free_listeners(struct Listener_head *listeners, struct ev_loop *loop) { struct Listener *iter; while ((iter = SLIST_FIRST(listeners)) != NULL) remove_listener(listeners, iter, loop); } /* * Listener reference counting * * Since when reloading the configuration a listener with active connections * could be removed and connections require a reference to to the listener on * which they where received we need to allow listeners to linger outside the * listeners list in the active configuration, and free them when their last * connection closes. * * Accomplishing this with reference counting, each connection counts as a one * reference, plus one for the active EV watchers and one for the listener * being a member on a configurations listeners list. */ void listener_ref_put(struct Listener *listener) { if (listener == NULL) return; assert(listener->reference_count > 0); listener->reference_count--; if (listener->reference_count == 0) free_listener(listener); } struct Listener * listener_ref_get(struct Listener *listener) { listener->reference_count++; return listener; } static void accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) { struct Listener *listener = (struct Listener *)w->data; if (revents & EV_READ) { int result = listener->accept_cb(listener, loop); if (result == 0 && (errno == EMFILE || errno == ENFILE)) { char address_buf[ADDRESS_BUFFER_SIZE]; int backoff_time = 2; err("File descriptor limit reached! " "Suspending accepting new connections on %s for %d seconds", display_address(listener->address, address_buf, sizeof(address_buf)), backoff_time); ev_io_stop(loop, w); ev_timer_set(&listener->backoff_timer, backoff_time, 0.0); ev_timer_start(loop, &listener->backoff_timer); } } } static void backoff_timer_cb(struct ev_loop *loop, struct ev_timer *w, int revents) { struct Listener *listener = (struct Listener *)w->data; if (revents & EV_TIMER) { ev_timer_stop(loop, &listener->backoff_timer); ev_io_set(&listener->watcher, listener->watcher.fd, EV_READ); ev_io_start(loop, &listener->watcher); } } sniproxy-0.6.0/src/listener.h000066400000000000000000000065431340212110200161670ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 LISTENER_H #define LISTENER_H #include #include #include #include "address.h" #include "table.h" SLIST_HEAD(Listener_head, Listener); struct Listener { /* Configuration fields */ struct Address *address, *fallback_address, *source_address; const struct Protocol *protocol; char *table_name; struct Logger *access_log; int log_bad_requests, reuseport, transparent_proxy, ipv6_v6only; int fallback_use_proxy_header; /* Runtime fields */ int reference_count; struct ev_io watcher; struct ev_timer backoff_timer; struct Table *table; int (*accept_cb)(struct Listener *, struct ev_loop *); SLIST_ENTRY(Listener) entries; }; struct Listener *new_listener(); int accept_listener_arg(struct Listener *, const char *); int accept_listener_table_name(struct Listener *, const char *); int accept_listener_fallback_address(struct Listener *, const char *); int accept_listener_source_address(struct Listener *, const char *); int accept_listener_protocol(struct Listener *, const char *); int accept_listener_reuseport(struct Listener *, const char *); int accept_listener_ipv6_v6only(struct Listener *, const char *); int accept_listener_bad_request_action(struct Listener *, const char *); void add_listener(struct Listener_head *, struct Listener *); void init_listeners(struct Listener_head *, const struct Table_head *, struct ev_loop *); void listeners_reload(struct Listener_head *, struct Listener_head *, const struct Table_head *, struct ev_loop *); void remove_listener(struct Listener_head *, struct Listener *, struct ev_loop *); void free_listeners(struct Listener_head *, struct ev_loop *); int valid_listener(const struct Listener *); struct LookupResult listener_lookup_server_address(const struct Listener *, const char *, size_t); void print_listener_config(FILE *, const struct Listener *); void listener_ref_put(struct Listener *); struct Listener *listener_ref_get(struct Listener *); #endif sniproxy-0.6.0/src/logger.c000066400000000000000000000276431340212110200156200ustar00rootroot00000000000000/* * Copyright (c) 2013, Dustin Lundquist * 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 HOLDERS 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 #include #include #include #include #include #include #include #include #include "logger.h" struct Logger { struct LogSink *sink; int priority; int facility; int reference_count; }; struct LogSink { enum { LOG_SINK_SYSLOG, LOG_SINK_STDERR, LOG_SINK_FILE } type; const char *filepath; FILE *fd; int reference_count; SLIST_ENTRY(LogSink) entries; }; static struct Logger *default_logger = NULL; static SLIST_HEAD(LogSink_head, LogSink) sinks = SLIST_HEAD_INITIALIZER(sinks); static void free_logger(struct Logger *); static void init_default_logger(); static void vlog_msg(struct Logger *, int, const char *, va_list); static void free_at_exit(); static int lookup_syslog_facility(const char *); static const char *timestamp(char *, size_t); static struct LogSink *obtain_stderr_sink(); static struct LogSink *obtain_syslog_sink(); static struct LogSink *obtain_file_sink(const char *); static struct LogSink *log_sink_ref_get(struct LogSink *); static void log_sink_ref_put(struct LogSink *); static void free_sink(struct LogSink *); struct Logger * new_syslog_logger(const char *facility) { struct Logger *logger = malloc(sizeof(struct Logger)); if (logger != NULL) { logger->sink = obtain_syslog_sink(); if (logger->sink == NULL) { free(logger); return NULL; } logger->priority = LOG_DEBUG; logger->facility = lookup_syslog_facility(facility); logger->reference_count = 0; log_sink_ref_get(logger->sink); } return logger; } struct Logger * new_file_logger(const char *filepath) { struct Logger *logger = malloc(sizeof(struct Logger)); if (logger != NULL) { logger->sink = obtain_file_sink(filepath); if (logger->sink == NULL) { free(logger); return NULL; } logger->priority = LOG_DEBUG; logger->facility = 0; logger->reference_count = 0; log_sink_ref_get(logger->sink); } return logger; } void reopen_loggers() { struct LogSink *sink; SLIST_FOREACH(sink, &sinks, entries) { if (sink->type == LOG_SINK_SYSLOG) { closelog(); openlog(PACKAGE_NAME, LOG_PID, 0); } else if (sink->type == LOG_SINK_FILE) { sink->fd = freopen(sink->filepath, "a", sink->fd); if (sink->fd == NULL) err("failed to reopen log file %s: %s", sink->filepath, strerror(errno)); else setvbuf(sink->fd, NULL, _IOLBF, 0); } } } void set_default_logger(struct Logger *new_logger) { struct Logger *old_default_logger = default_logger; assert(new_logger != NULL); default_logger = logger_ref_get(new_logger); logger_ref_put(old_default_logger); } void set_logger_priority(struct Logger *logger, int priority) { assert(logger != NULL); assert(priority >= LOG_EMERG && priority <= LOG_DEBUG); logger->priority = priority; } void logger_ref_put(struct Logger *logger) { if (logger == NULL) return; assert(logger->reference_count > 0); logger->reference_count--; if (logger->reference_count == 0) free_logger(logger); } struct Logger * logger_ref_get(struct Logger *logger) { if (logger != NULL) logger->reference_count++; return logger; } static void free_logger(struct Logger *logger) { if (logger == NULL) return; log_sink_ref_put(logger->sink); logger->sink = NULL; free(logger); } void log_msg(struct Logger *logger, int priority, const char *format, ...) { va_list args; va_start(args, format); vlog_msg(logger, priority, format, args); va_end(args); } void fatal(const char *format, ...) { va_list args; init_default_logger(); va_start(args, format); vlog_msg(default_logger, LOG_CRIT, format, args); va_end(args); exit(EXIT_FAILURE); } void err(const char *format, ...) { va_list args; init_default_logger(); va_start(args, format); vlog_msg(default_logger, LOG_ERR, format, args); va_end(args); } void warn(const char *format, ...) { va_list args; init_default_logger(); va_start(args, format); vlog_msg(default_logger, LOG_WARNING, format, args); va_end(args); } void notice(const char *format, ...) { va_list args; init_default_logger(); va_start(args, format); vlog_msg(default_logger, LOG_NOTICE, format, args); va_end(args); } void info(const char *format, ...) { va_list args; init_default_logger(); va_start(args, format); vlog_msg(default_logger, LOG_INFO, format, args); va_end(args); } void debug(const char *format, ...) { va_list args; init_default_logger(); va_start(args, format); vlog_msg(default_logger, LOG_DEBUG, format, args); va_end(args); } static void vlog_msg(struct Logger *logger, int priority, const char *format, va_list args) { assert(logger != NULL); if (priority > logger->priority) return; if (logger->sink->type == LOG_SINK_SYSLOG) { vsyslog(logger->facility|logger->priority, format, args); } else if (logger->sink->fd != NULL) { char buffer[1024]; timestamp(buffer, sizeof(buffer)); size_t len = strlen(buffer); vsnprintf(buffer + len, sizeof(buffer) - len, format, args); buffer[sizeof(buffer) - 1] = '\0'; /* ensure buffer null terminated */ fprintf(logger->sink->fd, "%s\n", buffer); } } static void init_default_logger() { struct Logger *logger = NULL; if (default_logger != NULL) return; logger = malloc(sizeof(struct Logger)); if (logger != NULL) { logger->sink = obtain_stderr_sink(); if (logger->sink == NULL) { free(logger); return; } logger->priority = LOG_DEBUG; logger->facility = 0; logger->reference_count = 0; log_sink_ref_get(logger->sink); } if (logger == NULL) return; atexit(free_at_exit); default_logger = logger_ref_get(logger); } static void free_at_exit() { logger_ref_put(default_logger); default_logger = NULL; } static int lookup_syslog_facility(const char *facility) { static const struct { const char *name; int number; } facilities[] = { { "auth", LOG_AUTH }, { "cron", LOG_CRON }, { "daemon", LOG_DAEMON }, { "ftp", LOG_FTP }, { "local0", LOG_LOCAL0 }, { "local1", LOG_LOCAL1 }, { "local2", LOG_LOCAL2 }, { "local3", LOG_LOCAL3 }, { "local4", LOG_LOCAL4 }, { "local5", LOG_LOCAL5 }, { "local6", LOG_LOCAL6 }, { "local7", LOG_LOCAL7 }, { "mail", LOG_MAIL }, { "news", LOG_NEWS }, { "user", LOG_USER }, { "uucp", LOG_UUCP }, }; for (size_t i = 0; i < sizeof(facilities) / sizeof(facilities[0]); i++) if (strncasecmp(facilities[i].name, facility, strlen(facility)) == 0) return facilities[i].number; /* fall back value */ return LOG_USER; } static struct LogSink * obtain_stderr_sink() { struct LogSink *sink; SLIST_FOREACH(sink, &sinks, entries) { if (sink->type == LOG_SINK_STDERR) return sink; } sink = malloc(sizeof(struct LogSink)); if (sink != NULL) { sink->type = LOG_SINK_STDERR; sink->filepath = NULL; sink->fd = stderr; sink->reference_count = 0; SLIST_INSERT_HEAD(&sinks, sink, entries); } return sink; } static struct LogSink * obtain_syslog_sink() { struct LogSink *sink; SLIST_FOREACH(sink, &sinks, entries) { if (sink->type == LOG_SINK_SYSLOG) return sink; } sink = malloc(sizeof(struct LogSink)); if (sink != NULL) { sink->type = LOG_SINK_SYSLOG; sink->filepath = NULL; sink->fd = NULL; sink->reference_count = 0; openlog(PACKAGE_NAME, LOG_PID, 0); SLIST_INSERT_HEAD(&sinks, sink, entries); } return sink; } static struct LogSink * obtain_file_sink(const char *filepath) { struct LogSink *sink; if (filepath == NULL) return NULL; SLIST_FOREACH(sink, &sinks, entries) { if (sink->type == LOG_SINK_FILE && strcmp(sink->filepath, filepath) == 0) return sink; } FILE *fd = fopen(filepath, "a"); if (fd == NULL) { err("Failed to open new log file: %s", filepath); return NULL; } setvbuf(fd, NULL, _IOLBF, 0); sink = malloc(sizeof(struct LogSink)); if (sink != NULL) { sink->type = LOG_SINK_FILE; sink->filepath = strdup(filepath); sink->fd = fd; sink->reference_count = 0; SLIST_INSERT_HEAD(&sinks, sink, entries); } return sink; } static struct LogSink * log_sink_ref_get(struct LogSink *sink) { if (sink != NULL) sink->reference_count++; return sink; } static void log_sink_ref_put(struct LogSink *sink) { if (sink == NULL) return; assert(sink->reference_count > 0); sink->reference_count--; if (sink->reference_count == 0) free_sink(sink); } static void free_sink(struct LogSink *sink) { if (sink == NULL) return; SLIST_REMOVE(&sinks, sink, LogSink, entries); switch(sink->type) { case LOG_SINK_SYSLOG: closelog(); break; case LOG_SINK_STDERR: fflush(sink->fd); sink->fd = NULL; break; case LOG_SINK_FILE: fclose(sink->fd); sink->fd = NULL; free((char *)sink->filepath); sink->filepath = NULL; break; default: assert(0); } free(sink); } static const char * timestamp(char *dst, size_t dst_len) { /* TODO change to ev_now() */ time_t now = time(NULL); static struct { time_t when; char string[32]; } timestamp_cache = { .when = 0, .string = {'\0'} }; if (now != timestamp_cache.when) { #ifdef RFC3339_TIMESTAMP struct tm *tmp = gmtime(&now); strftime(timestamp_cache.string, sizeof(timestamp_cache.string), "%FT%TZ ", tmp); #else struct tm *tmp = localtime(&now); strftime(timestamp_cache.string, sizeof(timestamp_cache.string), "%F %T ", tmp); #endif timestamp_cache.when = now; } if (dst != NULL) strncpy(dst, timestamp_cache.string, dst_len); return timestamp_cache.string; } sniproxy-0.6.0/src/logger.h000066400000000000000000000050051340212110200156110ustar00rootroot00000000000000/* * Copyright (c) 2013, Dustin Lundquist * 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 HOLDERS 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 struct Logger; #define LOG_EMERG 0 #define LOG_ALERT 1 #define LOG_CRIT 2 #define LOG_ERR 3 #define LOG_WARNING 4 #define LOG_NOTICE 5 #define LOG_INFO 6 #define LOG_DEBUG 7 struct Logger *new_syslog_logger(const char *facility); struct Logger *new_file_logger(const char *filepath); void set_default_logger(struct Logger *); void set_logger_priority(struct Logger *, int); struct Logger *logger_ref_get(struct Logger *); void logger_ref_put(struct Logger *); void reopen_loggers(); /* Shorthand to log to global error log */ void fatal(const char *, ...) __attribute__ ((format (printf, 1, 2))) __attribute__ ((noreturn)); void err(const char *, ...) __attribute__ ((format (printf, 1, 2))); void warn(const char *, ...) __attribute__ ((format (printf, 1, 2))); void notice(const char *, ...) __attribute__ ((format (printf, 1, 2))); void info(const char *, ...) __attribute__ ((format (printf, 1, 2))); void debug(const char *, ...) __attribute__ ((format (printf, 1, 2))); void log_msg(struct Logger *, int, const char *, ...) __attribute__ ((format (printf, 3, 4))); #endif sniproxy-0.6.0/src/protocol.h000066400000000000000000000032241340212110200161740ustar00rootroot00000000000000/* * Copyright (c) 2014, Dustin Lundquist * 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 HOLDERS 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 PROTOCOL_H #define PROTOCOL_H #include struct Protocol { const char *const name; const uint16_t default_port; int (*const parse_packet)(const char*, size_t, char **); const char *const abort_message; const size_t abort_message_len; }; #endif sniproxy-0.6.0/src/resolv.c000066400000000000000000000313211340212110200156370ustar00rootroot00000000000000/* * Copyright (c) 2014, Dustin Lundquist * 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 HOLDERS 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 #include #include #include #include #include #include #include #include #ifdef HAVE_LIBUDNS #include #endif #include "resolv.h" #include "address.h" #include "logger.h" #ifndef HAVE_LIBUDNS /* * If we do not have a DNS resolution library stub out module as no ops */ int resolv_init(struct ev_loop *loop, char **nameservers, char **search_domains, int mode) { return 0; } void resolv_shutdown(struct ev_loop *loop) { } struct ResolvQuery * resolv_query(const char *hostname, int mode, void (*client_cb)(struct Address *, void *), void (*client_free_cb)(void *), void *client_cb_data) { return NULL; } void resolv_cancel(struct ResolvQuery *query_handle) { } #else /* * Implement DNS resolution interface using libudns */ struct ResolvQuery { void (*client_cb)(struct Address *, void *); void (*client_free_cb)(void *); void *client_cb_data; int resolv_mode; struct dns_query *queries[2]; size_t response_count; struct Address **responses; }; static int default_resolv_mode = 1 /* RESOLV_MODE_IPV4_ONLY */; static struct ev_io resolv_io_watcher; static struct ev_timer resolv_timeout_watcher; static void resolv_sock_cb(struct ev_loop *, struct ev_io *, int); static void resolv_timeout_cb(struct ev_loop *, struct ev_timer *, int); static void dns_query_v4_cb(struct dns_ctx *, struct dns_rr_a4 *, void *); static void dns_query_v6_cb(struct dns_ctx *, struct dns_rr_a6 *, void *); static void dns_timer_setup_cb(struct dns_ctx *, int, void *); static void process_client_callback(struct ResolvQuery *); static inline int all_queries_are_null(struct ResolvQuery *); static struct Address *choose_ipv4_first(struct ResolvQuery *); static struct Address *choose_ipv6_first(struct ResolvQuery *); static struct Address *choose_any(struct ResolvQuery *); int resolv_init(struct ev_loop *loop, char **nameservers, char **search, int mode) { struct dns_ctx *ctx = &dns_defctx; if (nameservers == NULL) { /* Nameservers not specified, use system resolver config */ dns_init(ctx, 0); } else { dns_reset(ctx); for (int i = 0; nameservers[i] != NULL; i++) dns_add_serv(ctx, nameservers[i]); if (search != NULL) for (int i = 0; search[i] != NULL; i++) dns_add_srch(ctx, search[i]); } default_resolv_mode = mode; int sockfd = dns_open(ctx); if (sockfd < 0) fatal("Failed to open DNS resolver socket: %s", strerror(errno)); int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); ev_io_init(&resolv_io_watcher, resolv_sock_cb, sockfd, EV_READ); resolv_io_watcher.data = ctx; ev_io_start(loop, &resolv_io_watcher); ev_timer_init(&resolv_timeout_watcher, resolv_timeout_cb, 0.0, 0.0); resolv_timeout_watcher.data = ctx; dns_set_tmcbck(ctx, dns_timer_setup_cb, loop); return sockfd; } void resolv_shutdown(struct ev_loop * loop) { struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data; ev_io_stop(loop, &resolv_io_watcher); if (ev_is_active(&resolv_timeout_watcher)) ev_timer_stop(loop, &resolv_timeout_watcher); dns_close(ctx); } struct ResolvQuery * resolv_query(const char *hostname, int mode, void (*client_cb)(struct Address *, void *), void (*client_free_cb)(void *), void *client_cb_data) { struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data; /* * Wrap udns's call back in our own */ struct ResolvQuery *cb_data = malloc(sizeof(struct ResolvQuery)); if (cb_data == NULL) { err("Failed to allocate memory for DNS query callback data."); return NULL; } cb_data->client_cb = client_cb; cb_data->client_free_cb = client_free_cb; cb_data->client_cb_data = client_cb_data; cb_data->resolv_mode = mode != RESOLV_MODE_DEFAULT ? mode : default_resolv_mode; memset(cb_data->queries, 0, sizeof(cb_data->queries)); cb_data->response_count = 0; cb_data->responses = NULL; /* Submit A and AAAA queries */ if (cb_data->resolv_mode != RESOLV_MODE_IPV6_ONLY) { cb_data->queries[0] = dns_submit_a4(ctx, hostname, 0, dns_query_v4_cb, cb_data); if (cb_data->queries[0] == NULL) err("Failed to submit DNS query: %s", dns_strerror(dns_status(ctx))); }; if (cb_data->resolv_mode != RESOLV_MODE_IPV4_ONLY) { cb_data->queries[1] = dns_submit_a6(ctx, hostname, 0, dns_query_v6_cb, cb_data); if (cb_data->queries[1] == NULL) err("Failed to submit DNS query: %s", dns_strerror(dns_status(ctx))); } if (all_queries_are_null(cb_data)) { if (cb_data->client_free_cb != NULL) cb_data->client_free_cb(cb_data->client_cb_data); free(cb_data); cb_data = NULL; } return cb_data; } void resolv_cancel(struct ResolvQuery *query_handle) { struct ResolvQuery *cb_data = (struct ResolvQuery *)query_handle; struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data; for (size_t i = 0; i < sizeof(cb_data->queries) / sizeof(cb_data->queries[0]); i++) { if (cb_data->queries[i] != NULL) { dns_cancel(ctx, cb_data->queries[i]); free(cb_data->queries[i]); cb_data->queries[i] = NULL; } } if (cb_data->client_free_cb != NULL) cb_data->client_free_cb(cb_data->client_cb_data); free(cb_data); } /* * DNS UDP socket activity callback */ static void resolv_sock_cb(struct ev_loop *loop, struct ev_io *w, int revents) { struct dns_ctx *ctx = (struct dns_ctx *)w->data; if (revents & EV_READ) dns_ioevent(ctx, ev_now(loop)); } /* * Wrapper for client callback we provide to udns */ static void dns_query_v4_cb(struct dns_ctx *ctx, struct dns_rr_a4 *result, void *data) { struct ResolvQuery *cb_data = (struct ResolvQuery *)data; if (result == NULL) { info("resolv: %s\n", dns_strerror(dns_status(ctx))); } else if (result->dnsa4_nrr > 0) { struct Address **new_responses = realloc(cb_data->responses, (cb_data->response_count + (size_t)result->dnsa4_nrr) * sizeof(struct Address *)); if (new_responses == NULL) { err("Failed to allocate memory for additional DNS responses"); } else { cb_data->responses = new_responses; for (int i = 0; i < result->dnsa4_nrr; i++) { struct sockaddr_in sa = { .sin_family = AF_INET, .sin_port = 0, .sin_addr = result->dnsa4_addr[i], }; cb_data->responses[cb_data->response_count] = new_address_sa((struct sockaddr *)&sa, sizeof(sa)); if (cb_data->responses[cb_data->response_count] == NULL) err("Failed to allocate memory for DNS query result address"); else cb_data->response_count++; } } } free(result); cb_data->queries[0] = NULL; /* mark A query as being completed */ /* Once all queries have completed, call client callback */ if (all_queries_are_null(cb_data)) process_client_callback(cb_data); } static void dns_query_v6_cb(struct dns_ctx *ctx, struct dns_rr_a6 *result, void *data) { struct ResolvQuery *cb_data = (struct ResolvQuery *)data; if (result == NULL) { info("resolv: %s\n", dns_strerror(dns_status(ctx))); } else if (result->dnsa6_nrr > 0) { struct Address **new_responses = realloc(cb_data->responses, (cb_data->response_count + (size_t)result->dnsa6_nrr) * sizeof(struct Address *)); if (new_responses == NULL) { err("Failed to allocate memory for additional DNS responses"); } else { cb_data->responses = new_responses; for (int i = 0; i < result->dnsa6_nrr; i++) { struct sockaddr_in6 sa = { .sin6_family = AF_INET6, .sin6_port = 0, .sin6_addr = result->dnsa6_addr[i], }; cb_data->responses[cb_data->response_count] = new_address_sa((struct sockaddr *)&sa, sizeof(sa)); if (cb_data->responses[cb_data->response_count] == NULL) err("Failed to allocate memory for DNS query result address"); else cb_data->response_count++; } } } free(result); cb_data->queries[1] = NULL; /* mark AAAA query as being completed */ /* Once all queries have completed, call client callback */ if (all_queries_are_null(cb_data)) process_client_callback(cb_data); } /* * Called once all queries have been completed */ static void process_client_callback(struct ResolvQuery *cb_data) { struct Address *best_address = NULL; if (cb_data->resolv_mode == RESOLV_MODE_IPV4_FIRST) best_address = choose_ipv4_first(cb_data); else if (cb_data->resolv_mode == RESOLV_MODE_IPV6_FIRST) best_address = choose_ipv6_first(cb_data); else best_address = choose_any(cb_data); cb_data->client_cb(best_address, cb_data->client_cb_data); for (size_t i = 0; i < cb_data->response_count; i++) free(cb_data->responses[i]); free(cb_data->responses); if (cb_data->client_free_cb != NULL) cb_data->client_free_cb(cb_data->client_cb_data); free(cb_data); } static struct Address * choose_ipv4_first(struct ResolvQuery *cb_data) { for (size_t i = 0; i < cb_data->response_count; i++) if (address_is_sockaddr(cb_data->responses[i]) && address_sa(cb_data->responses[i])->sa_family == AF_INET) return cb_data->responses[i]; return choose_any(cb_data);; } static struct Address * choose_ipv6_first(struct ResolvQuery *cb_data) { for (size_t i = 0; i < cb_data->response_count; i++) if (address_is_sockaddr(cb_data->responses[i]) && address_sa(cb_data->responses[i])->sa_family == AF_INET6) return cb_data->responses[i]; return choose_any(cb_data);; } static struct Address * choose_any(struct ResolvQuery *cb_data) { if (cb_data->response_count >= 1) return cb_data->responses[0]; return NULL; } /* * DNS timeout callback */ static void resolv_timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents) { struct dns_ctx *ctx = (struct dns_ctx *)w->data; if (revents & EV_TIMER) dns_timeouts(ctx, 30, ev_now(loop)); } /* * Callback to setup DNS timeout callback */ static void dns_timer_setup_cb(struct dns_ctx *ctx, int timeout, void *data) { struct ev_loop *loop = (struct ev_loop *)data; if (ev_is_active(&resolv_timeout_watcher)) ev_timer_stop(loop, &resolv_timeout_watcher); if (ctx != NULL && timeout >= 0) { ev_timer_set(&resolv_timeout_watcher, timeout, 0.0); ev_timer_start(loop, &resolv_timeout_watcher); } } static inline int all_queries_are_null(struct ResolvQuery *cb_data) { int result = 1; for (size_t i = 0; i < sizeof(cb_data->queries) / sizeof(cb_data->queries[0]); i++) result = result && cb_data->queries[i] == NULL; return result; } #endif sniproxy-0.6.0/src/resolv.h000066400000000000000000000036571340212110200156570ustar00rootroot00000000000000/* * Copyright (c) 2014, Dustin Lundquist * 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 HOLDERS 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 RESOLV_H #define RESOLV_H #include "address.h" struct ResolvQuery; int resolv_init(struct ev_loop *, char **, char **, int); struct ResolvQuery *resolv_query(const char *, int, void(*)(struct Address *, void *), void (*)(void *), void *); void resolv_cancel(struct ResolvQuery *); void resolv_shutdown(struct ev_loop *); static const int RESOLV_MODE_DEFAULT = 0; static const int RESOLV_MODE_IPV4_ONLY = 1; static const int RESOLV_MODE_IPV6_ONLY = 2; static const int RESOLV_MODE_IPV4_FIRST = 3; static const int RESOLV_MODE_IPV6_FIRST = 4; #endif sniproxy-0.6.0/src/sniproxy.c000066400000000000000000000173001340212110200162210ustar00rootroot00000000000000/* * Copyright (c) 2011-2014, Dustin Lundquist * 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 HOLDERS 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "binder.h" #include "config.h" #include "connection.h" #include "listener.h" #include "resolv.h" #include "logger.h" static void usage(); static void daemonize(void); static void write_pidfile(const char *, pid_t); static void set_limits(rlim_t); static void drop_perms(const char* username, const char* groupname); static void perror_exit(const char *); static void signal_cb(struct ev_loop *, struct ev_signal *, int revents); static const char *sniproxy_version = PACKAGE_VERSION; static const char *default_username = "daemon"; static struct Config *config; static struct ev_signal sighup_watcher; static struct ev_signal sigusr1_watcher; static struct ev_signal sigint_watcher; static struct ev_signal sigterm_watcher; int main(int argc, char **argv) { const char *config_file = "/etc/sniproxy.conf"; int background_flag = 1; rlim_t max_nofiles = 65536; int opt; while ((opt = getopt(argc, argv, "fc:n:V")) != -1) { switch (opt) { case 'c': config_file = optarg; break; case 'f': /* foreground */ background_flag = 0; break; case 'n': max_nofiles = strtoul(optarg, NULL, 10); break; case 'V': printf("sniproxy %s\n", sniproxy_version); return EXIT_SUCCESS; default: usage(); return EXIT_FAILURE; } } config = init_config(config_file, EV_DEFAULT); if (config == NULL) { fprintf(stderr, "Unable to load %s\n", config_file); usage(); return EXIT_FAILURE; } /* ignore SIGPIPE, or it will kill us */ signal(SIGPIPE, SIG_IGN); if (background_flag) { if (config->pidfile != NULL) remove(config->pidfile); daemonize(); if (config->pidfile != NULL) write_pidfile(config->pidfile, getpid()); } start_binder(); set_limits(max_nofiles); init_listeners(&config->listeners, &config->tables, EV_DEFAULT); /* Drop permissions only when we can */ drop_perms(config->user ? config->user : default_username, config->group); ev_signal_init(&sighup_watcher, signal_cb, SIGHUP); ev_signal_init(&sigusr1_watcher, signal_cb, SIGUSR1); ev_signal_init(&sigint_watcher, signal_cb, SIGINT); ev_signal_init(&sigterm_watcher, signal_cb, SIGTERM); ev_signal_start(EV_DEFAULT, &sighup_watcher); ev_signal_start(EV_DEFAULT, &sigusr1_watcher); ev_signal_start(EV_DEFAULT, &sigint_watcher); ev_signal_start(EV_DEFAULT, &sigterm_watcher); resolv_init(EV_DEFAULT, config->resolver.nameservers, config->resolver.search, config->resolver.mode); init_connections(); ev_run(EV_DEFAULT, 0); free_connections(EV_DEFAULT); resolv_shutdown(EV_DEFAULT); free_config(config, EV_DEFAULT); stop_binder(); return 0; } static void daemonize(void) { #ifdef HAVE_DAEMON if (daemon(0,0) < 0) perror_exit("daemon()"); #else pid_t pid; /* daemon(0,0) part */ pid = fork(); if (pid < 0) perror_exit("fork()"); else if (pid != 0) exit(EXIT_SUCCESS); if (setsid() < 0) perror_exit("setsid()"); if (chdir("/") < 0) perror_exit("chdir()"); if (freopen("/dev/null", "r", stdin) == NULL) perror_exit("freopen(stdin)"); if (freopen("/dev/null", "a", stdout) == NULL) perror_exit("freopen(stdout)"); if (freopen("/dev/null", "a", stderr) == NULL) perror_exit("freopen(stderr)"); pid = fork(); if (pid < 0) perror_exit("fork()"); else if (pid != 0) exit(EXIT_SUCCESS); #endif /* local part */ umask(022); signal(SIGHUP, SIG_IGN); ev_default_fork(); return; } /** * Raise file handle limit to reasonable level * At some point we should make this a config parameter */ static void set_limits(rlim_t max_nofiles) { struct rlimit fd_limit = { .rlim_cur = max_nofiles, .rlim_max = max_nofiles, }; int result = setrlimit(RLIMIT_NOFILE, &fd_limit); if (result < 0) warn("Failed to set file handle limit: %s", strerror(errno)); } static void drop_perms(const char *username, const char *groupname) { /* check if we are already an unprivileged user */ if (getuid() != 0) return; errno = 0; struct passwd *user = getpwnam(username); if (errno) fatal("getpwnam(): %s", strerror(errno)); else if (user == NULL) fatal("getpwnam(): user %s does not exist", username); gid_t gid = user->pw_gid; if (groupname != NULL) { errno = 0; struct group *group = getgrnam(groupname); if (errno) fatal("getgrnam(): %s", strerror(errno)); else if (group == NULL) fatal("getgrnam(): group %s does not exist", groupname); gid = group->gr_gid; } /* drop any supplementary groups */ if (setgroups(1, &gid) < 0) fatal("setgroups(): %s", strerror(errno)); /* set the main gid */ if (setgid(gid) < 0) fatal("setgid(): %s", strerror(errno)); if (setuid(user->pw_uid) < 0) fatal("setuid(): %s", strerror(errno)); } static void perror_exit(const char *msg) { perror(msg); exit(EXIT_FAILURE); } static void usage() { fprintf(stderr, "Usage: sniproxy [-c ] [-f] [-n ] [-V]\n"); } static void write_pidfile(const char *path, pid_t pid) { FILE *fp = fopen(path, "w"); if (fp == NULL) { perror("fopen"); return; } fprintf(fp, "%d\n", pid); fclose(fp); } static void signal_cb(struct ev_loop *loop, struct ev_signal *w, int revents) { if (revents & EV_SIGNAL) { switch (w->signum) { case SIGHUP: reopen_loggers(); reload_config(config, loop); break; case SIGUSR1: print_connections(); break; case SIGINT: case SIGTERM: ev_unloop(loop, EVUNLOOP_ALL); } } } sniproxy-0.6.0/src/table.c000066400000000000000000000146441340212110200154250ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 #include #include #include #include #include "table.h" #include "backend.h" #include "address.h" #include "logger.h" static void free_table(struct Table *); static inline struct Backend * table_lookup_backend(const struct Table *table, const char *name, size_t name_len) { return lookup_backend(&table->backends, name, name_len); } static inline void __attribute__((unused)) remove_table_backend(struct Table *table, struct Backend *backend) { remove_backend(&table->backends, backend); } struct Table * new_table() { struct Table *table; table = malloc(sizeof(struct Table)); if (table == NULL) { err("malloc: %s", strerror(errno)); return NULL; } table->name = NULL; table->use_proxy_header = 0; table->reference_count = 0; STAILQ_INIT(&table->backends); return table; } int accept_table_arg(struct Table *table, const char *arg) { if (table->name == NULL) { table->name = strdup(arg); if (table->name == NULL) { err("strdup: %s", strerror(errno)); return -1; } } else { err("Unexpected table argument: %s", arg); return -1; } return 1; } void add_table(struct Table_head *tables, struct Table *table) { table_ref_get(table); SLIST_INSERT_HEAD(tables, table, entries); } void init_table(struct Table *table) { struct Backend *iter; STAILQ_FOREACH(iter, &table->backends, entries) init_backend(iter); } void free_tables(struct Table_head *tables) { struct Table *iter; while ((iter = SLIST_FIRST(tables)) != NULL) { SLIST_REMOVE_HEAD(tables, entries); table_ref_put(iter); } } struct Table * table_lookup(const struct Table_head *tables, const char *name) { struct Table *iter; SLIST_FOREACH(iter, tables, entries) { if (iter->name == NULL && name == NULL) { return iter; } else if (iter->name != NULL && name != NULL && strcmp(iter->name, name) == 0) { return iter; } } return NULL; } void remove_table(struct Table_head *tables, struct Table *table) { SLIST_REMOVE(tables, table, Table, entries); table_ref_put(table); } struct LookupResult table_lookup_server_address(const struct Table *table, const char *name, size_t name_len) { struct Backend *b = table_lookup_backend(table, name, name_len); if (b == NULL) { info("No match found for %.*s", (int)name_len, name); return (struct LookupResult){.address = NULL}; } return (struct LookupResult){.address = b->address, .use_proxy_header = b->use_proxy_header}; } void reload_tables(struct Table_head *tables, struct Table_head *new_tables) { struct Table *iter; /* Remove unused tables which were removed from the new configuration */ /* Unused elements at the beginning of the list */ while ((iter = SLIST_FIRST(tables)) != NULL && table_lookup(new_tables, SLIST_FIRST(tables)->name) == NULL) { SLIST_REMOVE_HEAD(tables, entries); table_ref_put(iter); } /* Remove elements following first used element */ SLIST_FOREACH(iter, tables, entries) { if (SLIST_NEXT(iter, entries) != NULL && table_lookup(new_tables, SLIST_NEXT(iter, entries)->name) == NULL) { struct Table *temp = SLIST_NEXT(iter, entries); /* SLIST remove next */ SLIST_NEXT(iter, entries) = SLIST_NEXT(temp, entries); table_ref_put(temp); } } while ((iter = SLIST_FIRST(new_tables)) != NULL) { SLIST_REMOVE_HEAD(new_tables, entries); /* Initialize table regular expressions */ init_table(iter); struct Table *existing = table_lookup(tables, iter->name); if (existing) { /* Swap table contents */ struct Backend_head temp = existing->backends; existing->backends = iter->backends; iter->backends = temp; } else { add_table(tables, iter); } table_ref_put(iter); } } void print_table_config(FILE *file, struct Table *table) { struct Backend *backend; if (table->name == NULL) fprintf(file, "table {\n"); else fprintf(file, "table %s {\n", table->name); STAILQ_FOREACH(backend, &table->backends, entries) { print_backend_config(file, backend); } fprintf(file, "}\n\n"); } static void free_table(struct Table *table) { struct Backend *iter; if (table == NULL) return; while ((iter = STAILQ_FIRST(&table->backends)) != NULL) remove_backend(&table->backends, iter); free(table->name); free(table); } void table_ref_put(struct Table *table) { if (table == NULL) return; assert(table->reference_count > 0); table->reference_count--; if (table->reference_count == 0) free_table(table); } struct Table * table_ref_get(struct Table *table) { table->reference_count++; return table; } sniproxy-0.6.0/src/table.h000066400000000000000000000050341340212110200154230ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 TABLE_H #define TABLE_H #include #include #include "backend.h" #include "address.h" #define TABLE_NAME_LEN 20 SLIST_HEAD(Table_head, Table); struct Table { char *name; int use_proxy_header; /* Runtime fields */ int reference_count; struct Backend_head backends; SLIST_ENTRY(Table) entries; }; struct LookupResult { const struct Address *address; int caller_free_address; int use_proxy_header; }; struct Table *new_table(); int accept_table_arg(struct Table *, const char *); void add_table(struct Table_head *, struct Table *); struct Table *table_lookup(const struct Table_head *, const char *); struct LookupResult table_lookup_server_address(const struct Table *, const char *, size_t); void reload_tables(struct Table_head *, struct Table_head *); void print_table_config(FILE *, struct Table *); int valid_table(struct Table *); void init_table(struct Table *); void table_ref_put(struct Table *); struct Table *table_ref_get(struct Table *); void tables_reload(struct Table_head *, struct Table_head *); void free_tables(struct Table_head *); #endif sniproxy-0.6.0/src/tls.c000066400000000000000000000170201340212110200151270ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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. */ /* * This is a minimal TLS implementation intended only to parse the server name * extension. This was created based primarily on Wireshark dissection of a * TLS handshake and RFC4366. */ #include #include /* malloc() */ #include #include /* strncpy() */ #include #include #include "tls.h" #include "protocol.h" #include "logger.h" #define SERVER_NAME_LEN 256 #define TLS_HEADER_LEN 5 #define TLS_HANDSHAKE_CONTENT_TYPE 0x16 #define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01 #ifndef MIN #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) #endif static int parse_tls_header(const uint8_t*, size_t, char **); static int parse_extensions(const uint8_t*, size_t, char **); static int parse_server_name_extension(const uint8_t*, size_t, char **); static const char tls_alert[] = { 0x15, /* TLS Alert */ 0x03, 0x01, /* TLS version */ 0x00, 0x02, /* Payload length */ 0x02, 0x28, /* Fatal, handshake failure */ }; const struct Protocol *const tls_protocol = &(struct Protocol){ .name = "tls", .default_port = 443, .parse_packet = (int (*const)(const char *, size_t, char **))&parse_tls_header, .abort_message = tls_alert, .abort_message_len = sizeof(tls_alert) }; /* Parse a TLS packet for the Server Name Indication extension in the client * hello handshake, returning the first servername found (pointer to static * array) * * Returns: * >=0 - length of the hostname and updates *hostname * caller is responsible for freeing *hostname * -1 - Incomplete request * -2 - No Host header included in this request * -3 - Invalid hostname pointer * -4 - malloc failure * < -4 - Invalid TLS client hello */ static int parse_tls_header(const uint8_t *data, size_t data_len, char **hostname) { uint8_t tls_content_type; uint8_t tls_version_major; uint8_t tls_version_minor; size_t pos = TLS_HEADER_LEN; size_t len; if (hostname == NULL) return -3; /* Check that our TCP payload is at least large enough for a TLS header */ if (data_len < TLS_HEADER_LEN) return -1; /* SSL 2.0 compatible Client Hello * * High bit of first byte (length) and content type is Client Hello * * See RFC5246 Appendix E.2 */ if (data[0] & 0x80 && data[2] == 1) { debug("Received SSL 2.0 Client Hello which can not support SNI."); return -2; } tls_content_type = data[0]; if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { debug("Request did not begin with TLS handshake."); return -5; } tls_version_major = data[1]; tls_version_minor = data[2]; if (tls_version_major < 3) { debug("Received SSL %" PRIu8 ".%" PRIu8 " handshake which can not support SNI.", tls_version_major, tls_version_minor); return -2; } /* TLS record length */ len = ((size_t)data[3] << 8) + (size_t)data[4] + TLS_HEADER_LEN; data_len = MIN(data_len, len); /* Check we received entire TLS record length */ if (data_len < len) return -1; /* * Handshake */ if (pos + 1 > data_len) { return -5; } if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { debug("Not a client hello"); return -5; } /* Skip past fixed length records: 1 Handshake Type 3 Length 2 Version (again) 32 Random to Session ID Length */ pos += 38; /* Session ID */ if (pos + 1 > data_len) return -5; len = (size_t)data[pos]; pos += 1 + len; /* Cipher Suites */ if (pos + 2 > data_len) return -5; len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1]; pos += 2 + len; /* Compression Methods */ if (pos + 1 > data_len) return -5; len = (size_t)data[pos]; pos += 1 + len; if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { debug("Received SSL 3.0 handshake without extensions"); return -2; } /* Extensions */ if (pos + 2 > data_len) return -5; len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1]; pos += 2; if (pos + len > data_len) return -5; return parse_extensions(data + pos, len, hostname); } static int parse_extensions(const uint8_t *data, size_t data_len, char **hostname) { size_t pos = 0; size_t len; /* Parse each 4 bytes for the extension header */ while (pos + 4 <= data_len) { /* Extension Length */ len = ((size_t)data[pos + 2] << 8) + (size_t)data[pos + 3]; /* Check if it's a server name extension */ if (data[pos] == 0x00 && data[pos + 1] == 0x00) { /* There can be only one extension of each type, so we break our state and move p to beinnging of the extension here */ if (pos + 4 + len > data_len) return -5; return parse_server_name_extension(data + pos + 4, len, hostname); } pos += 4 + len; /* Advance to the next extension header */ } /* Check we ended where we expected to */ if (pos != data_len) return -5; return -2; } static int parse_server_name_extension(const uint8_t *data, size_t data_len, char **hostname) { size_t pos = 2; /* skip server name list length */ size_t len; while (pos + 3 < data_len) { len = ((size_t)data[pos + 1] << 8) + (size_t)data[pos + 2]; if (pos + 3 + len > data_len) return -5; switch (data[pos]) { /* name type */ case 0x00: /* host_name */ *hostname = malloc(len + 1); if (*hostname == NULL) { err("malloc() failure"); return -4; } strncpy(*hostname, (const char *)(data + pos + 3), len); (*hostname)[len] = '\0'; return len; default: debug("Unknown server name extension name type: %" PRIu8, data[pos]); } pos += 3 + len; } /* Check we ended where we expected to */ if (pos != data_len) return -5; return -2; } sniproxy-0.6.0/src/tls.h000066400000000000000000000027461340212110200151450ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * 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 HOLDERS 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 TLS_H #define TLS_H #include "protocol.h" const struct Protocol *const tls_protocol; #endif sniproxy-0.6.0/test-coverage000077500000000000000000000006451340212110200160750ustar00rootroot00000000000000#!/bin/bash find . -name \*.gcno -delete find . -name \*.gcda -delete find . -name \*.gcov -delete ./configure CFLAGS="-fprofile-arcs -ftest-coverage" && \ make clean all check # Run additional network dependent tests pushd tests && \ ./wildcard_test && \ ./hostname_test && \ popd find . -name \*.gcda | while read FILE; do pushd $( dirname ${FILE} ) gcov $( basename ${FILE} ) popd done sniproxy-0.6.0/tests/000077500000000000000000000000001340212110200145345ustar00rootroot00000000000000sniproxy-0.6.0/tests/.gitignore000066400000000000000000000002011340212110200165150ustar00rootroot00000000000000address_test binder_test buffer_test cfg_tokenizer_test config_test http_test resolv_test table_test tls_test *.log *.trs *.pcap sniproxy-0.6.0/tests/Makefile.am000066400000000000000000000052171340212110200165750ustar00rootroot00000000000000AM_CPPFLAGS = -I$(top_srcdir)/src -g $(LIBEV_CFLAGS) $(LIBPCRE_CFLAGS) $(LIBUDNS_CFLAGS) TESTS = address_test \ buffer_test \ cfg_tokenizer_test \ table_test \ http_test \ tls_test \ binder_test TESTS += functional_test \ bad_request_test \ bind_source_test \ connection_reset_test \ fallback_test \ fd_limit_test \ ipv6_v6only_test \ proxy_header_test \ reload_test \ reuseport_test \ slow_client_test \ transparent_proxy_test if DNS_ENABLED TESTS += config_test \ resolv_test \ bad_dns_request_test endif check_PROGRAMS = http_test \ tls_test \ table_test \ binder_test \ buffer_test \ cfg_tokenizer_test \ address_test \ resolv_test \ config_test http_test_SOURCES = http_test.c \ ../src/http.c tls_test_SOURCES = tls_test.c \ ../src/tls.c \ ../src/logger.c binder_test_SOURCES = binder_test.c \ ../src/binder.c \ ../src/logger.c buffer_test_SOURCES = buffer_test.c \ ../src/buffer.c buffer_test_LDADD = $(LIBEV_LIBS) address_test_SOURCES = address_test.c \ ../src/address.c cfg_tokenizer_test_SOURCES = cfg_tokenizer_test.c \ ../src/cfg_tokenizer.c config_test_SOURCES = config_test.c \ ../src/binder.c \ ../src/config.c \ ../src/cfg_parser.c \ ../src/cfg_tokenizer.c \ ../src/address.c \ ../src/backend.c \ ../src/table.c \ ../src/listener.c \ ../src/connection.c \ ../src/buffer.c \ ../src/logger.c \ ../src/resolv.c \ ../src/resolv.h \ ../src/tls.c \ ../src/http.c config_test_LDADD = $(LIBEV_LIBS) $(LIBPCRE_LIBS) $(LIBUDNS_LIBS) resolv_test_SOURCES = resolv_test.c \ ../src/resolv.c \ ../src/address.c \ ../src/logger.c resolv_test_LDADD = $(LIBEV_LIBS) $(LIBUDNS_LIBS) table_test_SOURCES = table_test.c \ ../src/backend.c \ ../src/table.c \ ../src/address.c \ ../src/logger.c table_test_LDADD = $(LIBPCRE_LIBS) sniproxy-0.6.0/tests/TestHTTPD.pm000066400000000000000000000057751340212110200166330ustar00rootroot00000000000000package TestHTTPD; use warnings; use strict; require IO::Socket::INET; require Socket; require Exporter; require Time::HiRes; our @ISA = qw(Exporter); our @EXPORT = qw(new); our $VERSION = '0.01'; my $http_methods = { 'GET' => 1, 'POST' => 1, 'HEAD' => 1, 'PUT' => 1, 'DELETE' => 1, 'TRACE' => 1, 'DEBUG' => 1, 'CONNECT' => 1, 'OPTIONS' => 1, }; sub default_response_parser { my $sock = shift; my $status = 500; # Read HTTP request for (my $i = 0; my $line = $sock->getline(); $i++) { if ($i == 0 && $line =~ m/\A(\S+) (\S+) HTTP\/(\S+)\r\n\z/) { $status = 200 if exists($http_methods->{$1}); } # Wait for blank line indicating the end of the request last if $i > 0 && $line eq "\r\n"; } return $status; }; my $count = 0; # This represents the sizes of chunks of our responses my $responses = [ [ 20 ], [ 20, 18000], [ 22 ], [ 200 ], [ 20, 1, 1, 1, 1, 1, 1, 200 ], ]; my $http_status_line = { 200 => 'OK', 203 => 'Non-Authoritative Information', 500 => 'Internal Server Error', }; sub default_response_generator { $count ++; return sub($$) { my $sock = shift; my $status = shift; my @chunks = @{$responses->[$count % scalar @{$responses}]}; my $content_length = 0; map { $content_length += $_ } @chunks; print $sock "HTTP/1.1 $status " . $http_status_line->{$status} . "\r\n"; print $sock "Server: TestHTTPD/$VERSION\r\n"; print $sock "Content-Type: text/plain\r\n"; print $sock "Content-Length: $content_length\r\n"; print $sock "Connection: close\r\n"; print $sock "\r\n"; # Return data in chunks specified in responses while (my $length = shift @chunks) { print $sock 'X' x $length; $sock->flush(); Time::HiRes::usleep(100) if @chunks; } }; } sub httpd { my %args = @_; my $ip = $args{'ip'} || 'localhost'; my $port = $args{'port'} || 8081; my $request_parser = $args{'parser'} || \&default_response_parser; my $responder_generator = $args{'generator'} || \&default_response_generator; my $server = IO::Socket::INET->new(Listen => Socket::SOMAXCONN(), Proto => 'tcp', LocalAddr => $ip, LocalPort => $port, ReuseAddr => 1) or die $!; $SIG{CHLD} = 'IGNORE'; while(my $client = $server->accept()) { my $responder = $responder_generator->(); my $pid = fork(); next if $pid; # Parent die "fork: $!" unless defined $pid; # Child # my $status = $request_parser->($client); # Assume a GET request $responder->($client, $status); $client->close(); exit 0; } continue { # close child sockets $client->close(); } die "accept(): $!"; } 1; sniproxy-0.6.0/tests/TestUtils.pm000066400000000000000000000064671340212110200170470ustar00rootroot00000000000000package TestUtils; use warnings; use strict; use POSIX ":sys_wait_h"; use IO::Socket::INET; require File::Temp; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(start_child reap_children wait_for_type wait_for_port make_config); our $VERSION = '0.01'; $SIG{CHLD} = \&REAPER; my %children; sub REAPER { my $stiff; while (($stiff = waitpid(-1, &WNOHANG)) > 0) { # do something with $stiff if you want $children{$stiff}->{'running'} = undef; $children{$stiff}->{'exit_code'} = $? >> 8; $children{$stiff}->{'signal'} = $? & 127; $children{$stiff}->{'core_dumped'} = $? & 128; } $SIG{CHLD} = \&REAPER; # install *after* calling waitpid } # Make several requests through the proxy specifying the host header sub start_child { my $type = shift; my $child = shift; my @args = @_; my $pid = fork(); if (not defined $pid) { die("fork: $!"); } elsif ($pid == 0) { undef $SIG{CHLD}; $child->(@args); # Should not be reached exit(99); } $children{$pid} = { type => $type, pid => $pid, running => 1, core_dumped => undef, signal => undef, exit_core => undef, }; return $pid; } sub reap_children { while (my @hit_list = grep($children{$_}->{'running'}, keys %children)) { kill 15, @hit_list; sleep 1; } # Check that all our children exited cleanly my @failures = grep($_->{'exit_code'} != 0 || $_->{'core_dumped'}, values %children); if (@failures) { print "Test failed.\n"; foreach (@failures) { if ($_->{'core_dumped'}) { printf "%s died with signal %d, %s coredump\n", $_->{'type'}, $_->{'signal'}, $_->{'core_dumped'} ? 'with' : 'without'; } else { print "$_->{'type'} failed with exit code $_->{'exit_code'}\n"; } } exit 1; } else { # print "Test passed.\n"; return; } } sub wait_for_type($) { my $type = shift; while (grep($children{$_}->{'running'} && $children{$_}->{'type'} eq $type, keys %children) > 0) { sleep 1; } } sub wait_for_port { my %args = @_; my $ip = $args{'ip'} || 'localhost'; my $port = $args{'port'} or die "port required"; my $delay = 1; while ($delay < 60) { my $port_open = undef; eval { my $socket = IO::Socket::INET->new(PeerAddr => $ip, PeerPort => $port, Proto => "tcp", Type => SOCK_STREAM); if ($socket && $socket->connected()) { $socket->shutdown(2); $port_open = 1; } }; return 1 if ($port_open); sleep($delay); $delay *= 2; } return undef; } sub make_config($$) { my $proxy_port = shift; my $httpd_port = shift; my ($fh, $filename) = File::Temp::tempfile(); my ($unused, $logfile) = File::Temp::tempfile(); # Write out a test config file print $fh < #include #include #include #include #include "address.h" struct Test { char *input; char *output; int expected_type; int port; }; #define TYPE_HOSTNAME 1 #define TYPE_SOCKADDR 2 #define TYPE_WILDCARD 4 static const struct Test good[] = { {"www.example.com", "www.example.com", TYPE_HOSTNAME, 0}, {"www.example.com:80", "www.example.com:80", TYPE_HOSTNAME, 80}, {"hyphens-are-permited.example.com", "hyphens-are-permited.example.com", TYPE_HOSTNAME, 0}, {"79423.all-numeric-labels-are-permitted.com", "79423.all-numeric-labels-are-permitted.com", TYPE_HOSTNAME, 0}, {"localhost", "localhost", TYPE_HOSTNAME, 0}, {"192.0.2.10", "192.0.2.10", TYPE_SOCKADDR, 0}, {"192.0.2.10:80", "192.0.2.10:80", TYPE_SOCKADDR, 80}, {"0.0.0.0", "0.0.0.0", TYPE_SOCKADDR, 0}, {"0.0.0.0:80", "0.0.0.0:80", TYPE_SOCKADDR, 80}, {"255.255.255.255:80", "255.255.255.255:80", TYPE_SOCKADDR, 80}, {"192.0.2.10:65535", "192.0.2.10:65535", TYPE_SOCKADDR, 65535}, {"::", "[::]", TYPE_SOCKADDR, 0}, {"::1", "[::1]", TYPE_SOCKADDR, 0}, {"[::]", "[::]", TYPE_SOCKADDR, 0}, {"[::1]", "[::1]", TYPE_SOCKADDR, 0}, {"[::]:80", "[::]:80", TYPE_SOCKADDR, 80}, {"[::]:8080", "[::]:8080", TYPE_SOCKADDR, 8080}, {"2001:db8:0000:0000:0000:0000:0000:0001", "[2001:db8::1]", TYPE_SOCKADDR, 0}, {"[2001:db8:0000:0000:0000:0000:0000:0001]:65535", "[2001:db8::1]:65535", TYPE_SOCKADDR, 65535}, {"2001:db8::192.0.2.0", "[2001:db8::c000:200]", TYPE_SOCKADDR, 0}, {"unix:/tmp/foo.sock", "unix:/tmp/foo.sock", TYPE_SOCKADDR, 0}, {"*", "*", TYPE_WILDCARD, 0}, {"*:80", "*:80", TYPE_WILDCARD, 80}, /* Please don't do this (add the port to the end of an IPv6 address) */ {"2001:db8:0:0:0:0:0:1:80", "[2001:db8::1]:80", TYPE_SOCKADDR, 80} }; static const char *bad[] = { NULL, "www..example.com", "-www.example.com", "1n\\/l1|>|-|0$T|\\|4M" }; int compare_address_strings(const char *a, const char *b) { struct Address *addr_a = new_address(a); struct Address *addr_b = new_address(b); int result = address_compare(addr_a, addr_b); free(addr_a); free(addr_b); return result; } int main() { /* using volatile variables so we can examine core dumps */ for (volatile unsigned int i = 0; i < sizeof(good) / sizeof(struct Test); i++) { int port; char buffer[255]; struct Address *addr = new_address(good[i].input); assert(addr != NULL); assert(address_compare(addr, addr) == 0); assert(address_compare(NULL, addr) < 0); assert(address_compare(addr, NULL) > 0); assert(address_len(addr) > 0); if (good[i].expected_type & TYPE_HOSTNAME) { assert(address_is_hostname(addr)); assert(!address_is_sockaddr(addr)); assert(!address_is_wildcard(addr)); assert(address_hostname(addr) != NULL); assert(address_sa(addr) == NULL); assert(address_sa_len(addr) == 0); } else if (good[i].expected_type & TYPE_SOCKADDR) { assert(!address_is_hostname(addr)); assert(address_is_sockaddr(addr)); assert(!address_is_wildcard(addr)); assert(address_hostname(addr) == NULL); assert(address_sa(addr) != NULL); assert(address_sa_len(addr) > 0); } else if (good[i].expected_type & TYPE_WILDCARD) { assert(!address_is_hostname(addr)); assert(!address_is_sockaddr(addr)); assert(address_is_wildcard(addr)); assert(address_hostname(addr) == NULL); assert(address_sa(addr) == NULL); assert(address_sa_len(addr) == 0); } display_address(addr, buffer, sizeof(buffer)); if (strcmp(buffer, good[i].output)) { fprintf(stderr, "display_address(%p) returned \"%s\", expected \"%s\"\n", (void *)addr, buffer, good[i].output); return 1; } assert(display_address(addr, NULL, 0) == NULL); port = address_port(addr); if (good[i].port != port) { fprintf(stderr, "address_port(%p) return %d, expected %d\n", (void *)addr, port, good[i].port); return 1; } address_set_port(addr, port); if (good[i].port != port) { fprintf(stderr, "address_port(%p) return %d, expected %d\n", (void *)addr, port, good[i].port); return 1; } free(addr); } for (volatile unsigned int i = 0; i < sizeof(bad) / sizeof(const char *); i++) { struct Address *addr = new_address(bad[i]); if (addr != NULL) { fprintf(stderr, "Accepted bad hostname \"%s\"\n", bad[i]); return 1; } } assert(compare_address_strings("unix:/dev/log", "127.0.0.1") < 0); assert(compare_address_strings("unix:/dev/log", "unix:/dev/logsocket") < 0); assert(compare_address_strings("example.co", "example.com") != 0); assert(compare_address_strings("0.0.0.0", "127.0.0.1") < 0); assert(compare_address_strings("127.0.0.1", "0.0.0.0") > 0); assert(compare_address_strings("127.0.0.1", "127.0.0.1") == 0); assert(compare_address_strings("127.0.0.1:80", "127.0.0.1:81") < 0); assert(compare_address_strings("*:80", "*:81") < 0); assert(compare_address_strings("*:81", "*:80") > 0); assert(compare_address_strings("example.com", "example.net") < 0); assert(compare_address_strings("example.net", "example.com") > 0); assert(compare_address_strings("example.com", "example.com.net") < 0); assert(compare_address_strings("example.com.net", "example.com") > 0); assert(compare_address_strings("example.com", "example.com:80") < 0); assert(compare_address_strings("example.com:80", "example.com") > 0); assert(compare_address_strings(NULL, "example.com") < 0); assert(compare_address_strings("example.com", NULL) > 0); assert(compare_address_strings("example.com", "::") < 0); assert(compare_address_strings("::", "example.com") > 0); assert(compare_address_strings("0.0.0.0", "*") < 0); assert(compare_address_strings("*", "0.0.0.0") > 0); do { struct Address *addr = new_address("*"); assert(addr != NULL); assert(address_len(addr) > 0); free(addr); } while (0); return 0; } sniproxy-0.6.0/tests/bad_dns_request_test000077500000000000000000000076651340212110200207010ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; use IO::Socket::INET; my $bad_requests = [ { # Test bad name server request => "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", client => \&http_client, }, { # Invalid hostname request => "GET / HTTP/1.1\r\nHost: ...........\r\n\r\n", client => \&http_client, }, { # Exceed buffer size request => "PUT / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 65536\r\n\r\n" . 'x' x 65536, client => \&http_client, }, { # Exceed buffer size before host header request => "GET /" . 'x' x 65536, client => \&http_client, }, { # Invalid hostname request => "GET / HTTP/1.1\r\nHost: \0example.com\r\n\r\n", client => \&http_client, }, { # Test client aborting connection before DNS response received request => "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", client => \&http_client_abort, }, ]; sub http_client($$) { my $port = shift; my $request = shift; my $socket = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => $port, Proto => "tcp", Type => SOCK_STREAM) or die "couldn't connect $!"; $socket->send($request); my $buffer; $socket->recv($buffer, 4096); $socket->close(); return undef; } sub http_client_abort($$) { my $port = shift; my $request = shift; my $socket = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => $port, Proto => "tcp", Type => SOCK_STREAM) or die "couldn't connect $!"; $socket->send($request); sleep(1); $socket->close(); return undef; } sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub worker($$$) { my ($port, $requests, $offset) = @_; for (my $i = 0; $i < $requests; $i++) { my $test = $bad_requests->[($i + $offset) % int(@$bad_requests)]; my $error = $test->{client}($port, $test->{request}); die($error) if defined $error; } # Success exit 0; } sub make_wildcard_config($) { my $proxy_port = shift; my ($fh, $filename) = File::Temp::tempfile(); # Write out a test config file print $fh < $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, $proxy_port, $iterations, $i); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/bad_request_test000077500000000000000000000331731340212110200200260ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; use IO::Socket::INET; my $bad_requests = [ pack("C*", 0xff, 0xff, 0xff, 0xff, 0xff), pack("C*", 0x16, 0x04, 0x10, 0x00, 0x00), pack("C*", 0x16, 0x03, 0x00, 0x00, 0x7f, 0x01, 0x00, 0x00, 0x7b, 0x03, 0x00, 0x53, 0x11, 0x25, 0xc2, 0x92, 0xd6, 0xca, 0xf1, 0x79, 0x90, 0xba, 0x38, 0x8f, 0xad, 0xc8, 0x13, 0xa3, 0x1b, 0x57, 0xd9, 0xf4, 0x3e, 0xd2, 0x8b, 0xb6, 0x5e, 0xe3, 0x12, 0xca, 0x81, 0x2f, 0xc5, 0x00, 0x00, 0x54, 0xc0, 0x14, 0xc0, 0x0a, 0xc0, 0x22, 0xc0, 0x21, 0x00, 0x39, 0x00, 0x38, 0xc0, 0x0f, 0xc0, 0x05, 0x00, 0x35, 0xc0, 0x12, 0xc0, 0x08, 0xc0, 0x1c, 0xc0, 0x1b, 0x00, 0x16, 0x00, 0x13, 0xc0, 0x0d, 0xc0, 0x03, 0x00, 0x0a, 0xc0, 0x13, 0xc0, 0x09, 0xc0, 0x1f, 0xc0, 0x1e, 0x00, 0x33, 0x00, 0x32, 0xc0, 0x0e, 0xc0, 0x04, 0x00, 0x2f, 0xc0, 0x11, 0xc0, 0x07, 0xc0, 0x0c, 0xc0, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x15, 0x00, 0x12, 0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0x01, 0x00), pack("C*", 0x16, 0x03, 0x01, 0x00, 0x48, # Handshake 0x01, 0x00, 0x00, 0x42, 0x03, 0x03, # Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0xff, 0x01, 0x00, 0x01, 0x17, # Extension 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x00, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x00, 0x0f, 0x00, 0x01, 0x01), pack("C*", 0x16, # Content Type: Handshake 0x03, 0x01, # Version: TLS 1.0 0x00, 0x48, # Length # Handshake 0x01, # Handshake Type: Client Hello 0x00, 0x00, 0x42, # Length 0x03, 0x03, # Version: TLS 1.2 # Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, # Session ID Length 0x00, 0x04, # Cipher Suites Length 0x00, 0x01, # NULL-MD5 0x00, 0xff, # RENEGOTIATION INFO SCSV 0x01, # Compression Methods 0x00, # NULL 0x00, 0x17, # Extensions Length # Extension 0x00, 0x00, # Extension Type: Server Name 0x00, 0x0e, # Length 0x00, 0x0c, # Server Name Indication Length 0x00, # Server Name Type: host_name 0x00, 0x09, # Length # "local\0ost" 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x6f, 0x73, 0x74, # Extension 0x00, 0x0f, # Extension Type: Heart Beat 0x00, 0x01, # Length 0x01 # Mode: Peer allows to send requests ), pack("C*", 0x16, # Content Type: Handshake 0x03, 0x01, # Version: TLS 1.0 0x00, 0x3a, # Length # Handshake 0x01, # Handshake Type: Client Hello 0x00, 0x00, 0x34, # Length 0x03, 0x03, # Version: TLS 1.2 # Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, # Session ID Length 0x00, 0x04, # Cipher Suites Length 0x00, 0x01, # NULL-MD5 0x00, 0xff, # RENEGOTIATION INFO SCSV 0x01, # Compression Methods 0x00, # NULL 0x00, 0x09, # Extensions Length # Extension 0x00, 0x00, # Extension Type: Server Name 0x00, 0x05, # Length 0x00, 0x03, # Server Name Indication Length 0x00, # Server Name Type: host_name 0x00, 0x00 # Length ), pack("C*", 0x16, # Content Type: Handshake 0x03, 0x01, # Version: TLS 1.0 0x00, 0x3f, # Length # Handshake 0x01, # Handshake Type: Client Hello 0x00, 0x00, 0x39, # Length 0x03, 0x03, # Version: TLS 1.2 # Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, # Session ID Length 0x00, 0x04, # Cipher Suites Length 0x00, 0x01, # NULL-MD5 0x00, 0xff, # RENEGOTIATION INFO SCSV 0x01, # Compression Methods 0x00, # NULL 0x00, 0x0e, # Extensions Length # Extension 0x00, 0x00, # Extension Type: Server Name 0x00, 0x0e, # Length 0x00, 0x03, # Server Name Indication Length 0x00, # Server Name Type: host_name 0x00, 0x00, # Length # Extension 0x00, 0x0f, # Extension Type: Heart Beat 0x00, 0x01, # Length 0x01 # Mode: Peer allows to send requests ), pack("C*", 0x16, # Content Type: Handshake 0x03, 0x01, # Version: TLS 1.0 0x00, 0x48, # Length # Handshake 0x01, # Handshake Type: Client Hello 0x00, 0x00, 0x42, # Length 0x03, 0x03, # Version: TLS 1.2 # Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, # Session ID Length 0x00, 0x04, # Cipher Suites Length 0x00, 0x01, # NULL-MD5 0x00, 0xff, # RENEGOTIATION INFO SCSV 0x01, # Compression Methods 0x00, # NULL 0x00, 0x17, # Extensions Length # Extension 0x00, 0x00, # Extension Type: Server Name 0x00, 0x0e, # Length 0x00, 0x0c, # Server Name Indication Length 0x01, # Server Name Type: host_name 0x00, 0x09, # Length # "localhost" 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, # Extension 0x00, 0x0f, # Extension Type: Heart Beat 0x00, 0x01, # Length 0x01 # Mode: Peer allows to send requests ), pack("C*", 0x16, # Content Type: Handshake 0x03, 0x01, # Version: TLS 1.0 0x00, 0x31, # Length # Handshake 0x01, # Handshake Type: Client Hello 0x00, 0x00, 0x2b, # Length 0x03, 0x03, # Version: TLS 1.2 # Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, # Session ID Length 0x00, 0x04, # Cipher Suites Length 0x00, 0x01, # NULL-MD5 0x00, 0xff, # RENEGOTIATION INFO SCSV 0x01, # Compression Methods 0x00, # NULL 0x00, 0x00, # Extensions Length ), pack("C*", 0x16, # Content Type: Handshake 0x03, 0x01, # Version: TLS 1.0 0x00, 0x35, # Length # Handshake 0x01, # Handshake Type: Client Hello 0x00, 0x00, 0x2f, # Length 0x03, 0x03, # Version: TLS 1.2 # Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, # Session ID Length 0x00, 0x04, # Cipher Suites Length 0x00, 0x01, # NULL-MD5 0x00, 0xff, # RENEGOTIATION INFO SCSV 0x01, # Compression Methods 0x00, # NULL 0x00, 0x04, # Extensions Length 0x00, 0x23, # Extension Type: Session Ticket TLS 0x00, 0x00, # Length ), ]; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub tls_client($$) { my $port = shift; my $request = shift; my $socket = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => $port, Proto => "tcp", Type => SOCK_STREAM) or die "couldn't connect $!"; $socket->send($request); my $buffer; $socket->recv($buffer, 4096); $socket->close(); return "Unexpected response (" . length($buffer) . " bytes)\n" unless $buffer eq pack("C*", 0x15, 0x03, 0x01, 0x00, 0x02, 0x02, 0x28); return undef; } sub worker($$$$) { my ($hostname, $path, $port, $requests) = @_; for (my $i = 0; $i < $requests; $i++) { my $error = tls_client($port, $bad_requests->[$i % int(@$bad_requests)]); die($error) if defined $error; } # Success exit 0; } sub make_tls_config($) { my $proxy_port = shift; my ($fh, $filename) = File::Temp::tempfile(); # Write out a test config file print $fh < $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/bench_sniproxy000077500000000000000000000020131340212110200175100ustar00rootroot00000000000000#!/bin/sh SNI_PROXY_PORT=${SNI_PROXY_PORT:=8080} if [ -n "${LOCAL_HTTPD_PORT}" ]; then TEST_HTTPD_PORT=${LOCAL_HTTPD_PORT} else TEST_HTTPD_PORT=${TEST_HTTPD_PORT:=8081} # Start TestHTTPD perl -MTestHTTPD -e "TestHTTPD::httpd(port => ${TEST_HTTPD_PORT})" & TEST_HTTPD_PID=$! fi # Create a test configuration file CONFIG_FILE=$(perl -MTestUtils -e "print TestUtils::make_config(${SNI_PROXY_PORT}, ${TEST_HTTPD_PORT});") # Start sniproxy $@ ../src/sniproxy -f -c ${CONFIG_FILE} & SNI_PROXY_PID=$! echo -n "Wait for TestHTTPD to start" until netstat -ltn | grep -q :${TEST_HTTPD_PORT}; do echo -n . done echo "" echo -n "Wait for sniproxy to start" until netstat -ltn | grep -q :${SNI_PROXY_PORT}; do echo -n . done echo "" sleep 1; # Run apache bench ab -n 65536 -c 256 http://localhost:${SNI_PROXY_PORT}/ RESULT=$? # Cleanup if [ -n "${TEST_HTTPD_PID}" ]; then kill ${TEST_HTTPD_PID} wait ${TEST_HTTPD_PID} fi kill ${SNI_PROXY_PID} wait ${SNI_PROXY_PID} rm -f ${CONFIG_FILE} exit ${RESULT} sniproxy-0.6.0/tests/bench_test_httpd000077500000000000000000000006311340212110200200030ustar00rootroot00000000000000#!/bin/sh TEST_HTTPD_PORT=8081 perl -MTestHTTPD -e "TestHTTPD::httpd(port => ${TEST_HTTPD_PORT})" & TEST_HTTPD_PID=$! echo -n "Wait for TestHTTPD to start" until netstat -ltn | grep -q :${TEST_HTTPD_PORT}; do echo -n . done echo "" # Run apache bench ab -n 65536 -c 256 http://127.0.0.1:${TEST_HTTPD_PORT}/ RESULT=$? # Cleanup TestHTTPD kill ${TEST_HTTPD_PID} wait ${TEST_HTTPD_PID} exit ${RESULT} sniproxy-0.6.0/tests/bind_source_test000077500000000000000000000052441340212110200200220ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub worker($$$$) { my ($hostname, $path, $port, $requests) = @_; for (my $i = 0; $i < $requests; $i++) { system('curl', '-s', '-S', '-H', "Host: $hostname", '-o', '/dev/null', "http://localhost:$port/$path"); if ($? == -1) { die "failed to execute: $!\n"; } elsif ($? & 127) { printf STDERR "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without'; exit 255; } elsif ($? >> 8) { exit $? >> 8; } } # Success exit 0; } sub make_source_config($$) { my $proxy_port = shift; my $httpd_port = shift; my ($fh, $filename) = File::Temp::tempfile(); my ($unused, $logfile) = File::Temp::tempfile(); # Write out a test config file print $fh < $httpd_port) unless $local_httpd; # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid unless $local_httpd; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/binder_test.c000066400000000000000000000027771340212110200172170ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "binder.h" static int test_binder(int); int main() { int i; start_binder("binder_test"); for (i = 8080; i <= 8084; i++) test_binder(i); stop_binder(); return 0; } static int test_binder(int port) { int fd; struct sockaddr_in addr = { 0 }; struct sockaddr_storage addr_verify = { 0 }; socklen_t len; /* make valgrind happy by initializing to zero */ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); fd = bind_socket((struct sockaddr *)&addr, sizeof(addr)); assert(fd > 0); /* Verify we obtained the expected socket address */ len = sizeof(addr_verify); if (getsockname(fd, (struct sockaddr *)&addr_verify, &len) < 0) { perror("getsockname:"); exit(1); } assert(addr.sin_family == ((struct sockaddr_in *)&addr_verify)->sin_family); assert(addr.sin_addr.s_addr == ((struct sockaddr_in *)&addr_verify)->sin_addr.s_addr); assert(addr.sin_port == ((struct sockaddr_in *)&addr_verify)->sin_port); /* Verify we can listen to it */ if (listen(fd, 5) < 0) { perror("listen:"); exit(1); } /* Test error handling: */ fd = bind_socket((struct sockaddr *)&addr, sizeof(addr)); assert(fd == -1); return 0; } sniproxy-0.6.0/tests/buffer_test.c000066400000000000000000000077361340212110200172250ustar00rootroot00000000000000#include #include #include #include #include #include #include "buffer.h" static void test1() { struct Buffer *buffer; char input[] = "This is a test."; char output[sizeof(input)]; int len, i; buffer = new_buffer(256, EV_DEFAULT); assert(buffer != NULL); len = buffer_push(buffer, input, sizeof(input)); assert(len == sizeof(input)); len = buffer_peek(buffer, output, sizeof(output)); assert(len == sizeof(input)); for (i = 0; i < len; i++) assert(input[i] == output[i]); /* second peek to ensure the first didn't permute the state of the buffer */ len = buffer_peek(buffer, output, sizeof(output)); assert(len == sizeof(input)); for (i = 0; i < len; i++) assert(input[i] == output[i]); /* test pop */ len = buffer_pop(buffer, output, sizeof(output)); assert(len == sizeof(input)); for (i = 0; i < len; i++) assert(input[i] == output[i]); len = buffer_pop(buffer, output, sizeof(output)); assert(len == 0); free_buffer(buffer); } static void test2() { struct Buffer *buffer; char input[] = "Testing wrap around behaviour."; char output[sizeof(input)]; int len, i = 0; buffer = new_buffer(256, EV_DEFAULT); assert(buffer != NULL); while (i < 236) { len = buffer_push(buffer, input, sizeof(input)); assert(len == sizeof(input)); i += len; } while (len) { len = buffer_pop(buffer, output, sizeof(output)); } len = buffer_push(buffer, input, sizeof(input)); assert(len == sizeof(input)); len = buffer_peek(buffer, output, sizeof(output)); assert(len == sizeof(input)); for (i = 0; i < len; i++) assert(input[i] == output[i]); len = buffer_pop(buffer, output, sizeof(output)); assert(len == sizeof(input)); for (i = 0; i < len; i++) assert(input[i] == output[i]); len = buffer_push(buffer, input, sizeof(input)); assert(len == sizeof(input)); len = buffer_peek(buffer, output, sizeof(output)); assert(len == sizeof(input)); for (i = 0; i < len; i++) assert(input[i] == output[i]); free_buffer(buffer); } static void test3() { struct Buffer *buffer; char input[] = "Test buffer resizing."; char output[sizeof(input)]; int len, i; buffer = new_buffer(256, EV_DEFAULT); assert(buffer != NULL); len = buffer_push(buffer, input, sizeof(input)); assert(len == sizeof(input)); /* Test resizing to too small of a buffer size */ len = buffer_resize(buffer, 8); assert(len == -1); buffer_resize(buffer, 32); assert(buffer_room(buffer) == 32 - sizeof(input)); len = buffer_peek(buffer, output, sizeof(output)); assert(len == sizeof(input)); for (i = 0; i < len; i++) assert(input[i] == output[i]); free_buffer(buffer); } static void test4() { struct Buffer *buffer; int read_fd, write_fd; buffer = new_buffer(4096, EV_DEFAULT); read_fd = open("/dev/zero", O_RDONLY); if (read_fd < 0) { perror("open:"); exit(1); } write_fd = open("/dev/null", O_WRONLY); if (write_fd < 0) { perror("open:"); exit(1); } while (buffer->tx_bytes < 65536) { buffer_read(buffer, read_fd); buffer_write(buffer, write_fd); } free_buffer(buffer); } static void test_buffer_coalesce() { struct Buffer *buffer; char input[] = "Test buffer resizing."; char output[sizeof(input)]; int len; buffer = new_buffer(4096, EV_DEFAULT); len = buffer_push(buffer, input, sizeof(input)); assert(len == sizeof(input)); len = buffer_pop(buffer, output, sizeof(output)); assert(len == sizeof(output)); assert(buffer_len(buffer) == 0); assert(buffer->head != 0); len = buffer_coalesce(buffer, NULL); assert(len == 0); } int main() { test1(); test2(); test3(); test4(); test_buffer_coalesce(); } sniproxy-0.6.0/tests/cfg_tokenizer_test.c000066400000000000000000000032211340212110200205660ustar00rootroot00000000000000#include #include #include #include "cfg_tokenizer.h" struct Result { enum Token type; char *value; }; struct Test { char *config; struct Result *results; int len; }; static char config1[] = "# Comment\n" "numbers {\n" " one\n" " two\n" " three\n" " \"[0-9a-z-]+\\.edu\"\n" "}"; static struct Result results1[] = { { TOKEN_EOL, NULL }, { TOKEN_WORD, "numbers" }, { TOKEN_OBRACE, NULL }, { TOKEN_EOL, NULL }, { TOKEN_WORD, "one" }, { TOKEN_EOL, NULL }, { TOKEN_WORD, "two" }, { TOKEN_EOL, NULL }, { TOKEN_WORD, "three" }, { TOKEN_EOL, NULL }, { TOKEN_WORD, "[0-9a-z-]+.edu" }, { TOKEN_EOL, NULL }, { TOKEN_CBRACE, NULL }, { TOKEN_END, NULL }, }; static struct Test tests[] = { { config1, results1, sizeof(results1) / sizeof(struct Result) }, { NULL, NULL, 0 } /* End of tests */ }; int main() { FILE *cfg; char buffer[256]; enum Token token; struct Test *test; int i; cfg = tmpfile(); if (cfg == NULL) { perror("tmpfile"); return 1; } for (test = tests; test->config; test++) { fprintf(cfg, "%s", test->config); rewind(cfg); for (i = 0; i < test->len; i++) { token = next_token(cfg, buffer, sizeof(buffer)); assert(token == test->results[i].type); if (test->results[i].value) assert(strncmp(buffer, test->results[i].value, sizeof(buffer)) == 0); } rewind(cfg); } fclose(cfg); return (0); } sniproxy-0.6.0/tests/config_test.c000066400000000000000000000006661340212110200172140ustar00rootroot00000000000000#include #include "config.h" int main(int argc, char **argv) { char *config_file = "../sniproxy.conf"; struct Config *config; if (argc >= 2) config_file = argv[1]; config = init_config(config_file, EV_DEFAULT); if (config == NULL) { fprintf(stderr, "Failed to parse config\n"); return 1; } print_config(stdout, config); free_config(config, EV_DEFAULT); return 0; } sniproxy-0.6.0/tests/connection_reset_test000077500000000000000000000064151340212110200210700ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use IO::Socket; use Socket; use POSIX qw(:sys_wait_h); use File::Temp; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub simple_server($) { my $port = shift; my $server = IO::Socket::INET->new(LocalPort => $port, Type => SOCK_STREAM, Reuse => 1, Listen => 10) or die "listen: $!"; while (my $client = $server->accept()) { my $pid = fork(); next if $pid; # Parent die "fork: $!" unless defined $pid; $server->close(); while (my $line = <$client>) { last if $line eq "\r\n"; # End of headers } $client->send("HTTP/1.1 200 OK\r\n" . "Content-Type: text/plain\r\n" . "Context-Length: 16777216\r\n" . "\r\n"); # Send a bunch of data (more that will be buffered by the kernel for (my $i = 0; $i < 4096; $i++) { $client->send('x' x 4096); } $client->close(); exit; } $server->close(); # Wait for children 1 until (-1 == waitpid(-1, WNOHANG)); exit(0); } sub bad_client($) { my $port = shift; my $socket = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => $port, Proto => "tcp", Type => SOCK_STREAM) or die "couldn't connect $!"; # This causes the socket to terminate abnormally and # replicates the select invalid file descriptor error kill 9, $$; $socket->send("GET / HTTP/1.1\r\n" . "UserAgent: bad_client/0.1\r\n" . "Host: localhost:$port\r\n" . "Accept: */*\r\n" . "\r\n"); my $buffer; $socket->recv($buffer, 4096); $socket->recv($buffer, 4096); $socket->recv($buffer, 4096); $socket->close(); exit(0); } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $httpd_port = $ENV{TEST_HTTPD_PORT} || 8081; my $config = make_config($proxy_port, $httpd_port); my $proxy_pid = start_child('sniproxy', \&proxy, $config, @ARGV); my $httpd_pid = start_child('server', \&simple_server, $httpd_port); # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $proxy_port); for (my $i = 0; $i < 10; $i++) { start_child('worker', \&bad_client, $proxy_port); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/fallback_test000077500000000000000000000070751340212110200172710ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; use IO::Socket::INET; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub make_fallback_config($$$) { my $proxy_port = shift; my $httpd_port = shift; my $fallback_port = shift; my ($fh, $filename) = File::Temp::tempfile(); my ($unused, $logfile) = File::Temp::tempfile(); # Write out a test config file print $fh <new(PeerAddr => '127.0.0.1', PeerPort => $port, Proto => "tcp", Type => SOCK_STREAM) or die "couldn't connect $!"; $socket->send("GET /$path HTTP/1.0\r\n\r\n"); my $buffer; $socket->recv($buffer, 4096); $socket->close(); # Expect fallback HTTP server 203 rather than 200 from main test HTTPD instance return "Unexpected response: $buffer\n" unless $buffer =~ /\AHTTP\/1\.1 203/; return undef; } sub worker($$) { my ($hostname, $path, $port, $requests) = @_; for (my $i = 0; $i < $requests; $i++) { my $error = http10_client($path, $port); die $error if defined $error; } # Success exit 0; } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $httpd_port = $ENV{TEST_HTTPD_PORT} || 8081; my $fallback_port = $ENV{TEST_FALLBACK_PORT} || 8082; my $workers = $ENV{WORKERS} || 10; my $iterations = $ENV{ITERATIONS} || 10; my $local_httpd = $ENV{LOCAL_HTTPD_PORT}; my $config = make_fallback_config($proxy_port, $local_httpd || $httpd_port, $fallback_port); my $proxy_pid = start_child('server', \&proxy, $config, @ARGV); my $httpd_pid = start_child('server', \&TestHTTPD::httpd, port => $httpd_port) unless $local_httpd; my $fallback_httpd_pid = start_child('server', \&TestHTTPD::httpd, port => $fallback_port, generator => sub { return sub($$) { my $sock = shift; my $status = shift; print $sock "HTTP/1.1 203 Non-Authoritative Information\r\n"; print $sock "Server: TestHTTPD/$TestHTTPD::VERSION\r\n"; print $sock "Content-Type: text/plain\r\n"; print $sock "Content-Length: 15\r\n"; print $sock "Connection: close\r\n"; print $sock "\r\n"; print $sock "Fallback server"; } }); # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $proxy_port); wait_for_port(port => $fallback_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'nonexistant.host', '', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid unless $local_httpd; kill 15, $fallback_httpd_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/fd_limit_test000077500000000000000000000043311340212110200173110ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config, '-n', '16'); } sub worker($$$$) { my ($hostname, $path, $port, $requests) = @_; for (my $i = 0; $i < $requests; $i++) { system('curl', '-s', '-S', '-H', "Host: $hostname", '-o', '/dev/null', "http://localhost:$port/$path"); if ($? == -1) { die "failed to execute: $!\n"; } elsif ($? & 127) { printf STDERR "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without'; exit 255; } elsif ($? >> 8) { exit $? >> 8; } } # Success exit 0; } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $httpd_port = $ENV{TEST_HTTPD_PORT} || 8081; my $workers = $ENV{WORKERS} || 10; my $iterations = $ENV{ITERATIONS} || 3; my $local_httpd = $ENV{LOCAL_HTTPD_PORT}; my $config = make_config($proxy_port, $local_httpd || $httpd_port); my $proxy_pid = start_child('server', \&proxy, $config, @ARGV); my $httpd_pid = start_child('server', \&TestHTTPD::httpd, port => $httpd_port) unless $local_httpd; # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid unless $local_httpd; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/functional_test000077500000000000000000000060301340212110200176620ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub worker($$$$) { my ($hostname, $path, $port, $requests) = @_; for (my $i = 0; $i < $requests; $i++) { system('curl', '-s', '-S', '-H', "Host: $hostname", '-o', '/dev/null', "http://localhost:$port/$path"); if ($? == -1) { die "failed to execute: $!\n"; } elsif ($? & 127) { printf STDERR "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without'; exit 255; } elsif ($? >> 8) { exit $? >> 8; } } # Success exit 0; } sub connection_dump_files() { my $dir = '/tmp'; opendir(my $dh, $dir) or die("opendir(): $!"); my %files = map { $dir . '/' . $_ => 1 } grep { /^sniproxy-connections-.{6}$/ } readdir($dh); closedir($dh); return \%files; } sub dump_connections_worker($) { my ($proxy_pid) = @_; my $existing_dump_files = connection_dump_files(); while (kill('USR1', $proxy_pid)) { # Sleep 100ms select(undef, undef, undef, 0.25); my @new_dump_files = grep { !$existing_dump_files->{$_} } keys %{connection_dump_files()}; die "sniproxy didn't dump connections" unless @new_dump_files; foreach my $dump_file (@new_dump_files) { unlink $dump_file; } } exit 0; } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $httpd_port = $ENV{TEST_HTTPD_PORT} || 8081; my $workers = $ENV{WORKERS} || 10; my $iterations = $ENV{ITERATIONS} || 10; my $local_httpd = $ENV{LOCAL_HTTPD_PORT}; my $config = make_config($proxy_port, $local_httpd || $httpd_port); my $proxy_pid = start_child('server', \&proxy, $config, @ARGV); my $httpd_pid = start_child('server', \&TestHTTPD::httpd, port => $httpd_port) unless $local_httpd; # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $proxy_port); start_child('dump-connections-worker', \&dump_connections_worker, $proxy_pid); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid unless $local_httpd; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/hostname_test000077500000000000000000000042341340212110200173420ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; sub make_wildcard_config($) { my $proxy_port = shift; my ($fh, $filename) = File::Temp::tempfile(); # Write out a test config file print $fh <> 8) { exit $? >> 8; } } # Success exit 0; } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $workers = $ENV{WORKERS} || 3; my $iterations = $ENV{ITERATIONS} || 3; my $config = make_wildcard_config($proxy_port); my $proxy_pid = start_child('server', \&proxy, $config, @ARGV); # Wait for proxy to load and parse config wait_for_port(port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/http_test.c000066400000000000000000000036651340212110200167300ustar00rootroot00000000000000#include #include #include #include #include "http.h" static const char *good[] = { "GET / HTTP/1.1\r\n" "User-Agent: curl/7.21.0 (x86_64-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18\r\n" "Host: localhost\r\n" "Accept: */*\r\n" "\r\n", "GET / HTTP/1.1\r\n" "User-Agent: curl/7.21.0 (x86_64-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18\r\n" "HOST:\t localhost\r\n" "Accept: */*\r\n" "\r\n", "GET / HTTP/1.1\r\n" "User-Agent: curl/7.21.0 (x86_64-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18\r\n" "HOST:\t localhost:8080\r\n" "Accept: */*\r\n" "\r\n", }; static const char *bad[] = { "GET / HTTP/1.0\r\n" "\r\n", "", "G", "GET ", "GET / HTTP/1.0\n" "\n", "GET / HTTP/1.1\r\n" "User-Agent: curl/7.21.0 (x86_64-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18\r\n" "Hostname: localhost\r\n" "Accept: */*\r\n" "\r\n", "GET / HTTP/1.1\r\n" "User-Agent: curl/7.21.0 (x86_64-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18\r\n" "Accept: */*\r\n" "\r\n", }; int main() { unsigned int i; int result; char *hostname; for (i = 0; i < sizeof(good) / sizeof(const char *); i++) { hostname = NULL; result = http_protocol->parse_packet(good[i], strlen(good[i]), &hostname); assert(result == 9); assert(NULL != hostname); assert(0 == strcmp("localhost", hostname)); free(hostname); } for (i = 0; i < sizeof(bad) / sizeof(const char *); i++) { hostname = NULL; result = http_protocol->parse_packet(bad[i], strlen(bad[i]), &hostname); assert(result < 0); assert(hostname == NULL); } return 0; } sniproxy-0.6.0/tests/ipv6_v6only_test000077500000000000000000000053501340212110200177250ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub worker($$$$) { my ($hostname, $path, $port, $requests) = @_; for (my $i = 0; $i < $requests; $i++) { system('curl', '-s', '-S', '-H', "Host: $hostname", '-o', '/dev/null', "http://localhost:$port/$path"); if ($? == -1) { die "failed to execute: $!\n"; } elsif ($? & 127) { printf STDERR "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without'; exit 255; } elsif ($? >> 8) { exit $? >> 8; } } # Success exit 0; } sub make_reuseport_config($$) { my $proxy_port = shift; my $httpd_port = shift; my ($fh, $filename) = File::Temp::tempfile(); my ($unused, $logfile) = File::Temp::tempfile(); # Write out a test config file print $fh < $httpd_port) unless $local_httpd; # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid unless $local_httpd; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/proxy_header_test000077500000000000000000000067731340212110200202270ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; use IO::Socket::INET; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub make_proxy_config($$$) { my $proxy_port = shift; my $httpd_port = shift; my $httpd2_port = shift; my ($fh, $filename) = File::Temp::tempfile(); my ($unused, $logfile) = File::Temp::tempfile(); # Write out a test config file print $fh <> 8) { exit $? >> 8; } } # Success exit 0; } sub valid_port($) { my $port = shift; return $port eq int($port) && $port > 0 && $port <= 65535; } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $httpd_port = $ENV{TEST_HTTPD_PORT} || 8081; my $httpd2_port = $ENV{TEST_HTTPD_PORT2} || 8082; my $workers = $ENV{WORKERS} || 10; my $iterations = $ENV{ITERATIONS} || 10; my $local_httpd = $ENV{LOCAL_HTTPD_PORT}; my $config = make_proxy_config($proxy_port, $local_httpd || $httpd_port, $httpd2_port); my $proxy_pid = start_child('server', \&proxy, $config, @ARGV); my $httpd_pid = start_child('server', \&TestHTTPD::httpd, port => $httpd_port) unless $local_httpd; my $httpd2_pid = start_child('server', \&TestHTTPD::httpd, port => $httpd2_port, parser => sub { my $sock = shift; my $status = 500; for (my $i = 0; my $line = $sock->getline(); $i++) { if ($i == 0 && $line =~ m/\APROXY (\S+) (\S+) (\S+) (\S+) (\S+)\r\n\z/ && $1 eq 'TCP4' && $2 eq '127.0.0.1' && $3 eq '127.0.0.1' && valid_port($4) && valid_port($5)) { $status = 200; } # Wait for blank line indicating the end of the request last if $line eq "\r\n"; } return $status; }); # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $httpd2_port); wait_for_port(port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { my $req_hostname = ($i % 2) ? 'proxy-protocol.local' : 'localhost'; start_child('worker', \&worker, $req_hostname, '', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid unless $local_httpd; kill 15, $httpd2_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/reload_test000077500000000000000000000104431340212110200167710ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub worker($$$$) { my ($hostname, $path, $port, $requests) = @_; for (my $i = 0; $i < $requests; $i++) { system('curl', '-s', '-S', '-H', "Host: $hostname", '-o', '/dev/null', "http://localhost:$port/$path"); if ($? == -1) { die "failed to execute: $!\n"; } elsif ($? & 127) { printf STDERR "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without'; exit 255; } elsif ($? >> 8) { exit $? >> 8; } } # Success exit 0; } sub make_config($$$) { my $proxy_port1 = shift; my $proxy_port2 = shift; my $httpd_port = shift; my ($fh, $filename) = File::Temp::tempfile(); my ($unused, $logfile) = File::Temp::tempfile(); chmod(0644, $filename); chmod(0666, $logfile); # Write out a test config file print $fh <', $filename) or die("open(): $!"); # Write out a test config file print $fh < $httpd_port1); # Wait for proxy to load and parse config wait_for_port(port => $httpd_port1); wait_for_port(port => $proxy_port1); wait_for_port(port => $proxy_port2); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port1, $iterations); } for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port2, $iterations); } # Wait for all our children to finish wait_for_type('worker'); kill 15, $httpd_pid; # edit config alter_config($config, $proxy_port2, $proxy_port3, $httpd_port2); kill 1, $proxy_pid; $httpd_pid = start_child('server', \&TestHTTPD::httpd, port => $httpd_port2); wait_for_port(port => $httpd_port2); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port2, $iterations); } for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port3, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/resolv_test.c000066400000000000000000000025201340212110200172500ustar00rootroot00000000000000#include #include #include #include #include "resolv.h" #include "address.h" static int query_count = 0; static void query_cb(struct Address *result, void *data) { int *query_count = (int *)data; char ip_buf[INET6_ADDRSTRLEN]; if (result != NULL && address_is_sockaddr(result) && display_address(result, ip_buf, sizeof(ip_buf))) { fprintf(stderr, "query resolved to %s\n", ip_buf); query_count++; } } static void test_init_cb(struct ev_loop *loop __attribute__((unused)), struct ev_timer *w __attribute__((unused)), int revents) { if (revents & EV_TIMER) resolv_query("localhost", RESOLV_MODE_DEFAULT, query_cb, NULL, &query_count); } static void timeout_cb(struct ev_loop *loop, struct ev_timer *w __attribute__((unused)), int revents) { if (revents & EV_TIMER) ev_break(loop, EVBREAK_ALL); } int main() { struct ev_loop *loop = EV_DEFAULT; struct ev_timer timeout_watcher; struct ev_timer init_watcher; resolv_init(loop, NULL, NULL, 0); ev_timer_init(&init_watcher, &test_init_cb, 0.0, 0.0); ev_timer_start(loop, &init_watcher); ev_timer_init(&timeout_watcher, &timeout_cb, 5.0, 0.0); ev_timer_start(loop, &timeout_watcher); ev_run(loop, 0); resolv_shutdown(loop); return 0; } sniproxy-0.6.0/tests/reuseport_test000077500000000000000000000054071340212110200175570ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub worker($$$$) { my ($hostname, $path, $port, $requests) = @_; for (my $i = 0; $i < $requests; $i++) { system('curl', '-s', '-S', '-H', "Host: $hostname", '-o', '/dev/null', "http://localhost:$port/$path"); if ($? == -1) { die "failed to execute: $!\n"; } elsif ($? & 127) { printf STDERR "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without'; exit 255; } elsif ($? >> 8) { exit $? >> 8; } } # Success exit 0; } sub make_reuseport_config($$) { my $proxy_port = shift; my $httpd_port = shift; my ($fh, $filename) = File::Temp::tempfile(); my ($unused, $logfile) = File::Temp::tempfile(); # Write out a test config file print $fh < $httpd_port) unless $local_httpd; # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy1_pid; kill 15, $proxy2_pid; kill 15, $httpd_pid unless $local_httpd; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/slow_client_test000077500000000000000000000046211340212110200200460ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; use IO::Socket::INET; use Time::HiRes; sub proxy { my $config = shift; exec(@_, '../src/sniproxy', '-f', '-c', $config); } sub slow_client($$) { my $port = shift; my $requests = shift; my $request = "GET / HTTP/1.1\r\n" . "UserAgent: slow_client/0.1\r\n" . "Host: localhost:$port\r\n" . "Accept: */*\r\n" . "\r\n"; local $SIG{ALRM} = sub { die "alarm\n" }; alarm 10; my $socket = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => $port, Proto => "tcp", Type => SOCK_STREAM) or die "couldn't connect $!"; $socket->send($request); foreach (split("\r\n", $request)) { $socket->send("$_\r\n"); sleep(1); } my $buffer; $socket->recv($buffer, 4096); $socket->close(); die("Unexpected response: $buffer") unless ($buffer =~ /\AHTTP\/1\.1 200 OK/); exit(0); } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $httpd_port = $ENV{TEST_HTTPD_PORT} || 8081; my $workers = $ENV{WORKERS} || 3; my $iterations = $ENV{ITERATIONS} || 3; my $local_httpd = $ENV{LOCAL_HTTPD_PORT}; my $config = make_config($proxy_port, $local_httpd || $httpd_port); my $proxy_pid = start_child('server', \&proxy, $config, @ARGV); my $httpd_pid = start_child('server', \&TestHTTPD::httpd, port => $httpd_port) unless $local_httpd; # Wait for proxy to load and parse config wait_for_port(port => $httpd_port); wait_for_port(port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&slow_client, $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid unless $local_httpd; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main(); sniproxy-0.6.0/tests/table_test.c000066400000000000000000000115621340212110200170330ustar00rootroot00000000000000#include #include #include #include #include "table.h" #include "backend.h" static void test_empty_table(); static void test_single_entry_table(); static void append_entry(struct Table *, const char *, const char *); static void add_new_table(struct Table_head *, const char *, const char **); static void test_add_table(); static void test_tables_reload(); static int count_tables(const struct Table_head *); int main() { test_empty_table(); test_single_entry_table(); test_add_table(); test_tables_reload(); } static void test_empty_table() { struct Table *table = new_table(); assert(table != NULL); table_ref_get(table); const char *name = "table_name"; accept_table_arg(table, name); assert(table->name != NULL); assert(strcmp(name, table->name) == 0); assert(name != table->name); const char *server_query = "example.com"; struct LookupResult result = table_lookup_server_address(table, server_query, strlen(server_query)); assert(result.address == NULL); table_ref_put(table); } static void append_entry(struct Table *table, const char *pattern, const char *address) { struct Backend *backend = new_backend(); assert(backend != NULL); accept_backend_arg(backend, pattern); accept_backend_arg(backend, address); assert(strcmp(pattern, backend->pattern) == 0); assert(pattern != backend->pattern); add_backend(&table->backends, backend); } static void test_single_entry_table() { struct Table *table = new_table(); assert(table != NULL); table_ref_get(table); const char *name = "table_name"; accept_table_arg(table, name); assert(table->name != NULL); assert(strcmp(name, table->name) == 0); assert(name != table->name); append_entry(table, "^example\\.com$", "192.0.2.10"); init_table(table); const char *server_query = "example.com"; struct LookupResult result = table_lookup_server_address(table, server_query, strlen(server_query)); assert(result.address != NULL); table_ref_put(table); } static void add_new_table(struct Table_head *tables, const char *name, const char **entries) { struct Table *table = new_table(); assert(table != NULL); accept_table_arg(table, name); assert(table->name != NULL); assert(table->name != name); assert(strcmp(table->name, name) == 0); while (entries != NULL && entries[0] != NULL && entries[1] != NULL) { append_entry(table, entries[0], entries[1]); entries += 2; } add_table(tables, table); } static int count_tables(const struct Table_head *tables) { struct Table *iter; int count = 0; SLIST_FOREACH(iter, tables, entries) count++; return count; } static void test_add_table() { struct Table_head tables = SLIST_HEAD_INITIALIZER(); add_new_table(&tables, "foo", (const char *[]){ "^example\\.com$", "192.0.2.10", "^.*$", "*", NULL}); add_new_table(&tables, "bar", (const char *[]){ "^example\\.net$", "192.0.2.11", NULL}); add_new_table(&tables, "baz", (const char *[]){ "^example\\.org$", "192.0.2.12", NULL}); struct Table *result = table_lookup(&tables, "baz"); assert(result != NULL); assert(strcmp("baz", result->name) == 0); result = table_lookup(&tables, "foo"); assert(result != NULL); assert(strcmp("foo", result->name) == 0); result = table_lookup(&tables, "bar"); assert(result != NULL); assert(strcmp("bar", result->name) == 0); result = table_lookup(&tables, "qvf"); assert(result == NULL); assert(count_tables(&tables) == 3); free_tables(&tables); } static void test_tables_reload() { struct Table_head existing = SLIST_HEAD_INITIALIZER(); struct Table_head new = SLIST_HEAD_INITIALIZER(); struct Table *table = NULL; add_new_table(&existing, "foo", (const char *[]){ "^example\\.com$", "192.0.2.10", "^.*$", "*", NULL}); add_new_table(&existing, "bar", (const char *[]){ "^example\\.net$", "192.0.2.11", NULL}); add_new_table(&new, "baz", (const char *[]){ "^example\\.org$", "192.0.2.12", NULL}); add_new_table(&new, "foo", (const char *[]){ "^.*$", "*", NULL}); struct Table *bar = table_lookup(&existing, "bar"); assert(bar != NULL); table_ref_get(bar); reload_tables(&existing, &new); assert(count_tables(&new) == 0); free_tables(&new); assert(count_tables(&existing) == 2); table = table_lookup(&existing, "baz"); assert(table != NULL); assert(strcmp("baz", table->name) == 0); table = table_lookup(&existing, "foo"); assert(table != NULL); assert(strcmp("foo", table->name) == 0); free_tables(&existing); table_ref_put(bar); } sniproxy-0.6.0/tests/tls_test.c000066400000000000000000000363231340212110200165500ustar00rootroot00000000000000#include #include #include #include #include "tls.h" struct test_packet { const char *packet; size_t len; }; const unsigned char good_data_1[] = { // TLS record 0x16, // Content Type: Handshake 0x03, 0x01, // Version: TLS 1.0 0x00, 0x68, // Length // Handshake 0x01, // Handshake Type: Client Hello 0x00, 0x00, 0x64, // Length 0x03, 0x01, // Version: TLS 1.0 // Random 0x4e, 0x55, 0xde, 0x32, 0x80, 0x07, 0x92, 0x9f, 0x50, 0x41, 0xe4, 0xf9, 0x58, 0x32, 0xfc, 0x4f, 0x10, 0xb3, 0xde, 0x44, 0x4d, 0xa9, 0x67, 0x78, 0xea, 0xd1, 0x5f, 0x29, 0x09, 0x04, 0xc1, 0x06, 0x00, // Session ID Length 0x00, 0x28, // Cipher Suites Length 0x00, 0x39, 0x00, 0x38, 0x00, 0x35, 0x00, 0x16, 0x00, 0x13, 0x00, 0x0a, 0x00, 0x33, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x05, 0x00, 0x04, 0x00, 0x15, 0x00, 0x12, 0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0x02, // Compression Methods 0x01, 0x00, 0x00, 0x12, // Extensions Length 0x00, 0x00, // Extension Type: Server Name 0x00, 0x0e, // Length 0x00, 0x0c, // Server Name Indication Length 0x00, // Server Name Type: host_name 0x00, 0x09, // Length // "localhost" 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74 }; const unsigned char good_data_2[] = { // TLS record 0x16, // Content Type: Handshake 0x03, 0x01, // Version: TLS 1.0 0x00, 0x48, // Length // Handshake 0x01, // Handshake Type: Client Hello 0x00, 0x00, 0x42, // Length 0x03, 0x03, // Version: TLS 1.2 // Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, // Session ID Length 0x00, 0x04, // Cipher Suites Length 0x00, 0x01, // NULL-MD5 0x00, 0xff, // RENEGOTIATION INFO SCSV 0x01, // Compression Methods 0x00, // NULL 0x00, 0x17, // Extensions Length // Extension 0x00, 0x00, // Extension Type: Server Name 0x00, 0x0e, // Length 0x00, 0x0c, // Server Name Indication Length 0x00, // Server Name Type: host_name 0x00, 0x09, // Length // "localhost" 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, // Extension 0x00, 0x0f, // Extension Type: Heart Beat 0x00, 0x01, // Length 0x01 // Mode: Peer allows to send requests }; const unsigned char good_data_3[] = { // Testing different extension order // TLS record 0x16, // Content Type: Handshake 0x03, 0x01, // Version: TLS 1.0 0x00, 0x48, // Length // Handshake 0x01, // Handshake Type: Client Hello 0x00, 0x00, 0x42, // Length 0x03, 0x03, // Version: TLS 1.2 // Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, // Session ID Length 0x00, 0x04, // Cipher Suites Length 0x00, 0x01, // NULL-MD5 0x00, 0xff, // RENEGOTIATION INFO SCSV 0x01, // Compression Methods 0x00, // NULL 0x00, 0x17, // Extensions Length // Extension 0x00, 0x0f, // Extension Type: Heart Beat 0x00, 0x01, // Length 0x01, // Mode: Peer allows to send requests // Extension 0x00, 0x00, // Extension Type: Server Name 0x00, 0x0e, // Length 0x00, 0x0c, // Server Name Indication Length 0x00, // Server Name Type: host_name 0x00, 0x09, // Length // "localhost" 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74 }; const unsigned char good_data_4[] = { // TLS record 0x16, // Content Type: Handshake 0x03, 0x01, // Version: TLS 1.0 0x00, 0x47, // Length // Handshake 0x01, // Handshake Type: Client Hello 0x00, 0x00, 0x41, // Length 0x03, 0x03, // Version: TLS 1.2 // Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, // Session ID Length 0x00, 0x04, // Cipher Suites Length 0x00, 0x01, // NULL-MD5 0x00, 0xff, // RENEGOTIATION INFO SCSV 0x01, // Compression Methods 0x00, // NULL 0x00, 0x16, // Extensions Length // Extension 0x00, 0x00, // Extension Type: Server Name 0x00, 0x0e, // Length 0x00, 0x0c, // Server Name Indication Length 0x00, // Server Name Type: host_name 0x00, 0x09, // Length // "localhost" 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, // Extension 0x00, 0x23, // Extension Type: Session Ticket TLS 0x00, 0x00, // Length }; const unsigned char good_data_5[] = { // TLS record 0x16, // Content Type: Handshake 0x03, 0x01, // Version: TLS 1.0 0x00, 0xec, // Length 104 // Handshake 0x01, // Handshake Type: Client Hello 0x00, 0x00, 0xe8, // Length 100 0x03, 0x01, // Version: TLS 1.0 // Random 0x4e, 0x55, 0xde, 0x32, 0x80, 0x07, 0x92, 0x9f, 0x50, 0x41, 0xe4, 0xf9, 0x58, 0x32, 0xfc, 0x4f, 0x10, 0xb3, 0xde, 0x44, 0x4d, 0xa9, 0x67, 0x78, 0xea, 0xd1, 0x5f, 0x29, 0x09, 0x04, 0xc1, 0x06, 0x00, // Session ID Length 0x00, 0x28, // Cipher Suites Length 0x00, 0x39, 0x00, 0x38, 0x00, 0x35, 0x00, 0x16, 0x00, 0x13, 0x00, 0x0a, 0x00, 0x33, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x05, 0x00, 0x04, 0x00, 0x15, 0x00, 0x12, 0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0x02, // Compression Methods 0x01, 0x00, 0x00, 0x96, // Extensions Length 18 + 4 + 132 = 150 0x00, 0x15, // Extension Type: Padding 0x00, 0x80, // Length 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, // Extension Type: Server Name 0x00, 0x0e, // Length 0x00, 0x0c, // Server Name Indication Length 0x00, // Server Name Type: host_name 0x00, 0x09, // Length // "localhost" 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74 }; const unsigned char ssl30_request[] = { // TLS record 0x16, // Content Type: Handshake 0x03, 0x00, // Version: SSL 3.0 0x00, 0x7f, // Length // Handshake 0x01, // Handshake Type: Client Hello 0x00, 0x00, 0x7b, // Length 0x03, 0x00, // Version: SSL 3.0 // Random 0x53, 0x11, 0x25, 0xc2, 0x92, 0xd6, 0xca, 0xf1, 0x79, 0x90, 0xba, 0x38, 0x8f, 0xad, 0xc8, 0x13, 0xa3, 0x1b, 0x57, 0xd9, 0xf4, 0x3e, 0xd2, 0x8b, 0xb6, 0x5e, 0xe3, 0x12, 0xca, 0x81, 0x2f, 0xc5, 0x00, // Session ID Length 0x00, 0x54, // Cipher Suites Length 0xc0, 0x14, 0xc0, 0x0a, 0xc0, 0x22, 0xc0, 0x21, 0x00, 0x39, 0x00, 0x38, 0xc0, 0x0f, 0xc0, 0x05, 0x00, 0x35, 0xc0, 0x12, 0xc0, 0x08, 0xc0, 0x1c, 0xc0, 0x1b, 0x00, 0x16, 0x00, 0x13, 0xc0, 0x0d, 0xc0, 0x03, 0x00, 0x0a, 0xc0, 0x13, 0xc0, 0x09, 0xc0, 0x1f, 0xc0, 0x1e, 0x00, 0x33, 0x00, 0x32, 0xc0, 0x0e, 0xc0, 0x04, 0x00, 0x2f, 0xc0, 0x11, 0xc0, 0x07, 0xc0, 0x0c, 0xc0, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x15, 0x00, 0x12, 0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0x01, // Compression Methods 0x00 }; const unsigned char ssl20_client_hello[] = { 0x80, 0x67, // Length (leading bit set) 0x01, // Handshake Type: Client Hello 0x03, 0x01, // Version 0x00, 0x4e, // Cipher spec length 0x00, 0x00, // Session ID length 0x00, 0x10, // Challenge length // Cipher Suites 0x00, 0x00, 0x39, 0x00, 0x00, 0x38, 0x00, 0x00, 0x35, 0x00, 0x00, 0x16, 0x00, 0x00, 0x13, 0x00, 0x00, 0x0a, 0x07, 0x00, 0xc0, 0x00, 0x00, 0x33, 0x00, 0x00, 0x32, 0x00, 0x00, 0x2f, 0x03, 0x00, 0x80, 0x00, 0x00, 0x05, 0x00, 0x00, 0x04, 0x01, 0x00, 0x80, 0x00, 0x00, 0x15, 0x00, 0x00, 0x12, 0x00, 0x00, 0x09, 0x06, 0x00, 0x40, 0x00, 0x00, 0x14, 0x00, 0x00, 0x11, 0x00, 0x00, 0x08, 0x00, 0x00, 0x06, 0x04, 0x00, 0x80, 0x00, 0x00, 0x03, 0x02, 0x00, 0x80, 0x00, 0x00, 0xff, // Session ID 0x74, 0x15, 0xdc, 0x11, 0x0b, 0xcb, 0x2b, 0x03, 0x5d, 0xb1, 0x5a, 0x2f, 0xac, 0x72, 0x45, 0x2e }; const unsigned char bad_data_1[] = { 0x16, 0x03, 0x01, 0x00, 0x68, 0x01, 0x00, 0x00, 0x64, 0x03, 0x01, 0x4e, 0x4e, 0xbe, 0xc2, 0xa1, 0x21, 0xad, 0xbc, 0x28, 0x33, 0xca, 0xa1, 0xd6, 0x6e, 0x57, 0xb9, 0x1f, 0x8c, 0x19, 0x0e, 0x44, 0x16, 0x9e, 0x7d, 0x20, 0x35, 0x4b, 0x65, 0xb2, 0xc0, 0xd5, 0xa8, 0x00, 0x00, 0x28, 0x00, 0x39, 0x00, 0x38, 0x00, 0x35, 0x00, 0x16, 0x00, 0x13, 0x00, 0x0a, 0x00, 0x33, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x05, 0x00, 0x04, 0x00, 0x15, 0x00, 0x12, 0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0x02, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x00, 0x09, 0x6c, 0x6f, 0x64, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74 }; const unsigned char bad_data_2[] = { 0x16, 0x03, 0x01, 0x00, 0x68, 0x01, 0x00, 0x00, 0x64, 0x03, 0x01, 0x4e, 0x4e, 0xbe, 0xc2, 0xa1, 0x21, 0xad, 0xbc, 0x28, 0x33, 0xca, 0xa1, 0xd6, 0x6e, 0x57, 0xb9, 0x1f, 0x8c, 0x19, 0x0e, 0x44, 0x16, 0x9e, 0x7d, 0x20, 0x35, 0x4b, 0x65, 0xb2, 0xc0, 0xd5, 0xa8, 0x00, 0x00, 0x28, 0x00, 0x39, 0x00, 0x38, 0x00, 0x35, 0x00, 0x16, 0x00, 0x13, 0x00, 0x0a, 0x00, 0x33, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x05, 0x00, 0x04, 0x00, 0x15, 0x00, 0x12, 0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0x02, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0e, 0x00 }; const unsigned char bad_data_3[] = { 0x16, 0x03, 0x01, 0x00 }; const unsigned char bad_data_4[] = { // TLS record 0x16, // Content Type: Handshake 0x03, 0x01, // Version: TLS 1.0 0x00, 0x48, // Length // Handshake 0x01, // Handshake Type: Client Hello 0x00, 0x00, 0x42, // Length 0x03, 0x03, // Version: TLS 1.2 // Random 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, // Session ID Length 0x00, 0x04, // Cipher Suites Length 0x00, 0x01, // NULL-MD5 0x00, 0xff, // RENEGOTIATION INFO SCSV 0x01, // Compression Methods 0x00, // NULL 0x00, 0x17, // Extensions Length // Extension 0x00, 0x00, // Extension Type: Server Name 0x00, 0x0e, // Length 0x00, 0x0c, // Server Name Indication Length 0x00, // Server Name Type: host_name 0x00, 0x09, // Length // "localhost" 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, // Extension 0x00, 0x0f, // Extension Type: Heart Beat 0x00, 0x01, // Length 0x01 // Mode: Peer allows to send requests }; static struct test_packet good[] = { { (char *)good_data_1, sizeof(good_data_1) }, { (char *)good_data_2, sizeof(good_data_2) }, { (char *)good_data_3, sizeof(good_data_3) }, { (char *)good_data_4, sizeof(good_data_4) }, { (char *)good_data_5, sizeof(good_data_5) } }; static struct test_packet bad[] = { { (char *)ssl30_request, sizeof(ssl30_request) }, { (char *)ssl20_client_hello, sizeof(ssl20_client_hello) }, { (char *)bad_data_1, sizeof(bad_data_1) }, { (char *)bad_data_2, sizeof(bad_data_2) }, { (char *)bad_data_3, sizeof(bad_data_3) } }; int main() { unsigned int i; int result; char *hostname; for (i = 0; i < sizeof(good) / sizeof(struct test_packet); i++) { hostname = NULL; result = tls_protocol->parse_packet(good[i].packet, good[i].len, &hostname); assert(result == 9); assert(NULL != hostname); assert(0 == strcmp("localhost", hostname)); free(hostname); } result = tls_protocol->parse_packet(good[0].packet, good[0].len, NULL); assert(result == -3); for (i = 0; i < sizeof(bad) / sizeof(struct test_packet); i++) { hostname = NULL; result = tls_protocol->parse_packet(bad[i].packet, bad[i].len, &hostname); // parse failure or not "localhost" assert(result < 0 || hostname == NULL || strcmp("localhost", hostname) != 0); free(hostname); } return 0; } sniproxy-0.6.0/tests/transparent_proxy_test000077500000000000000000000225001340212110200213220ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; sub checked_system { my $result = system(@_); if ($result == -1) { die "@_ failed to execute: $!\n"; } elsif ($result & 127) { printf STDERR "@_ died with signal %d, %s coredump\n", ($result & 127), ($result & 128) ? 'with' : 'without'; exit 255; } elsif ($result >> 8) { exit $result >> 8; } } sub make_config($$) { my $proxy_port = shift; my $httpd_port = shift; my ($fh, $filename) = File::Temp::tempfile(); my ($unused, $logfile) = File::Temp::tempfile(); # Write out a test config file print $fh <> 8) { exit $? >> 8; } } # Success exit 0; } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $httpd_port = $ENV{TEST_HTTPD_PORT} || 8081; my $workers = $ENV{WORKERS} || 10; my $iterations = $ENV{ITERATIONS} || 10; my $config = make_config($proxy_port, $httpd_port); unless ($> == 0 and $^O eq 'linux') { print STDERR "This test requires Linux and root privileges\n"; exit 77; } # Setup a test network using network namespaces with the ns-test-proxy # namespace configured like a normal transparent proxy box. # # +-----------------------------------------------------------+ # | Host (default) Network Namespace | # | | # | Route 192.0.2.4/30 via 192.0.2.1 dev veth-srv-proxy | # | | # | TestHTTPD process o veth-srv-proxy | # | | 192.0.2.2/30 | # | +-----------------------------------|---------------+ | # | | SNIproxy Network Namespace | | | # | | ns-test-proxy o veth-proxy-srv| | # | | 192.0.2.1/30 | | # | | IP Forwarding enabled | | # | | IPTables and ip rules configured (see below) | | # | | | | # | | o veth-proxy-clt| | # | | sniproxy process | 192.0.2.5/30 | | # | | | | | # | +-----------------------------------|---------------+ | # | | | # | +-----------------------------------|---------------+ | # | | Client Network Namespace | veth-clt-proxy| | # | | ns-test-clt o 192.0.2.6/30 | | # | | | | # | | Route default via 192.0.2.5 dev veth-clt-proxy | | # | | | | # | | curl process | | # | | | | # | +---------------------------------------------------+ | # | | # +-----------------------------------------------------------+ # # IPTables NAT rules: # Chain POSTROUTING (policy ACCEPT) # target prot opt source destination # MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 # # IPTables Mangle rules: # Chain PREROUTING (policy ACCEPT) # target prot opt source destination # DIVERT tcp -- 0.0.0.0/0 0.0.0.0/0 socket # # Chain DIVERT (1 references) # target prot opt source destination # MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK set 0x1 # ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 # # IP rules: # 0: from all lookup local # 32765: from all fwmark 0x1 lookup 100 # 32766: from all lookup main # 32767: from all lookup default # # IP route table 100: # local default dev lo scope host checked_system('ip', 'netns', 'add', 'ns-test-proxy'); checked_system('ip', 'netns', 'add', 'ns-test-clt'); checked_system('ip', 'link', 'add', 'veth-proxy-srv', 'type', 'veth', 'peer', 'name', 'veth-srv-proxy'); checked_system('ip', 'link', 'set', 'veth-srv-proxy', 'up'); checked_system('ip', 'addr', 'add', '192.0.2.2/30', 'dev', 'veth-srv-proxy'); checked_system('ip', 'route', 'add', '192.0.2.4/30', 'via', '192.0.2.1', 'dev', 'veth-srv-proxy'); checked_system('ip', 'link', 'set', 'veth-proxy-srv', 'up', 'netns', 'ns-test-proxy'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'ip', 'addr', 'add', '192.0.2.1/30', 'dev', 'veth-proxy-srv'); checked_system('ip', 'link', 'add', 'veth-proxy-clt', 'type', 'veth', 'peer', 'name', 'veth-clt-proxy'); checked_system('ip', 'link', 'set', 'veth-proxy-clt', 'up', 'netns', 'ns-test-proxy'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'ip', 'addr', 'add', '192.0.2.5/30', 'dev', 'veth-proxy-clt'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'sysctl', 'net.ipv4.conf.all.forwarding=1'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'iptables', '-t', 'nat', '-A', 'POSTROUTING', '-o', 'veth-proxy-clt', '-j', 'MASQUERADE'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'iptables', '-t', 'mangle', '-N', 'DIVERT'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'iptables', '-t', 'mangle', '-A', 'PREROUTING', '-p', 'tcp', '-m', 'socket', '-j', 'DIVERT'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'iptables', '-t', 'mangle', '-A', 'DIVERT', '-j', 'MARK', '--set-mark', '1'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'iptables', '-t', 'mangle', '-A', 'DIVERT', '-j', 'ACCEPT'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'ip', 'rule', 'add', 'fwmark', '1', 'lookup', '100'); checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'ip', 'route', 'add', 'local', '0.0.0.0/0', 'dev', 'lo', 'table', '100'); #checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'iptables-save'); #checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'ip', '-d', 'addr', 'show'); #checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'ip', '-d', 'route', 'show'); checked_system('ip', 'link', 'set', 'veth-clt-proxy', 'up', 'netns', 'ns-test-clt'); checked_system('ip', 'netns', 'exec', 'ns-test-clt', 'ip', 'addr', 'add', '192.0.2.6/30', 'dev', 'veth-clt-proxy'); #checked_system('ip', 'netns', 'exec', 'ns-test-clt', 'ip', '-d', 'addr', 'show'); #checked_system('ip', 'netns', 'exec', 'ns-test-clt', 'ip', '-d', 'route', 'show'); my $proxy_pid = start_child('proxy', \&proxy, $config, @ARGV); my $httpd_pid = start_child('server', \&TestHTTPD::httpd, ip => '192.0.2.2', port => $httpd_port); my $tcpdump1_pid = start_child('tcpdump', \&exec_cmd, 'ip', 'netns', 'exec', 'ns-test-proxy', 'tcpdump', '-npi' ,'veth-proxy-clt', '-s', '0', '-w', 'veth-proxy-clt.pcap'); my $tcpdump2_pid = start_child('tcpdump', \&exec_cmd, 'ip', 'netns', 'exec', 'ns-test-proxy', 'tcpdump', '-npi' ,'veth-proxy-srv', '-s', '0', '-w', 'veth-proxy-srv.pcap'); #checked_system('ip', 'netns', 'exec', 'ns-test-proxy', 'ss', '-lptn'); # Wait for proxy to load and parse config wait_for_port(ip => '192.0.2.2', port => $httpd_port); wait_for_port(ip => '192.0.2.5', port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, 'localhost', '', '192.0.2.5', $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # Orderly shutdown of the server kill 15, $proxy_pid; kill 15, $httpd_pid; kill 15, $tcpdump1_pid; kill 15, $tcpdump2_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); # Cleanup our network name spaces checked_system('ip', 'netns', 'delete', 'ns-test-clt'); checked_system('ip', 'netns', 'delete', 'ns-test-proxy'); } main(); sniproxy-0.6.0/tests/wildcard_test000077500000000000000000000044751340212110200173240ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Basename; use lib dirname (__FILE__); use TestUtils; use TestHTTPD; use File::Temp; my $test_domains = [ 'example.com', 'example.net' ]; sub make_wildcard_config($) { my $proxy_port = shift; my ($fh, $filename) = File::Temp::tempfile(); # Write out a test config file print $fh <[$i % scalar(@$test_domains)]; system('curl', '-s', '-S', '-H', "Host: $hostname", '-o', '/dev/null', "http://localhost:$port/"); if ($? == -1) { die "failed to execute: $!\n"; } elsif ($? & 127) { printf STDERR "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without'; exit 255; } elsif ($? >> 8) { exit $? >> 8; } } # Success exit 0; } sub main { my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080; my $workers = $ENV{WORKERS} || 3; my $iterations = $ENV{ITERATIONS} || 3; my $config = make_wildcard_config($proxy_port); my $proxy_pid = start_child('server', \&proxy, $config, @ARGV); # Wait for proxy to load and parse config wait_for_port(port => $proxy_port); for (my $i = 0; $i < $workers; $i++) { start_child('worker', \&worker, $proxy_port, $iterations); } # Wait for all our children to finish wait_for_type('worker'); # Give the proxy a second to flush buffers and close server connections sleep 1; # For troubleshooting connections stuck in CLOSE_WAIT state #kill 10, $proxy_pid; #system("netstat -ptn | grep $proxy_pid\/sniproxy"); # For troubleshooting 100% CPU usage #system("top -n 1 -p $proxy_pid -b"); # Orderly shutdown of the server kill 15, $proxy_pid; sleep 1; # Delete our test configuration unlink($config); # Kill off any remaining children reap_children(); } main();