diff --git a/.github/ISSUE_TEMPLATE/new-issue--bug-report--question.md b/.github/ISSUE_TEMPLATE/new-issue--bug-report--question.md new file mode 100644 index 00000000..32d49956 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-issue--bug-report--question.md @@ -0,0 +1,20 @@ +--- +name: New Issue, Bug report, Question +about: New Issue, Bug report, Question +title: '' +labels: '' +assignees: '' + +--- + +# IMPORTANT NOTICE + +Before filing an issue here PLEASE keep in mind that **tinyproxy 1.10.0 and older are no longer supported**. +Do not report issues with 1.10.0 or older, first try latest release 1.11.0, or even better, git master, and see whether the issue is already fixed. + +## Tinyproxy version +State the tinyproxy version you're using; whether git master or 1.11.0 stable. + +## Issue +Fill in your Issue text here. +A good issue report is detailed and includes full error messages from tinyproxy's output, not "X doesn't work". diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..067105da --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: ./autogen.sh + - run: ./configure + - run: make + - run: make test + - run: cd docs/web ; make + test-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: brew install automake + - run: ./autogen.sh + - run: ./configure + - run: make + valgrind-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: sudo apt update + - run: sudo apt install --assume-yes valgrind + - run: ./autogen.sh + - run: ./configure --enable-debug --enable-transparent --enable-reverse + - run: make + - run: make test + - run: make valgrind-test diff --git a/.github/workflows/release_tarball.yml b/.github/workflows/release_tarball.yml new file mode 100644 index 00000000..7999f179 --- /dev/null +++ b/.github/workflows/release_tarball.yml @@ -0,0 +1,40 @@ +name: Generate Source Tarball + +# Trigger whenever a release is created +on: + release: + types: + - created + +jobs: + build: + name: build + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: archive + id: archive + run: | + sudo apt install -y gperf + rm -rf .git + autoreconf -i + VERSION=$(cat VERSION) + PKGNAME="tinyproxy-$VERSION" + ./configure + make dist + echo "tarball_xz=${PKGNAME}.tar.xz" >> "$GITHUB_OUTPUT" + echo "tarball_gz=${PKGNAME}.tar.gz" >> "$GITHUB_OUTPUT" + echo "tarball_bz2=${PKGNAME}.tar.bz2" >> "$GITHUB_OUTPUT" + + - name: upload tarballs + uses: softprops/action-gh-release@v2 + with: + files: | + ${{ steps.archive.outputs.tarball_xz }} + ${{ steps.archive.outputs.tarball_gz }} + ${{ steps.archive.outputs.tarball_bz2 }} + diff --git a/.github/workflows/shellcheck.yaml b/.github/workflows/shellcheck.yaml new file mode 100644 index 00000000..7e5edc7b --- /dev/null +++ b/.github/workflows/shellcheck.yaml @@ -0,0 +1,33 @@ +name: shellcheck +on: + push: + branches: + - master + pull_request: + paths-ignore: + branches: + - master + +# cancel the in-progress workflow when PR is refreshed. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + shellcheck: + name: Shellcheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: install shellcheck + run: | + sudo apt-get update + sudo apt-get install -y shellcheck + - name: Run autogen + run: ./autogen.sh + - name: Run ShellCheck + run: make shellcheck + diff --git a/.travis.yml b/.travis.yml index c38a1d21..47c3b8d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,15 +4,15 @@ sudo: true before_install: - sudo apt-get update -qq -- sudo apt-get install --assume-yes asciidoc valgrind +- sudo apt-get install --assume-yes valgrind script: - ./autogen.sh - ./configure - make - make test +- make clean - ./configure --enable-debug --enable-transparent --enable-reverse - make - make test - make valgrind-test -- make distcheck diff --git a/AUTHORS b/AUTHORS index 9c82e402..d0e6b303 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,43 +1,39 @@ -Tinyproxy AUTHORS -================= - -//// -This file is generated from authors.xml, do not edit it directly. -//// - -Coding ------- - -The following people have contributed code to Tinyproxy: - - * Andrew Stribblehill - * Chris Lightfoot - * Daniel Egger - * David Shanks - * Dmitry Semyonov - * George Talusan - * James E. Flemer - * Jeremy Hinegardner - * John van der Kamp - * Jordi Mallach - * Kim Holviala - * Mathew Mrosko - * Matthew Dempsky - * Michael Adam - * Moritz Muehlenhoff - * Mukund Sivaraman - * Petr Lampa - * Robert James Kaes - * Steven Young - - -Documentation -------------- - -The following people have helped to document Tinyproxy: - - * Marc Silver - * Michael Adam - * Mukund Sivaraman - * Robert James Kaes - * Steven Young +Andrew Stribblehill +bertliao +Bob Showalter +Brian Cain +cvs2svn +Daniel Egger +Daniel M. Drucker +David Shanks +Dmitry Semyonov +dmz-uk +Drew G. Wallace +Frank Morgner +gary-wzl77 +Gaudenz Steinlin +goba62000374 +Gonzalo Tornaria +Greg +Jeremy Hinegardner +John Spencer +John van der Kamp +John Weldon +Jordi +Jordi Mallach +Julien Hartmann +kikuchan +Mathew Mrosko +Matthew Dempsky +Michael Adam +Mike Mead +Mukund Sivaraman +Pablo Panero +Peter H. Froehlich +Robert James Kaes +rofl0r +Stephan Leemburg +Steven Conaway +Steven Young +Valen Blanco +Vladimir Belov diff --git a/Makefile.am b/Makefile.am index 9d62d2cc..f721d58e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,27 +4,13 @@ SUBDIRS = \ etc \ docs \ m4macros \ - tests + tests \ + scripts # tools want this on a single line ACLOCAL_AMFLAGS = -I m4macros -AUTHORS: authors.xml authors.xsl -if HAVE_XSLTPROC - $(AM_V_GEN) $(XSLTPROC) authors.xsl $< > $(@) || rm -f $(@) -else - @echo "*** xsltproc is required to regenerate $(@) ***"; exit 1; -endif - -validate-authors: -if HAVE_XMLLINT - @$(XMLLINT) --noout --path $(srcdir) --valid authors.xml || \ - ( echo "*** authors.xml IS INVALID ***"; exit 1; ) -endif - -all-local: AUTHORS - -check-local: validate-authors +all-local: dist_doc_DATA = \ AUTHORS \ @@ -33,16 +19,18 @@ dist_doc_DATA = \ README.md EXTRA_DIST = \ - authors.dtd \ - authors.xml \ - authors.xsl \ autogen.sh \ tinyproxy-indent.sh \ - TODO + TODO \ + VERSION test: all ./tests/scripts/run_tests.sh +.PHONY: shellcheck +shellcheck: + @shellcheck `find . -name '*.sh'` + test-wait: TINYPROXY_TESTS_WAIT=yes $(MAKE) test diff --git a/NEWS b/NEWS index 550e7fe0..c0fddc8c 100644 --- a/NEWS +++ b/NEWS @@ -1,12 +1 @@ -Tinyproxy NEWS -============== - -Version 1.9.0 -------------- - -Bugs fixed -~~~~~~~~~~ - -Contributors -~~~~~~~~~~~~ - +See git log for recent changes in Tinyproxy. diff --git a/README.md b/README.md index f13e670c..83eb99be 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ Enable support for proxying connections through another proxy server. - `--enable-transparent`: Allow Tinyproxy to be used as a transparent proxy daemon. +Unlike other work modes, transparent proxying doesn't require explicit +configuration and works automatically when traffic is redirected to +the proxy using the appropriate firewall rules. - `--enable-reverse`: Enable reverse proxying. @@ -86,4 +89,4 @@ and create a [pull request](https://github.com/tinyproxy/tinyproxy/pulls). You can meet developers and users to discuss development, patches and deployment issues in the `#tinyproxy` IRC channel on -Freenode (`irc.freenode.net`). +libera (`irc.libera.chat`). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..93ef8148 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,28 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| --------- | ------------------ | +| 1.11.x | :white_check_mark: | +| <= 1.10.x | :x: | + +## Reporting a Vulnerability + +Open a public issue on github. The issue will most likely be fixed +within a day, unless all maintainers happen to just be taking a +vacation at the same time, which is unlikely. + +Even then, having the bug publicly known will allow competent people +to come up with custom patches for distros, most likely quicker +than black hats can craft a remote execution exploit. + +If you really really do not want to make the issue public, come +to the tinyproxy IRC channel and ask for a maintainer, which you +can then contact via private messages. + +Do not, however, like ["TALOS Intelligence"](https://talosintelligence.com/vulnerability_reports/TALOS-2023-1889) +pull a random email address out of git log, then send an email +nobody reads or responds to, and wait for 6 months for publication. +this only gives black hats plenty time to sell, use and circulate +zero days and get the best possible ROI. diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..0a5af26d --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.11.3 diff --git a/authors.dtd b/authors.dtd deleted file mode 100644 index 77d43c53..00000000 --- a/authors.dtd +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/authors.xml b/authors.xml deleted file mode 100644 index 7fc94f2a..00000000 --- a/authors.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - Andrew Stribblehill - Chris Lightfoot - Daniel Egger - David Shanks - Dmitry Semyonov - George Talusan - James E. Flemer - Jeremy Hinegardner - John van der Kamp - Jordi Mallach - Kim Holviala - Marc Silver - Mathew Mrosko - Matthew Dempsky - Michael Adam - Moritz Muehlenhoff - Mukund Sivaraman - Petr Lampa - Robert James Kaes - Steven Young - - diff --git a/authors.xsl b/authors.xsl deleted file mode 100644 index 77ba4e0f..00000000 --- a/authors.xsl +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - Tinyproxy AUTHORS -================= - -//// -This file is generated from authors.xml, do not edit it directly. -//// - -Coding ------- - -The following people have contributed code to Tinyproxy: - - - - - - - -Documentation -------------- - -The following people have helped to document Tinyproxy: - - - - - - - - - - * - - - - diff --git a/autogen.sh b/autogen.sh index fb6da78b..b3011ccd 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,24 +1,31 @@ #!/bin/sh -srcdir=`dirname $0` +srcdir=$(dirname "$0") test -z "$srcdir" && srcdir=. -ORIGDIR=`pwd` +ORIGDIR=$(pwd) set -x -cd $srcdir +cd "$srcdir" || { + echo "error changing to dir '$srcdir'" + exit +} aclocal -I m4macros \ && autoheader \ && automake --gnu --add-missing \ && autoconf -cd $ORIGDIR +cd "$ORIGDIR" || { + echo "error changing to idir '$ORIGDIR'" + exit + +} set - -echo $srcdir/configure "$@" -$srcdir/configure "$@" +echo "$srcdir"/configure "$@" +"$srcdir"/configure "$@" RC=$? if test $RC -ne 0; then echo diff --git a/configure.ac b/configure.ac index c3d4ac6d..329c5633 100644 --- a/configure.ac +++ b/configure.ac @@ -3,25 +3,17 @@ AC_PREREQ(2.54) -m4_define([tinyproxy_major_version], [1]) -m4_define([tinyproxy_minor_version], [9]) -m4_define([tinyproxy_micro_version], [0]) -m4_define([tinyproxy_real_version], - [tinyproxy_major_version.tinyproxy_minor_version.tinyproxy_micro_version]) -m4_define([tinyproxy_version], [tinyproxy_real_version]) - -# For overriding the version string. Comment out if not needed. -# m4_define([tinyproxy_version], [1.9.0]) - -m4_define([tinyproxy_unstable], - m4_if(m4_eval(tinyproxy_minor_version % 2), [1], [yes], [no])) -m4_define([tinyproxy_stable], - m4_if(m4_eval(tinyproxy_minor_version % 2), [0], [yes], [no])) +m4_define([tinyproxy_version], esyscmd(sh scripts/version.sh | tr -d '\n')) AC_INIT([Tinyproxy], [tinyproxy_version], [https://tinyproxy.github.io/], [tinyproxy]) +tpv=tinyproxy_version +if test "x$tpv" = "x" ; then +AC_MSG_ERROR([got empty result from version script!]) +fi + AC_CANONICAL_TARGET AM_INIT_AUTOMAKE([dist-bzip2 dist-xz]) AC_CONFIG_HEADERS(config.h) @@ -29,31 +21,9 @@ AC_CONFIG_MACRO_DIR([m4macros]) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) -TINYPROXY_MAJOR_VERSION=tinyproxy_major_version -TINYPROXY_MINOR_VERSION=tinyproxy_minor_version -TINYPROXY_MICRO_VERSION=tinyproxy_micro_version -TINYPROXY_REAL_VERSION=tinyproxy_real_version -TINYPROXY_VERSION=tinyproxy_version -TINYPROXY_UNSTABLE=tinyproxy_unstable -AC_SUBST(TINYPROXY_MAJOR_VERSION) -AC_SUBST(TINYPROXY_MINOR_VERSION) -AC_SUBST(TINYPROXY_MICRO_VERSION) -AC_SUBST(TINYPROXY_REAL_VERSION) -AC_SUBST(TINYPROXY_VERSION) -AC_SUBST(TINYPROXY_UNSTABLE) - dnl Temporarily defined here until we get tinyproxy-version.h AC_DEFINE(TINYPROXY_VERSION, "tinyproxy_version", [Tinyproxy version number]) -# The symbol TINYPROXY_UNSTABLE is defined above for substitution in -# Makefiles and conditionally defined here as a preprocessor symbol -# and automake conditional. -if test "x$TINYPROXY_UNSTABLE" = "xyes"; then - AC_DEFINE(TINYPROXY_UNSTABLE, 1, - [Define to 1 if this is an unstable version of Tinyproxy]) -fi -AM_CONDITIONAL(TINYPROXY_UNSTABLE, test "x$TINYPROXY_UNSTABLE" = "xyes") - dnl Check if we're compiling on a weird platform :) AC_USE_SYSTEM_EXTENSIONS @@ -124,13 +94,22 @@ dnl Include the transparent proxy support AH_TEMPLATE([TRANSPARENT_PROXY], [Include support for using tinyproxy as a transparent proxy.]) TP_ARG_ENABLE(transparent, - [Enable transparent proxying code (default is NO)], - no) + [Enable transparent proxying code (default is YES)], + yes) if test x"$transparent_enabled" = x"yes"; then ADDITIONAL_OBJECTS="$ADDITIONAL_OBJECTS transparent-proxy.o" AC_DEFINE(TRANSPARENT_PROXY) fi +dnl Let user decide whether he wants support for manpages +dnl Which require either pod2man or a tarball release +AH_TEMPLATE([MANPAGE_SUPPORT], + [Build manpages with pod2man if they are missing from the distribution.]) +TP_ARG_ENABLE(manpage_support, + [Enable support for building manpages (default is YES)], + yes) +AM_CONDITIONAL(HAVE_MANPAGE_INTEREST, test x"$manpage_support_enabled" = x"yes") + # This is required to build test programs below AC_PROG_CC @@ -162,74 +141,77 @@ AC_HEADER_STDC AC_HEADER_TIME AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([sys/ioctl.h alloca.h memory.h malloc.h sysexits.h \ - values.h]) + values.h poll.h]) dnl Checks for libary functions AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK -AC_FUNC_MALLOC -AC_FUNC_REALLOC -AC_CHECK_FUNCS([inet_ntoa strdup]) -AC_CHECK_FUNCS([strlcpy strlcat setgroups]) +AC_CHECK_FUNCS([strlcpy setgroups]) dnl Enable extra warnings -DESIRED_FLAGS="-fdiagnostics-show-option -Wall -Wextra -Wno-unused-parameter -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations -Wfloat-equal -Wundef -Wformat=2 -Wlogical-op -Wmissing-include-dirs -Wformat-nonliteral -Wold-style-definition -Wpointer-arith -Waggregate-return -Winit-self -Wpacked --std=c89 -ansi -pedantic -Wno-overlength-strings -Wno-long-long -Wno-overlength-strings -Wdeclaration-after-statement -Wredundant-decls -Wmissing-noreturn -Wshadow -Wendif-labels -Wcast-qual -Wcast-align -Wwrite-strings -Wp,-D_FORTIFY_SOURCE=2 -fno-common" +DESIRED_FLAGS="-fdiagnostics-show-option -Wall -Wextra -Wno-unused-parameter -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations -Wfloat-equal -Wundef -Wformat=2 -Wlogical-op -Wmissing-include-dirs -Wformat-nonliteral -Wold-style-definition -Wpointer-arith -Waggregate-return -Winit-self -Wpacked --std=c89 -ansi -Wno-overlength-strings -Wno-long-long -Wno-overlength-strings -Wdeclaration-after-statement -Wredundant-decls -Wmissing-noreturn -Wshadow -Wendif-labels -Wcast-qual -Wcast-align -Wwrite-strings -Wp,-D_FORTIFY_SOURCE=2 -fno-common" if test -n "${MAINTAINER_MODE_FALSE}"; then DESIRED_FLAGS="-Werror $DESIRED_FLAGS" fi +all_desired_work=false +AS_COMPILER_FLAG([$DESIRED_FLAGS], [all_desired_work=true]) +if $all_desired_work ; then + CFLAGS="$CFLAGS $DESIRED_FLAGS" +else for flag in $DESIRED_FLAGS; do AS_COMPILER_FLAG([$flag], [CFLAGS="$CFLAGS $flag"]) done +fi dnl Disable debugging if it's not specified if test x"$debug_enabled" != x"yes" ; then CFLAGS="-DNDEBUG $CFLAGS" fi -AS_ECHO_N(["checking to see if linker understands -z,defs... "]) -LDFLAGS_OLD="-Wl $LDFLAGS" -LDFLAGS="-Wl,-z,defs $LDFLAGS" -AC_LINK_IFELSE([AC_LANG_PROGRAM()], - AS_ECHO("yes"), - AS_ECHO("no"); LDFLAGS="$LDFLAGS_OLD") - -dnl -dnl Make sure we can actually handle the "--with-*" and "--enable-*" stuff. -dnl - dnl dnl Substitute the variables into the various Makefiles dnl +# runstatedir isn't available for Autoconf < 2.70 +AS_IF([test -z "${runstatedir}"], [runstatedir='${localstatedir}/run']) +AC_SUBST([runstatedir]) AC_SUBST(CFLAGS) AC_SUBST(LDFLAGS) AC_SUBST(CPPFLAGS) AC_SUBST(LIBS) AC_SUBST(ADDITIONAL_OBJECTS) -# Check for xml tools -AC_PATH_PROG(XSLTPROC, xsltproc, no) -AM_CONDITIONAL(HAVE_XSLTPROC, test "x$XSLTPROC" != "xno") - -# Check for asciidoc -AC_PATH_PROG(A2X, a2x, no) -AM_CONDITIONAL(HAVE_A2X, test "x$A2X" != "xno") - -# checking xmllint -AC_PATH_PROG(XMLLINT, xmllint, no) -if test "x$XMLLINT" != "xno"; then - AS_ECHO_N("testing xmllint... ") - echo "TEST" > conftest.txt - if $A2X -f docbook conftest.txt 2>/dev/null; then - AS_ECHO("ok") +if test x"$manpage_support_enabled" = x"yes"; then +AC_PATH_PROG(POD2MAN, pod2man, no) + +if test "x$POD2MAN" = "xno" && \ + ! test -e docs/man5/tinyproxy.conf.5 -a -e docs/man8/tinyproxy.8 ; then +AC_MSG_ERROR([ + manpage generation requested, but neither pod2man + nor pre-generated manpages found. + Use --disable-manpage-support if you want to compile anyway.]) +fi +fi #manpage_support_enabled + +AM_CONDITIONAL(HAVE_POD2MAN, test "x$POD2MAN" != "x" -a "x$POD2MAN" != "xno") + +AC_PATH_PROG(GPERF, gperf, no) +AH_TEMPLATE([HAVE_GPERF], + [Whether you have gperf installed for faster config parsing.]) + +tmp_gperf=false +if test "x$GPERF" != "x" -a "x$GPERF" != "xno" ; then + AS_ECHO_N(["checking whether gperf is recent enough... "]) + if "$GPERF" < src/conf-tokens.gperf >/dev/null 2>&1 ; then + AS_ECHO("yes") + AC_DEFINE(HAVE_GPERF) + tmp_gperf=true else - AS_ECHO("failed") - XMLLINT="no" + AS_ECHO("no") fi - rm -f conftest.txt conftest.xml fi -AM_CONDITIONAL(HAVE_XMLLINT, test "x$XMLLINT" != "xno") +AM_CONDITIONAL(HAVE_GPERF, $tmp_gperf) AC_CONFIG_FILES([ Makefile @@ -241,21 +223,21 @@ docs/Makefile docs/man5/Makefile docs/man5/tinyproxy.conf.txt docs/man8/Makefile -docs/man8/tinyproxy.txt m4macros/Makefile tests/Makefile tests/scripts/Makefile +scripts/Makefile ]) AC_OUTPUT # the manpages are shipped in the release tarball and we don't want them to -# get regenerated if a2x is not available. the intermediate files from +# get regenerated if pod2man is not available. the intermediate files from # AC_CONFIG_FILES are created with config.status, which is created at configure # runtime, so we need to touch them after config.status terminated to prevent # make from rebuild them. -if test "x$A2X" = "xno"; then +if test "x$POD2MAN" = "xno" ; then touch docs/man5/tinyproxy.conf.txt touch docs/man8/tinyproxy.txt if test -e docs/man5/tinyproxy.conf.5 ; then @@ -265,3 +247,7 @@ if test "x$A2X" = "xno"; then touch docs/man8/tinyproxy.8 fi fi + +if test "x$HAVE_GPERF" = "xno" && test -e src/conf-tokens-gperf.inc ; then + touch src/conf-tokens-gperf.inc +fi diff --git a/data/templates/debug.html b/data/templates/debug.html index 6ee33674..0e7f0549 100644 --- a/data/templates/debug.html +++ b/data/templates/debug.html @@ -30,9 +30,6 @@

{cause}

clienthost
{clienthost}
-
version
-
{version}
-
package
{package}
@@ -49,7 +46,7 @@

{cause}


-

Generated by {package} version {version}.

+

Generated by {package}.

diff --git a/data/templates/default.html b/data/templates/default.html index 67354b7a..8a9c8f6c 100644 --- a/data/templates/default.html +++ b/data/templates/default.html @@ -16,7 +16,7 @@

{cause}


-

Generated by {package} version {version}.

+

Generated by {package}.

diff --git a/data/templates/stats.html b/data/templates/stats.html index 71798592..f039c970 100644 --- a/data/templates/stats.html +++ b/data/templates/stats.html @@ -1,69 +1,95 @@ - - - - - - -{package} version {version} run-time statistics - - - - - - - - -

{package} version {version} run-time statistics

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameValue
Number of open connections{opens}
Number of requests{reqs}
Number of bad connections{badconns}
Number of denied connections{deniedconns}
Number of refused connections due to high load{refusedconns}
- -
- -

Generated by {package} version {version}.

- - - + + + + + Stats [{package}] + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{package} statistics
Open connections{opens}
Bad connections{badconns}
Denied connections{deniedconns}
Refused (high load){refusedconns}
Total requests{reqs}
+
+
+ diff --git a/docs/Makefile.am b/docs/Makefile.am index 50652556..e2ba221a 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -4,6 +4,5 @@ SUBDIRS = \ EXTRA_DIST = \ http-error-codes.txt \ - http-rfcs.txt \ - filter-howto.txt + http-rfcs.txt diff --git a/docs/filter-howto.txt b/docs/filter-howto.txt deleted file mode 100644 index 3acd813e..00000000 --- a/docs/filter-howto.txt +++ /dev/null @@ -1,52 +0,0 @@ -Using tinyproxy with Your Home/Small Business Network - -Written: Patrick L. McGillan -Edited: Robert James Kaes (2002-06-04) ------------------------------------------------------ - -Being as this will be the most common usage and there were no clear -basic instructions for this scenario, I thought I would write up what -I did for my home system. - -First the layout of the network. A cable modem is connected through a -Linksys Router to a small hub. The computers hanging off the hub and -have a clear shot to the Internet. - -So, the connection from the Internet to the hub looks like this: - - Internet->Cable TV Line->Cable Modem->Linksys Router->Hub/Switch - -Restricting Internet web access on some of those computers (connected -to the hub) is what using tinyproxy is all about. Using the web -interface to the Linksys router, turn off all Internet access to those -computers that shouldn't have direct access to the Internet. This is -done by clicking on the advanced tab and entering the IP number in the -filter range. Now those computers have to go through a proxy, for -their access, as they have no direct access. - -On one of the Linux computers which still has Internet access (I use -an old 486) load up tinyproxy. Now have the users configure their -Internet Explorer/Netscape Navigator programs to use the proxy on the -tinyproxy computer box, along with the port number declared in the -tinyproxy configuration file. By default, there is no blocking of web -sites with this program, so I created a file, called "filter", to -start blocking some sites. - -Example "filter" file entries: - -bannerads.zwire.com -ad.doubleclick.net -ads.fortunecity.com - -This filter file usually goes into the same folder, as your -configuration file. Be sure and uncomment the 'Filter' line in your -configuration file and make sure it points at your newly created -filter file. - ------------------------------------------------------------------------- - -Copyright (c) 2002 Patrick L. McGillan - -This document is released under the same copyright license as -tinyproxy. You should have found a COPYING file in the top level -directory of this distribution which contains the current license. \ No newline at end of file diff --git a/docs/man5/Makefile.am b/docs/man5/Makefile.am index 247b7ef3..31b8ddfa 100644 --- a/docs/man5/Makefile.am +++ b/docs/man5/Makefile.am @@ -1,25 +1,25 @@ +if HAVE_MANPAGE_INTEREST MAN5_FILES = \ tinyproxy.conf.txt - -if HAVE_XMLLINT -A2X_ARGS = -d manpage -f manpage -else -A2X_ARGS = -d manpage -f manpage -L endif +M_SECTION=5 +M_NAME=TINYPROXY.CONF + man_MANS = \ $(MAN5_FILES:.txt=.5) .txt.5: -if HAVE_A2X - $(AM_V_GEN) $(A2X) $(A2X_ARGS) $< +if HAVE_POD2MAN + $(AM_V_GEN) $(POD2MAN) --center="Tinyproxy manual" \ + --section=$(M_SECTION) --name=$(M_NAME) --release="Version @VERSION@" \ + $< > $@ else - @echo "*** a2x (asciidoc) is required to regenerate $(@) ***"; exit 1; + @echo "*** pod2man is required to regenerate $(@) ***"; exit 1; endif -CLEANFILES = \ - $(MAN5_FILES:.txt=.5) \ - $(MAN5_FILES:.txt=.xml) +MAINTAINERCLEANFILES = \ + $(MAN5_FILES:.txt=.5) EXTRA_DIST = \ $(MAN5_FILES:.txt=.5) diff --git a/docs/man5/tinyproxy.conf.txt.in b/docs/man5/tinyproxy.conf.txt.in index e40135b4..9938ce19 100644 --- a/docs/man5/tinyproxy.conf.txt.in +++ b/docs/man5/tinyproxy.conf.txt.in @@ -1,24 +1,20 @@ -TINYPROXY.CONF(5) -================= -:man source: Version @VERSION@ -:man manual: Tinyproxy manual +=pod -NAME ----- +=encoding utf8 + +=head1 NAME tinyproxy.conf - Tinyproxy HTTP proxy daemon configuration file -SYNOPSIS --------- +=head1 SYNOPSIS -*tinyproxy.conf* +B -DESCRIPTION ------------ +=head1 DESCRIPTION -`tinyproxy(8)` reads its configuration file, typically stored in +L reads its configuration file, typically stored in `/etc/tinyproxy/tinyproxy.conf` (or passed to Tinyproxy with -c on the command line). This manpage describes the syntax and contents of the configuration file. @@ -26,345 +22,419 @@ configuration file. The Tinyproxy configuration file contains key-value pairs, one per line. Lines starting with `#` and empty lines are comments and are ignored. Keywords are case-insensitive, whereas values are -case-sensitive. Values may be enclosed in double-quotes (") if they -contain spaces. +case-sensitive. Some string values must be enclosed in double +quotes (") as noted below. The possible keywords and their descriptions are as follows: -*User*:: +=over 4 + +=item B + +The user which the Tinyproxy process should run as, after the +initial port-binding has been done as the `root` user. Either the +user name or the UID may be specified. + +=item B + +The group which the Tinyproxy process should run as, after the +initial port-binding has been done as the `root` user. Either the +group name or the GID may be specified. + +=item B + +The port which the Tinyproxy service will listen on. If the port is +less than 1024, you will need to start the Tinyproxy process as the +`root` user. + +=item B + +By default, Tinyproxy listens for connections on all available +interfaces (i.e. it listens on the wildcard address `0.0.0.0`). +With this configuration parameter, Tinyproxy can be told to listen +only on one specific address. + +=item B + +This allows you to specify which address Tinyproxy will bind +to for outgoing connections. +This parameter may be specified multiple times, then Tinyproxy +will try all the specified addresses in order. + +=item B + +If this boolean parameter is set to `yes`, then Tinyproxy will +bind the outgoing connection to the IP address of the incoming +connection that triggered the outgoing request. + +=item B + +The maximum number of seconds of inactivity a connection is +allowed to have before it is closed by Tinyproxy. + +=item B + +This parameter controls which HTML file Tinyproxy returns when a +given HTTP error occurs. It takes two arguments, the error number +and the location of the HTML error file. Enclose the file location +in double quotes. + +=item B + +The HTML template file returned when an error occurs for which no +specific error file has been set. Enclose in double quotes. + +=item B + +The host name or IP address that is treated as the `stat host`. +Enclose in double quotes. Whenever Tinyproxy receives a request for +the `stat host` it returns an internal statistics page instead of +forwarding the request to that host. The template for this page can be +configured with the `StatFile` configuration option. The default value +of `StatHost` is `@TINYPROXY_STATHOST@`. + +=item B + +The HTML file that Tinyproxy sends in response to a request for the +`stat host`. Enclose in double quotes. If this parameter is not set, +Tinyproxy returns a hard-coded basic statistics page. See the STATHOST +section in the L manual page for details. + +Note that the StatFile and the error files configured with ErrorFile +and DefaultErrorFile are template files that can contain a few +template variables that Tinyproxy expands prior to delivery. +Examples are "{cause}" for an abbreviated error description and +"{detail}" for a detailed error message. The L +manual page contains a description of all template variables. + +=item B + +The location of the file to which Tinyproxy writes its debug output. +Enclose in double quotes. Alternatively, Tinyproxy can log to syslog +-- see the Syslog option. + +=item B + +When set to `On`, this option tells Tinyproxy to write its +debug messages to syslog instead of to a log file configured +with `LogFile`. These two options are mutually exclusive. + +=item B + +Sets the log level. Messages from the set level and above are +logged. For example, if the LogLevel was set to Warning, then all +log messages from Warning to Critical would be output, but Notice +and below would be suppressed. Allowed values are: + +=over 4 + +=item * Critical (least verbose) + +=item * Error - The user which the Tinyproxy process should run as, after the - initial port-binding has been done as the `root` user. Either the - user name or the UID may be specified. +=item * Warning -*Group*:: +=item * Notice - The group which the Tinyproxy process should run as, after the - initial port-binding has been done as the `root` user. Either the - group name or the GID may be specified. +=item * Connect (log connections without Info's noise) -*Port*:: +=item * Info (most verbose) - The port which the Tinyproxy service will listen on. If the port is - less than 1024, you will need to start the Tinyproxy process as the - `root` user. +=back -*Listen*:: +=item B - By default, Tinyproxy listens for connections on all available - interfaces (i.e. it listens on the wildcard address `0.0.0.0`). - With this configuration parameter, Tinyproxy can be told to listen - only on one specific address. +The location of the file where the main Tinyproxy process stores its +process ID for signaling purposes. Enclose in double quotes. -*Bind*:: +=item B - This allows you to specify which address Tinyproxy will bind - to for outgoing connections to web servers or upstream proxies. +Setting this option to `Yes` tells Tinyproxy to add a header +`X-Tinyproxy` containing the client's IP address to the request. -*BindSame*:: +=item B - If this boolean parameter is set to `yes`, then Tinyproxy will - bind the outgoing connection to the IP address of the incoming - connection that triggered the outgoing request. +This option allows you to set up a set of rules for deciding +whether an upstream proxy server is to be used, based on the +host or domain of the site being accessed. The rules are stored +in the order encountered in the configuration file and the +LAST matching rule wins. The following forms for specifying upstream +rules exist: -*Timeout*:: +=over 4 - The maximum number of seconds of inactivity a connection is - allowed to have before it is closed by Tinyproxy. +=item * I turns proxy upstream support on generally. -*ErrorFile*:: +=item * I +does the same, but uses the supplied credentials for authentication. - This parameter controls which HTML file Tinyproxy returns when a - given HTTP error occurs. It takes two arguments, the error number - and the location of the HTML error file. +=item * I +turns on the upstream proxy for the sites matching `site_spec`. -*DefaultErrorFile*:: +`type` can be one of `http`, `socks4`, `socks5`, `none`. - This parameter controls the HTML template file returned when an - error occurs for which no specific error file has been set. +a `site_spec` is either a full domain name, a domain name starting with a +`.`, in which case it is treated as a suffix, or an ip/mask tuple. +the `site_spec` needs to be double-quoted. -*StatHost*:: +=item * I +turns off upstream support for sites matching `site_spec`, that means the +connection is done directly. - This configures the host name or IP address that is treated - as the `stat host`: Whenever a request for this host is received, - Tinyproxy will return an internal statistics page instead of - forwarding the request to that host. The template for this - page can be configured with the `StatFile` configuration option. - The default value of `StatHost` is `@TINYPROXY_STATHOST@`. +=back -*StatFile*:: +It's recommended to use raw IP addresses to specify the upstream host, so +no costly DNS lookup has to be done everytime it is used. +IPv6 addresses need to be enclosed in square brackets. - This configures the HTML file that Tinyproxy sends when - a request for the stathost is received. If this parameter is - not set, Tinyproxy returns a hard-coded basic statistics page. - See the STATHOST section in the `tinyproxy(8)` manual page - for details. - + - Note that the StatFile and the error files configured with ErrorFile - and DefaultErrorFile are template files that can contain a few - template variables that Tinyproxy expands prior to delivery. - Examples are "\{cause}" for an abbreviated error description and - "\{detail}" for a detailed error message. The `tinyproxy(8)` - manual page contains a description of all template variables. +The site can be specified in various forms as a hostname, domain +name or as an IP range: -*LogFile*:: +=over 4 - This controls the location of the file to which Tinyproxy - writes its debug output. Alternatively, Tinyproxy can log - to syslog -- see the Syslog option. +=item * I matches host exactly -*Syslog*:: +=item * I<.name> matches any host in domain "name" - When set to `On`, this option tells Tinyproxy to write its - debug messages to syslog instead of to a log file configured - with `LogFile`. These two options are mutually exclusive. +=item * I<.> matches any host with no domain (in 'empty' domain) -*LogLevel*:: +=item * I matches network/mask - Sets the log level. Messages from the set level and above are - logged. For example, if the LogLevel was set to Warning, then all - log messages from Warning to Critical would be output, but Notice - and below would be suppressed. Allowed values are: +=item * I matches network/mask - * Critical (least verbose) - * Error - * Warning - * Notice - * Connect (log connections without Info's noise) - * Info (most verbose) +=back -*PidFile*:: +Note that the upstream directive can also be used to null-route +a specific target domain/host, e.g.: +`upstream http 0.0.0.0:0 ".adserver.com"` - This option controls the location of the file where the main - Tinyproxy process stores its process ID for signaling purposes. +=item B -*XTinyproxy*:: +Tinyproxy creates one thread for each connected client. +This options specifies the absolute highest number processes that +will be created. With other words, only MaxClients clients can be +connected to Tinyproxy simultaneously. - Setting this option to `Yes` tells Tinyproxy to add a header - `X-Tinyproxy` containing the client's IP address to the request. +=item B -*Upstream*:: -*No Upstream*:: +=item B - This option allows you to set up a set of rules for deciding - whether an upstream proxy server is to be used, based on the - host or domain of the site being accessed. The rules are stored - in the order encountered in the configuration file and the - LAST matching rule wins. There are three possible forms for - specifying upstream rules: +The `Allow` and `Deny` options provide a means to customize +which clients are allowed to access Tinyproxy. `Allow` and `Deny` +lines can be specified multiple times to build the access control +list for Tinyproxy. The order in the config file is important. +If there are no `Allow` or `Deny` lines, then all clients are +allowed. Otherwise, the default action is to deny access. +The argument to `Allow` or `Deny` can be a single IP address +of a client host, like `127.0.0.1` or `::1`, an IP address range, like +`192.168.0.1/24` or a string that will be matched against the +end of the client host name, i.e, this can be a full host name +like `host.example.com` or a domain name like `.example.com` or +even a top level domain name like `.com`. +Note that by adding a rule using a host or domain name, a costly name +lookup has to be done for every new connection, which could slow down +the service considerably. - * 'upstream host:port' turns proxy upstream support on generally. +=item B - * 'upstream host:port "site_spec"' turns on the upstream proxy for - the sites matching `site_spec`. +Configure HTTP "Basic Authentication" username and password +for accessing the proxy. If there are any entries specified, +access is only granted for authenticated users. - * 'no upstream "site_spec"' turns off upstream support for sites - matching `site_spec`. + BasicAuth user password - The site can be specified in various forms as a hostname, domain - name or as an IP range: +=item B - * 'name' matches host exactly - * '.name' matches any host in domain "name" - * '.' matches any host with no domain (in 'empty' domain) - * 'IP/bits' matches network/mask - * 'IP/mask' matches network/mask +In case "BasicAuth" is configured, the "realm" information. +"Proxy Authentication Required" status http 407 "error-response" can be +customized. -*MaxClients*:: +- defaults in code to "Tinyproxy" (PACKAGE_NAME), if not configured. - Tinyproxy creates one child process for each connected client. - This options specifies the absolute highest number processes that - will be created. With other words, only MaxClients clients can be - connected to Tinyproxy simultaneously. +=item B -*MinSpareServers*:: -*MaxSpareServers*:: +Configure one or more HTTP request headers to be added to outgoing +HTTP requests that Tinyproxy makes. Note that this option will not +work for HTTPS traffic, as Tinyproxy has no control over what +headers are exchanged. - Tinyproxy always keeps a certain number of idle child processes - so that it can handle new incoming client requests quickly. - `MinSpareServer` and `MaxSpareServers` control the lower and upper - limits for the number of spare processes. I.e. when the number of - spare servers drops below `MinSpareServers` then Tinyproxy will - start forking new spare processes in the background and when the - number of spare processes exceeds `MaxSpareServers` then Tinyproxy - will kill off extra processes. - -*StartServers*:: - - The number of servers to start initially. This should usually be - set to a value between MinSpareServers and MaxSpareServers. - -*MaxRequestsPerChild*:: - - This limits the number of connections that a child process - will handle before it is killed. The default value is `0` - which disables this feature. This option is meant as an - emergency measure in the case of problems with memory leakage. - In that case, setting `MaxRequestsPerChild` to a value of e.g. - 1000, or 10000 can be useful. - -*Allow*:: -*Deny*:: + AddHeader "X-My-Header" "Powered by Tinyproxy" - The `Allow` and `Deny` options provide a means to customize - which clients are allowed to access Tinyproxy. `Allow` and `Deny` - lines can be specified multiple times to build the access control - list for Tinyproxy. The order in the config file is important. - If there are no `Allow` or `Deny` lines, then all clients are - allowed. Otherwise, the default action is to deny access. - The argument to `Allow` or `Deny` can be a single IP address - of a client host, like `127.0.0.1`, an IP address range, like - `192.168.0.1/24` or a string that will be matched against the - end of the client host name, i.e, this can be a full host name - like `host.example.com` or a domain name like `.example.com` or - even a top level domain name like `.com`. - -*AddHeader*:: - - Configure one or more HTTP request headers to be added to outgoing - HTTP requests that Tinyproxy makes. Note that this option will not - work for HTTPS traffic, as Tinyproxy has no control over what - headers are exchanged. - + ----- -AddHeader "X-My-Header" "Powered by Tinyproxy" ----- - -*ViaProxyName*:: - - RFC 2616 requires proxies to add a `Via` header to the HTTP - requests, but using the real host name can be a security - concern. If the `ViaProxyname` option is present, then its - string value will be used as the host name in the Via header. - Otherwise, the server's host name will be used. - -*DisableViaHeader*:: - - When this is set to yes, Tinyproxy does NOT add the `Via` header - to the requests. This virtually puts Tinyproxy into stealth mode. - Note that RFC 2616 requires proxies to set the `Via` header, so by - enabling this option, you break compliance. - Don't disable the `Via` header unless you know what you are doing... - -*Filter*:: - - Tinyproxy supports filtering of web sites based on URLs or - domains. This option specifies the location of the file - containing the filter rules, one rule per line. - -*FilterURLs*:: - - If this boolean option is set to `Yes` or `On`, filtering is - performed for URLs rather than for domains. The default is to - filter based on domains. - -*FilterExtended*:: - - If this boolean option is set to `Yes`, then extended POSIX - regular expressions are used for matching the filter rules. - The default is to use basic POSIX regular expressions. - -*FilterCaseSensitive*:: - - If this boolean option is set to `Yes`, then the filter rules - are matched in a case sensitive manner. The default is to - match case-insensitively. - -*FilterDefaultDeny*:: - - The default filtering policy is to allow everything that is - not matched by a filtering rule. Setting `FilterDefaultDeny` - to `Yes` changes the policy do deny everything but the domains - or URLs matched by the filtering rules. - -*Anonymous*:: - - If an `Anonymous` keyword is present, then anonymous proxying - is enabled. The headers listed with `Anonymous` are allowed - through, while all others are denied. If no Anonymous keyword - is present, then all headers are allowed through. You must - include quotes around the headers. - + - Most sites require cookies to be enabled for them to work correctly, so - you will need to allow cookies through if you access those sites. - + - Example: - + ----- -Anonymous "Host" -Anonymous "Authorization" -Anonymous "Cookie" ----- - -*ConnectPort*:: - - This option can be used to specify the ports allowed for the - CONNECT method. If no `ConnectPort` line is found, then all - ports are allowed. To disable CONNECT altogether, include a - single ConnectPort line with a value of `0`. - -*ReversePath*:: - - Configure one or more ReversePath directives to enable reverse proxy - support. With reverse proxying it's possible to make a number of - sites appear as if they were part of a single site. - + - If you uncomment the following two directives and run Tinyproxy - on your own computer at port 8888, you can access example.com, - using http://localhost:8888/example/. - + ----- -ReversePath "/example/" "http://www.example.com/" ----- +=item B -*ReverseOnly*:: +RFC 2616 requires proxies to add a `Via` header to the HTTP +requests, but using the real host name can be a security +concern. If the `ViaProxyname` option is present, then its +string value will be used as the host name in the Via header. +Otherwise, the server's host name will be used. Enclose in double +quotes. - When using Tinyproxy as a reverse proxy, it is STRONGLY - recommended that the normal proxy is turned off by setting - this boolean option to `Yes`. - -*ReverseMagic*:: +=item B - Setting this option to `Yes`, makes Tinyproxy use a cookie to - track reverse proxy mappings. If you need to reverse proxy - sites which have absolute links you must use this option. +When this is set to yes, Tinyproxy does NOT add the `Via` header +to the requests. This virtually puts Tinyproxy into stealth mode. +Note that RFC 2616 requires proxies to set the `Via` header, so by +enabling this option, you break compliance. +Don't disable the `Via` header unless you know what you are doing... -*ReverseBaseURL*:: +=item B - The URL that is used to access this reverse proxy. The URL is - used to rewrite HTTP redirects so that they won't escape the - proxy. If you have a chain of reverse proxies, you'll need to - put the outermost URL here (the address which the end user - types into his/her browser). If this option is not set then - no rewriting of redirects occurs. +Tinyproxy supports filtering of web sites based on URLs or +domains. This option specifies the location of the file +containing the filter rules, one rule per line. +Rules are specified as POSIX basic regular expressions (BRE), unless +another FilterType is specified. +Comment lines start with a `#` character. -BUGS ----- +Example filter file contents: + + # filter exactly cnn.com + ^cnn\.com$ + + # filter all subdomains of cnn.com, but not cnn.com itself + .*\.cnn.com$ + + # filter any domain that has cnn.com in it, like xcnn.comfy.org + cnn\.com + + # filter any domain that ends in cnn.com + cnn\.com$ + + # filter any domain that starts with adserver + ^adserver + +=item B + +This option can be set to one of `bre`, `ere`, or `fnmatch`. +If `bre` is set, the rules specified in the filter file are matched +using POSIX basic regular expressions, when set to `ere`, using +POSIX extended regular expressions, and when set to `fnmatch` using +the `fnmatch` function as specified in the manpage `man 3p fnmatch`. +`fnmatch` matching is identical to what's used in the shell to match +filenames, so for example `*.google.com` matches everything that +ends with `.google.com`. +If you don't know what regular expressions are or you're using filter +lists from 3rd party sources, `fnmatch` is probably what you want. +It's also the fastest matching method of the three. + +=item B + +If this boolean option is set to `Yes` or `On`, filtering is +performed for URLs rather than for domains. The default is to +filter based on domains. + +Note that filtering for URLs works only in plain HTTP scenarios. +Since HTTPS has become ubiquitous during the last years, this +will only work on a tiny fraction of websites, so it is +recommended not to use this option. + +=item B + +Deprecated. Use `FilterType ere` instead. +If this boolean option is set to `Yes`, then extended POSIX +regular expressions are used for matching the filter rules. +The default is to use basic POSIX regular expressions. + +=item B + +If this boolean option is set to `Yes`, then the filter rules +are matched in a case sensitive manner. The default is to +match case-insensitively, unfortunately. +If you set this to `Yes`, then your matching will be almost +twice as fast. +This setting affects only `bre` and `ere` FilterTypes, fnmatch +is always case sensitive. + +=item B + +The default filtering policy is to allow everything that is +not matched by a filtering rule. Setting `FilterDefaultDeny` +to `Yes` changes the policy do deny everything but the domains +or URLs matched by the filtering rules. +In other words, if set to `No` the Filter list acts as a +blacklist, if set to `Yes` as a whitelist. + +=item B + +If an `Anonymous` keyword is present, then anonymous proxying +is enabled. The headers listed with `Anonymous` are allowed +through, while all others are denied. If no Anonymous keyword +is present, then all headers are allowed through. You must +include double quotes around the headers. + +Most sites require cookies to be enabled for them to work correctly, so +you will need to allow cookies through if you access those sites. + +Example: + + Anonymous "Host" + Anonymous "Authorization" + Anonymous "Cookie" + +=item B + +This option can be used to specify the ports allowed for the +CONNECT method. If no `ConnectPort` line is found, then all +ports are allowed. To disable CONNECT altogether, include a +single ConnectPort line with a value of `0`. + +=item B + +Configure one or more ReversePath directives to enable reverse proxy +support. With reverse proxying it's possible to make a number of +sites appear as if they were part of a single site. + +If you uncomment the following two directives and run Tinyproxy +on your own computer at port 8888, you can access example.com, +using http://localhost:8888/example/. + + ReversePath "/example/" "http://www.example.com/" + +=item B + +When using Tinyproxy as a reverse proxy, it is STRONGLY +recommended that the normal proxy is turned off by setting +this boolean option to `Yes`. + +=item B + +Setting this option to `Yes`, makes Tinyproxy use a cookie to +track reverse proxy mappings. If you need to reverse proxy +sites which have absolute links you must use this option. + +=item B + +The URL that is used to access this reverse proxy. The URL is +used to rewrite HTTP redirects so that they won't escape the +proxy. If you have a chain of reverse proxies, you'll need to +put the outermost URL here (the address which the end user +types into his/her browser). If this option is not set then +no rewriting of redirects occurs. + +=back + +=head1 BUGS To report bugs in Tinyproxy, please visit -. +L. -SEE ALSO --------- -tinyproxy(8) +=head1 SEE ALSO +L -AUTHOR ------- + +=head1 AUTHOR This manpage was written by the Tinyproxy project team. -COPYRIGHT ---------- +=head1 COPYRIGHT -Copyright (c) 1998-2000 Steven Young; -Copyright (c) 2000-2001 Robert James Kaes; -Copyright (c) 2009-2010 Mukund Sivaraman; -Copyright (c) 2009-2010 Michael Adam. +Copyright (c) 1998-2024 the Tinyproxy authors. This program is distributed under the terms of the GNU General Public License version 2 or above. See the COPYING file for additional information. + diff --git a/docs/man8/Makefile.am b/docs/man8/Makefile.am index 1bd0ffa7..28e361ef 100644 --- a/docs/man8/Makefile.am +++ b/docs/man8/Makefile.am @@ -1,25 +1,40 @@ -MAN8_FILES = \ +if HAVE_MANPAGE_INTEREST +MAN8_FILES = \ tinyproxy.txt - -if HAVE_XMLLINT -A2X_ARGS = -d manpage -f manpage -else -A2X_ARGS = -d manpage -f manpage -L endif +M_SECTION=8 +M_NAME=TINYPROXY + man_MANS = \ $(MAN8_FILES:.txt=.8) +edit = sed \ + -e 's|@localstatedir[@]|$(localstatedir)|g' \ + -e 's|@runstatedir[@]|$(runstatedir)|g' \ + -e 's|@sysconfdir[@]|$(sysconfdir)|g' \ + -e 's|@TINYPROXY_STATHOST[@]|$(TINYPROXY_STATHOST)|g' + +tinyproxy.txt: $(top_srcdir)/docs/man8/tinyproxy.txt.in Makefile + @rm -f $@ $@.tmp + $(AM_V_GEN) $(edit) $(top_srcdir)/docs/man8/$@.in > $@.tmp + @mv $@.tmp $@ + .txt.8: -if HAVE_A2X - $(AM_V_GEN) $(A2X) $(A2X_ARGS) $< +if HAVE_POD2MAN + $(AM_V_GEN) $(POD2MAN) --center="Tinyproxy manual" \ + --section=$(M_SECTION) --name=$(M_NAME) --release="Version @VERSION@" \ + $< > $@ else - @echo "*** a2x (asciidoc) is required to regenerate $(@) ***"; exit 1; + @echo "*** pod2man is required to regenerate $(@) ***"; exit 1; endif CLEANFILES = \ - $(MAN8_FILES:.txt=.8) \ - $(MAN8_FILES:.txt=.xml) + tinyproxy.txt -EXTRA_DIST= \ +MAINTAINERCLEANFILES = \ $(MAN8_FILES:.txt=.8) + +EXTRA_DIST = \ + $(MAN8_FILES:.txt=.8) \ + tinyproxy.txt.in diff --git a/docs/man8/tinyproxy.txt.in b/docs/man8/tinyproxy.txt.in index e259dcb7..9cf2d426 100644 --- a/docs/man8/tinyproxy.txt.in +++ b/docs/man8/tinyproxy.txt.in @@ -1,24 +1,20 @@ -TINYPROXY(8) -============ -:man source: Version @VERSION@ -:man manual: Tinyproxy manual +=pod -NAME ----- +=encoding utf8 + +=head1 NAME tinyproxy - A light-weight HTTP proxy daemon -SYNOPSIS --------- +=head1 SYNOPSIS -*tinyproxy* [-vldch] +B [-vdch] -DESCRIPTION ------------ +=head1 DESCRIPTION -*tinyproxy* is a light-weight HTTP proxy daemon designed to consume a +B is a light-weight HTTP proxy daemon designed to consume a minimum amount of system resources. It listens on a given TCP port and handles HTTP proxy requests. Designed from the ground up to be fast and yet small, it is an ideal solution for use cases such as embedded @@ -26,49 +22,66 @@ deployments where a full featured HTTP proxy is required, but the system resources for a larger proxy are unavailable. -OPTIONS -------- +=head1 OPTIONS + +B accepts the following options: + +=over 4 + +=item B<-c > -*tinyproxy* accepts the following options: +Use an alternate configuration file. -*-c *:: - Use an alternate configuration file. +=item B<-d> -*-d*:: - Don't daemonize and stay in the foreground. Useful for debugging purposes. +Don't daemonize and stay in the foreground. Useful for debugging purposes. -*-h*:: - Display a short help screen of command line arguments and exit. +=item B<-h> -*-l*:: - Display the licensing agreement. +Display a short help screen of command line arguments and exit. -*-v*:: - Display version information and exit. +=item B<-v> +Display version information and exit. -SIGNALS -------- +=back + +=head1 SIGNALS In addition to command-line options, there are also several signals that -can be sent to *tinyproxy* while it is running to generate debugging +can be sent to B while it is running to generate debugging information and to force certain events. -*SIGHUP*:: - Force Tinyproxy to do a garbage collection on the current - connections linked list. This is usually done automatically after a - certain number of connections have been handled. +=over 4 + +=item B + +Force Tinyproxy to do a garbage collection on the current +connections linked list. This is usually done automatically after a +certain number of connections have been handled. +(Daemon mode only) +=item B -TEMPLATE FILES --------------- +Force reload of config file and filter list. +This is handy to update the configuration if Tinyproxy is running +in foreground without dropping active connections. + +=back + +=head1 TEMPLATE FILES There are two occasions when Tinyproxy delivers HTML pages to the client on it's own right: -. When an error occurred, a corresponding error page is returned. -. When a request for the stathost is made, a page summarizing the - connection statistics is returned. (See STATHOST below.) +=over 4 + +=item * When an error occurred, a corresponding error page is returned. + +=item * When a request for the stathost is made, a page summarizing the +connection statistics is returned. (See STATHOST below.) + +=back The layout of both error pages and the statistics page can be controlled via configurable HTML template files that are plain @@ -76,46 +89,60 @@ HTML files that additionally understand a few template variables. -TEMPLATE VARIABLES ------------------- +=head1 TEMPLATE VARIABLES There are several standard HTML variables that are available in every template file: -*request*:: - The full HTTP request line. +=over 4 + +=item B + +The full HTTP request line. -*cause*:: - The abbreviated cause of the error condition. +=item B -*clientip*:: - The IP address of the client making the request. +The abbreviated cause of the error condition. -*clienthost*:: - The hostname of the client making the request. +=item B -*version*:: - The version of Tinyproxy. +The IP address of the client making the request. -*package*:: - The package name. Presently, resolves to 'tinyproxy'. +=item B -*date*:: - The current date/time in HTTP format. +The hostname of the client making the request. + +=item B + +The version of Tinyproxy. + +=item B + +The package name. Presently, resolves to 'tinyproxy'. + +=item B + +The current date/time in HTTP format. + +=back In addition, almost all templates support: -*detail*:: - A detailed, plain English explanation of the error and possible - causes. +=over 4 + +=item B + +A detailed, plain English explanation of the error and possible +causes. + +=back When Tinyproxy finds a variable name enclosed in braces, e.g. -"\{request}", then this is replaced by the value of the corresponding +"{request}", then this is replaced by the value of the corresponding variable before delivery of the page. -STATHOST --------- +=head1 STATHOST Tinyproxy returns a HTML page with connection statistics when it receives a HTTP request for a certain host -- the stathost. The @@ -127,36 +154,33 @@ The stat file template can be changed at runtime through the configuration variable `StatFile`. -FILES ------ +=head1 FILES -`/etc/tinyproxy/tinyproxy.conf`, `/var/run/tinyproxy/tinyproxy.pid`, `/var/log/tinyproxy/tinyproxy.log` +F<@sysconfdir@/tinyproxy/tinyproxy.conf> -BUGS ----- +F<@runstatedir@/tinyproxy/tinyproxy.pid> + +F<@localstatedir@/log/tinyproxy/tinyproxy.log> + +=head1 BUGS To report bugs in Tinyproxy, please visit -. +L. + +=head1 SEE ALSO -SEE ALSO --------- -tinyproxy.conf(5) +L -AUTHOR ------- +=head1 AUTHOR This manpage was written by the Tinyproxy project team. -COPYRIGHT ---------- +=head1 COPYRIGHT -Copyright (c) 1998-2000 Steven Young; -Copyright (c) 2000-2001 Robert James Kaes; -Copyright (c) 2009-2010 Mukund Sivaraman; -Copyright (c) 2009-2010 Michael Adam. +Copyright (c) 1998-2020 the Tinyproxy authors. This program is distributed under the terms of the GNU General Public License version 2 or above. See the COPYING file for additional diff --git a/docs/web/Makefile b/docs/web/Makefile new file mode 100644 index 00000000..9b7c1ff6 --- /dev/null +++ b/docs/web/Makefile @@ -0,0 +1,15 @@ +# test webpage with `python -m SimpleHTTPServer` + +all: index.html + +tp.html.conf: ../man5/tinyproxy.conf.txt + pod2html --noindex < $^ | awk -f podhtml-filter.awk > $@ + +index.html: tp.html.head tp.html.conf tp.html.foot + cat $^ > $@ + +clean: + rm tp.html.conf index.html *.tmp + +.PHONY: all clean + diff --git a/docs/web/podhtml-filter.awk b/docs/web/podhtml-filter.awk new file mode 100644 index 00000000..4ea7892f --- /dev/null +++ b/docs/web/podhtml-filter.awk @@ -0,0 +1,5 @@ +BEGIN {i=0} +/<\/{0,1}h1/ {if(!i)i=1; gsub("h1", "h4", $0);} +#/<\/body>/ {i=0;} +/BUGS/ {i=-1} +{if(i==1) print;} diff --git a/docs/web/stylesheets/stylesheet.css b/docs/web/stylesheets/stylesheet.css new file mode 100644 index 00000000..eac0014c --- /dev/null +++ b/docs/web/stylesheets/stylesheet.css @@ -0,0 +1,426 @@ +/******************************************************************************* +Slate Theme for GitHub Pages +by Jason Costello, @jsncostello +*******************************************************************************/ + +@import url(github-light.css); + +/******************************************************************************* +MeyerWeb Reset +*******************************************************************************/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +ol, ul { + list-style: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +/******************************************************************************* +Theme Styles +*******************************************************************************/ + +body { + box-sizing: border-box; + color:#373737; + background: #212121; + font-size: 16px; + font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +h1, h2, h3, h4, h5, h6 { + margin: 10px 0; + font-weight: 700; + color:#222222; + font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif; + letter-spacing: -1px; +} + +h1 { + font-size: 36px; + font-weight: 700; +} + +h2 { + padding-bottom: 10px; + font-size: 32px; + background: url('../images/bg_hr.png') repeat-x bottom; +} + +h3 { + font-size: 24px; +} + +h4 { + font-size: 21px; +} + +h5 { + font-size: 18px; +} + +h6 { + font-size: 16px; +} + +p { + margin: 10px 0 15px 0; +} + +footer p { + color: #f2f2f2; +} + +a { + text-decoration: none; + color: #007edf; + text-shadow: none; + + transition: color 0.5s ease; + transition: text-shadow 0.5s ease; + -webkit-transition: color 0.5s ease; + -webkit-transition: text-shadow 0.5s ease; + -moz-transition: color 0.5s ease; + -moz-transition: text-shadow 0.5s ease; + -o-transition: color 0.5s ease; + -o-transition: text-shadow 0.5s ease; + -ms-transition: color 0.5s ease; + -ms-transition: text-shadow 0.5s ease; +} + +a:hover, a:focus {text-decoration: underline;} + +footer a { + color: #F2F2F2; + text-decoration: underline; +} + +em { + font-style: italic; +} + +strong { + font-weight: bold; +} + +img { + position: relative; + margin: 0 auto; + max-width: 739px; + padding: 5px; + margin: 10px 0 10px 0; + border: 1px solid #ebebeb; + + box-shadow: 0 0 5px #ebebeb; + -webkit-box-shadow: 0 0 5px #ebebeb; + -moz-box-shadow: 0 0 5px #ebebeb; + -o-box-shadow: 0 0 5px #ebebeb; + -ms-box-shadow: 0 0 5px #ebebeb; +} + +p img { + display: inline; + margin: 0; + padding: 0; + vertical-align: middle; + text-align: center; + border: none; +} + +pre, code { + width: 100%; + color: #222; + background-color: #fff; + + font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; + font-size: 14px; + + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +pre { + width: 100%; + padding: 10px; + margin-bottom: 20px; + box-shadow: 0 0 10px rgba(0,0,0,.1); + overflow: auto; +} + +code { + padding: 3px; + margin: 0 3px; + box-shadow: 0 0 10px rgba(0,0,0,.1); +} + +pre code { + display: block; + box-shadow: none; +} + +blockquote { + color: #666; + margin-bottom: 20px; + padding: 0 0 0 20px; + border-left: 3px solid #bbb; +} + + +ul, ol, dl { + margin-bottom: 15px +} + +ul { + list-style-position: inside; + list-style: disc; + padding-left: 20px; +} + +ol { + list-style-position: inside; + list-style: decimal; + padding-left: 20px; +} + +dl dt { + font-weight: bold; +} + +dl dd { + padding-left: 20px; +/* font-style: italic; */ +} + +dl p { + padding-left: 20px; +/* font-style: italic; */ +} + +hr { + height: 1px; + margin-bottom: 5px; + border: none; + background: url('../images/bg_hr.png') repeat-x center; +} + +table { + border: 1px solid #373737; + margin-bottom: 20px; + text-align: left; + } + +th { + font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; + padding: 10px; + background: #373737; + color: #fff; + } + +td { + padding: 10px; + border: 1px solid #373737; + } + +form { + background: #f2f2f2; + padding: 20px; +} + +/******************************************************************************* +Full-Width Styles +*******************************************************************************/ + +.outer { + width: 100%; +} + +.inner { + position: relative; + max-width: 640px; + padding: 20px 10px; + margin: 0 auto; +} + +#forkme_banner { + display: block; + position: absolute; + top:0; + right: 10px; + z-index: 10; + padding: 10px 50px 10px 10px; + color: #fff; + background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%; + font-weight: 700; + box-shadow: 0 0 10px rgba(0,0,0,.5); + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +#header_wrap { + background: #212121; + background: -moz-linear-gradient(top, #373737, #212121); + background: -webkit-linear-gradient(top, #373737, #212121); + background: -ms-linear-gradient(top, #373737, #212121); + background: -o-linear-gradient(top, #373737, #212121); + background: linear-gradient(top, #373737, #212121); +} + +#header_wrap .inner { + padding: 50px 10px 30px 10px; +} + +#project_title { + margin: 0; + color: #fff; + font-size: 42px; + font-weight: 700; + text-shadow: #111 0px 0px 10px; +} + +#project_tagline { + color: #fff; + font-size: 24px; + font-weight: 300; + background: none; + text-shadow: #111 0px 0px 10px; +} + +#downloads { + position: absolute; + width: 210px; + z-index: 10; + bottom: -40px; + right: 0; + height: 70px; + background: url('../images/icon_download.png') no-repeat 0% 90%; +} + +.zip_download_link { + display: block; + float: right; + width: 90px; + height:70px; + text-indent: -5000px; + overflow: hidden; + background: url(../images/sprite_download.png) no-repeat bottom left; +} + +.tar_download_link { + display: block; + float: right; + width: 90px; + height:70px; + text-indent: -5000px; + overflow: hidden; + background: url(../images/sprite_download.png) no-repeat bottom right; + margin-left: 10px; +} + +.zip_download_link:hover { + background: url(../images/sprite_download.png) no-repeat top left; +} + +.tar_download_link:hover { + background: url(../images/sprite_download.png) no-repeat top right; +} + +#main_content_wrap { + background: #f2f2f2; + border-top: 1px solid #111; + border-bottom: 1px solid #111; +} + +#main_content { + padding-top: 40px; +} + +#footer_wrap { + background: #212121; +} + + + +/******************************************************************************* +Small Device Styles +*******************************************************************************/ + +@media screen and (max-width: 480px) { + body { + font-size:14px; + } + + #downloads { + display: none; + } + + .inner { + min-width: 320px; + max-width: 480px; + } + + #project_title { + font-size: 32px; + } + + h1 { + font-size: 28px; + } + + h2 { + font-size: 24px; + } + + h3 { + font-size: 21px; + } + + h4 { + font-size: 18px; + } + + h5 { + font-size: 14px; + } + + h6 { + font-size: 12px; + } + + code, pre { + min-width: 320px; + max-width: 480px; + font-size: 11px; + } + +} diff --git a/docs/web/tp.html.foot b/docs/web/tp.html.foot new file mode 100644 index 00000000..90654f9c --- /dev/null +++ b/docs/web/tp.html.foot @@ -0,0 +1,21 @@ +

+Support

+ +
    +
  • Feel free to report a new bug or suggest features via github issues.
  • +
  • Tinyproxy developers hang out in #tinyproxy on irc.libera.chat.
  • +
+ + + + + + + + + + diff --git a/docs/web/tp.html.head b/docs/web/tp.html.head new file mode 100644 index 00000000..351564ce --- /dev/null +++ b/docs/web/tp.html.head @@ -0,0 +1,98 @@ + + + + + + + + + + + Tinyproxy + + + + + +
+
+ View on GitHub + +

Tinyproxy

+

lightweight http(s) proxy daemon

+ +
+
+ + +
+
+

+Tinyproxy

+ +

Tinyproxy is a light-weight HTTP/HTTPS proxy daemon for POSIX operating systems. Designed from the ground up to be fast and yet small, it is an ideal solution for use cases such as embedded deployments where a full featured HTTP proxy is required, but the system resources for a larger proxy are unavailable.

+ +

Tinyproxy is distributed using the GNU GPL license (version 2 or above).

+ +

+Features

+ +

Tinyproxy has a small footprint and requires very little in the way of system resources. The memory footprint tends to be around 2 MB with glibc, and the CPU load increases linearly with the number of simultaneous connections (depending on the speed of the connection). Thus, Tinyproxy can be run on an older machine, or on a network appliance such as a Linux-based broadband router, without any noticeable impact on performance.

+ +

Tinyproxy requires only a minimal POSIX environment to build and operate. It can use additional libraries to add functionality though.

+ +

Tinyproxy allows forwarding of HTTPS connections without modifying traffic in any way through the CONNECT method (see the ConnectPort directive, which you should disable, unless you want to restrict the users).

+ +

Tinyproxy supports being configured as a transparent proxy, so that a proxy can be used without requiring any client-side configuration. You can also use it as a reverse proxy front-end to your websites.

+ +

Using the AddHeader directive, you can add/insert HTTP headers to outgoing traffic (HTTP only).

+ +

If you're looking to build a custom web proxy, Tinyproxy is easy to modify to your custom needs. The source is straightforward, adhering to the KISS principle. As such, it can be used as a foundation for anything you may need a web proxy to do.

+ +

Tinyproxy has privacy features which can let you configure which HTTP headers should be allowed through, and which should be blocked. This allows you to restrict both what data comes to your web browser from the HTTP server (e.g., cookies), and to restrict what data is allowed through from your web browser to the HTTP server (e.g., version information). Note that these features do not affect HTTPS connections.

+ +

Using the remote monitoring facility, you can access proxy statistics from afar, letting you know exactly how busy the proxy is.

+ +

You can configure Tinyproxy to control access by only allowing requests from a certain subnet, or from a certain interface, thus ensuring that random, unauthorized people will not be using your proxy.

+ +

With a bit of configuration (specifically, making Tinyproxy created files owned by a non-root user and running it on a port greater than 1024), Tinyproxy can be made to run without any special privileges, thus minimizing the chance of system compromise. In fact, it is recommended to run it as a regular/restricted user. Furthermore, it was designed with an eye towards preventing buffer overflows. The simplicity of the code ensures it remains easy to spot such bugs.

+ +

+Downloads

+ +

Note that many distributions ship horribly outdated versions of tinyproxy, therefore it is recommended to compile it from source.

+ +
    +
  • On Red Hat Enterprise Linux, or its derivatives such as CentOS, install Tinyproxy from the EPEL repository by running yum install tinyproxy.
  • +
  • On Fedora, install Tinyproxy by running yum install tinyproxy.
  • +
  • On Debian and derived distributions, run apt-get install tinyproxy to install Tinyproxy.
  • +
  • For openSUSE run: zypper in tinyproxy
  • +
  • Arch users can install the Tinyproxy package from the community repository. Run pacman -S tinyproxy to install it.
  • +
  • FreeBSD, OpenBSD or NetBSD users can use the pkg_add utility to install the tinyproxy package.
  • +
  • Mac OS X users can check MacPorts to see if the Tinyproxy port there is recent enough.
  • +
+ +

If you feel that the Tinyproxy binary package in your operating system is not recent (likely), please contact the package maintainer for that particular operating system. If this fails, you can always compile the latest stable, or even better, the latest git master version, from source code.

+ +

We distribute Tinyproxy in source code form, and it has to be compiled in order to be used on your system. Please see the INSTALL file in the source code tree for build instructions. The current stable version of Tinyproxy is available on the releases page. The Tinyproxy NEWS file contains the release notes. You can verify the tarball using its PGP signature. You can also browse the older releases of Tinyproxy.

+ +

We use Git as the version control system for the Tinyproxy source code repository. To get a copy of the Tinyproxy repository, use the command:

+ +

git clone https://github.com/tinyproxy/tinyproxy.git

+ +

+Quickstart

+ +

The quickest way to get started is using a minimal config file like the below:

+ +

+Port 8888
+Listen 127.0.0.1
+Timeout 600
+Allow 127.0.0.1
+
+ +

And then simply run tinyproxy -d -c tinyproxy.conf as your current user. This starts tinyproxy in foreground mode with tinyproxy.conf as its config, while logging to stdout. Now, all programs supporting a HTTP proxy can use 127.0.0.1:8888 as a proxy. You can try it out using http_proxy=127.0.0.1:8888 curl example.com.

+ +

+Documentation

diff --git a/etc/Makefile.am b/etc/Makefile.am index 57a5c010..045baac3 100644 --- a/etc/Makefile.am +++ b/etc/Makefile.am @@ -12,6 +12,7 @@ edit = sed \ -e 's|@datarootdir[@]|$(datarootdir)|g' \ -e 's|@pkgsysconfdir[@]|$(pkgsysconfdir)|g' \ -e 's|@localstatedir[@]|$(localstatedir)|g' \ + -e 's|@runstatedir[@]|$(runstatedir)|g' \ -e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \ -e 's|@prefix[@]|$(prefix)|g' \ -e 's|@TINYPROXY_STATHOST[@]|$(TINYPROXY_STATHOST)|g' diff --git a/etc/tinyproxy.conf.in b/etc/tinyproxy.conf.in index c43266bb..b7d46a71 100644 --- a/etc/tinyproxy.conf.in +++ b/etc/tinyproxy.conf.in @@ -3,7 +3,7 @@ ## ## This example tinyproxy.conf file contains example settings ## with explanations in comments. For decriptions of all -## parameters, see the tinproxy.conf(5) manual page. +## parameters, see the tinyproxy.conf(5) manual page. ## # @@ -56,8 +56,8 @@ Timeout 600 # /usr/share/tinyproxy # /etc/tinyproxy # -#ErrorFile 404 "@pkgdatadir@/404.html" #ErrorFile 400 "@pkgdatadir@/400.html" +#ErrorFile 502 "@pkgdatadir@/502.html" #ErrorFile 503 "@pkgdatadir@/503.html" #ErrorFile 403 "@pkgdatadir@/403.html" #ErrorFile 408 "@pkgdatadir@/408.html" @@ -124,7 +124,7 @@ LogLevel Info # can be used for signalling purposes. # If not specified, no pidfile will be written. # -#PidFile "@localstatedir@/run/tinyproxy/tinyproxy.pid" +#PidFile "@runstatedir@/tinyproxy/tinyproxy.pid" # # XTinyproxy: Tell Tinyproxy to include the X-Tinyproxy header, which @@ -140,29 +140,37 @@ LogLevel Info # The upstream rules allow you to selectively route upstream connections # based on the host/domain of the site being accessed. # +# Syntax: upstream type (user:pass@)ip:port ("domain") +# Or: upstream none "domain" +# The parts in parens are optional. +# Possible types are http, socks4, socks5, none +# # For example: # # connection to test domain goes through testproxy -# upstream testproxy:8008 ".test.domain.invalid" -# upstream testproxy:8008 ".our_testbed.example.com" -# upstream testproxy:8008 "192.168.128.0/255.255.254.0" +# upstream http testproxy:8008 ".test.domain.invalid" +# upstream http testproxy:8008 ".our_testbed.example.com" +# upstream http testproxy:8008 "192.168.128.0/255.255.254.0" +# +# # upstream proxy using basic authentication +# upstream http user:pass@testproxy:8008 ".test.domain.invalid" # # # no upstream proxy for internal websites and unqualified hosts -# no upstream ".internal.example.com" -# no upstream "www.example.com" -# no upstream "10.0.0.0/8" -# no upstream "192.168.0.0/255.255.254.0" -# no upstream "." +# upstream none ".internal.example.com" +# upstream none "www.example.com" +# upstream none "10.0.0.0/8" +# upstream none "192.168.0.0/255.255.254.0" +# upstream none "." # # # connection to these boxes go through their DMZ firewalls -# upstream cust1_firewall:8008 "testbed_for_cust1" -# upstream cust2_firewall:8008 "testbed_for_cust2" +# upstream http cust1_firewall:8008 "testbed_for_cust1" +# upstream http cust2_firewall:8008 "testbed_for_cust2" # # # default upstream is internet firewall -# upstream firewall.internal.example.com:80 +# upstream http firewall.internal.example.com:80 # -# You may also use SOCKS4/SOCKS5 upstream proxies by using upstream4/upstream5: -# upstream4 127.0.0.1:9050 -# upstream5 socksproxy:1080 +# You may also use SOCKS4/SOCKS5 upstream proxies: +# upstream socks4 127.0.0.1:9050 +# upstream socks5 socksproxy:1080 # # The LAST matching rule wins the route decision. As you can see, you # can use a host, or a domain: @@ -172,7 +180,7 @@ LogLevel Info # IP/bits matches network/mask # IP/mask matches network/mask # -#Upstream some.remote.proxy:port +#Upstream http some.remote.proxy:port # # MaxClients: This is the absolute highest number of threads which will @@ -181,30 +189,6 @@ LogLevel Info # MaxClients 100 -# -# MinSpareServers/MaxSpareServers: These settings set the upper and -# lower limit for the number of spare servers which should be available. -# -# If the number of spare servers falls below MinSpareServers then new -# server processes will be spawned. If the number of servers exceeds -# MaxSpareServers then the extras will be killed off. -# -MinSpareServers 5 -MaxSpareServers 20 - -# -# StartServers: The number of servers to start initially. -# -StartServers 10 - -# -# MaxRequestsPerChild: The number of connections a thread will handle -# before it is killed. In practise this should be set to 0, which -# disables thread reaping. If you do notice problems with memory -# leakage, then set this to something like 10000. -# -MaxRequestsPerChild 0 - # # Allow: Customization of authorization controls. If there are any # access control keywords then the default action is to DENY. Otherwise, @@ -214,12 +198,20 @@ MaxRequestsPerChild 0 # tested against the controls based on order. # Allow 127.0.0.1 +Allow ::1 # BasicAuth: HTTP "Basic Authentication" for accessing the proxy. # If there are any entries specified, access is only granted for authenticated # users. #BasicAuth user password +# BasicAuthRealm : In case BasicAuth is configured, the "realm" information. +# "Proxy Authentication Required" status http 407 "error-response" can be +# customized. +# +# - defaults in code to "Tinyproxy" (PACKAGE_NAME), if not configured. +#BasicAuthRealm "Tinyproxy" + # # AddHeader: Adds the specified headers to outgoing HTTP requests that # Tinyproxy makes. Note that this option will not work for HTTPS @@ -255,10 +247,9 @@ ViaProxyName "tinyproxy" #FilterURLs On # -# FilterExtended: Use POSIX Extended regular expressions rather than -# basic. +# FilterType: Use bre (default), ere, or fnmatch for filtering. # -#FilterExtended On +#FilterType fnmatch # # FilterCaseSensitive: Use case sensitive regular expressions. @@ -336,6 +327,3 @@ ViaProxyName "tinyproxy" # If not set then no rewriting occurs. # #ReverseBaseURL "http://localhost:8888/" - - - diff --git a/scripts/Makefile.am b/scripts/Makefile.am new file mode 100644 index 00000000..4876c5d9 --- /dev/null +++ b/scripts/Makefile.am @@ -0,0 +1,2 @@ +EXTRA_DIST = \ + version.sh diff --git a/scripts/gen-authors.sh b/scripts/gen-authors.sh new file mode 100755 index 00000000..671f0c17 --- /dev/null +++ b/scripts/gen-authors.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)" +BASE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +AUTHORS_FILE="${BASE_DIR}/AUTHORS" + +type git > /dev/null || exit +test -d "${BASE_DIR}/.git" || exit + +git log --all --format='%aN' | sort -u > "${AUTHORS_FILE}" diff --git a/scripts/version.sh b/scripts/version.sh new file mode 100755 index 00000000..4ad08b61 --- /dev/null +++ b/scripts/version.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)" +GIT_DIR="${SCRIPT_DIR}/../.git" + +if test -d "${GIT_DIR}" ; then + if type git >/dev/null 2>&1 ; then + gitstr=$(git describe --tags --match '[0-9]*.[0-9]*.*' 2>/dev/null) + if test "x$?" != x0 ; then + sed 's/$/-git/' < VERSION + else + printf "%s\n" "$gitstr" | sed -e 's/-g/-git-/' + fi + else + sed 's/$/-git/' < VERSION + fi +else + cat VERSION +fi diff --git a/src/Makefile.am b/src/Makefile.am index 565eb4db..f9597b5a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,23 +17,23 @@ pkgsysconfdir = $(sysconfdir)/$(PACKAGE) -sbin_PROGRAMS = tinyproxy +bin_PROGRAMS = tinyproxy AM_CPPFLAGS = \ -DSYSCONFDIR=\"${pkgsysconfdir}\" \ -DLOCALSTATEDIR=\"${localstatedir}\" tinyproxy_SOURCES = \ + hostspec.c hostspec.h \ acl.c acl.h \ anonymous.c anonymous.h \ - authors.c authors.h \ buffer.c buffer.h \ child.c child.h \ common.h \ + conf-tokens.c conf-tokens.h \ conf.c conf.h \ conns.c conns.h \ daemon.c daemon.h \ - hashmap.c hashmap.h \ heap.c heap.h \ html-error.c html-error.h \ http-message.c http-message.h \ @@ -45,27 +45,27 @@ tinyproxy_SOURCES = \ text.c text.h \ main.c main.h \ utils.c utils.h \ - vector.c vector.h \ upstream.c upstream.h \ basicauth.c basicauth.h \ base64.c base64.h \ + sblist.c sblist.h \ + hsearch.c hsearch.h \ + pseudomap.c pseudomap.h \ + loop.c loop.h \ + mypoll.c mypoll.h \ connect-ports.c connect-ports.h EXTRA_tinyproxy_SOURCES = filter.c filter.h \ reverse-proxy.c reverse-proxy.h \ transparent-proxy.c transparent-proxy.h tinyproxy_DEPENDENCIES = @ADDITIONAL_OBJECTS@ -tinyproxy_LDADD = @ADDITIONAL_OBJECTS@ +tinyproxy_LDADD = @ADDITIONAL_OBJECTS@ -lpthread -EXTRA_DIST = \ - authors.xsl - -authors.c: $(top_srcdir)/authors.xml $(srcdir)/authors.xsl -if HAVE_XSLTPROC - $(AM_V_GEN) $(XSLTPROC) $(srcdir)/authors.xsl $< > $(@) || rm -f $(@) -else - @echo "*** xsltproc is required to regenerate $(@) ***"; exit 1; +if HAVE_GPERF +conf-tokens.c: conf-tokens-gperf.inc +conf-tokens-gperf.inc: conf-tokens.gperf + $(GPERF) $< > $@ endif -BUILT_SOURCES = \ - authors.c +EXTRA_DIST = conf-tokens.gperf conf-tokens-gperf.inc + diff --git a/src/acl.c b/src/acl.c index b7a334c9..2d141ba6 100644 --- a/src/acl.c +++ b/src/acl.c @@ -28,17 +28,8 @@ #include "log.h" #include "network.h" #include "sock.h" -#include "vector.h" - -#include - -/* Define how long an IPv6 address is in bytes (128 bits, 16 bytes) */ -#define IPV6_LEN 16 - -enum acl_type { - ACL_STRING, - ACL_NUMERIC -}; +#include "sblist.h" +#include "hostspec.h" /* * Hold the information about a particular access control. We store @@ -47,74 +38,17 @@ enum acl_type { */ struct acl_s { acl_access_t access; - enum acl_type type; - union { - char *string; - struct { - unsigned char network[IPV6_LEN]; - unsigned char mask[IPV6_LEN]; - } ip; - } address; + struct hostspec h; }; -/* - * Fills in the netmask array given a numeric value. - * - * Returns: - * 0 on success - * -1 on failure (invalid mask value) - * - */ -static int -fill_netmask_array (char *bitmask_string, int v6, - unsigned char array[], size_t len) -{ - unsigned int i; - unsigned long int mask; - char *endptr; - - errno = 0; /* to distinguish success/failure after call */ - mask = strtoul (bitmask_string, &endptr, 10); - - /* check for various conversion errors */ - if ((errno == ERANGE && mask == ULONG_MAX) - || (errno != 0 && mask == 0) || (endptr == bitmask_string)) - return -1; - - if (v6 == 0) { - /* The mask comparison is done as an IPv6 address, so - * convert to a longer mask in the case of IPv4 - * addresses. */ - mask += 12 * 8; - } - - /* check valid range for a bit mask */ - if (mask > (8 * len)) - return -1; - - /* we have a valid range to fill in the array */ - for (i = 0; i != len; ++i) { - if (mask >= 8) { - array[i] = 0xff; - mask -= 8; - } else if (mask > 0) { - array[i] = (unsigned char) (0xff << (8 - mask)); - mask = 0; - } else { - array[i] = 0; - } - } - - return 0; -} /** * If the access list has not been set up, create it. */ -static int init_access_list(vector_t *access_list) +static int init_access_list(acl_list_t *access_list) { if (!*access_list) { - *access_list = vector_create (); + *access_list = sblist_new(sizeof(struct acl_s), 16); if (!*access_list) { log_message (LOG_ERR, "Unable to allocate memory for access list"); @@ -135,80 +69,25 @@ static int init_access_list(vector_t *access_list) * 0 otherwise. */ int -insert_acl (char *location, acl_access_t access_type, vector_t *access_list) +insert_acl (char *location, acl_access_t access_type, acl_list_t *access_list) { struct acl_s acl; - int ret; - char *p, ip_dst[IPV6_LEN]; assert (location != NULL); - ret = init_access_list(access_list); - if (ret != 0) { + if (init_access_list(access_list) != 0) return -1; - } /* * Start populating the access control structure. */ memset (&acl, 0, sizeof (struct acl_s)); acl.access = access_type; + if(hostspec_parse(location, &acl.h) || acl.h.type == HST_NONE) + return -1; - /* - * Check for a valid IP address (the simplest case) first. - */ - if (full_inet_pton (location, ip_dst) > 0) { - acl.type = ACL_NUMERIC; - memcpy (acl.address.ip.network, ip_dst, IPV6_LEN); - memset (acl.address.ip.mask, 0xff, IPV6_LEN); - } else { - int i; - - /* - * At this point we're either a hostname or an - * IP address with a slash. - */ - p = strchr (location, '/'); - if (p != NULL) { - char dst[sizeof(struct in6_addr)]; - int v6; - - /* - * We have a slash, so it's intended to be an - * IP address with mask - */ - *p = '\0'; - if (full_inet_pton (location, ip_dst) <= 0) - return -1; - - acl.type = ACL_NUMERIC; - - /* Check if the IP address before the netmask is - * an IPv6 address */ - if (inet_pton(AF_INET6, location, dst) > 0) - v6 = 1; - else - v6 = 0; - - if (fill_netmask_array - (p + 1, v6, &(acl.address.ip.mask[0]), IPV6_LEN) - < 0) - return -1; - - for (i = 0; i < IPV6_LEN; i++) - acl.address.ip.network[i] = ip_dst[i] & - acl.address.ip.mask[i]; - } else { - /* In all likelihood a string */ - acl.type = ACL_STRING; - acl.address.string = safestrdup (location); - if (!acl.address.string) - return -1; - } - } - - ret = vector_append (*access_list, &acl, sizeof (struct acl_s)); - return ret; + if(!sblist_add(*access_list, &acl)) return -1; + return 0; } /* @@ -221,28 +100,27 @@ insert_acl (char *location, acl_access_t access_type, vector_t *access_list) * -1 if no tests match, so skip */ static int -acl_string_processing (struct acl_s *acl, - const char *ip_address, const char *string_address) +acl_string_processing (struct acl_s *acl, const char *ip_address, + union sockaddr_union *addr, char *string_addr) { int match; struct addrinfo hints, *res, *ressave; size_t test_length, match_length; char ipbuf[512]; - assert (acl && acl->type == ACL_STRING); + assert (acl && acl->h.type == HST_STRING); assert (ip_address && strlen (ip_address) > 0); - assert (string_address && strlen (string_address) > 0); /* * If the first character of the ACL string is a period, we need to * do a string based test only; otherwise, we can do a reverse * lookup test as well. */ - if (acl->address.string[0] != '.') { + if (acl->h.address.string[0] != '.') { memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - if (getaddrinfo (acl->address.string, NULL, &hints, &res) != 0) + if (getaddrinfo (acl->h.address.string, NULL, &hints, &res) != 0) goto STRING_TEST; ressave = res; @@ -267,8 +145,16 @@ acl_string_processing (struct acl_s *acl, } STRING_TEST: - test_length = strlen (string_address); - match_length = strlen (acl->address.string); + if(string_addr[0] == 0) { + /* only do costly hostname resolution when it is absolutely needed, + and only once */ + if(getnameinfo ((void *) addr, sizeof (*addr), + string_addr, HOSTNAME_LENGTH, NULL, 0, 0) != 0) + return -1; + } + + test_length = strlen (string_addr); + match_length = strlen (acl->h.address.string); /* * If the string length is shorter than AC string, return a -1 so @@ -278,8 +164,8 @@ acl_string_processing (struct acl_s *acl, return -1; if (strcasecmp - (string_address + (test_length - match_length), - acl->address.string) == 0) { + (string_addr + (test_length - match_length), + acl->h.address.string) == 0) { if (acl->access == ACL_DENY) return 0; else @@ -298,20 +184,16 @@ acl_string_processing (struct acl_s *acl, * 0 IP address is denied * -1 neither allowed nor denied. */ -static int check_numeric_acl (const struct acl_s *acl, const char *ip) +static int check_numeric_acl (const struct acl_s *acl, uint8_t addr[IPV6_LEN]) { - uint8_t addr[IPV6_LEN], x, y; + uint8_t x, y; int i; - assert (acl && acl->type == ACL_NUMERIC); - assert (ip && strlen (ip) > 0); - - if (full_inet_pton (ip, &addr) <= 0) - return -1; + assert (acl && acl->h.type == HST_NUMERIC); for (i = 0; i != IPV6_LEN; ++i) { - x = addr[i] & acl->address.ip.mask[i]; - y = acl->address.ip.network[i]; + x = addr[i] & acl->h.address.ip.mask[i]; + y = acl->h.address.ip.network[i]; /* If x and y don't match, the IP addresses don't match */ if (x != y) @@ -329,14 +211,18 @@ static int check_numeric_acl (const struct acl_s *acl, const char *ip) * 1 if allowed * 0 if denied */ -int check_acl (const char *ip, const char *host, vector_t access_list) +int check_acl (const char *ip, union sockaddr_union *addr, acl_list_t access_list) { struct acl_s *acl; - int perm = 0; + int perm = 0, is_numeric_addr; size_t i; + char string_addr[HOSTNAME_LENGTH]; + uint8_t numeric_addr[IPV6_LEN]; assert (ip != NULL); - assert (host != NULL); + assert (addr != NULL); + + string_addr[0] = 0; /* * If there is no access list allow everything. @@ -344,17 +230,26 @@ int check_acl (const char *ip, const char *host, vector_t access_list) if (!access_list) return 1; - for (i = 0; i != (size_t) vector_length (access_list); ++i) { - acl = (struct acl_s *) vector_getentry (access_list, i, NULL); - switch (acl->type) { - case ACL_STRING: - perm = acl_string_processing (acl, ip, host); + is_numeric_addr = (full_inet_pton (ip, &numeric_addr) > 0); + + for (i = 0; i < sblist_getsize (access_list); ++i) { + acl = sblist_get (access_list, i); + switch (acl->h.type) { + case HST_STRING: + perm = acl_string_processing (acl, ip, addr, string_addr); break; - case ACL_NUMERIC: + case HST_NUMERIC: if (ip[0] == '\0') continue; - perm = check_numeric_acl (acl, ip); + + perm = is_numeric_addr + ? check_numeric_acl (acl, numeric_addr) + : -1; + break; + + case HST_NONE: + perm = -1; break; } @@ -371,12 +266,12 @@ int check_acl (const char *ip, const char *host, vector_t access_list) /* * Deny all connections by default. */ - log_message (LOG_NOTICE, "Unauthorized connection from \"%s\" [%s].", - host, ip); + log_message (LOG_NOTICE, "Unauthorized connection from \"%s\".", + ip); return 0; } -void flush_access_list (vector_t access_list) +void flush_access_list (acl_list_t access_list) { struct acl_s *acl; size_t i; @@ -390,12 +285,12 @@ void flush_access_list (vector_t access_list) * before we can free the acl entries themselves. * A hierarchical memory system would be great... */ - for (i = 0; i != (size_t) vector_length (access_list); ++i) { - acl = (struct acl_s *) vector_getentry (access_list, i, NULL); - if (acl->type == ACL_STRING) { - safefree (acl->address.string); + for (i = 0; i < sblist_getsize (access_list); ++i) { + acl = sblist_get (access_list, i); + if (acl->h.type == HST_STRING) { + safefree (acl->h.address.string); } } - vector_delete (access_list); + sblist_free (access_list); } diff --git a/src/acl.h b/src/acl.h index 2d11cef1..867e6f0b 100644 --- a/src/acl.h +++ b/src/acl.h @@ -21,14 +21,16 @@ #ifndef TINYPROXY_ACL_H #define TINYPROXY_ACL_H -#include "vector.h" +#include "sblist.h" +#include "sock.h" typedef enum { ACL_ALLOW, ACL_DENY } acl_access_t; +typedef sblist* acl_list_t; extern int insert_acl (char *location, acl_access_t access_type, - vector_t *access_list); -extern int check_acl (const char *ip_address, const char *string_address, - vector_t access_list); -extern void flush_access_list (vector_t access_list); + acl_list_t *access_list); +extern int check_acl (const char *ip_address, union sockaddr_union *addr, + acl_list_t access_list); +extern void flush_access_list (acl_list_t access_list); #endif diff --git a/src/anonymous.c b/src/anonymous.c index 3049acf3..91e490c4 100644 --- a/src/anonymous.c +++ b/src/anonymous.c @@ -23,26 +23,26 @@ #include "main.h" #include "anonymous.h" -#include "hashmap.h" +#include "hsearch.h" #include "heap.h" #include "log.h" #include "conf.h" -short int is_anonymous_enabled (void) +short int is_anonymous_enabled (struct config_s *conf) { - return (config.anonymous_map != NULL) ? 1 : 0; + return (conf->anonymous_map != NULL) ? 1 : 0; } /* * Search for the header. This function returns a positive value greater than * zero if the string was found, zero if it wasn't and negative upon error. */ -int anonymous_search (const char *s) +int anonymous_search (struct config_s *conf, const char *s) { assert (s != NULL); - assert (config.anonymous_map != NULL); + assert (conf->anonymous_map != NULL); - return hashmap_search (config.anonymous_map, s); + return !!htab_find (conf->anonymous_map, s); } /* @@ -51,23 +51,21 @@ int anonymous_search (const char *s) * Return -1 if there is an error, otherwise a 0 is returned if the insert was * successful. */ -int anonymous_insert (const char *s) +int anonymous_insert (struct config_s *conf, char *s) { - char data = 1; - assert (s != NULL); - if (!config.anonymous_map) { - config.anonymous_map = hashmap_create (32); - if (!config.anonymous_map) + if (!conf->anonymous_map) { + conf->anonymous_map = htab_create (32); + if (!conf->anonymous_map) return -1; } - if (hashmap_search (config.anonymous_map, s) > 0) { - /* The key was already found, so return a positive number. */ + if (htab_find (conf->anonymous_map, s)) { + /* The key was already found. */ return 0; } /* Insert the new key */ - return hashmap_insert (config.anonymous_map, s, &data, sizeof (data)); + return htab_insert (conf->anonymous_map, s, HTV_N(1)) ? 0 : -1; } diff --git a/src/anonymous.h b/src/anonymous.h index 0ca980e1..78ce7710 100644 --- a/src/anonymous.h +++ b/src/anonymous.h @@ -21,8 +21,8 @@ #ifndef _TINYPROXY_ANONYMOUS_H_ #define _TINYPROXY_ANONYMOUS_H_ -extern short int is_anonymous_enabled (void); -extern int anonymous_search (const char *s); -extern int anonymous_insert (const char *s); +extern short int is_anonymous_enabled (struct config_s *conf); +extern int anonymous_search (struct config_s *conf, const char *s); +extern int anonymous_insert (struct config_s *conf, char *s); #endif diff --git a/src/authors.c b/src/authors.c deleted file mode 100644 index 496df93a..00000000 --- a/src/authors.c +++ /dev/null @@ -1,50 +0,0 @@ - -/* NOTE: This file is auto-generated from authors.xml, do not edit it. */ - -#include "authors.h" - -static const char * const authors[] = -{ - "Andrew Stribblehill", - "Chris Lightfoot", - "Daniel Egger", - "David Shanks", - "Dmitry Semyonov", - "George Talusan", - "James E. Flemer", - "Jeremy Hinegardner", - "John van der Kamp", - "Jordi Mallach", - "Kim Holviala", - "Mathew Mrosko", - "Matthew Dempsky", - "Michael Adam", - "Moritz Muehlenhoff", - "Mukund Sivaraman", - "Petr Lampa", - "Robert James Kaes", - "Steven Young", - NULL -}; - -static const char * const documenters[] = -{ - "Marc Silver", - "Michael Adam", - "Mukund Sivaraman", - "Robert James Kaes", - "Steven Young", - NULL -}; - -const char * const * -authors_get_authors (void) -{ - return authors; -} - -const char * const * -authors_get_documenters (void) -{ - return documenters; -} diff --git a/src/authors.h b/src/authors.h deleted file mode 100644 index 10e5110e..00000000 --- a/src/authors.h +++ /dev/null @@ -1,30 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2010 Mukund Sivaraman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef __AUTHORS_H__ -#define __AUTHORS_H__ - -#include "common.h" - -const char * const * -authors_get_authors (void); - -const char * const * -authors_get_documenters (void); - -#endif /* __AUTHORS_H__ */ diff --git a/src/authors.xsl b/src/authors.xsl deleted file mode 100644 index 1388b9a7..00000000 --- a/src/authors.xsl +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - -/* NOTE: This file is auto-generated from authors.xml, do not edit it. */ - -#include "authors.h" - -static const char * const authors[] = -{ - - - - - NULL -}; - - - -static const char * const documenters[] = -{ - - - - - NULL -}; - -const char * const * -authors_get_authors (void) -{ - return authors; -} - -const char * const * -authors_get_documenters (void) -{ - return documenters; -} - - - - "", - - - diff --git a/src/basicauth.c b/src/basicauth.c index 4360a1a0..ed0553b8 100644 --- a/src/basicauth.c +++ b/src/basicauth.c @@ -27,34 +27,46 @@ #include "base64.h" /* - * Add entry to the basicauth list + * Create basic-auth token in buf. + * Returns strlen of token on success, + * -1 if user/pass missing + * 0 if user/pass too long */ -void basicauth_add (vector_t authlist, - const char *user, const char *pass) +ssize_t basicauth_string(const char *user, const char *pass, + char *buf, size_t bufsize) { char tmp[256+2]; - char b[BASE64ENC_BYTES((sizeof tmp)-1) + 1]; int l; - size_t bl; + if (!user || !pass) return -1; + l = snprintf(tmp, sizeof tmp, "%s:%s", user, pass); + if (l < 0 || l >= (ssize_t) sizeof tmp) return 0; + if (bufsize < (BASE64ENC_BYTES((unsigned)l) + 1)) return 0; + base64enc(buf, tmp, l); + return BASE64ENC_BYTES(l); +} - if (user == NULL || pass == NULL) { +/* + * Add entry to the basicauth list + */ +void basicauth_add (sblist *authlist, + const char *user, const char *pass) +{ + char b[BASE64ENC_BYTES((256+2)-1) + 1], *s; + ssize_t ret; + + ret = basicauth_string(user, pass, b, sizeof b); + if (ret == -1) { log_message (LOG_WARNING, "Illegal basicauth rule: missing user or pass"); return; - } - - l = snprintf(tmp, sizeof tmp, "%s:%s", user, pass); - - if(l >= (ssize_t) sizeof tmp) { + } else if (ret == 0) { log_message (LOG_WARNING, - "User / pass in basicauth rule too long"); + "User / pass in basicauth rule too long"); return; } - base64enc(b, tmp, l); - bl = BASE64ENC_BYTES(l) + 1; - - if (vector_append(authlist, b, bl) == -ENOMEM) { + if (!(s = safestrdup(b)) || !sblist_add(authlist, &s)) { + safefree(s); log_message (LOG_ERR, "Unable to allocate memory in basicauth_add()"); return; @@ -69,19 +81,16 @@ void basicauth_add (vector_t authlist, * is in the basicauth list. * return 1 on success, 0 on failure. */ -int basicauth_check (vector_t authlist, const char *authstring) +int basicauth_check (sblist *authlist, const char *authstring) { - ssize_t vl, i; - size_t al, el; - const char* entry; + size_t i; + char** entry; - vl = vector_length (authlist); - if (vl == -EINVAL) return 0; + if (!authlist) return 0; - al = strlen (authstring); - for (i = 0; i < vl; i++) { - entry = vector_getentry (authlist, i, &el); - if (strncmp (authstring, entry, al) == 0) + for (i = 0; i < sblist_getsize(authlist); i++) { + entry = sblist_get (authlist, i); + if (entry && strcmp (authstring, *entry) == 0) return 1; } return 0; diff --git a/src/basicauth.h b/src/basicauth.h index e9366bb5..ef25b665 100644 --- a/src/basicauth.h +++ b/src/basicauth.h @@ -21,11 +21,15 @@ #ifndef TINYPROXY_BASICAUTH_H #define TINYPROXY_BASICAUTH_H -#include "vector.h" +#include +#include "sblist.h" -extern void basicauth_add (vector_t authlist, +extern ssize_t basicauth_string(const char *user, const char *pass, + char *buf, size_t bufsize); + +extern void basicauth_add (sblist *authlist, const char *user, const char *pass); -extern int basicauth_check (vector_t authlist, const char *authstring); +extern int basicauth_check (sblist *authlist, const char *authstring); #endif diff --git a/src/buffer.c b/src/buffer.c index b3381838..4cf15a0f 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -241,13 +241,6 @@ ssize_t read_buffer (int fd, struct buffer_s * buffptr) bytesin = -1; } else { switch (errno) { -#ifdef EWOULDBLOCK - case EWOULDBLOCK: -#else -# ifdef EAGAIN - case EAGAIN: -# endif -#endif case EINTR: bytesin = 0; break; @@ -295,13 +288,6 @@ ssize_t write_buffer (int fd, struct buffer_s * buffptr) return bytessent; } else { switch (errno) { -#ifdef EWOULDBLOCK - case EWOULDBLOCK: -#else -# ifdef EAGAIN - case EAGAIN: -# endif -#endif case EINTR: return 0; case ENOBUFS: diff --git a/src/child.c b/src/child.c index effb2ae4..8bae89b2 100644 --- a/src/child.c +++ b/src/child.c @@ -31,269 +31,145 @@ #include "sock.h" #include "utils.h" #include "conf.h" +#include "sblist.h" +#include "loop.h" +#include "conns.h" +#include "mypoll.h" +#include -static vector_t listen_fds; +static sblist* listen_fds; -/* - * Stores the internal data needed for each child (connection) - */ -enum child_status_t { T_EMPTY, T_WAITING, T_CONNECTED }; -struct child_s { - pid_t tid; - unsigned int connects; - enum child_status_t status; +struct client { + union sockaddr_union addr; }; -/* - * A pointer to an array of children. A certain number of children are - * created when the program is started. - */ -static struct child_s *child_ptr; - -static struct child_config_s { - unsigned int maxclients, maxrequestsperchild; - unsigned int maxspareservers, minspareservers, startservers; -} child_config; - -static unsigned int *servers_waiting; /* servers waiting for a connection */ - -/* - * Lock/Unlock the "servers_waiting" variable so that two children cannot - * modify it at the same time. - */ -#define SERVER_COUNT_LOCK() _child_lock_wait() -#define SERVER_COUNT_UNLOCK() _child_lock_release() - -/* START OF LOCKING SECTION */ - -/* - * These variables are required for the locking mechanism. Also included - * are the "private" functions for locking/unlocking. - */ -static struct flock lock_it, unlock_it; -static int lock_fd = -1; - -static void _child_lock_init (void) -{ - char lock_file[] = "/tmp/tinyproxy.servers.lock.XXXXXX"; - - /* Only allow u+rw bits. This may be required for some versions - * of glibc so that mkstemp() doesn't make us vulnerable. - */ - umask (0177); - - lock_fd = mkstemp (lock_file); - unlink (lock_file); - - lock_it.l_type = F_WRLCK; - lock_it.l_whence = SEEK_SET; - lock_it.l_start = 0; - lock_it.l_len = 0; - - unlock_it.l_type = F_UNLCK; - unlock_it.l_whence = SEEK_SET; - unlock_it.l_start = 0; - unlock_it.l_len = 0; -} - -static void _child_lock_wait (void) -{ - int rc; - - while ((rc = fcntl (lock_fd, F_SETLKW, &lock_it)) < 0) { - if (errno == EINTR) - continue; - else - return; - } -} +struct child { + pthread_t thread; + struct client client; + struct conn_s conn; + volatile int done; +}; -static void _child_lock_release (void) +static void* child_thread(void* data) { - if (fcntl (lock_fd, F_SETLKW, &unlock_it) < 0) - return; + struct child *c = data; + handle_connection (&c->conn, &c->client.addr); + c->done = 1; + return NULL; } -/* END OF LOCKING SECTION */ - -#define SERVER_INC() do { \ - SERVER_COUNT_LOCK(); \ - ++(*servers_waiting); \ - DEBUG2("INC: servers_waiting: %d", *servers_waiting); \ - SERVER_COUNT_UNLOCK(); \ -} while (0) - -#define SERVER_DEC() do { \ - SERVER_COUNT_LOCK(); \ - assert(*servers_waiting > 0); \ - --(*servers_waiting); \ - DEBUG2("DEC: servers_waiting: %d", *servers_waiting); \ - SERVER_COUNT_UNLOCK(); \ -} while (0) - -/* - * Set the configuration values for the various child related settings. - */ -short int child_configure (child_config_t type, unsigned int val) -{ - switch (type) { - case CHILD_MAXCLIENTS: - child_config.maxclients = val; - break; - case CHILD_MAXSPARESERVERS: - child_config.maxspareservers = val; - break; - case CHILD_MINSPARESERVERS: - child_config.minspareservers = val; - break; - case CHILD_STARTSERVERS: - child_config.startservers = val; - break; - case CHILD_MAXREQUESTSPERCHILD: - child_config.maxrequestsperchild = val; - break; - default: - DEBUG2 ("Invalid type (%d)", type); - return -1; - } - - return 0; -} +static sblist *childs; -/** - * child signal handler for sighup - */ -static void child_sighup_handler (int sig) +static void collect_threads(void) { - if (sig == SIGHUP) { - /* - * Ignore the return value of reload_config for now. - * This should actually be handled somehow... - */ - reload_config (); - -#ifdef FILTER_ENABLE - filter_reload (); -#endif /* FILTER_ENABLE */ - } + size_t i; + for (i = 0; i < sblist_getsize(childs); ) { + struct child *c = *((struct child**)sblist_get(childs, i)); + if (c->done) { + pthread_join(c->thread, 0); + sblist_delete(childs, i); + safefree(c); + } else i++; + } } /* - * This is the main (per child) loop. + * This is the main loop accepting new connections. */ -static void child_main (struct child_s *ptr) +void child_main_loop (void) { int connfd; - struct sockaddr *cliaddr; + union sockaddr_union cliaddr_storage; + struct sockaddr *cliaddr = (void*) &cliaddr_storage; socklen_t clilen; - fd_set rfds; - int maxfd = 0; + int nfds = sblist_getsize(listen_fds); + pollfd_struct *fds = safecalloc(nfds, sizeof *fds); ssize_t i; - int ret; + int ret, listenfd, was_full = 0; + pthread_attr_t *attrp, attr; + struct child *child; - cliaddr = (struct sockaddr *) - safemalloc (sizeof(struct sockaddr_storage)); - if (!cliaddr) { - log_message (LOG_CRIT, - "Could not allocate memory for child address."); - exit (0); - } + childs = sblist_new(sizeof (struct child*), config->maxclients); - ptr->connects = 0; - srand(time(NULL)); + for (i = 0; i < nfds; i++) { + int *fd = sblist_get(listen_fds, i); + fds[i].fd = *fd; + fds[i].events |= MYPOLL_READ; + } /* * We have to wait for connections on multiple fds, - * so use select. + * so use select/poll/whatever. */ + while (!config->quit) { - FD_ZERO(&rfds); - - for (i = 0; i < vector_length(listen_fds); i++) { - int *fd = (int *) vector_getentry(listen_fds, i, NULL); + collect_threads(); - ret = socket_nonblocking(*fd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set the listening " - "socket %d to non-blocking: %s", - fd, strerror(errno)); - exit(1); + if (sblist_getsize(childs) >= config->maxclients) { + if (!was_full) + log_message (LOG_WARNING, + "Maximum number of connections reached. " + "Refusing new connections."); + was_full = 1; + usleep(16); + continue; } - FD_SET(*fd, &rfds); - maxfd = max(maxfd, *fd); - } + was_full = 0; + listenfd = -1; - while (!config.quit) { - int listenfd = -1; + /* Handle log rotation if it was requested */ + if (received_sighup) { - ptr->status = T_WAITING; + reload_config (1); - clilen = sizeof(struct sockaddr_storage); +#ifdef FILTER_ENABLE + filter_reload (); +#endif /* FILTER_ENABLE */ + + received_sighup = FALSE; + } + + ret = mypoll(fds, nfds, -1); - ret = select(maxfd + 1, &rfds, NULL, NULL, NULL); if (ret == -1) { if (errno == EINTR) { continue; } - log_message (LOG_ERR, "error calling select: %s", + log_message (LOG_ERR, "error calling " SELECT_OR_POLL ": %s", strerror(errno)); - exit(1); + continue; } else if (ret == 0) { - log_message (LOG_WARNING, "Strange: select returned 0 " + log_message (LOG_WARNING, "Strange: " SELECT_OR_POLL " returned 0 " "but we did not specify a timeout..."); continue; } - for (i = 0; i < vector_length(listen_fds); i++) { - int *fd = (int *) vector_getentry(listen_fds, i, NULL); - - if (FD_ISSET(*fd, &rfds)) { + for (i = 0; i < nfds; i++) { + if (fds[i].revents & MYPOLL_READ) { /* * only accept the connection on the first * fd that we find readable. - fair? */ - listenfd = *fd; + listenfd = fds[i].fd; break; } } if (listenfd == -1) { log_message(LOG_WARNING, "Strange: None of our listen " - "fds was readable after select"); + "fds was readable after " SELECT_OR_POLL); continue; } - ret = socket_blocking(listenfd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set listening " - "socket %d to blocking for accept: %s", - listenfd, strerror(errno)); - exit(1); - } - /* * We have a socket that is readable. * Continue handling this connection. */ + clilen = sizeof(cliaddr_storage); connfd = accept (listenfd, cliaddr, &clilen); -#ifndef NDEBUG - /* - * Enable the TINYPROXY_DEBUG environment variable if you - * want to use the GDB debugger. - */ - if (getenv ("TINYPROXY_DEBUG")) { - /* Pause for 10 seconds to allow us to connect debugger */ - fprintf (stderr, - "Process has accepted connection: %ld\n", - (long int) ptr->tid); - sleep (10); - fprintf (stderr, "Continuing process: %ld\n", - (long int) ptr->tid); - } -#endif /* * Make sure no error occurred... @@ -305,225 +181,41 @@ static void child_main (struct child_s *ptr) continue; } - ptr->status = T_CONNECTED; - - SERVER_DEC (); - - handle_connection (connfd); - ptr->connects++; - - if (child_config.maxrequestsperchild != 0) { - DEBUG2 ("%u connections so far...", ptr->connects); - - if (ptr->connects == child_config.maxrequestsperchild) { - log_message (LOG_NOTICE, - "Child has reached MaxRequestsPerChild (%u). " - "Killing child.", ptr->connects); - break; - } - } - - SERVER_COUNT_LOCK (); - if (*servers_waiting > child_config.maxspareservers) { - /* - * There are too many spare children, kill ourself - * off. - */ - log_message (LOG_NOTICE, - "Waiting servers (%d) exceeds MaxSpareServers (%d). " - "Killing child.", - *servers_waiting, - child_config.maxspareservers); - SERVER_COUNT_UNLOCK (); - - break; - } else { - SERVER_COUNT_UNLOCK (); + child = safecalloc(1, sizeof(struct child)); + if (!child) { +oom: + close(connfd); + log_message (LOG_CRIT, + "Could not allocate memory for child."); + usleep(16); /* prevent 100% CPU usage in OOM situation */ + continue; } - SERVER_INC (); - } - - ptr->status = T_EMPTY; - - safefree (cliaddr); - exit (0); -} - -/* - * Fork a child "child" (or in our case a process) and then start up the - * child_main() function. - */ -static pid_t child_make (struct child_s *ptr) -{ - pid_t pid; - - if ((pid = fork ()) > 0) - return pid; /* parent */ - - /* - * Reset the SIGNALS so that the child can be reaped. - */ - set_signal_handler (SIGCHLD, SIG_DFL); - set_signal_handler (SIGTERM, SIG_DFL); - set_signal_handler (SIGHUP, child_sighup_handler); - - child_main (ptr); /* never returns */ - return -1; -} - -/* - * Create a pool of children to handle incoming connections - */ -short int child_pool_create (void) -{ - unsigned int i; - - /* - * Make sure the number of MaxClients is not zero, since this - * variable determines the size of the array created for children - * later on. - */ - if (child_config.maxclients == 0) { - log_message (LOG_ERR, - "child_pool_create: \"MaxClients\" must be " - "greater than zero."); - return -1; - } - if (child_config.startservers == 0) { - log_message (LOG_ERR, - "child_pool_create: \"StartServers\" must be " - "greater than zero."); - return -1; - } - - child_ptr = - (struct child_s *) calloc_shared_memory (child_config.maxclients, - sizeof (struct child_s)); - if (!child_ptr) { - log_message (LOG_ERR, - "Could not allocate memory for children."); - return -1; - } - - servers_waiting = - (unsigned int *) malloc_shared_memory (sizeof (unsigned int)); - if (servers_waiting == MAP_FAILED) { - log_message (LOG_ERR, - "Could not allocate memory for child counting."); - return -1; - } - *servers_waiting = 0; - - /* - * Create a "locking" file for use around the servers_waiting - * variable. - */ - _child_lock_init (); - - if (child_config.startservers > child_config.maxclients) { - log_message (LOG_WARNING, - "Can not start more than \"MaxClients\" servers. " - "Starting %u servers instead.", - child_config.maxclients); - child_config.startservers = child_config.maxclients; - } - - for (i = 0; i != child_config.maxclients; i++) { - child_ptr[i].status = T_EMPTY; - child_ptr[i].connects = 0; - } - - for (i = 0; i != child_config.startservers; i++) { - DEBUG2 ("Trying to create child %d of %d", i + 1, - child_config.startservers); - child_ptr[i].status = T_WAITING; - child_ptr[i].tid = child_make (&child_ptr[i]); + child->done = 0; - if (child_ptr[i].tid < 0) { - log_message (LOG_WARNING, - "Could not create child number %d of %d", - i, child_config.startservers); - return -1; - } else { - log_message (LOG_INFO, - "Creating child number %d of %d ...", - i + 1, child_config.startservers); - - SERVER_INC (); + if (!sblist_add(childs, &child)) { + free(child); + goto oom; } - } - log_message (LOG_INFO, "Finished creating all children."); + conn_struct_init(&child->conn); + child->conn.client_fd = connfd; - return 0; -} + memcpy(&child->client.addr, &cliaddr_storage, sizeof(cliaddr_storage)); -/* - * Keep the proper number of servers running. This is the birth of the - * servers. It monitors this at least once a second. - */ -void child_main_loop (void) -{ - unsigned int i; - - while (1) { - if (config.quit) - return; - - /* If there are not enough spare servers, create more */ - SERVER_COUNT_LOCK (); - if (*servers_waiting < child_config.minspareservers) { - log_message (LOG_NOTICE, - "Waiting servers (%d) is less than MinSpareServers (%d). " - "Creating new child.", - *servers_waiting, - child_config.minspareservers); - - SERVER_COUNT_UNLOCK (); - - for (i = 0; i != child_config.maxclients; i++) { - if (child_ptr[i].status == T_EMPTY) { - child_ptr[i].status = T_WAITING; - child_ptr[i].tid = - child_make (&child_ptr[i]); - if (child_ptr[i].tid < 0) { - log_message (LOG_NOTICE, - "Could not create child"); - - child_ptr[i].status = T_EMPTY; - break; - } - - SERVER_INC (); - - break; - } - } - } else { - SERVER_COUNT_UNLOCK (); + attrp = 0; + if (pthread_attr_init(&attr) == 0) { + attrp = &attr; + pthread_attr_setstacksize(attrp, 256*1024); } - sleep (5); - - /* Handle log rotation if it was requested */ - if (received_sighup) { - /* - * Ignore the return value of reload_config for now. - * This should actually be handled somehow... - */ - reload_config (); - -#ifdef FILTER_ENABLE - filter_reload (); -#endif /* FILTER_ENABLE */ - - /* propagate filter reload to all children */ - child_kill_children (SIGHUP); - - received_sighup = FALSE; - } + if (pthread_create(&child->thread, attrp, child_thread, child) != 0) { + sblist_delete(childs, sblist_getsize(childs) -1); + free(child); + goto oom; + } } + safefree(fds); } /* @@ -531,27 +223,48 @@ void child_main_loop (void) */ void child_kill_children (int sig) { - unsigned int i; - - for (i = 0; i != child_config.maxclients; i++) { - if (child_ptr[i].status != T_EMPTY) - kill (child_ptr[i].tid, sig); - } + size_t i, tries = 0; + + if (sig != SIGTERM) return; + log_message (LOG_INFO, + "trying to bring down %zu threads...", + sblist_getsize(childs) + ); + + +again: + for (i = 0; i < sblist_getsize(childs); i++) { + struct child *c = *((struct child**)sblist_get(childs, i)); + if (!c->done) pthread_kill(c->thread, SIGCHLD); + } + usleep(8192); + collect_threads(); + if (sblist_getsize(childs) != 0) + if(tries++ < 8) goto again; + if (sblist_getsize(childs) != 0) + log_message (LOG_CRIT, + "child_kill_children: %zu threads still alive!", + sblist_getsize(childs) + ); } +void child_free_children(void) { + sblist_free(childs); + childs = 0; +} /** * Listen on the various configured interfaces */ -int child_listening_sockets(vector_t listen_addrs, uint16_t port) +int child_listening_sockets(sblist *listen_addrs, uint16_t port) { int ret; - ssize_t i; + size_t i; assert (port > 0); if (listen_fds == NULL) { - listen_fds = vector_create(); + listen_fds = sblist_new(sizeof(int), 16); if (listen_fds == NULL) { log_message (LOG_ERR, "Could not create the list " "of listening fds"); @@ -559,8 +272,7 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port) } } - if ((listen_addrs == NULL) || - (vector_length(listen_addrs) == 0)) + if (!listen_addrs || !sblist_getsize(listen_addrs)) { /* * no Listen directive: @@ -570,17 +282,17 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port) return ret; } - for (i = 0; i < vector_length(listen_addrs); i++) { - const char *addr; + for (i = 0; i < sblist_getsize(listen_addrs); i++) { + char **addr; - addr = (char *)vector_getentry(listen_addrs, i, NULL); - if (addr == NULL) { + addr = sblist_get(listen_addrs, i); + if (!addr || !*addr) { log_message(LOG_WARNING, "got NULL from listen_addrs - skipping"); continue; } - ret = listen_sock(addr, port, listen_fds); + ret = listen_sock(*addr, port, listen_fds); if (ret != 0) { return ret; } @@ -591,14 +303,14 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port) void child_close_sock (void) { - ssize_t i; + size_t i; - for (i = 0; i < vector_length(listen_fds); i++) { - int *fd = (int *) vector_getentry(listen_fds, i, NULL); + for (i = 0; i < sblist_getsize(listen_fds); i++) { + int *fd = sblist_get(listen_fds, i); close (*fd); } - vector_delete(listen_fds); + sblist_free(listen_fds); listen_fds = NULL; } diff --git a/src/child.h b/src/child.h index 70f52b7f..ffcd9d06 100644 --- a/src/child.h +++ b/src/child.h @@ -21,7 +21,7 @@ #ifndef TINYPROXY_CHILD_H #define TINYPROXY_CHILD_H -#include "vector.h" +#include "sblist.h" typedef enum { CHILD_MAXCLIENTS, @@ -32,10 +32,11 @@ typedef enum { } child_config_t; extern short int child_pool_create (void); -extern int child_listening_sockets (vector_t listen_addrs, uint16_t port); +extern int child_listening_sockets (sblist *listen_addrs, uint16_t port); extern void child_close_sock (void); extern void child_main_loop (void); extern void child_kill_children (int sig); +extern void child_free_children(void); extern short int child_configure (child_config_t type, unsigned int val); diff --git a/src/common.h b/src/common.h index 47a1ed18..a4925825 100644 --- a/src/common.h +++ b/src/common.h @@ -68,7 +68,7 @@ # include # include # include -# include +# include /* rest - some oddball headers */ #ifdef HAVE_VALUES_H diff --git a/src/conf-tokens.c b/src/conf-tokens.c new file mode 100644 index 00000000..4463d230 --- /dev/null +++ b/src/conf-tokens.c @@ -0,0 +1,72 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "conf-tokens.h" + +#ifdef HAVE_GPERF +#include "conf-tokens-gperf.inc" +#else + +#include + +const struct config_directive_entry * +config_directive_find (register const char *str, register size_t len) +{ + size_t i; + static const struct config_directive_entry wordlist[] = + { + {"",CD_NIL}, + {"allow", CD_allow}, + {"stathost", CD_stathost}, + {"listen", CD_listen}, + {"timeout", CD_timeout}, + {"statfile", CD_statfile}, + {"pidfile", CD_pidfile}, + {"bindsame", CD_bindsame}, + {"reversebaseurl", CD_reversebaseurl}, + {"viaproxyname", CD_viaproxyname}, + {"upstream", CD_upstream}, + {"anonymous", CD_anonymous}, + {"group", CD_group}, + {"defaulterrorfile", CD_defaulterrorfile}, + {"startservers", CD_startservers}, + {"filtercasesensitive", CD_filtercasesensitive}, + {"filtertype", CD_filtertype}, + {"filterurls", CD_filterurls}, + {"filter", CD_filter}, + {"reversemagic", CD_reversemagic}, + {"errorfile", CD_errorfile}, + {"minspareservers", CD_minspareservers}, + {"user", CD_user}, + {"disableviaheader", CD_disableviaheader}, + {"deny", CD_deny}, + {"xtinyproxy", CD_xtinyproxy}, + {"reversepath", CD_reversepath}, + {"bind", CD_bind}, + {"maxclients", CD_maxclients}, + {"reverseonly", CD_reverseonly}, + {"port", CD_port}, + {"maxspareservers", CD_maxspareservers}, + {"syslog", CD_syslog}, + {"filterdefaultdeny", CD_filterdefaultdeny}, + {"loglevel", CD_loglevel}, + {"filterextended", CD_filterextended}, + {"connectport", CD_connectport}, + {"logfile", CD_logfile}, + {"basicauth", CD_basicauth}, + {"basicauthrealm", CD_basicauthrealm}, + {"addheader", CD_addheader}, + {"maxrequestsperchild", CD_maxrequestsperchild} + }; + + for(i=0;i +#include +#include "conf-tokens.h" +%} + +struct config_directive_entry { const char* name; enum config_directive value; }; + +%struct-type +%define slot-name name +%define initializer-suffix ,CD_NIL +%define lookup-function-name config_directive_find +%ignore-case +%7bit +%compare-lengths +%readonly-tables +%define constants-prefix CDS_ +%omit-struct-type + +%% +logfile, CD_logfile +pidfile, CD_pidfile +anonymous, CD_anonymous +viaproxyname, CD_viaproxyname +defaulterrorfile, CD_defaulterrorfile +statfile, CD_statfile +stathost, CD_stathost +xtinyproxy, CD_xtinyproxy +syslog, CD_syslog +bindsame, CD_bindsame +disableviaheader, CD_disableviaheader +port, CD_port +maxclients, CD_maxclients +maxspareservers, CD_maxspareservers +minspareservers, CD_minspareservers +startservers, CD_startservers +maxrequestsperchild, CD_maxrequestsperchild +timeout, CD_timeout +connectport, CD_connectport +user, CD_user +group, CD_group +listen, CD_listen +allow, CD_allow +deny, CD_deny +bind, CD_bind +basicauth, CD_basicauth +basicauthrealm, CD_basicauthrealm +errorfile, CD_errorfile +addheader, CD_addheader +filter, CD_filter +filterurls, CD_filterurls +filterextended, CD_filterextended +filterdefaultdeny, CD_filterdefaultdeny +filtercasesensitive, CD_filtercasesensitive +filtertype, CD_filtertype +reversebaseurl, CD_reversebaseurl +reverseonly, CD_reverseonly +reversemagic, CD_reversemagic +reversepath, CD_reversepath +upstream, CD_upstream +loglevel, CD_loglevel +%% + diff --git a/src/conf-tokens.h b/src/conf-tokens.h new file mode 100644 index 00000000..01c8ccb2 --- /dev/null +++ b/src/conf-tokens.h @@ -0,0 +1,55 @@ +#ifndef CONF_TOKENS_H +#define CONF_TOKENS_H + +enum config_directive { +CD_NIL = 0, +CD_logfile, +CD_pidfile, +CD_anonymous, +CD_viaproxyname, +CD_defaulterrorfile, +CD_statfile, +CD_stathost, +CD_xtinyproxy, +CD_syslog, +CD_bindsame, +CD_disableviaheader, +CD_port, +CD_maxclients, +CD_maxspareservers, +CD_minspareservers, +CD_startservers, +CD_maxrequestsperchild, +CD_timeout, +CD_connectport, +CD_user, +CD_group, +CD_listen, +CD_allow, +CD_deny, +CD_bind, +CD_basicauth, +CD_basicauthrealm, +CD_errorfile, +CD_addheader, +CD_filter, +CD_filterurls, +CD_filtertype, +CD_filterextended, +CD_filterdefaultdeny, +CD_filtercasesensitive, +CD_reversebaseurl, +CD_reverseonly, +CD_reversemagic, +CD_reversepath, +CD_upstream, +CD_loglevel, +}; + +struct config_directive_entry { const char* name; enum config_directive value; }; + +const struct config_directive_entry * +config_directive_find (register const char *str, register size_t len); + +#endif + diff --git a/src/conf.c b/src/conf.c index 4ee209d2..372c73f8 100644 --- a/src/conf.c +++ b/src/conf.c @@ -23,11 +23,12 @@ * add new directives to. Who knows if I'm right though. */ +#include "common.h" +#include #include "conf.h" #include "acl.h" #include "anonymous.h" -#include "child.h" #include "filter.h" #include "heap.h" #include "html-error.h" @@ -37,6 +38,13 @@ #include "upstream.h" #include "connect-ports.h" #include "basicauth.h" +#include "conf-tokens.h" + +#ifdef LINE_MAX +#define TP_LINE_MAX LINE_MAX +#else +#define TP_LINE_MAX 1024 +#endif /* * The configuration directives are defined in the structure below. Each @@ -47,50 +55,43 @@ * can (and likely should) be used when building the regex for the * given directive. */ -#define WS "[[:space:]]+" +#define DIGIT "[0-9]" +#define SPACE "[ \t]" +#define WS SPACE "+" #define STR "\"([^\"]+)\"" #define BOOL "(yes|on|no|off)" -#define INT "((0x)?[[:digit:]]+)" +#define INT "(()" DIGIT "+)" #define ALNUM "([-a-z0-9._]+)" +#define USERNAME "([^:]*)" +#define PASSWORD "([^@]*)" #define IP "((([0-9]{1,3})\\.){3}[0-9]{1,3})" -#define IPMASK "(" IP "(/[[:digit:]]+)?)" +#define IPMASK "(" IP "(/" DIGIT "+)?)" +#define IPV6SCOPE "((%[^ \t\\/]{1,16})?)" #define IPV6 "(" \ - "(([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,6})|" \ - "(([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5})|" \ - "(([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4})|" \ - "(([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3})|" \ - "(([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2})|" \ - "(([0-9a-f]{1,4}:){1,6}(:[0-9a-f]{1,4}){1,1})|" \ - "((([0-9a-f]{1,4}:){1,7}|:):)|" \ - "(:(:[0-9a-f]{1,4}){1,7})|" \ - "([0-9a-f]{1,4}(:[0-9a-f]{1,4}){1,7})|" \ - "(((([0-9a-f]{1,4}:){6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}))|" \ - "((([0-9a-f]{1,4}:){5}[0-9a-f]{1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}))|" \ - "(([0-9a-f]{1,4}:){5}:[0-9a-f]{1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,3}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,2}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,1}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "((([0-9a-f]{1,4}:){1,5}|:):(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})|" \ - "(:(:[0-9a-f]{1,4}){1,5}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})" \ + "([0-9a-f:]{2,39})" IPV6SCOPE "|" \ + "([0-9a-f:]{0,29}:" IP ")" IPV6SCOPE \ ")" -#define IPV6MASK "(" IPV6 "(/[[:digit:]]+)?)" -#define BEGIN "^[[:space:]]*" -#define END "[[:space:]]*$" +#define IPV6MASK "(" IPV6 "(/" DIGIT "+)?)" +#define BEGIN "^" SPACE "*" +#define END SPACE "*$" /* * Limit the maximum number of substring matches to a reasonably high * number. Given the usual structure of the configuration file, sixteen * substring matches should be plenty. */ -#define RE_MAX_MATCHES 16 +#define RE_MAX_MATCHES 33 + +#define CP_WARN(FMT, ...) \ + log_message (LOG_WARNING, "line %lu: " FMT, lineno, __VA_ARGS__) /* * All configuration handling functions are REQUIRED to be defined * with the same function template as below. */ -typedef int (*CONFFILE_HANDLER) (struct config_s *, const char *, regmatch_t[]); +typedef int (*CONFFILE_HANDLER) (struct config_s *, const char *, + unsigned long, regmatch_t[]); /* * Define the pattern used by any directive handling function. The @@ -105,19 +106,23 @@ typedef int (*CONFFILE_HANDLER) (struct config_s *, const char *, regmatch_t[]); */ #define HANDLE_FUNC(func) \ int func(struct config_s* conf, const char* line, \ - regmatch_t match[]) + unsigned long lineno, regmatch_t match[]) /* * List all the handling functions. These are defined later, but they need * to be in-scope before the big structure below. */ -static HANDLE_FUNC (handle_nop) +static HANDLE_FUNC (handle_disabled_feature) { - return 0; -} /* do nothing function */ + fprintf (stderr, "ERROR: accessing feature that was disabled at compiletime on line %lu\n", + lineno); + + return -1; +} static HANDLE_FUNC (handle_allow); static HANDLE_FUNC (handle_basicauth); +static HANDLE_FUNC (handle_basicauthrealm); static HANDLE_FUNC (handle_anonymous); static HANDLE_FUNC (handle_bind); static HANDLE_FUNC (handle_bindsame); @@ -132,15 +137,14 @@ static HANDLE_FUNC (handle_filtercasesensitive); static HANDLE_FUNC (handle_filterdefaultdeny); static HANDLE_FUNC (handle_filterextended); static HANDLE_FUNC (handle_filterurls); +static HANDLE_FUNC (handle_filtertype); #endif static HANDLE_FUNC (handle_group); static HANDLE_FUNC (handle_listen); static HANDLE_FUNC (handle_logfile); static HANDLE_FUNC (handle_loglevel); static HANDLE_FUNC (handle_maxclients); -static HANDLE_FUNC (handle_maxrequestsperchild); -static HANDLE_FUNC (handle_maxspareservers); -static HANDLE_FUNC (handle_minspareservers); +static HANDLE_FUNC (handle_obsolete); static HANDLE_FUNC (handle_pidfile); static HANDLE_FUNC (handle_port); #ifdef REVERSE_SUPPORT @@ -149,7 +153,6 @@ static HANDLE_FUNC (handle_reversemagic); static HANDLE_FUNC (handle_reverseonly); static HANDLE_FUNC (handle_reversepath); #endif -static HANDLE_FUNC (handle_startservers); static HANDLE_FUNC (handle_statfile); static HANDLE_FUNC (handle_stathost); static HANDLE_FUNC (handle_syslog); @@ -162,9 +165,6 @@ static HANDLE_FUNC (handle_xtinyproxy); #ifdef UPSTREAM_SUPPORT static HANDLE_FUNC (handle_upstream); -static HANDLE_FUNC (handle_upstream4); -static HANDLE_FUNC (handle_upstream5); -static HANDLE_FUNC (handle_upstream_no); #endif static void config_free_regex (void); @@ -180,7 +180,7 @@ static void config_free_regex (void); * do not follow the pattern above. This macro is for convenience * only. */ -#define STDCONF(d, re, func) { BEGIN "(" d ")" WS re END, func, NULL } +#define STDCONF(d, re, func) [CD_ ## d] = { BEGIN "()" WS re END, func, NULL } /* * Holds the regular expression used to match the configuration directive, @@ -193,116 +193,117 @@ struct { CONFFILE_HANDLER handler; regex_t *cre; } directives[] = { - /* comments */ - { - BEGIN "#", handle_nop, NULL - }, - /* blank lines */ - { - "^[[:space:]]+$", handle_nop, NULL - }, /* string arguments */ - STDCONF ("logfile", STR, handle_logfile), - STDCONF ("pidfile", STR, handle_pidfile), - STDCONF ("anonymous", STR, handle_anonymous), - STDCONF ("viaproxyname", STR, handle_viaproxyname), - STDCONF ("defaulterrorfile", STR, handle_defaulterrorfile), - STDCONF ("statfile", STR, handle_statfile), - STDCONF ("stathost", STR, handle_stathost), - STDCONF ("xtinyproxy", BOOL, handle_xtinyproxy), + STDCONF (basicauthrealm, STR, handle_basicauthrealm), + STDCONF (logfile, STR, handle_logfile), + STDCONF (pidfile, STR, handle_pidfile), + STDCONF (anonymous, STR, handle_anonymous), + STDCONF (viaproxyname, STR, handle_viaproxyname), + STDCONF (defaulterrorfile, STR, handle_defaulterrorfile), + STDCONF (statfile, STR, handle_statfile), + STDCONF (stathost, STR, handle_stathost), + STDCONF (xtinyproxy, BOOL, handle_xtinyproxy), /* boolean arguments */ - STDCONF ("syslog", BOOL, handle_syslog), - STDCONF ("bindsame", BOOL, handle_bindsame), - STDCONF ("disableviaheader", BOOL, handle_disableviaheader), + STDCONF (syslog, BOOL, handle_syslog), + STDCONF (bindsame, BOOL, handle_bindsame), + STDCONF (disableviaheader, BOOL, handle_disableviaheader), /* integer arguments */ - STDCONF ("port", INT, handle_port), - STDCONF ("maxclients", INT, handle_maxclients), - STDCONF ("maxspareservers", INT, handle_maxspareservers), - STDCONF ("minspareservers", INT, handle_minspareservers), - STDCONF ("startservers", INT, handle_startservers), - STDCONF ("maxrequestsperchild", INT, handle_maxrequestsperchild), - STDCONF ("timeout", INT, handle_timeout), - STDCONF ("connectport", INT, handle_connectport), + STDCONF (port, INT, handle_port), + STDCONF (maxclients, INT, handle_maxclients), + STDCONF (maxspareservers, INT, handle_obsolete), + STDCONF (minspareservers, INT, handle_obsolete), + STDCONF (startservers, INT, handle_obsolete), + STDCONF (maxrequestsperchild, INT, handle_obsolete), + STDCONF (timeout, INT, handle_timeout), + STDCONF (connectport, INT, handle_connectport), /* alphanumeric arguments */ - STDCONF ("user", ALNUM, handle_user), - STDCONF ("group", ALNUM, handle_group), + STDCONF (user, ALNUM, handle_user), + STDCONF (group, ALNUM, handle_group), /* ip arguments */ - STDCONF ("listen", "(" IP "|" IPV6 ")", handle_listen), - STDCONF ("allow", "(" "(" IPMASK "|" IPV6MASK ")" "|" ALNUM ")", + STDCONF (listen, "(" IP "|" IPV6 ")", handle_listen), + STDCONF (allow, "(" "(" IPMASK "|" IPV6MASK ")" "|" ALNUM ")", handle_allow), - STDCONF ("deny", "(" "(" IPMASK "|" IPV6MASK ")" "|" ALNUM ")", + STDCONF (deny, "(" "(" IPMASK "|" IPV6MASK ")" "|" ALNUM ")", handle_deny), - STDCONF ("bind", "(" IP "|" IPV6 ")", handle_bind), + STDCONF (bind, "(" IP "|" IPV6 ")", handle_bind), /* other */ - STDCONF ("basicauth", ALNUM WS ALNUM, handle_basicauth), - STDCONF ("errorfile", INT WS STR, handle_errorfile), - STDCONF ("addheader", STR WS STR, handle_addheader), + STDCONF (basicauth, USERNAME WS PASSWORD, handle_basicauth), + STDCONF (errorfile, INT WS STR, handle_errorfile), + STDCONF (addheader, STR WS STR, handle_addheader), #ifdef FILTER_ENABLE /* filtering */ - STDCONF ("filter", STR, handle_filter), - STDCONF ("filterurls", BOOL, handle_filterurls), - STDCONF ("filterextended", BOOL, handle_filterextended), - STDCONF ("filterdefaultdeny", BOOL, handle_filterdefaultdeny), - STDCONF ("filtercasesensitive", BOOL, handle_filtercasesensitive), + STDCONF (filter, STR, handle_filter), + STDCONF (filterurls, BOOL, handle_filterurls), + STDCONF (filterextended, BOOL, handle_filterextended), + STDCONF (filterdefaultdeny, BOOL, handle_filterdefaultdeny), + STDCONF (filtercasesensitive, BOOL, handle_filtercasesensitive), + STDCONF (filtertype, "(bre|ere|fnmatch)", handle_filtertype), #endif #ifdef REVERSE_SUPPORT /* Reverse proxy arguments */ - STDCONF ("reversebaseurl", STR, handle_reversebaseurl), - STDCONF ("reverseonly", BOOL, handle_reverseonly), - STDCONF ("reversemagic", BOOL, handle_reversemagic), - STDCONF ("reversepath", STR "(" WS STR ")?", handle_reversepath), + STDCONF (reversebaseurl, STR, handle_reversebaseurl), + STDCONF (reverseonly, BOOL, handle_reverseonly), + STDCONF (reversemagic, BOOL, handle_reversemagic), + STDCONF (reversepath, STR "(" WS STR ")?", handle_reversepath), #endif #ifdef UPSTREAM_SUPPORT - /* upstream is rather complicated */ - { - BEGIN "(no" WS "upstream)" WS STR END, handle_upstream_no, NULL - }, - { - BEGIN "(upstream)" WS "(" IP "|" ALNUM ")" ":" INT "(" WS STR - ")?" END, handle_upstream, NULL - }, - { - BEGIN "(upstream4)" WS "(" IP "|" ALNUM ")" ":" INT "(" WS STR - ")?" END, handle_upstream4, NULL - }, - { - BEGIN "(upstream5)" WS "(" IP "|" ALNUM ")" ":" INT "(" WS STR - ")?" END, handle_upstream5, NULL - }, + STDCONF (upstream, + "(" "(none)" WS STR ")|" \ + "(" "(http|socks4|socks5)" WS \ + "(" USERNAME /*username*/ ":" PASSWORD /*password*/ "@" ")?" + "(" IP "|" "\\[(" IPV6 ")\\]" "|" ALNUM ")" + ":" INT "(" WS STR ")?" ")", handle_upstream), #endif /* loglevel */ - STDCONF ("loglevel", "(critical|error|warning|notice|connect|info)", + STDCONF (loglevel, "(critical|error|warning|notice|connect|info)", handle_loglevel) }; const unsigned int ndirectives = sizeof (directives) / sizeof (directives[0]); static void -free_added_headers (vector_t add_headers) +free_added_headers (sblist* add_headers) { - ssize_t i; + size_t i; - for (i = 0; i < vector_length (add_headers); i++) { - http_header_t *header = (http_header_t *) - vector_getentry (add_headers, i, NULL); + if (!add_headers) return; + + for (i = 0; i < sblist_getsize (add_headers); i++) { + http_header_t *header = sblist_get (add_headers, i); safefree (header->name); safefree (header->value); } - vector_delete (add_headers); + sblist_free (add_headers); +} + +static void stringlist_free(sblist *sl) { + size_t i; + char **s; + if(sl) { + for(i = 0; i < sblist_getsize(sl); i++) { + s = sblist_get(sl, i); + if(s) safefree(*s); + } + sblist_free(sl); + } } -static void free_config (struct config_s *conf) +void free_config (struct config_s *conf) { - safefree (conf->config_file); + char *k; + htab_value *v; + size_t it; + safefree (conf->basicauth_realm); safefree (conf->logf_name); safefree (conf->stathost); safefree (conf->user); safefree (conf->group); - vector_delete(conf->listen_addrs); - vector_delete(conf->basicauth_list); + stringlist_free(conf->basicauth_list); + stringlist_free(conf->listen_addrs); + stringlist_free(conf->bind_addrs); #ifdef FILTER_ENABLE safefree (conf->filter); #endif /* FILTER_ENABLE */ @@ -314,34 +315,50 @@ static void free_config (struct config_s *conf) free_upstream_list (conf->upstream_list); #endif /* UPSTREAM_SUPPORT */ safefree (conf->pidpath); - safefree (conf->bind_address); safefree (conf->via_proxy_name); - hashmap_delete (conf->errorpages); + if (conf->errorpages) { + it = 0; + while((it = htab_next(conf->errorpages, it, &k, &v))) { + safefree(k); + safefree(v->p); + } + htab_destroy (conf->errorpages); + } free_added_headers (conf->add_headers); safefree (conf->errorpage_undef); safefree (conf->statpage); flush_access_list (conf->access_list); free_connect_ports_list (conf->connect_ports); - hashmap_delete (conf->anonymous_map); + if (conf->anonymous_map) { + it = 0; + while((it = htab_next(conf->anonymous_map, it, &k, &v))) + safefree(k); + htab_destroy (conf->anonymous_map); + } memset (conf, 0, sizeof(*conf)); } /* + * Initializes Config parser. Currently this means: * Compiles the regular expressions used by the configuration file. This * routine MUST be called before trying to parse the configuration file. * * Returns 0 on success; negative upon failure. */ int -config_compile_regex (void) +config_init (void) { unsigned int i, r; for (i = 0; i != ndirectives; ++i) { - assert (directives[i].handler); assert (!directives[i].cre); + if (!directives[i].handler) { + directives[i].handler = handle_disabled_feature; + continue; + } + directives[i].cre = (regex_t *) safemalloc (sizeof (regex_t)); if (!directives[i].cre) return -1; @@ -384,20 +401,18 @@ config_free_regex (void) * Returns 0 if a match was found and successfully processed; otherwise, * a negative number is returned. */ -static int check_match (struct config_s *conf, const char *line) +static int check_match (struct config_s *conf, const char *line, + unsigned long lineno, enum config_directive cd) { regmatch_t match[RE_MAX_MATCHES]; - unsigned int i; + unsigned int i = cd; - assert (ndirectives > 0); - - for (i = 0; i != ndirectives; ++i) { - assert (directives[i].cre); - if (!regexec - (directives[i].cre, line, RE_MAX_MATCHES, match, 0)) - return (*directives[i].handler) (conf, line, match); - } + if (!directives[i].cre) + return (*directives[i].handler) (conf, line, lineno, match); + if (!regexec + (directives[i].cre, line, RE_MAX_MATCHES, match, 0)) + return (*directives[i].handler) (conf, line, lineno, match); return -1; } @@ -406,15 +421,25 @@ static int check_match (struct config_s *conf, const char *line) */ static int config_parse (struct config_s *conf, FILE * f) { - char buffer[1024]; /* 1KB lines should be plenty */ + char buffer[TP_LINE_MAX], *p, *q, c; + const struct config_directive_entry *e; unsigned long lineno = 1; - while (fgets (buffer, sizeof (buffer), f)) { - if (check_match (conf, buffer)) { - printf ("Syntax error on line %ld\n", lineno); + for (;fgets (buffer, sizeof (buffer), f);++lineno) { + if(buffer[0] == '#') continue; + p = buffer; + while(isspace(*p))p++; + if(!*p) continue; + q = p; + while(*q && !isspace(*q))q++; + c = *q; + *q = 0; + e = config_directive_find(p, strlen(p)); + *q = c; + if (!e || e->value == CD_NIL || check_match (conf, q, lineno, e->value)) { + fprintf (stderr, "ERROR: Syntax error on line %lu\n", lineno); return 1; } - ++lineno; } return 0; } @@ -450,121 +475,31 @@ static int load_config_file (const char *config_fname, struct config_s *conf) return ret; } -static void initialize_with_defaults (struct config_s *conf, - struct config_s *defaults) +static void initialize_config_defaults (struct config_s *conf) { - if (defaults->logf_name) { - conf->logf_name = safestrdup (defaults->logf_name); - } - - if (defaults->config_file) { - conf->config_file = safestrdup (defaults->config_file); - } - - conf->syslog = defaults->syslog; - conf->port = defaults->port; - - if (defaults->stathost) { - conf->stathost = safestrdup (defaults->stathost); - } - - conf->godaemon = defaults->godaemon; - conf->quit = defaults->quit; - - if (defaults->user) { - conf->user = safestrdup (defaults->user); - } - - if (defaults->group) { - conf->group = safestrdup (defaults->group); - } - - if (defaults->listen_addrs) { - ssize_t i; - - conf->listen_addrs = vector_create(); - for (i=0; i < vector_length(defaults->listen_addrs); i++) { - char *addr; - size_t size; - addr = (char *)vector_getentry(defaults->listen_addrs, - i, &size); - vector_append(conf->listen_addrs, addr, size); - } - - } - -#ifdef FILTER_ENABLE - if (defaults->filter) { - conf->filter = safestrdup (defaults->filter); - } - - conf->filter_url = defaults->filter_url; - conf->filter_extended = defaults->filter_extended; - conf->filter_casesensitive = defaults->filter_casesensitive; -#endif /* FILTER_ENABLE */ - -#ifdef XTINYPROXY_ENABLE - conf->add_xtinyproxy = defaults->add_xtinyproxy; -#endif - -#ifdef REVERSE_SUPPORT - /* struct reversepath *reversepath_list; */ - conf->reverseonly = defaults->reverseonly; - conf->reversemagic = defaults->reversemagic; - - if (defaults->reversebaseurl) { - conf->reversebaseurl = safestrdup (defaults->reversebaseurl); - } -#endif - -#ifdef UPSTREAM_SUPPORT - /* struct upstream *upstream_list; */ -#endif /* UPSTREAM_SUPPORT */ - - if (defaults->pidpath) { - conf->pidpath = safestrdup (defaults->pidpath); - } - - conf->idletimeout = defaults->idletimeout; - - if (defaults->bind_address) { - conf->bind_address = safestrdup (defaults->bind_address); - } - - conf->bindsame = defaults->bindsame; - - if (defaults->via_proxy_name) { - conf->via_proxy_name = safestrdup (defaults->via_proxy_name); - } - - conf->disable_viaheader = defaults->disable_viaheader; - - if (defaults->errorpage_undef) { - conf->errorpage_undef = safestrdup (defaults->errorpage_undef); - } - - if (defaults->statpage) { - conf->statpage = safestrdup (defaults->statpage); - } + memset (conf, 0, sizeof(*conf)); - /* vector_t access_list; */ - /* vector_t connect_ports; */ - /* hashmap_t anonymous_map; */ + /* + * Make sure the HTML error pages array is NULL to begin with. + * (FIXME: Should have a better API for all this) + */ + conf->errorpages = NULL; + conf->basicauth_realm = safestrdup (PACKAGE_NAME); + conf->stathost = safestrdup (TINYPROXY_STATHOST); + conf->idletimeout = MAX_IDLE_TIME; + conf->logf_name = NULL; + conf->pidpath = NULL; + conf->maxclients = 100; } /** * Load the configuration. */ -int reload_config_file (const char *config_fname, struct config_s *conf, - struct config_s *defaults) +int reload_config_file (const char *config_fname, struct config_s *conf) { int ret; - log_message (LOG_INFO, "Reloading config file"); - - free_config (conf); - - initialize_with_defaults (conf, defaults); + initialize_config_defaults (conf); ret = load_config_file (config_fname, conf); if (ret != 0) { @@ -583,7 +518,7 @@ int reload_config_file (const char *config_fname, struct config_s *conf, goto done; } - if (!conf->user) { + if (!conf->user && !geteuid()) { log_message (LOG_WARNING, "You SHOULD set a UserName in the " "config file. Using current user instead."); } @@ -703,6 +638,11 @@ set_int_arg (unsigned int *var, const char *line, regmatch_t * match) * ***********************************************************************/ +static HANDLE_FUNC (handle_basicauthrealm) +{ + return set_string_arg (&conf->basicauth_realm, line, &match[2]); +} + static HANDLE_FUNC (handle_logfile) { return set_string_arg (&conf->logf_name, line, &match[2]); @@ -720,8 +660,12 @@ static HANDLE_FUNC (handle_anonymous) if (!arg) return -1; - anonymous_insert (arg); - safefree (arg); + if(anonymous_insert (conf, arg) < 0) { + CP_WARN ("anonymous_insert() failed: '%s'", arg); + safefree(arg); + return -1; + } + return 0; } @@ -775,6 +719,8 @@ static HANDLE_FUNC (handle_xtinyproxy) #ifdef XTINYPROXY_ENABLE return set_bool_arg (&conf->add_xtinyproxy, line, &match[2]); #else + if(!get_bool_arg(line, &match[2])) + return 0; fprintf (stderr, "XTinyproxy NOT Enabled! Recompile with --enable-xtinyproxy\n"); return 1; @@ -783,12 +729,7 @@ static HANDLE_FUNC (handle_xtinyproxy) static HANDLE_FUNC (handle_syslog) { -#ifdef HAVE_SYSLOG_H return set_bool_arg (&conf->syslog, line, &match[2]); -#else - fprintf (stderr, "Syslog support not compiled in executable.\n"); - return 1; -#endif } static HANDLE_FUNC (handle_bindsame) @@ -816,34 +757,14 @@ static HANDLE_FUNC (handle_port) static HANDLE_FUNC (handle_maxclients) { - child_configure (CHILD_MAXCLIENTS, get_long_arg (line, &match[2])); - return 0; -} - -static HANDLE_FUNC (handle_maxspareservers) -{ - child_configure (CHILD_MAXSPARESERVERS, - get_long_arg (line, &match[2])); - return 0; -} - -static HANDLE_FUNC (handle_minspareservers) -{ - child_configure (CHILD_MINSPARESERVERS, - get_long_arg (line, &match[2])); - return 0; -} - -static HANDLE_FUNC (handle_startservers) -{ - child_configure (CHILD_STARTSERVERS, get_long_arg (line, &match[2])); + set_int_arg (&conf->maxclients, line, &match[2]); return 0; } -static HANDLE_FUNC (handle_maxrequestsperchild) +static HANDLE_FUNC (handle_obsolete) { - child_configure (CHILD_MAXREQUESTSPERCHILD, - get_long_arg (line, &match[2])); + fprintf (stderr, "WARNING: obsolete config item on line %lu\n", + lineno); return 0; } @@ -869,11 +790,16 @@ static HANDLE_FUNC (handle_group) return set_string_arg (&conf->group, line, &match[2]); } +static void warn_invalid_address(char *arg, unsigned long lineno) { + CP_WARN ("Invalid address %s", arg); +} + static HANDLE_FUNC (handle_allow) { char *arg = get_string_arg (line, &match[2]); - insert_acl (arg, ACL_ALLOW, &conf->access_list); + if(insert_acl (arg, ACL_ALLOW, &conf->access_list) < 0) + warn_invalid_address (arg, lineno); safefree (arg); return 0; } @@ -882,22 +808,36 @@ static HANDLE_FUNC (handle_deny) { char *arg = get_string_arg (line, &match[2]); - insert_acl (arg, ACL_DENY, &conf->access_list); + if(insert_acl (arg, ACL_DENY, &conf->access_list) < 0) + warn_invalid_address (arg, lineno); safefree (arg); return 0; } static HANDLE_FUNC (handle_bind) { -#ifndef TRANSPARENT_PROXY - int r = set_string_arg (&conf->bind_address, line, &match[2]); + char *arg = get_string_arg (line, &match[2]); + + if (arg == NULL) { + return -1; + } + + if (conf->bind_addrs == NULL) { + conf->bind_addrs = sblist_new(sizeof(char*), 16); + if (conf->bind_addrs == NULL) { + CP_WARN ("Could not create a list " + "of bind addresses.", ""); + safefree(arg); + return -1; + } + } + + sblist_add (conf->bind_addrs, &arg); - if (r) - return r; log_message (LOG_INFO, - "Outgoing connections bound to IP %s", conf->bind_address); + "Added bind address [%s] for outgoing connections.", arg); + return 0; -#endif } static HANDLE_FUNC (handle_listen) @@ -909,20 +849,19 @@ static HANDLE_FUNC (handle_listen) } if (conf->listen_addrs == NULL) { - conf->listen_addrs = vector_create(); + conf->listen_addrs = sblist_new(sizeof(char*), 16); if (conf->listen_addrs == NULL) { - log_message(LOG_WARNING, "Could not create a list " - "of listen addresses."); + CP_WARN ("Could not create a list " + "of listen addresses.", ""); safefree(arg); return -1; } } - vector_append (conf->listen_addrs, arg, strlen(arg) + 1); + sblist_add (conf->listen_addrs, &arg); log_message(LOG_INFO, "Added address [%s] to listen addresses.", arg); - safefree (arg); return 0; } @@ -938,8 +877,10 @@ static HANDLE_FUNC (handle_errorfile) unsigned long int err = get_long_arg (line, &match[2]); char *page = get_string_arg (line, &match[4]); - add_new_errorpage (page, err); - safefree (page); + if(add_new_errorpage (conf, page, err) < 0) { + CP_WARN ("add_new_errorpage() failed: '%s'", page); + safefree (page); + } return 0; } @@ -947,19 +888,16 @@ static HANDLE_FUNC (handle_addheader) { char *name = get_string_arg (line, &match[2]); char *value = get_string_arg (line, &match[3]); - http_header_t *header; + http_header_t header; if (!conf->add_headers) { - conf->add_headers = vector_create (); + conf->add_headers = sblist_new (sizeof(http_header_t), 16); } - header = (http_header_t *) safemalloc (sizeof (http_header_t)); - header->name = name; - header->value = value; + header.name = name; + header.value = value; - vector_prepend (conf->add_headers, header, sizeof *header); - - safefree (header); + sblist_add (conf->add_headers, &header); /* Don't free name or value here, as they are referenced in the * struct inserted into the vector. */ @@ -1016,7 +954,7 @@ static HANDLE_FUNC (handle_basicauth) return -1; } if (!conf->basicauth_list) { - conf->basicauth_list = vector_create (); + conf->basicauth_list = sblist_new (sizeof(char*), 16); } basicauth_add (conf->basicauth_list, user, pass); @@ -1026,6 +964,11 @@ static HANDLE_FUNC (handle_basicauth) } #ifdef FILTER_ENABLE + +static void warn_deprecated(const char *arg, unsigned long lineno) { + CP_WARN ("deprecated option %s", arg); +} + static HANDLE_FUNC (handle_filter) { return set_string_arg (&conf->filter, line, &match[2]); @@ -1033,26 +976,53 @@ static HANDLE_FUNC (handle_filter) static HANDLE_FUNC (handle_filterurls) { - return set_bool_arg (&conf->filter_url, line, &match[2]); + conf->filter_opts |= + get_bool_arg (line, &match[2]) * FILTER_OPT_URL; + return 0; } static HANDLE_FUNC (handle_filterextended) { - return set_bool_arg (&conf->filter_extended, line, &match[2]); + warn_deprecated("FilterExtended, use FilterType", lineno); + conf->filter_opts |= + get_bool_arg (line, &match[2]) * FILTER_OPT_TYPE_ERE; + return 0; } static HANDLE_FUNC (handle_filterdefaultdeny) { assert (match[2].rm_so != -1); - - if (get_bool_arg (line, &match[2])) - filter_set_default_policy (FILTER_DEFAULT_DENY); + conf->filter_opts |= + get_bool_arg (line, &match[2]) * FILTER_OPT_DEFAULT_DENY; return 0; } static HANDLE_FUNC (handle_filtercasesensitive) { - return set_bool_arg (&conf->filter_casesensitive, line, &match[2]); + conf->filter_opts |= + get_bool_arg (line, &match[2]) * FILTER_OPT_CASESENSITIVE; + return 0; +} + +static HANDLE_FUNC (handle_filtertype) +{ + static const struct { unsigned short flag; char type[8]; } + ftmap[] = { + {FILTER_OPT_TYPE_ERE, "ere"}, + {FILTER_OPT_TYPE_BRE, "bre"}, + {FILTER_OPT_TYPE_FNMATCH, "fnmatch"}, + }; + char *type; + unsigned i; + type = get_string_arg(line, &match[2]); + if (!type) return -1; + + for(i=0;ifilter_opts |= ftmap[i].flag; + + safefree (type); + return 0; } #endif @@ -1101,59 +1071,84 @@ static HANDLE_FUNC (handle_reversepath) #endif #ifdef UPSTREAM_SUPPORT -static int _handle_upstream(struct config_s* conf, const char* line, - regmatch_t match[], proxy_type type) -{ - char *ip; - int port; - char *domain; - ip = get_string_arg (line, &match[2]); - if (!ip) - return -1; - port = (int) get_long_arg (line, &match[7]); +static enum proxy_type pt_from_string(const char *s) +{ + static const char pt_map[][7] = { + [PT_NONE] = "none", + [PT_HTTP] = "http", + [PT_SOCKS4] = "socks4", + [PT_SOCKS5] = "socks5", + }; + unsigned i; + for (i = 0; i < sizeof(pt_map)/sizeof(pt_map[0]); i++) + if (!strcmp(pt_map[i], s)) + return i; + return PT_NONE; +} - if (match[10].rm_so != -1) { - domain = get_string_arg (line, &match[10]); - if (domain) { - upstream_add (ip, port, domain, type, &conf->upstream_list); +static HANDLE_FUNC (handle_upstream) +{ + char *ip; + int port, mi; + char *domain = 0, *user = 0, *pass = 0, *tmp; + enum proxy_type pt; + enum upstream_build_error ube; + + if (match[3].rm_so != -1) { + tmp = get_string_arg (line, &match[3]); + if(!strcmp(tmp, "none")) { + safefree(tmp); + if (match[4].rm_so == -1) return -1; + domain = get_string_arg (line, &match[4]); + if (!domain) + return -1; + ube = upstream_add (NULL, 0, domain, 0, 0, PT_NONE, &conf->upstream_list); safefree (domain); + goto check_err; } - } else { - upstream_add (ip, port, NULL, type, &conf->upstream_list); } - safefree (ip); + mi = 6; - return 0; -} + tmp = get_string_arg (line, &match[mi]); + pt = pt_from_string(tmp); + safefree(tmp); + mi += 2; -static HANDLE_FUNC (handle_upstream) -{ - return _handle_upstream(conf, line, match, HTTP_TYPE); -} + if (match[mi].rm_so != -1) + user = get_string_arg (line, &match[mi]); + mi++; -static HANDLE_FUNC (handle_upstream4) -{ - return _handle_upstream(conf, line, match, SOCKS4_TYPE); -} + if (match[mi].rm_so != -1) + pass = get_string_arg (line, &match[mi]); + mi++; -static HANDLE_FUNC (handle_upstream5) -{ - return _handle_upstream(conf, line, match, SOCKS5_TYPE); -} + if (match[mi+4].rm_so != -1) /* IPv6 address in square brackets */ + ip = get_string_arg (line, &match[mi+4]); + else + ip = get_string_arg (line, &match[mi]); + if (!ip) + return -1; + mi += 16; -static HANDLE_FUNC (handle_upstream_no) -{ - char *domain; + port = (int) get_long_arg (line, &match[mi]); + mi += 3; - domain = get_string_arg (line, &match[2]); - if (!domain) - return -1; + if (match[mi].rm_so != -1) + domain = get_string_arg (line, &match[mi]); + + ube = upstream_add (ip, port, domain, user, pass, pt, &conf->upstream_list); - upstream_add (NULL, 0, domain, HTTP_TYPE, &conf->upstream_list); + safefree (user); + safefree (pass); safefree (domain); + safefree (ip); +check_err:; + if(ube != UBE_SUCCESS) + CP_WARN("%s", upstream_build_error_string(ube)); return 0; } + #endif diff --git a/src/conf.h b/src/conf.h index beb2b01b..0b25afa4 100644 --- a/src/conf.h +++ b/src/conf.h @@ -22,8 +22,9 @@ #ifndef TINYPROXY_CONF_H #define TINYPROXY_CONF_H -#include "hashmap.h" -#include "vector.h" +#include "hsearch.h" +#include "sblist.h" +#include "acl.h" /* * Stores a HTTP header created using the AddHeader directive. @@ -37,22 +38,20 @@ typedef struct { * Hold all the configuration time information. */ struct config_s { - vector_t basicauth_list; + sblist *basicauth_list; + char *basicauth_realm; char *logf_name; - char *config_file; unsigned int syslog; /* boolean */ unsigned int port; char *stathost; - unsigned int godaemon; /* boolean */ unsigned int quit; /* boolean */ + unsigned int maxclients; char *user; char *group; - vector_t listen_addrs; + sblist *listen_addrs; #ifdef FILTER_ENABLE char *filter; - unsigned int filter_url; /* boolean */ - unsigned int filter_extended; /* boolean */ - unsigned int filter_casesensitive; /* boolean */ + unsigned int filter_opts; /* enum filter_options */ #endif /* FILTER_ENABLE */ #ifdef XTINYPROXY_ENABLE unsigned int add_xtinyproxy; /* boolean */ @@ -68,7 +67,7 @@ struct config_s { #endif /* UPSTREAM_SUPPORT */ char *pidpath; unsigned int idletimeout; - char *bind_address; + sblist *bind_addrs; unsigned int bindsame; /* @@ -81,7 +80,7 @@ struct config_s { /* * Error page support. Map error numbers to file paths. */ - hashmap_t errorpages; + struct htab *errorpages; /* * Error page to be displayed if appropriate page cannot be located @@ -94,28 +93,28 @@ struct config_s { */ char *statpage; - vector_t access_list; + acl_list_t access_list; /* * Store the list of port allowed by CONNECT. */ - vector_t connect_ports; + sblist *connect_ports; /* * Map of headers which should be let through when the * anonymous feature is turned on. */ - hashmap_t anonymous_map; + struct htab *anonymous_map; /* * Extra headers to be added to outgoing HTTP requests. */ - vector_t add_headers; + sblist* add_headers; }; -extern int reload_config_file (const char *config_fname, struct config_s *conf, - struct config_s *defaults); +extern int reload_config_file (const char *config_fname, struct config_s *conf); -int config_compile_regex (void); +int config_init (void); +void free_config (struct config_s *conf); #endif diff --git a/src/connect-ports.c b/src/connect-ports.c index 41b4e3d1..6070e92a 100644 --- a/src/connect-ports.c +++ b/src/connect-ports.c @@ -25,10 +25,10 @@ * Now, this routine adds a "port" to the list. It also creates the list if * it hasn't already by done. */ -void add_connect_port_allowed (int port, vector_t *connect_ports) +void add_connect_port_allowed (int port, sblist **connect_ports) { if (!*connect_ports) { - *connect_ports = vector_create (); + *connect_ports = sblist_new (sizeof(int), 16); if (!*connect_ports) { log_message (LOG_WARNING, "Could not create a list of allowed CONNECT ports"); @@ -38,7 +38,7 @@ void add_connect_port_allowed (int port, vector_t *connect_ports) log_message (LOG_INFO, "Adding Port [%d] to the list allowed by CONNECT", port); - vector_append (*connect_ports, &port, sizeof (port)); + sblist_add (*connect_ports, &port); } /* @@ -47,7 +47,7 @@ void add_connect_port_allowed (int port, vector_t *connect_ports) * Returns: 1 if allowed * 0 if denied */ -int check_allowed_connect_ports (int port, vector_t connect_ports) +int check_allowed_connect_ports (int port, sblist *connect_ports) { size_t i; int *data; @@ -59,8 +59,8 @@ int check_allowed_connect_ports (int port, vector_t connect_ports) if (!connect_ports) return 1; - for (i = 0; i != (size_t) vector_length (connect_ports); ++i) { - data = (int *) vector_getentry (connect_ports, i, NULL); + for (i = 0; i < sblist_getsize (connect_ports); ++i) { + data = sblist_get (connect_ports, i); if (data && *data == port) return 1; } @@ -71,7 +71,7 @@ int check_allowed_connect_ports (int port, vector_t connect_ports) /** * Free a connect_ports list. */ -void free_connect_ports_list (vector_t connect_ports) +void free_connect_ports_list (sblist *connect_ports) { - vector_delete (connect_ports); + sblist_free (connect_ports); } diff --git a/src/connect-ports.h b/src/connect-ports.h index 4b3aaf72..38f511ca 100644 --- a/src/connect-ports.h +++ b/src/connect-ports.h @@ -22,10 +22,10 @@ #define _TINYPROXY_CONNECT_PORTS_H_ #include "common.h" -#include "vector.h" +#include "sblist.h" -extern void add_connect_port_allowed (int port, vector_t *connect_ports); -int check_allowed_connect_ports (int port, vector_t connect_ports); -void free_connect_ports_list (vector_t connect_ports); +extern void add_connect_port_allowed (int port, sblist **connect_ports); +int check_allowed_connect_ports (int port, sblist *connect_ports); +void free_connect_ports_list (sblist *connect_ports); #endif /* _TINYPROXY_CONNECT_PORTS_ */ diff --git a/src/conns.c b/src/conns.c index 94faeea6..19aaa49c 100644 --- a/src/conns.c +++ b/src/conns.c @@ -30,14 +30,20 @@ #include "log.h" #include "stats.h" -struct conn_s *initialize_conn (int client_fd, const char *ipaddr, - const char *string_addr, +void conn_struct_init(struct conn_s *connptr) { + connptr->error_number = -1; + connptr->client_fd = -1; + connptr->server_fd = -1; + /* There is _no_ content length initially */ + connptr->content_length.server = connptr->content_length.client = -1; +} + +int conn_init_contents (struct conn_s *connptr, const char *ipaddr, const char *sock_ipaddr) { - struct conn_s *connptr; struct buffer_s *cbuffer, *sbuffer; - assert (client_fd >= 0); + assert (connptr->client_fd >= 0); /* * Allocate the memory for all the internal components @@ -48,48 +54,16 @@ struct conn_s *initialize_conn (int client_fd, const char *ipaddr, if (!cbuffer || !sbuffer) goto error_exit; - /* - * Allocate the space for the conn_s structure itself. - */ - connptr = (struct conn_s *) safemalloc (sizeof (struct conn_s)); - if (!connptr) - goto error_exit; - - connptr->client_fd = client_fd; - connptr->server_fd = -1; - connptr->cbuffer = cbuffer; connptr->sbuffer = sbuffer; - connptr->request_line = NULL; - - /* These store any error strings */ - connptr->error_variables = NULL; - connptr->error_string = NULL; - connptr->error_number = -1; - - connptr->connect_method = FALSE; - connptr->show_stats = FALSE; - - connptr->protocol.major = connptr->protocol.minor = 0; - - /* There is _no_ content length initially */ - connptr->content_length.server = connptr->content_length.client = -1; - connptr->server_ip_addr = (sock_ipaddr ? safestrdup (sock_ipaddr) : NULL); connptr->client_ip_addr = safestrdup (ipaddr); - connptr->client_string_addr = safestrdup (string_addr); - - connptr->upstream_proxy = NULL; update_stats (STAT_OPEN); -#ifdef REVERSE_SUPPORT - connptr->reversepath = NULL; -#endif - - return connptr; + return 1; error_exit: /* @@ -100,10 +74,10 @@ struct conn_s *initialize_conn (int client_fd, const char *ipaddr, if (sbuffer) delete_buffer (sbuffer); - return NULL; + return 0; } -void destroy_conn (struct conn_s *connptr) +void conn_destroy_contents (struct conn_s *connptr) { assert (connptr != NULL); @@ -111,10 +85,12 @@ void destroy_conn (struct conn_s *connptr) if (close (connptr->client_fd) < 0) log_message (LOG_INFO, "Client (%d) close message: %s", connptr->client_fd, strerror (errno)); + connptr->client_fd = -1; if (connptr->server_fd != -1) if (close (connptr->server_fd) < 0) log_message (LOG_INFO, "Server (%d) close message: %s", connptr->server_fd, strerror (errno)); + connptr->server_fd = -1; if (connptr->cbuffer) delete_buffer (connptr->cbuffer); @@ -124,8 +100,16 @@ void destroy_conn (struct conn_s *connptr) if (connptr->request_line) safefree (connptr->request_line); - if (connptr->error_variables) - hashmap_delete (connptr->error_variables); + if (connptr->error_variables) { + char *k; + htab_value *v; + size_t it = 0; + while((it = htab_next(connptr->error_variables, it, &k, &v))) { + safefree(v->p); + safefree(k); + } + htab_destroy (connptr->error_variables); + } if (connptr->error_string) safefree (connptr->error_string); @@ -134,15 +118,11 @@ void destroy_conn (struct conn_s *connptr) safefree (connptr->server_ip_addr); if (connptr->client_ip_addr) safefree (connptr->client_ip_addr); - if (connptr->client_string_addr) - safefree (connptr->client_string_addr); #ifdef REVERSE_SUPPORT if (connptr->reversepath) safefree (connptr->reversepath); #endif - safefree (connptr); - update_stats (STAT_CLOSE); } diff --git a/src/conns.h b/src/conns.h index b63d0266..9618efbb 100644 --- a/src/conns.h +++ b/src/conns.h @@ -22,7 +22,7 @@ #define TINYPROXY_CONNS_H #include "main.h" -#include "hashmap.h" +#include "hsearch.h" /* * Connection Definition @@ -45,7 +45,7 @@ struct conn_s { * This structure stores key -> value mappings for substitution * in the error HTML files. */ - hashmap_t error_variables; + struct htab *error_variables; int error_number; char *error_string; @@ -62,10 +62,9 @@ struct conn_s { char *server_ip_addr; /* - * Store the client's IP and hostname information + * Store the client's IP information */ char *client_ip_addr; - char *client_string_addr; /* * Store the incoming request's HTTP protocol. @@ -88,12 +87,13 @@ struct conn_s { struct upstream *upstream_proxy; }; -/* - * Functions for the creation and destruction of a connection structure. - */ -extern struct conn_s *initialize_conn (int client_fd, const char *ipaddr, - const char *string_addr, +/* expects pointer to zero-initialized struct, set up struct + with default values for initial use */ +extern void conn_struct_init(struct conn_s *connptr); + +/* second stage initializiation, sets up buffers and connection details */ +extern int conn_init_contents (struct conn_s *connptr, const char *ipaddr, const char *sock_ipaddr); -extern void destroy_conn (struct conn_s *connptr); +extern void conn_destroy_contents (struct conn_s *connptr); #endif diff --git a/src/filter.c b/src/filter.c index 31641916..0dbc93d2 100644 --- a/src/filter.c +++ b/src/filter.c @@ -24,60 +24,66 @@ #include "main.h" +#include +#include #include "filter.h" #include "heap.h" #include "log.h" #include "reqs.h" #include "conf.h" +#include "sblist.h" #define FILTER_BUFFER_LEN (512) static int err; struct filter_list { - struct filter_list *next; - char *pat; - regex_t *cpat; + union { + regex_t cpatb; + char *pattern; + } u; }; -static struct filter_list *fl = NULL; +static sblist *fl = NULL; static int already_init = 0; -static filter_policy_t default_policy = FILTER_DEFAULT_ALLOW; /* - * Initializes a linked list of strings containing hosts/urls to be filtered + * Initializes a list of strings containing hosts/urls to be filtered */ void filter_init (void) { FILE *fd; - struct filter_list *p; + struct filter_list fe; char buf[FILTER_BUFFER_LEN]; - char *s; - int cflags; + char *s, *start; + int cflags, lineno = 0; if (fl || already_init) { return; } - fd = fopen (config.filter, "r"); + fd = fopen (config->filter, "r"); if (!fd) { - return; + perror ("filter file"); + exit (EX_DATAERR); } - p = NULL; - cflags = REG_NEWLINE | REG_NOSUB; - if (config.filter_extended) - cflags |= REG_EXTENDED; - if (!config.filter_casesensitive) - cflags |= REG_ICASE; + cflags |= (REG_EXTENDED * !!(config->filter_opts & FILTER_OPT_TYPE_ERE)); + cflags |= (REG_ICASE * !(config->filter_opts & FILTER_OPT_CASESENSITIVE)); while (fgets (buf, FILTER_BUFFER_LEN, fd)) { + ++lineno; + /* skip leading whitespace */ + s = buf; + while (*s && isspace ((unsigned char) *s)) + s++; + start = s; + /* * Remove any trailing white space and * comments. */ - s = buf; while (*s) { if (isspace ((unsigned char) *s)) break; @@ -93,34 +99,34 @@ void filter_init (void) ++s; } *s = '\0'; - - /* skip leading whitespace */ - s = buf; - while (*s && isspace ((unsigned char) *s)) - s++; + s = start; /* skip blank lines and comments */ if (*s == '\0') continue; - if (!p) /* head of list */ - fl = p = - (struct filter_list *) - safecalloc (1, sizeof (struct filter_list)); - else { /* next entry */ - p->next = - (struct filter_list *) - safecalloc (1, sizeof (struct filter_list)); - p = p->next; + if (!fl) fl = sblist_new(sizeof(struct filter_list), + 4096/sizeof(struct filter_list)); + + if (config->filter_opts & FILTER_OPT_TYPE_FNMATCH) { + fe.u.pattern = safestrdup(s); + if (!fe.u.pattern) goto oom; + } else { + + err = regcomp (&fe.u.cpatb, s, cflags); + if (err != 0) { + if (err == REG_ESPACE) goto oom; + fprintf (stderr, + "Bad regex in %s: line %d - %s\n", + config->filter, lineno, s); + exit (EX_DATAERR); + } } - - p->pat = safestrdup (s); - p->cpat = (regex_t *) safemalloc (sizeof (regex_t)); - err = regcomp (p->cpat, p->pat, cflags); - if (err != 0) { + if (!sblist_add(fl, &fe)) { + oom:; fprintf (stderr, - "Bad regex in %s: %s\n", - config.filter, p->pat); + "out of memory parsing filter file %s: line %d\n", + config->filter, lineno); exit (EX_DATAERR); } } @@ -136,15 +142,19 @@ void filter_init (void) /* unlink the list */ void filter_destroy (void) { - struct filter_list *p, *q; + struct filter_list *p; + size_t i; if (already_init) { - for (p = q = fl; p; p = q) { - regfree (p->cpat); - safefree (p->cpat); - safefree (p->pat); - q = p->next; - safefree (p); + if (fl) { + for (i = 0; i < sblist_getsize(fl); ++i) { + p = sblist_get(fl, i); + if (config->filter_opts & FILTER_OPT_TYPE_FNMATCH) + safefree(p->u.pattern); + else + regfree (&p->u.cpatb); + } + sblist_free(fl); } fl = NULL; already_init = 0; @@ -156,7 +166,7 @@ void filter_destroy (void) */ void filter_reload (void) { - if (config.filter) { + if (config->filter) { log_message (LOG_NOTICE, "Re-reading filter file."); filter_destroy (); filter_init (); @@ -164,20 +174,25 @@ void filter_reload (void) } /* Return 0 to allow, non-zero to block */ -int filter_domain (const char *host) +int filter_run (const char *str) { struct filter_list *p; + size_t i; int result; if (!fl || !already_init) goto COMMON_EXIT; - for (p = fl; p; p = p->next) { - result = - regexec (p->cpat, host, (size_t) 0, (regmatch_t *) 0, 0); + for (i = 0; i < sblist_getsize(fl); ++i) { + p = sblist_get(fl, i); + if (config->filter_opts & FILTER_OPT_TYPE_FNMATCH) + result = fnmatch (p->u.pattern, str, 0); + else + result = + regexec (&p->u.cpatb, str, (size_t) 0, (regmatch_t *) 0, 0); if (result == 0) { - if (default_policy == FILTER_DEFAULT_ALLOW) + if (!(config->filter_opts & FILTER_OPT_DEFAULT_DENY)) return 1; else return 0; @@ -185,44 +200,8 @@ int filter_domain (const char *host) } COMMON_EXIT: - if (default_policy == FILTER_DEFAULT_ALLOW) + if (!(config->filter_opts & FILTER_OPT_DEFAULT_DENY)) return 0; else return 1; } - -/* returns 0 to allow, non-zero to block */ -int filter_url (const char *url) -{ - struct filter_list *p; - int result; - - if (!fl || !already_init) - goto COMMON_EXIT; - - for (p = fl; p; p = p->next) { - result = - regexec (p->cpat, url, (size_t) 0, (regmatch_t *) 0, 0); - - if (result == 0) { - if (default_policy == FILTER_DEFAULT_ALLOW) - return 1; - else - return 0; - } - } - -COMMON_EXIT: - if (default_policy == FILTER_DEFAULT_ALLOW) - return 0; - else - return 1; -} - -/* - * Set the default filtering policy - */ -void filter_set_default_policy (filter_policy_t policy) -{ - default_policy = policy; -} diff --git a/src/filter.h b/src/filter.h index 8c6f270b..e5f34687 100644 --- a/src/filter.h +++ b/src/filter.h @@ -21,17 +21,22 @@ #ifndef _TINYPROXY_FILTER_H_ #define _TINYPROXY_FILTER_H_ -typedef enum { - FILTER_DEFAULT_ALLOW, - FILTER_DEFAULT_DENY -} filter_policy_t; +enum filter_options { + FILTER_OPT_CASESENSITIVE = 1 << 0, + FILTER_OPT_URL = 1 << 1, + FILTER_OPT_DEFAULT_DENY = 1 << 2, + + FILTER_OPT_TYPE_BRE = 1 << 8, + FILTER_OPT_TYPE_ERE = 1 << 9, + FILTER_OPT_TYPE_FNMATCH = 1 << 10, +}; + +#define FILTER_TYPE_MASK \ + (FILTER_OPT_TYPE_BRE | FILTER_OPT_TYPE_ERE | FILTER_OPT_TYPE_FNMATCH) extern void filter_init (void); extern void filter_destroy (void); extern void filter_reload (void); -extern int filter_domain (const char *host); -extern int filter_url (const char *url); - -extern void filter_set_default_policy (filter_policy_t policy); +extern int filter_run (const char *str); #endif diff --git a/src/hashmap.c b/src/hashmap.c deleted file mode 100644 index 7793d08a..00000000 --- a/src/hashmap.c +++ /dev/null @@ -1,516 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2002 Robert James Kaes - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* A hashmap implementation. The keys are case-insensitive NULL terminated - * strings, and the data is arbitrary lumps of data. Copies of both the - * key and the data in the hashmap itself, so you must free the original - * key and data to avoid a memory leak. The hashmap returns a pointer - * to the data when a key is searched for, so take care in modifying the - * data as it's modifying the data stored in the hashmap. (In other words, - * don't try to free the data, or realloc the memory. :) - */ - -#include "main.h" - -#include "hashmap.h" -#include "heap.h" - -/* - * These structures are the storage for the hashmap. Entries are stored in - * struct hashentry_s (the key, data, and length), and all the "buckets" are - * grouped together in hashmap_s. The hashmap_s.size member is for - * internal use. It stores the number of buckets the hashmap was created - * with. - */ -struct hashentry_s { - char *key; - void *data; - size_t len; - - struct hashentry_s *prev, *next; -}; - -struct hashbucket_s { - struct hashentry_s *head, *tail; -}; - -struct hashmap_s { - uint32_t seed; - unsigned int size; - hashmap_iter end_iterator; - - struct hashbucket_s *buckets; -}; - -/* - * A NULL terminated string is passed to this function and a "hash" value - * is produced within the range of [0 .. size) (In other words, 0 to one - * less than size.) - * The contents of the key are converted to lowercase, so this function - * is not case-sensitive. - * - * This is Dan Bernstein's hash function as described, for example, here: - * http://www.cse.yorku.ca/~oz/hash.html - * - * If any of the arguments are invalid a negative number is returned. - */ -static int hashfunc (const char *key, unsigned int size, uint32_t seed) -{ - uint32_t hash; - - if (key == NULL) - return -EINVAL; - if (size == 0) - return -ERANGE; - - for (hash = seed; *key != '\0'; key++) { - hash = ((hash << 5) + hash) ^ tolower (*key); - } - - /* Keep the hash within the table limits */ - return hash % size; -} - -/* - * Create a hashmap with the requested number of buckets. If "nbuckets" is - * not greater than zero a NULL is returned; otherwise, a _token_ to the - * hashmap is returned. - * - * NULLs are also returned if memory could not be allocated for hashmap. - */ -hashmap_t hashmap_create (unsigned int nbuckets) -{ - struct hashmap_s *ptr; - - if (nbuckets == 0) - return NULL; - - ptr = (struct hashmap_s *) safecalloc (1, sizeof (struct hashmap_s)); - if (!ptr) - return NULL; - - ptr->seed = (uint32_t)rand(); - ptr->size = nbuckets; - ptr->buckets = (struct hashbucket_s *) safecalloc (nbuckets, - sizeof (struct - hashbucket_s)); - if (!ptr->buckets) { - safefree (ptr); - return NULL; - } - - /* This points to "one" past the end of the hashmap. */ - ptr->end_iterator = 0; - - return ptr; -} - -/* - * Follow the chain of hashentries and delete them (including the data and - * the key.) - * - * Returns: 0 if the function completed successfully - * negative number is returned if "entry" was NULL - */ -static int delete_hashbucket (struct hashbucket_s *bucket) -{ - struct hashentry_s *nextptr; - struct hashentry_s *ptr; - - if (bucket == NULL || bucket->head == NULL) - return -EINVAL; - - ptr = bucket->head; - while (ptr) { - nextptr = ptr->next; - - safefree (ptr->key); - safefree (ptr->data); - safefree (ptr); - - ptr = nextptr; - } - - return 0; -} - -/* - * Deletes a hashmap. All the key/data pairs are also deleted. - * - * Returns: 0 on success - * negative if a NULL "map" was supplied - */ -int hashmap_delete (hashmap_t map) -{ - unsigned int i; - - if (map == NULL) - return -EINVAL; - - for (i = 0; i != map->size; i++) { - if (map->buckets[i].head != NULL) { - delete_hashbucket (&map->buckets[i]); - } - } - - safefree (map->buckets); - safefree (map); - - return 0; -} - -/* - * Inserts a NULL terminated string (as the key), plus any arbitrary "data" - * of "len" bytes. Both the key and the data are copied, so the original - * key/data must be freed to avoid a memory leak. - * The "data" must be non-NULL and "len" must be greater than zero. You - * cannot insert NULL data in association with the key. - * - * Returns: 0 on success - * negative number if there are errors - */ -int -hashmap_insert (hashmap_t map, const char *key, const void *data, size_t len) -{ - struct hashentry_s *ptr; - int hash; - char *key_copy; - void *data_copy; - - assert (map != NULL); - assert (key != NULL); - assert (data != NULL); - assert (len > 0); - - if (map == NULL || key == NULL) - return -EINVAL; - if (!data || len < 1) - return -ERANGE; - - hash = hashfunc (key, map->size, map->seed); - if (hash < 0) - return hash; - - /* - * First make copies of the key and data in case there is a memory - * problem later. - */ - key_copy = safestrdup (key); - if (!key_copy) - return -ENOMEM; - - data_copy = safemalloc (len); - if (!data_copy) { - safefree (key_copy); - return -ENOMEM; - } - memcpy (data_copy, data, len); - - ptr = (struct hashentry_s *) safemalloc (sizeof (struct hashentry_s)); - if (!ptr) { - safefree (key_copy); - safefree (data_copy); - return -ENOMEM; - } - - ptr->key = key_copy; - ptr->data = data_copy; - ptr->len = len; - - /* - * Now add the entry to the end of the bucket chain. - */ - ptr->next = NULL; - ptr->prev = map->buckets[hash].tail; - if (map->buckets[hash].tail) - map->buckets[hash].tail->next = ptr; - - map->buckets[hash].tail = ptr; - if (!map->buckets[hash].head) - map->buckets[hash].head = ptr; - - map->end_iterator++; - return 0; -} - -/* - * Get an iterator to the first entry. - * - * Returns: an negative value upon error. - */ -hashmap_iter hashmap_first (hashmap_t map) -{ - assert (map != NULL); - - if (!map) - return -EINVAL; - - if (map->end_iterator == 0) - return -1; - else - return 0; -} - -/* - * Checks to see if the iterator is pointing at the "end" of the entries. - * - * Returns: 1 if it is the end - * 0 otherwise - */ -int hashmap_is_end (hashmap_t map, hashmap_iter iter) -{ - assert (map != NULL); - assert (iter >= 0); - - if (!map || iter < 0) - return -EINVAL; - - if (iter == map->end_iterator) - return 1; - else - return 0; -} - -/* - * Return a "pointer" to the first instance of the particular key. It can - * be tested against hashmap_is_end() to see if the key was not found. - * - * Returns: negative upon an error - * an "iterator" pointing at the first key - * an "end-iterator" if the key wasn't found - */ -hashmap_iter hashmap_find (hashmap_t map, const char *key) -{ - unsigned int i; - hashmap_iter iter = 0; - struct hashentry_s *ptr; - - assert (map != NULL); - assert (key != NULL); - - if (!map || !key) - return -EINVAL; - - /* - * Loop through all the keys and look for the first occurrence - * of a particular key. - */ - for (i = 0; i != map->size; i++) { - ptr = map->buckets[i].head; - - while (ptr) { - if (strcasecmp (ptr->key, key) == 0) { - /* Found it, so return the current count */ - return iter; - } - - iter++; - ptr = ptr->next; - } - } - - return iter; -} - -/* - * Retrieve the data associated with a particular iterator. - * - * Returns: the length of the data block upon success - * negative upon error - */ -ssize_t -hashmap_return_entry (hashmap_t map, hashmap_iter iter, char **key, void **data) -{ - unsigned int i; - struct hashentry_s *ptr; - hashmap_iter count = 0; - - assert (map != NULL); - assert (iter >= 0); - assert (iter != map->end_iterator); - assert (key != NULL); - assert (data != NULL); - - if (!map || iter < 0 || !key || !data) - return -EINVAL; - - for (i = 0; i != map->size; i++) { - ptr = map->buckets[i].head; - while (ptr) { - if (count == iter) { - /* This is the data so return it */ - *key = ptr->key; - *data = ptr->data; - return ptr->len; - } - - ptr = ptr->next; - count++; - } - } - - return -EFAULT; -} - -/* - * Searches for _any_ occurrences of "key" within the hashmap. - * - * Returns: negative upon an error - * zero if no key is found - * count found - */ -ssize_t hashmap_search (hashmap_t map, const char *key) -{ - int hash; - struct hashentry_s *ptr; - ssize_t count = 0; - - if (map == NULL || key == NULL) - return -EINVAL; - - hash = hashfunc (key, map->size, map->seed); - if (hash < 0) - return hash; - - ptr = map->buckets[hash].head; - - /* All right, there is an entry here, now see if it's the one we want */ - while (ptr) { - if (strcasecmp (ptr->key, key) == 0) - ++count; - - /* This entry didn't contain the key; move to the next one */ - ptr = ptr->next; - } - - return count; -} - -/* - * Get the first entry (assuming there is more than one) for a particular - * key. The data MUST be non-NULL. - * - * Returns: negative upon error - * zero if no entry is found - * length of data for the entry - */ -ssize_t hashmap_entry_by_key (hashmap_t map, const char *key, void **data) -{ - int hash; - struct hashentry_s *ptr; - - if (!map || !key || !data) - return -EINVAL; - - hash = hashfunc (key, map->size, map->seed); - if (hash < 0) - return hash; - - ptr = map->buckets[hash].head; - - while (ptr) { - if (strcasecmp (ptr->key, key) == 0) { - *data = ptr->data; - return ptr->len; - } - - ptr = ptr->next; - } - - return 0; -} - -/* - * Go through the hashmap and remove the particular key. - * NOTE: This will invalidate any iterators which have been created. - * - * Remove: negative upon error - * 0 if the key was not found - * positive count of entries deleted - */ -ssize_t hashmap_remove (hashmap_t map, const char *key) -{ - int hash; - struct hashentry_s *ptr, *next; - short int deleted = 0; - - if (map == NULL || key == NULL) - return -EINVAL; - - hash = hashfunc (key, map->size, map->seed); - if (hash < 0) - return hash; - - ptr = map->buckets[hash].head; - while (ptr) { - if (strcasecmp (ptr->key, key) == 0) { - /* - * Found the data, now need to remove everything - * and update the hashmap. - */ - next = ptr->next; - - if (ptr->prev) - ptr->prev->next = ptr->next; - if (ptr->next) - ptr->next->prev = ptr->prev; - - if (map->buckets[hash].head == ptr) - map->buckets[hash].head = ptr->next; - if (map->buckets[hash].tail == ptr) - map->buckets[hash].tail = ptr->prev; - - safefree (ptr->key); - safefree (ptr->data); - safefree (ptr); - - ++deleted; - --map->end_iterator; - - ptr = next; - continue; - } - - /* This entry didn't contain the key; move to the next one */ - ptr = ptr->next; - } - - /* The key was not found, so return 0 */ - return deleted; -} - -/* - * Look up the value for a variable. - */ -char *lookup_variable (hashmap_t map, const char *varname) -{ - hashmap_iter result_iter; - char *key; - char *data; - - result_iter = hashmap_find (map, varname); - - if (hashmap_is_end (map, result_iter)) - return (NULL); - - if (hashmap_return_entry (map, result_iter, - &key, (void **) &data) < 0) - return (NULL); - - return (data); -} diff --git a/src/hashmap.h b/src/hashmap.h deleted file mode 100644 index 92067370..00000000 --- a/src/hashmap.h +++ /dev/null @@ -1,125 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2002 Robert James Kaes - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* See 'hashmap.c' for detailed information. */ - -#ifndef _HASHMAP_H -#define _HASHMAP_H - -#include "common.h" - -/* - * We're using a typedef here to "hide" the implementation details of the - * hash map. Sure, it's a pointer, but the struct is hidden in the C file. - * So, just use the hashmap_t like it's a cookie. :) - */ -typedef struct hashmap_s *hashmap_t; -typedef int hashmap_iter; - -/* - * hashmap_create() takes one argument, which is the number of buckets to - * use internally. hashmap_delete() is self explanatory. - */ -extern hashmap_t hashmap_create (unsigned int nbuckets); -extern int hashmap_delete (hashmap_t map); - -/* - * When the you insert a key/data pair into the hashmap it will the key - * and data are duplicated, so you must free your copy if it was created - * on the heap. The key must be a NULL terminated string. "data" must be - * non-NULL and length must be greater than zero. - * - * Returns: negative on error - * 0 upon successful insert - */ -extern int hashmap_insert (hashmap_t map, const char *key, - const void *data, size_t len); - -/* - * Get an iterator to the first entry. - * - * Returns: an negative value upon error. - */ -extern hashmap_iter hashmap_first (hashmap_t map); - -/* - * Checks to see if the iterator is pointing at the "end" of the entries. - * - * Returns: 1 if it is the end - * 0 otherwise - */ -extern int hashmap_is_end (hashmap_t map, hashmap_iter iter); - -/* - * Return a "pointer" to the first instance of the particular key. It can - * be tested against hashmap_is_end() to see if the key was not found. - * - * Returns: negative upon an error - * an "iterator" pointing at the first key - * an "end-iterator" if the key wasn't found - */ -extern hashmap_iter hashmap_find (hashmap_t map, const char *key); - -/* - * Retrieve the key/data associated with a particular iterator. - * NOTE: These are pointers to the actual data, so don't mess around with them - * too much. - * - * Returns: the length of the data block upon success - * negative upon error - */ -extern ssize_t hashmap_return_entry (hashmap_t map, hashmap_iter iter, - char **key, void **data); - -/* - * Get the first entry (assuming there is more than one) for a particular - * key. The data MUST be non-NULL. - * - * Returns: negative upon error - * zero if no entry is found - * length of data for the entry - */ -extern ssize_t hashmap_entry_by_key (hashmap_t map, const char *key, - void **data); - -/* - * Searches for _any_ occurrances of "key" within the hashmap and returns the - * number of matching entries. - * - * Returns: negative upon an error - * zero if no key is found - * count found (positive value) - */ -extern ssize_t hashmap_search (hashmap_t map, const char *key); - -/* - * Go through the hashmap and remove the particular key. - * NOTE: This will invalidate any iterators which have been created. - * - * Remove: negative upon error - * 0 if the key was not found - * positive count of entries deleted - */ -extern ssize_t hashmap_remove (hashmap_t map, const char *key); - -/* - * Look up the value for a variable. - */ -extern char *lookup_variable (hashmap_t map, const char *varname); - -#endif /* _HASHMAP_H */ diff --git a/src/heap.c b/src/heap.c index c7d85600..0611c396 100644 --- a/src/heap.c +++ b/src/heap.c @@ -97,61 +97,3 @@ char *debugging_strdup (const char *s, const char *file, unsigned long line) #endif /* !NDEBUG */ -/* - * Allocate a block of memory in the "shared" memory region. - * - * FIXME: This uses the most basic (and slowest) means of creating a - * shared memory location. It requires the use of a temporary file. We might - * want to look into something like MM (Shared Memory Library) for a better - * solution. - */ -void *malloc_shared_memory (size_t size) -{ - int fd; - void *ptr; - char buffer[32]; - - static const char *shared_file = "/tmp/tinyproxy.shared.XXXXXX"; - - assert (size > 0); - - strlcpy (buffer, shared_file, sizeof (buffer)); - - /* Only allow u+rw bits. This may be required for some versions - * of glibc so that mkstemp() doesn't make us vulnerable. - */ - umask (0177); - - if ((fd = mkstemp (buffer)) == -1) - return MAP_FAILED; - unlink (buffer); - - if (ftruncate (fd, size) == -1) - return MAP_FAILED; - ptr = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - - return ptr; -} - -/* - * Allocate a block of memory from the "shared" region an initialize it to - * zero. - */ -void *calloc_shared_memory (size_t nmemb, size_t size) -{ - void *ptr; - long length; - - assert (nmemb > 0); - assert (size > 0); - - length = nmemb * size; - - ptr = malloc_shared_memory (length); - if (ptr == MAP_FAILED) - return ptr; - - memset (ptr, 0, length); - - return ptr; -} diff --git a/src/heap.h b/src/heap.h index f3cf6715..da644616 100644 --- a/src/heap.h +++ b/src/heap.h @@ -52,10 +52,4 @@ extern char *debugging_strdup (const char *s, const char *file, #endif -/* - * Allocate memory from the "shared" region of memory. - */ -extern void *malloc_shared_memory (size_t size); -extern void *calloc_shared_memory (size_t nmemb, size_t size); - #endif diff --git a/src/hostspec.c b/src/hostspec.c new file mode 100644 index 00000000..1f956f2b --- /dev/null +++ b/src/hostspec.c @@ -0,0 +1,179 @@ +#include "common.h" +#include "hostspec.h" +#include "heap.h" +#include "network.h" + +static int dotted_mask(char *bitmask_string, unsigned char array[]) +{ + unsigned char v4bits[4]; + if (1 != inet_pton (AF_INET, bitmask_string, v4bits)) return -1; + memset (array, 0xff, IPV6_LEN-4); + memcpy (array + IPV6_LEN-4, v4bits, 4); + return 0; +} + +/* + * Fills in the netmask array given a numeric value. + * + * Returns: + * 0 on success + * -1 on failure (invalid mask value) + * + */ +static int +fill_netmask_array (char *bitmask_string, int v6, + unsigned char array[]) +{ + unsigned int i; + unsigned long int mask; + char *endptr; + + errno = 0; /* to distinguish success/failure after call */ + if (strchr (bitmask_string, '.')) { + if (v6) return -1; /* ipv6 doesn't supported dotted netmasks */ + return dotted_mask(bitmask_string, array); + } + mask = strtoul (bitmask_string, &endptr, 10); + + /* check for various conversion errors */ + if ((errno == ERANGE && mask == ULONG_MAX) + || (errno != 0 && mask == 0) || (endptr == bitmask_string)) + return -1; + + if (v6 == 0) { + /* The mask comparison is done as an IPv6 address, so + * convert to a longer mask in the case of IPv4 + * addresses. */ + mask += 12 * 8; + } + + /* check valid range for a bit mask */ + if (mask > (8 * IPV6_LEN)) + return -1; + + /* we have a valid range to fill in the array */ + for (i = 0; i != IPV6_LEN; ++i) { + if (mask >= 8) { + array[i] = 0xff; + mask -= 8; + } else if (mask > 0) { + array[i] = (unsigned char) (0xff << (8 - mask)); + mask = 0; + } else { + array[i] = 0; + } + } + + return 0; +} + + +/* parse a location string containing either an ipv4/ipv4 + hostmask tuple + or a dnsname into a struct hostspec. + returns 0 on success, non-0 on error (might be memory allocation, bogus + ip address or mask). +*/ +int hostspec_parse(char *location, struct hostspec *h) { + char *mask, ip_dst[IPV6_LEN]; + + h->type = HST_NONE; + if(!location) return 0; + + memset(h, 0, sizeof(*h)); + if ((mask = strrchr(location, '/'))) + *(mask++) = 0; + + /* + * Check for a valid IP address (the simplest case) first. + */ + if (full_inet_pton (location, ip_dst) > 0) { + h->type = HST_NUMERIC; + memcpy (h->address.ip.network, ip_dst, IPV6_LEN); + if(!mask) memset (h->address.ip.mask, 0xff, IPV6_LEN); + else { + char dst[sizeof(struct in6_addr)]; + int v6, i; + /* Check if the IP address before the netmask is + * an IPv6 address */ + if (inet_pton(AF_INET6, location, dst) > 0) + v6 = 1; + else + v6 = 0; + + if (fill_netmask_array + (mask, v6, &(h->address.ip.mask[0])) + < 0) + goto err; + + for (i = 0; i < IPV6_LEN; i++) + h->address.ip.network[i] = ip_dst[i] & + h->address.ip.mask[i]; + } + } else { + /* either bogus IP or hostname */ + /* bogus ipv6 ? */ + if (mask || strchr (location, ':')) + goto err; + + /* In all likelihood a string */ + h->type = HST_STRING; + h->address.string = safestrdup (location); + if (!h->address.string) + goto err; + } + /* restore mask */ + if(mask) *(--mask) = '/'; + return 0; +err:; + if(mask) *(--mask) = '/'; + return -1; +} + +static int string_match(const char *ip, const char *addrspec) +{ + size_t test_length, match_length; + if(!strcasecmp(ip, addrspec)) return 1; + if(addrspec[0] != '.') return 0; + test_length = strlen (ip); + match_length = strlen (addrspec); + if (test_length < match_length) return 0; + return (strcasecmp + (ip + (test_length - match_length), + addrspec) == 0); +} + +static int numeric_match(const uint8_t addr[], const struct hostspec *h) +{ + uint8_t x, y; + int i; + + for (i = 0; i != IPV6_LEN; ++i) { + x = addr[i] & h->address.ip.mask[i]; + y = h->address.ip.network[i]; + + /* If x and y don't match, the IP addresses don't match */ + if (x != y) + return 0; + } + + return 1; +} + +/* check whether ip matches hostspec. + return 1 on match, 0 on non-match */ +int hostspec_match(const char *ip, const struct hostspec *h) { + int is_numeric_addr; + uint8_t numeric_addr[IPV6_LEN]; + if (ip[0] == '\0') return 0; + is_numeric_addr = (full_inet_pton (ip, &numeric_addr) > 0); + switch (h->type) { + case HST_STRING: + if(is_numeric_addr) return 0; + return string_match (ip, h->address.string); + case HST_NUMERIC: + return numeric_match (numeric_addr, h); + case HST_NONE: + return 0; + } + return 0; +} diff --git a/src/hostspec.h b/src/hostspec.h new file mode 100644 index 00000000..9d1d7bfb --- /dev/null +++ b/src/hostspec.h @@ -0,0 +1,26 @@ +#ifndef HOSTSPEC_H +#define HOSTSPEC_H + +#define IPV6_LEN 16 + +enum hostspec_type { + HST_NONE, + HST_STRING, + HST_NUMERIC, +}; + +struct hostspec { + enum hostspec_type type; + union { + char *string; + struct { + unsigned char network[IPV6_LEN]; + unsigned char mask[IPV6_LEN]; + } ip; + } address; +}; + +int hostspec_parse(char *domain, struct hostspec *h); +int hostspec_match(const char *ip, const struct hostspec *h); + +#endif diff --git a/src/hsearch.c b/src/hsearch.c new file mode 100644 index 00000000..dfe1404a --- /dev/null +++ b/src/hsearch.c @@ -0,0 +1,222 @@ +/* +musl license, hsearch.c originally written by Szabolcs Nagy + +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include "hsearch.h" + +/* +open addressing hash table with 2^n table size +quadratic probing is used in case of hash collision +tab indices and hash are size_t +after resize fails with ENOMEM the state of tab is still usable +*/ + +typedef struct htab_entry { + char *key; + htab_value data; +} htab_entry; + +struct elem { + htab_entry item; + size_t hash; +}; + +struct htab { + struct elem *elems; + size_t mask; + size_t used; + size_t seed; + size_t dead; +}; + +#define MINSIZE 8 +#define MAXSIZE ((size_t)-1/2 + 1) + +#define CASE_INSENSITIVE +#ifdef CASE_INSENSITIVE +#include +#include +#define LOWER_OR_NOT(X) tolower(X) +#define STRCMP(X, Y) strcasecmp(X, Y) +#else +#define LOWER_OR_NOT(X) X +#define STRCMP(X, Y) strcmp(X, Y) +#endif + +static size_t keyhash(const char *k, size_t seed) +{ + const unsigned char *p = (const void *)k; + size_t h = seed; + + while (*p) + h = 31*h + LOWER_OR_NOT(*p++); + return h; +} + +static int resize(struct htab *htab, size_t nel) +{ + size_t newsize; + size_t i, j; + size_t oldmask = htab->mask; + struct elem *e, *newe; + struct elem *oldtab = htab->elems; + struct elem *oldend; + + if (nel > MAXSIZE) + nel = MAXSIZE; + for (newsize = MINSIZE; newsize < nel; newsize *= 2); + htab->elems = calloc(newsize, sizeof *htab->elems); + if (!htab->elems) { + htab->elems = oldtab; + return 0; + } + htab->mask = newsize - 1; + if (!oldtab) + return 1; + + oldend = oldtab + oldmask + 1; + for (e = oldtab; e < oldend; e++) + if (e->item.key) { + for (i=e->hash,j=1; ; i+=j++) { + newe = htab->elems + (i & htab->mask); + if (!newe->item.key) + break; + } + *newe = *e; + } + free(oldtab); + return 1; +} + +static struct elem *lookup(struct htab *htab, const char *key, size_t hash, size_t dead) +{ + size_t i, j; + struct elem *e; + + for (i=hash,j=1; ; i+=j++) { + e = htab->elems + (i & htab->mask); + if ((!e->item.key && (!e->hash || e->hash == dead)) || + (e->hash==hash && STRCMP(e->item.key, key)==0)) + break; + } + return e; +} + +struct htab *htab_create(size_t nel) +{ + struct htab *r = calloc(1, sizeof *r); + if(r && !resize(r, nel)) { + free(r); + r = 0; + } + r->seed = rand(); + return r; +} + +void htab_destroy(struct htab *htab) +{ + free(htab->elems); + free(htab); +} + +static struct elem *htab_find_elem(struct htab *htab, const char* key) +{ + size_t hash = keyhash(key, htab->seed); + struct elem *e = lookup(htab, key, hash, 0); + + if (e->item.key) { + return e; + } + return 0; +} + +htab_value* htab_find(struct htab *htab, const char* key) +{ + struct elem *e = htab_find_elem(htab, key); + if(!e) return 0; + return &e->item.data; +} + +htab_value* htab_find2(struct htab *htab, const char* key, char **saved_key) +{ + struct elem *e = htab_find_elem(htab, key); + if(!e) return 0; + *saved_key = e->item.key; + return &e->item.data; +} + +int htab_delete(struct htab *htab, const char* key) +{ + struct elem *e = htab_find_elem(htab, key); + if(!e) return 0; + e->item.key = 0; + e->hash = 0xdeadc0de; + --htab->used; + ++htab->dead; + return 1; +} + +int htab_insert(struct htab *htab, char* key, htab_value value) +{ + size_t hash = keyhash(key, htab->seed), oh; + struct elem *e = lookup(htab, key, hash, 0xdeadc0de); + if(e->item.key) { + /* it's not allowed to overwrite existing data */ + return 0; + } + + oh = e->hash; /* save old hash in case it's tombstone marker */ + e->item.key = key; + e->item.data = value; + e->hash = hash; + if (++htab->used + htab->dead > htab->mask - htab->mask/4) { + if (!resize(htab, 2*htab->used)) { + htab->used--; + e->item.key = 0; + e->hash = oh; + return 0; + } + htab->dead = 0; + } else if (oh == 0xdeadc0de) { + /* re-used tomb */ + --htab->dead; + } + return 1; +} + +size_t htab_next(struct htab *htab, size_t iterator, char** key, htab_value **v) +{ + size_t i; + for(i=iterator;imask+1;++i) { + struct elem *e = htab->elems + i; + if(e->item.key) { + *key = e->item.key; + *v = &e->item.data; + return i+1; + } + } + return 0; +} diff --git a/src/hsearch.h b/src/hsearch.h new file mode 100644 index 00000000..7e9d7709 --- /dev/null +++ b/src/hsearch.h @@ -0,0 +1,23 @@ +#ifndef HSEARCH_H +#define HSEARCH_H + +#include + +typedef union htab_value { + void *p; + size_t n; +} htab_value; + +#define HTV_N(N) (htab_value) {.n = N} +#define HTV_P(P) (htab_value) {.p = P} + +struct htab * htab_create(size_t); +void htab_destroy(struct htab *); +htab_value* htab_find(struct htab *, const char* key); +/* same as htab_find, but can retrieve the saved key (for freeing) */ +htab_value* htab_find2(struct htab *htab, const char* key, char **saved_key); +int htab_insert(struct htab *, char*, htab_value); +int htab_delete(struct htab *htab, const char* key); +size_t htab_next(struct htab *, size_t iterator, char** key, htab_value **v); + +#endif diff --git a/src/html-error.c b/src/html-error.c index 0c011a76..2b870402 100644 --- a/src/html-error.c +++ b/src/html-error.c @@ -20,9 +20,9 @@ * HTML error pages with variable substitution. */ +#include "common.h" #include "main.h" -#include "common.h" #include "buffer.h" #include "conns.h" #include "heap.h" @@ -30,6 +30,9 @@ #include "network.h" #include "utils.h" #include "conf.h" +#include "log.h" + +#include /* * Add an error number -> filename mapping to the errorpages list. @@ -37,19 +40,25 @@ #define ERRORNUM_BUFSIZE 8 /* this is more than required */ #define ERRPAGES_BUCKETCOUNT 16 -int add_new_errorpage (char *filepath, unsigned int errornum) +int add_new_errorpage (struct config_s *conf, char *filepath, + unsigned int errornum) { - char errornbuf[ERRORNUM_BUFSIZE]; + char errornbuf[ERRORNUM_BUFSIZE], *k; - config.errorpages = hashmap_create (ERRPAGES_BUCKETCOUNT); - if (!config.errorpages) + if (!conf->errorpages) + conf->errorpages = htab_create (ERRPAGES_BUCKETCOUNT); + if (!conf->errorpages) return (-1); snprintf (errornbuf, ERRORNUM_BUFSIZE, "%u", errornum); - if (hashmap_insert (config.errorpages, errornbuf, - filepath, strlen (filepath) + 1) < 0) + k = safestrdup(errornbuf); + if (!k) return -1; + + if (!htab_insert (conf->errorpages, k, HTV_P(filepath))) { + safefree(k); return (-1); + } return (0); } @@ -59,28 +68,51 @@ int add_new_errorpage (char *filepath, unsigned int errornum) */ static char *get_html_file (unsigned int errornum) { - hashmap_iter result_iter; char errornbuf[ERRORNUM_BUFSIZE]; - char *key; - char *val; + htab_value *hv; assert (errornum >= 100 && errornum < 1000); - if (!config.errorpages) - return (config.errorpage_undef); + if (!config->errorpages) + return (config->errorpage_undef); snprintf (errornbuf, ERRORNUM_BUFSIZE, "%u", errornum); - result_iter = hashmap_find (config.errorpages, errornbuf); - - if (hashmap_is_end (config.errorpages, result_iter)) - return (config.errorpage_undef); + hv = htab_find (config->errorpages, errornbuf); + if (!hv) return (config->errorpage_undef); + return hv->p; +} - if (hashmap_return_entry (config.errorpages, result_iter, - &key, (void **) &val) < 0) - return (config.errorpage_undef); +static char *lookup_variable (struct htab *map, const char *varname) { + htab_value *v; + v = htab_find(map, varname); + return v ? v->p : 0; +} - return (val); +static void varsubst_sendline(struct conn_s *connptr, regex_t *re, char *p) { + int fd = connptr->client_fd; + while(*p) { + regmatch_t match; + char varname[32+1], *varval; + size_t l; + int st = regexec(re, p, 1, &match, 0); + if(st == 0) { + if(match.rm_so > 0) safe_write(fd, p, match.rm_so); + l = match.rm_eo - match.rm_so; + assert(l>2 && l-2 < sizeof(varname)); + p += match.rm_so; + memcpy(varname, p+1, l-2); + varname[l-2] = 0; + varval = lookup_variable(connptr->error_variables, varname); + if(varval) write_message(fd, "%s", varval); + else if(varval && !*varval) write_message(fd, "(unknown)"); + else safe_write(fd, p, l); + p += l; + } else { + write_message(fd, "%s", p); + break; + } + } } /* @@ -89,91 +121,34 @@ static char *get_html_file (unsigned int errornum) int send_html_file (FILE *infile, struct conn_s *connptr) { - char *inbuf; - char *varstart = NULL; - char *p; - const char *varval; - int in_variable = 0; - int r = 0; - - inbuf = (char *) safemalloc (4096); - - while (fgets (inbuf, 4096, infile) != NULL) { - for (p = inbuf; *p; p++) { - switch (*p) { - case '}': - if (in_variable) { - *p = '\0'; - varval = (const char *) - lookup_variable (connptr->error_variables, - varstart); - if (!varval) - varval = "(unknown)"; - r = write_message (connptr->client_fd, - "%s", varval); - in_variable = 0; - } else { - r = write_message (connptr->client_fd, - "%c", *p); - } - - break; - - case '{': - /* a {{ will print a single {. If we are NOT - * already in a { variable, then proceed with - * setup. If we ARE already in a { variable, - * this code will fallthrough to the code that - * just dumps a character to the client fd. - */ - if (!in_variable) { - varstart = p + 1; - in_variable++; - } else - in_variable = 0; - - default: - if (!in_variable) { - r = write_message (connptr->client_fd, - "%c", *p); - } - } + regex_t re; + char *inbuf = safemalloc (4096); + (void) regcomp(&re, "{[a-z]\\{1,32\\}}", 0); - if (r) - break; - } - - if (r) - break; - - in_variable = 0; + while (fgets (inbuf, 4096, infile)) { + varsubst_sendline(connptr, &re, inbuf); } + regfree (&re); safefree (inbuf); - - return r; + return 1; } -int send_http_headers (struct conn_s *connptr, int code, const char *message) +int send_http_headers ( + struct conn_s *connptr, int code, + const char *message, const char *extra) { const char headers[] = - "HTTP/1.0 %d %s\r\n" - "Server: %s/%s\r\n" + "HTTP/1.%u %d %s\r\n" + "Server: %s\r\n" "Content-Type: text/html\r\n" "%s" "Connection: close\r\n" "\r\n"; - const char auth_str[] = - "Proxy-Authenticate: Basic realm=\"" - PACKAGE_NAME "\"\r\n"; - - /* according to rfc7235, the 407 error must be accompanied by - a Proxy-Authenticate header field. */ - const char *add = code == 407 ? auth_str : ""; - return (write_message (connptr->client_fd, headers, - code, message, PACKAGE, VERSION, - add)); + connptr->protocol.major != 1 ? 0 : connptr->protocol.minor, + code, message, PACKAGE, + extra)); } /* @@ -194,20 +169,47 @@ int send_http_error_message (struct conn_s *connptr) "

%s

\n" "

%s

\n" "
\n" - "

Generated by %s version %s.

\n" "\n" + "

Generated by %s.

\n" "\n" "\n"; + /* according to rfc7235, the 407 error must be accompanied by + a Proxy-Authenticate header field. */ + const char *auth_str_type = + connptr->error_number == 407 ? "Proxy-Authenticate" : + (connptr->error_number == 401 ? "WWW-Authenticate" : ""); + + const char auth_str_tpl[] = "%s: Basic realm=\"%s\"\r\n"; + char* auth_str_add = NULL; + + if (auth_str_type[0] != 0) { + int auth_str_size = snprintf (NULL, 0, auth_str_tpl, + auth_str_type, config->basicauth_realm) + 1; + if (auth_str_size > 0) { + auth_str_add = safemalloc (auth_str_size); + if (auth_str_add != NULL) { + snprintf (auth_str_add, auth_str_size, auth_str_tpl, + auth_str_type, config->basicauth_realm); + } + } + } + send_http_headers (connptr, connptr->error_number, - connptr->error_string); + connptr->error_string, auth_str_add ? auth_str_add : ""); + + if (auth_str_add) safefree (auth_str_add); error_file = get_html_file (connptr->error_number); - if (!(infile = fopen (error_file, "r"))) { - char *detail = lookup_variable (connptr->error_variables, "detail"); + if (!error_file || !(infile = fopen (error_file, "r"))) { + char *detail; + if (error_file) log_message (LOG_ERR, + "Error opening error file '%s' (%s)", + error_file, strerror (errno)); + detail = lookup_variable (connptr->error_variables, "detail"); return (write_message (connptr->client_fd, fallback_error, connptr->error_number, connptr->error_string, connptr->error_string, - detail, PACKAGE, VERSION)); + detail, PACKAGE)); } ret = send_html_file (infile, connptr); @@ -224,14 +226,25 @@ int send_http_error_message (struct conn_s *connptr) int add_error_variable (struct conn_s *connptr, const char *key, const char *val) { + char *k, *v; + if (!connptr->error_variables) if (! (connptr->error_variables = - hashmap_create (ERRVAR_BUCKETCOUNT))) + htab_create (ERRVAR_BUCKETCOUNT))) return (-1); - return hashmap_insert (connptr->error_variables, key, val, - strlen (val) + 1); + k = safestrdup(key); + v = safestrdup(val); + + if (!v || !k) goto oom; + + if(htab_insert (connptr->error_variables, k, HTV_P(v))) + return 1; +oom:; + safefree(k); + safefree(v); + return -1; } #define ADD_VAR_RET(x, y) \ @@ -250,6 +263,7 @@ int add_standard_vars (struct conn_s *connptr) char errnobuf[16]; char timebuf[30]; time_t global_time; + struct tm tm_buf; snprintf (errnobuf, sizeof errnobuf, "%d", connptr->error_number); ADD_VAR_RET ("errno", errnobuf); @@ -257,7 +271,6 @@ int add_standard_vars (struct conn_s *connptr) ADD_VAR_RET ("cause", connptr->error_string); ADD_VAR_RET ("request", connptr->request_line); ADD_VAR_RET ("clientip", connptr->client_ip_addr); - ADD_VAR_RET ("clienthost", connptr->client_string_addr); /* The following value parts are all non-NULL and will * trigger warnings in ADD_VAR_RET(), so we use @@ -266,7 +279,7 @@ int add_standard_vars (struct conn_s *connptr) global_time = time (NULL); strftime (timebuf, sizeof (timebuf), "%a, %d %b %Y %H:%M:%S GMT", - gmtime (&global_time)); + gmtime_r (&global_time, &tm_buf)); add_error_variable (connptr, "date", timebuf); add_error_variable (connptr, "website", diff --git a/src/html-error.h b/src/html-error.h index 03cec988..bc9b7ce9 100644 --- a/src/html-error.h +++ b/src/html-error.h @@ -23,8 +23,9 @@ /* Forward declaration */ struct conn_s; +struct config_s; -extern int add_new_errorpage (char *filepath, unsigned int errornum); +extern int add_new_errorpage (struct config_s *, char *filepath, unsigned int errornum); extern int send_http_error_message (struct conn_s *connptr); extern int indicate_http_error (struct conn_s *connptr, int number, const char *message, ...); @@ -32,7 +33,7 @@ extern int add_error_variable (struct conn_s *connptr, const char *key, const char *val); extern int send_html_file (FILE * infile, struct conn_s *connptr); extern int send_http_headers (struct conn_s *connptr, int code, - const char *message); + const char *message, const char *extra); extern int add_standard_vars (struct conn_s *connptr); #endif /* !TINYPROXY_HTML_ERROR_H */ diff --git a/src/http-message.c b/src/http-message.c index 8b94f191..4ff37ae9 100644 --- a/src/http-message.c +++ b/src/http-message.c @@ -232,6 +232,7 @@ int http_message_send (http_message_t msg, int fd) char timebuf[30]; time_t global_time; unsigned int i; + struct tm tm_buf; assert (is_http_message_valid (msg)); @@ -254,11 +255,11 @@ int http_message_send (http_message_t msg, int fd) /* Output the date */ global_time = time (NULL); strftime (timebuf, sizeof (timebuf), "%a, %d %b %Y %H:%M:%S GMT", - gmtime (&global_time)); + gmtime_r (&global_time, &tm_buf)); write_message (fd, "Date: %s\r\n", timebuf); /* Output the content-length */ - write_message (fd, "Content-length: %u\r\n", msg->body.length); + write_message (fd, "Content-length: %lu\r\n", (unsigned long) msg->body.length); /* Write the separator between the headers and body */ safe_write (fd, "\r\n", 2); diff --git a/src/log.c b/src/log.c index c5368b8f..a9ff71d2 100644 --- a/src/log.c +++ b/src/log.c @@ -27,8 +27,9 @@ #include "heap.h" #include "log.h" #include "utils.h" -#include "vector.h" +#include "sblist.h" #include "conf.h" +#include static const char *syslog_level[] = { NULL, @@ -45,6 +46,8 @@ static const char *syslog_level[] = { #define TIME_LENGTH 16 #define STRING_LENGTH 800 +static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; + /* * Global file descriptor for the log file */ @@ -61,7 +64,7 @@ static int log_level = LOG_INFO; * The key is the actual messages (already filled in full), and the value * is the log level. */ -static vector_t log_message_storage; +static sblist *log_message_storage; static unsigned int logging_initialized = FALSE; /* boolean */ @@ -71,10 +74,7 @@ static unsigned int logging_initialized = FALSE; /* boolean */ int open_log_file (const char *log_file_name) { if (log_file_name == NULL) { - if(config.godaemon == FALSE) - log_file_fd = fileno(stdout); - else - log_file_fd = -1; + log_file_fd = fileno(stdout); } else { log_file_fd = create_file_safely (log_file_name, FALSE); } @@ -108,16 +108,14 @@ void set_log_level (int level) void log_message (int level, const char *fmt, ...) { va_list args; - time_t nowtime; + struct timespec nowtime; + struct tm tm_buf; char time_string[TIME_LENGTH]; char str[STRING_LENGTH]; ssize_t ret; - if(!config.syslog && log_file_fd == -1) - return; - #ifdef NDEBUG /* * Figure out if we should write the message or not. @@ -132,7 +130,7 @@ void log_message (int level, const char *fmt, ...) return; #endif - if (config.syslog && level == LOG_CONN) + if (config && config->syslog && level == LOG_CONN) level = LOG_INFO; va_start (args, fmt); @@ -145,7 +143,7 @@ void log_message (int level, const char *fmt, ...) char *entry_buffer; if (!log_message_storage) { - log_message_storage = vector_create (); + log_message_storage = sblist_new (sizeof(char*), 64); if (!log_message_storage) goto out; } @@ -157,30 +155,30 @@ void log_message (int level, const char *fmt, ...) goto out; sprintf (entry_buffer, "%d %s", level, str); - vector_append (log_message_storage, entry_buffer, - strlen (entry_buffer) + 1); - - safefree (entry_buffer); + if(!sblist_add (log_message_storage, &entry_buffer)) + safefree (entry_buffer); goto out; } - if (config.syslog) { -#ifdef HAVE_VSYSLOG_H - vsyslog (level, fmt, args); -#else + if(!config->syslog && log_file_fd == -1) + goto out; + + if (config->syslog) { + pthread_mutex_lock(&log_mutex); vsnprintf (str, STRING_LENGTH, fmt, args); syslog (level, "%s", str); -#endif + pthread_mutex_unlock(&log_mutex); } else { char *p; - nowtime = time (NULL); + clock_gettime(CLOCK_REALTIME, &nowtime); /* Format is month day hour:minute:second (24 time) */ strftime (time_string, TIME_LENGTH, "%b %d %H:%M:%S", - localtime (&nowtime)); + localtime_r (&nowtime.tv_sec, &tm_buf)); - snprintf (str, STRING_LENGTH, "%-9s %s [%ld]: ", + snprintf (str, STRING_LENGTH, "%-9s %s.%03lu [%ld]: ", syslog_level[level], time_string, + (unsigned long) nowtime.tv_nsec/1000000ul, (long int) getpid ()); /* @@ -196,18 +194,24 @@ void log_message (int level, const char *fmt, ...) assert (log_file_fd >= 0); + pthread_mutex_lock(&log_mutex); ret = write (log_file_fd, str, strlen (str)); + pthread_mutex_unlock(&log_mutex); + if (ret == -1) { - config.syslog = TRUE; + config->syslog = TRUE; log_message(LOG_CRIT, "ERROR: Could not write to log " "file %s: %s.", - config.logf_name, strerror(errno)); + config->logf_name, strerror(errno)); log_message(LOG_CRIT, "Falling back to syslog logging"); } + pthread_mutex_lock(&log_mutex); fsync (log_file_fd); + pthread_mutex_unlock(&log_mutex); + } out: @@ -217,9 +221,9 @@ void log_message (int level, const char *fmt, ...) /* * This needs to send any stored log messages. */ -void send_stored_logs (void) +static void send_stored_logs (void) { - char *string; + char **string; char *ptr; int level; size_t i; @@ -229,12 +233,12 @@ void send_stored_logs (void) log_message(LOG_DEBUG, "sending stored logs"); - for (i = 0; (ssize_t) i != vector_length (log_message_storage); ++i) { - string = - (char *) vector_getentry (log_message_storage, i, NULL); + for (i = 0; i < sblist_getsize (log_message_storage); ++i) { + string = sblist_get (log_message_storage, i); + if (!string || !*string) continue; - ptr = strchr (string, ' ') + 1; - level = atoi (string); + ptr = strchr (*string, ' ') + 1; + level = atoi (*string); #ifdef NDEBUG if (log_level == LOG_CONN && level == LOG_INFO) @@ -247,9 +251,10 @@ void send_stored_logs (void) #endif log_message (level, "%s", ptr); + safefree(*string); } - vector_delete (log_message_storage); + sblist_free (log_message_storage); log_message_storage = NULL; log_message(LOG_DEBUG, "done sending stored logs"); @@ -264,27 +269,24 @@ void send_stored_logs (void) */ int setup_logging (void) { - if (!config.syslog) { - if (open_log_file (config.logf_name) < 0) { + if (!config->syslog) { + if (open_log_file (config->logf_name) < 0) { /* * If opening the log file fails, we try * to fall back to syslog logging... */ - config.syslog = TRUE; + config->syslog = TRUE; log_message (LOG_CRIT, "ERROR: Could not create log " "file %s: %s.", - config.logf_name, strerror (errno)); + config->logf_name, strerror (errno)); log_message (LOG_CRIT, "Falling back to syslog logging."); } } - if (config.syslog) { - if (config.godaemon == TRUE) - openlog ("tinyproxy", LOG_PID, LOG_DAEMON); - else - openlog ("tinyproxy", LOG_PID, LOG_USER); + if (config->syslog) { + openlog ("tinyproxy", LOG_PID, LOG_USER); } logging_initialized = TRUE; @@ -302,7 +304,7 @@ void shutdown_logging (void) return; } - if (config.syslog) { + if (config->syslog) { closelog (); } else { close_log_file (); diff --git a/src/log.h b/src/log.h index 68c89c3e..76bfe6bb 100644 --- a/src/log.h +++ b/src/log.h @@ -106,7 +106,6 @@ extern void close_log_file (void); extern void log_message (int level, const char *fmt, ...); extern void set_log_level (int level); -extern void send_stored_logs (void); extern int setup_logging (void); extern void shutdown_logging (void); diff --git a/src/loop.c b/src/loop.c new file mode 100644 index 00000000..d696760d --- /dev/null +++ b/src/loop.c @@ -0,0 +1,81 @@ +#include +#include +#include "loop.h" +#include "conf.h" +#include "main.h" +#include "sblist.h" +#include "sock.h" + +struct loop_record { + union sockaddr_union addr; + time_t tstamp; +}; + +static sblist *loop_records; +static pthread_mutex_t loop_records_lock = PTHREAD_MUTEX_INITIALIZER; + +void loop_records_init(void) { + loop_records = sblist_new(sizeof (struct loop_record), 32); +} + +void loop_records_destroy(void) { + sblist_free(loop_records); + loop_records = 0; +} + +#if 0 +static void su_to_str(union sockaddr_union *addr, char *buf) { + int af = addr->v4.sin_family; + unsigned port = ntohs(af == AF_INET ? addr->v4.sin_port : addr->v6.sin6_port); + char portb[32]; + sprintf(portb, ":%u", port); + getpeer_information (addr, buf, 256); + strcat(buf, portb); +} +#endif + +void loop_records_add(union sockaddr_union *addr) { + time_t now =time(0); + struct loop_record rec; + pthread_mutex_lock(&loop_records_lock); + rec.tstamp = now; + rec.addr = *addr; + sblist_add(loop_records, &rec); + pthread_mutex_unlock(&loop_records_lock); +} + +#define TIMEOUT_SECS 15 + +int connection_loops (union sockaddr_union *addr) { + int ret = 0, af, our_af = addr->v4.sin_family; + void *ipdata, *our_ipdata = our_af == AF_INET ? (void*)&addr->v4.sin_addr.s_addr : (void*)&addr->v6.sin6_addr.s6_addr; + size_t i, cmp_len = our_af == AF_INET ? sizeof(addr->v4.sin_addr.s_addr) : sizeof(addr->v6.sin6_addr.s6_addr); + unsigned port, our_port = ntohs(our_af == AF_INET ? addr->v4.sin_port : addr->v6.sin6_port); + time_t now = time(0); + + pthread_mutex_lock(&loop_records_lock); + for (i = 0; i < sblist_getsize(loop_records); ) { + struct loop_record *rec = sblist_get(loop_records, i); + + if (rec->tstamp + TIMEOUT_SECS < now) { + sblist_delete(loop_records, i); + continue; + } + + if (!ret) { + af = rec->addr.v4.sin_family; + if (af != our_af) goto next; + port = ntohs(af == AF_INET ? rec->addr.v4.sin_port : rec->addr.v6.sin6_port); + if (port != our_port) goto next; + ipdata = af == AF_INET ? (void*)&rec->addr.v4.sin_addr.s_addr : (void*)&rec->addr.v6.sin6_addr.s6_addr; + if (!memcmp(ipdata, our_ipdata, cmp_len)) { + ret = 1; + } + } +next: + i++; + } + pthread_mutex_unlock(&loop_records_lock); + return ret; +} + diff --git a/src/loop.h b/src/loop.h new file mode 100644 index 00000000..27a26c77 --- /dev/null +++ b/src/loop.h @@ -0,0 +1,12 @@ +#ifndef LOOP_H +#define LOOP_H + +#include "sock.h" + +void loop_records_init(void); +void loop_records_destroy(void); +void loop_records_add(union sockaddr_union *addr); +int connection_loops (union sockaddr_union *addr); + +#endif + diff --git a/src/main.c b/src/main.c index 35ff704f..268255f1 100644 --- a/src/main.c +++ b/src/main.c @@ -32,13 +32,13 @@ #include "main.h" #include "anonymous.h" -#include "authors.h" #include "buffer.h" #include "conf.h" #include "daemon.h" #include "heap.h" #include "filter.h" #include "child.h" +#include "loop.h" #include "log.h" #include "reqs.h" #include "sock.h" @@ -48,10 +48,18 @@ /* * Global Structures */ -struct config_s config; -struct config_s config_defaults; +struct config_s *config; +static struct config_s configs[2]; +static const char* config_file; unsigned int received_sighup = FALSE; /* boolean */ +static struct config_s* +get_next_config(void) +{ + if (config == &configs[0]) return &configs[1]; + return &configs[0]; +} + /* * Handle a signal */ @@ -62,12 +70,14 @@ takesig (int sig) int status; switch (sig) { + case SIGUSR1: case SIGHUP: received_sighup = TRUE; break; + case SIGINT: case SIGTERM: - config.quit = TRUE; + config->quit = TRUE; break; case SIGCHLD: @@ -87,55 +97,6 @@ display_version (void) printf ("%s %s\n", PACKAGE, VERSION); } -/* - * Display the copyright and license for this program. - */ -static void -display_license (void) -{ - const char * const *authors; - const char * const *documenters; - - display_version (); - - printf ("\ -\n\ - Copyright 1998 Steven Young (sdyoung@well.com)\n\ - Copyright 1998-2002 Robert James Kaes (rjkaes@users.sourceforge.net)\n\ - Copyright 1999 George Talusan (gstalusan@uwaterloo.ca)\n\ - Copyright 2000 Chris Lightfoot (chris@ex-parrot.com)\n\ - Copyright 2009-2010 Mukund Sivaraman (muks@banu.com)\n\ - Copyright 2009-2010 Michael Adam (obnox@samba.org)\n\ -\n\ - This program is free software; you can redistribute it and/or modify\n\ - it under the terms of the GNU General Public License as published by\n\ - the Free Software Foundation; either version 2, or (at your option)\n\ - any later version.\n\ -\n\ - This program is distributed in the hope that it will be useful,\n\ - but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ - GNU General Public License for more details.\n\ -\n\ - You should have received a copy of the GNU General Public License\n\ - along with this program; if not, write to the Free Software\n\ - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.\n\ -\n"); - - printf ("\nAUTHORS:\n"); - for (authors = authors_get_authors (); *authors; authors++) { - printf (" %s\n", *authors); - } - - printf ("\nDOCUMENTERS:\n"); - for (documenters = authors_get_documenters (); - *documenters; documenters++) { - printf (" %s\n", *documenters); - } - - printf ("\n"); -} - /* * Display usage to the user. */ @@ -150,7 +111,6 @@ display_usage (void) " -d Do not daemonize (run in foreground).\n" " -c FILE Use an alternate configuration file.\n" " -h Display this usage information.\n" - " -l Display the license.\n" " -v Display version information.\n"); /* Display the modes compiled into tinyproxy */ @@ -212,56 +172,6 @@ get_id (char *str) return atoi (str); } -/** - * process_cmdline: - * @argc: argc as passed to main() - * @argv: argv as passed to main() - * - * This function parses command line arguments. - **/ -static void -process_cmdline (int argc, char **argv, struct config_s *conf) -{ - int opt; - - while ((opt = getopt (argc, argv, "c:vldh")) != EOF) { - switch (opt) { - case 'v': - display_version (); - exit (EX_OK); - - case 'l': - display_license (); - exit (EX_OK); - - case 'd': - conf->godaemon = FALSE; - break; - - case 'c': - if (conf->config_file != NULL) { - safefree (conf->config_file); - } - conf->config_file = safestrdup (optarg); - if (!conf->config_file) { - fprintf (stderr, - "%s: Could not allocate memory.\n", - argv[0]); - exit (EX_SOFTWARE); - } - break; - - case 'h': - display_usage (); - exit (EX_OK); - - default: - display_usage (); - exit (EX_USAGE); - } - } -} - /** * change_user: * @program: The name of the program. Pass argv[0] here. @@ -273,16 +183,16 @@ process_cmdline (int argc, char **argv, struct config_s *conf) static void change_user (const char *program) { - if (config.group && strlen (config.group) > 0) { - int gid = get_id (config.group); + if (config->group && strlen (config->group) > 0) { + int gid = get_id (config->group); if (gid < 0) { - struct group *thisgroup = getgrnam (config.group); + struct group *thisgroup = getgrnam (config->group); if (!thisgroup) { fprintf (stderr, "%s: Unable to find group \"%s\".\n", - program, config.group); + program, config->group); exit (EX_NOUSER); } @@ -292,7 +202,7 @@ change_user (const char *program) if (setgid (gid) < 0) { fprintf (stderr, "%s: Unable to change to group \"%s\".\n", - program, config.group); + program, config->group); exit (EX_NOPERM); } @@ -307,19 +217,19 @@ change_user (const char *program) #endif log_message (LOG_INFO, "Now running as group \"%s\".", - config.group); + config->group); } - if (config.user && strlen (config.user) > 0) { - int uid = get_id (config.user); + if (config->user && strlen (config->user) > 0) { + int uid = get_id (config->user); if (uid < 0) { - struct passwd *thisuser = getpwnam (config.user); + struct passwd *thisuser = getpwnam (config->user); if (!thisuser) { fprintf (stderr, "%s: Unable to find user \"%s\".\n", - program, config.user); + program, config->user); exit (EX_NOUSER); } @@ -329,78 +239,100 @@ change_user (const char *program) if (setuid (uid) < 0) { fprintf (stderr, "%s: Unable to change to user \"%s\".\n", - program, config.user); + program, config->user); exit (EX_NOPERM); } log_message (LOG_INFO, "Now running as user \"%s\".", - config.user); + config->user); } } -static void initialize_config_defaults (struct config_s *conf) -{ - memset (conf, 0, sizeof(*conf)); - - conf->config_file = safestrdup (SYSCONFDIR "/tinyproxy.conf"); - if (!conf->config_file) { - fprintf (stderr, PACKAGE ": Could not allocate memory.\n"); - exit (EX_SOFTWARE); - } - conf->godaemon = TRUE; - /* - * Make sure the HTML error pages array is NULL to begin with. - * (FIXME: Should have a better API for all this) - */ - conf->errorpages = NULL; - conf->stathost = safestrdup (TINYPROXY_STATHOST); - conf->idletimeout = MAX_IDLE_TIME; - conf->logf_name = NULL; - conf->pidpath = NULL; -} - /** * convenience wrapper around reload_config_file * that also re-initializes logging. */ -int reload_config (void) +int reload_config (int reload_logging) { - int ret; + int ret, ret2; + struct config_s *c_next = get_next_config(); - shutdown_logging (); + log_message (LOG_NOTICE, "Reloading config file (%s)", config_file); + + if (reload_logging) shutdown_logging (); - ret = reload_config_file (config_defaults.config_file, &config, - &config_defaults); - if (ret != 0) { - goto done; + ret = reload_config_file (config_file, c_next); + + if (ret == 0) { + if(config) free_config (config); + config = c_next; } - ret = setup_logging (); + ret2 = reload_logging ? setup_logging () : 0; -done: - return ret; + if (ret != 0) + log_message (LOG_WARNING, "Reloading config file failed!"); + else + log_message (LOG_NOTICE, "Reloading config file finished"); + + return ret ? ret : ret2; +} + +static void setup_sig(int sig, signal_func *sigh, + const char* signame, const char* argv0) { + if (set_signal_handler (sig, sigh) == SIG_ERR) { + fprintf (stderr, "%s: Could not set the \"%s\" signal.\n", + argv0, signame); + exit (EX_OSERR); + } } int main (int argc, char **argv) { + int opt, daemonized = TRUE; + + srand(time(NULL)); /* for hashmap seeds */ + /* Only allow u+rw bits. This may be required for some versions * of glibc so that mkstemp() doesn't make us vulnerable. */ umask (0177); - log_message (LOG_INFO, "Initializing " PACKAGE " ..."); + log_message (LOG_NOTICE, "Initializing " PACKAGE " ..."); - if (config_compile_regex()) { + if (config_init()) { + fprintf(stderr, "ERROR: config_init() failed\n"); exit (EX_SOFTWARE); } - initialize_config_defaults (&config_defaults); - process_cmdline (argc, argv, &config_defaults); + config_file = SYSCONFDIR "/tinyproxy.conf"; + + while ((opt = getopt (argc, argv, "c:vdh")) != EOF) { + switch (opt) { + case 'v': + display_version (); + exit (EX_OK); + + case 'd': + daemonized = FALSE; + break; + + case 'c': + config_file = optarg; + break; + + case 'h': + display_usage (); + exit (EX_OK); + + default: + display_usage (); + exit (EX_USAGE); + } + } - if (reload_config_file (config_defaults.config_file, - &config, - &config_defaults)) { + if (reload_config(0)) { exit (EX_SOFTWARE); } @@ -410,40 +342,36 @@ main (int argc, char **argv) * in the list of allowed headers, since it is required in a * HTTP/1.0 request. Also add the Content-Type header since it * goes hand in hand with Content-Length. */ - if (is_anonymous_enabled ()) { - anonymous_insert ("Content-Length"); - anonymous_insert ("Content-Type"); + if (is_anonymous_enabled (config)) { + anonymous_insert (config, safestrdup("Content-Length")); + anonymous_insert (config, safestrdup("Content-Type")); } - if (config.godaemon == TRUE) { - if (!config.syslog && config.logf_name == NULL) + if (daemonized == TRUE) { + if (!config->syslog && config->logf_name == NULL) fprintf(stderr, "WARNING: logging deactivated " "(can't log to stdout when daemonized)\n"); makedaemon (); } - if (set_signal_handler (SIGPIPE, SIG_IGN) == SIG_ERR) { - fprintf (stderr, "%s: Could not set the \"SIGPIPE\" signal.\n", - argv[0]); - exit (EX_OSERR); - } + setup_sig(SIGPIPE, SIG_IGN, "SIGPIPE", argv[0]); #ifdef FILTER_ENABLE - if (config.filter) + if (config->filter) filter_init (); #endif /* FILTER_ENABLE */ /* Start listening on the selected port. */ - if (child_listening_sockets(config.listen_addrs, config.port) < 0) { + if (child_listening_sockets(config->listen_addrs, config->port) < 0) { fprintf (stderr, "%s: Could not create listening sockets.\n", argv[0]); exit (EX_OSERR); } /* Create pid file before we drop privileges */ - if (config.pidpath) { - if (pidfile_create (config.pidpath) < 0) { + if (config->pidpath) { + if (pidfile_create (config->pidpath) < 0) { fprintf (stderr, "%s: Could not create PID file.\n", argv[0]); exit (EX_OSERR); @@ -454,7 +382,7 @@ main (int argc, char **argv) if (geteuid () == 0) change_user (argv[0]); else - log_message (LOG_WARNING, + log_message (LOG_INFO, "Not running as root, so not changing UID/GID."); /* Create log file after we drop privileges */ @@ -462,56 +390,44 @@ main (int argc, char **argv) exit (EX_SOFTWARE); } - if (child_pool_create () < 0) { - fprintf (stderr, - "%s: Could not create the pool of children.\n", - argv[0]); - exit (EX_SOFTWARE); - } - /* These signals are only for the parent process. */ log_message (LOG_INFO, "Setting the various signals."); - if (set_signal_handler (SIGCHLD, takesig) == SIG_ERR) { - fprintf (stderr, "%s: Could not set the \"SIGCHLD\" signal.\n", - argv[0]); - exit (EX_OSERR); - } + setup_sig (SIGCHLD, takesig, "SIGCHLD", argv[0]); + setup_sig (SIGTERM, takesig, "SIGTERM", argv[0]); + setup_sig (SIGINT, takesig, "SIGINT", argv[0]); + if (daemonized) setup_sig (SIGHUP, takesig, "SIGHUP", argv[0]); + setup_sig (SIGUSR1, takesig, "SIGUSR1", argv[0]); - if (set_signal_handler (SIGTERM, takesig) == SIG_ERR) { - fprintf (stderr, "%s: Could not set the \"SIGTERM\" signal.\n", - argv[0]); - exit (EX_OSERR); - } - - if (set_signal_handler (SIGHUP, takesig) == SIG_ERR) { - fprintf (stderr, "%s: Could not set the \"SIGHUP\" signal.\n", - argv[0]); - exit (EX_OSERR); - } + loop_records_init(); /* Start the main loop */ log_message (LOG_INFO, "Starting main loop. Accepting connections."); child_main_loop (); - log_message (LOG_INFO, "Shutting down."); + log_message (LOG_NOTICE, "Shutting down."); child_kill_children (SIGTERM); child_close_sock (); + child_free_children(); + + loop_records_destroy(); /* Remove the PID file */ - if (config.pidpath != NULL && unlink (config.pidpath) < 0) { + if (config->pidpath != NULL && unlink (config->pidpath) < 0) { log_message (LOG_WARNING, "Could not remove PID file \"%s\": %s.", - config.pidpath, strerror (errno)); + config->pidpath, strerror (errno)); } #ifdef FILTER_ENABLE - if (config.filter) + if (config->filter) filter_destroy (); #endif /* FILTER_ENABLE */ + free_config (config); + shutdown_logging (); return EXIT_SUCCESS; diff --git a/src/main.h b/src/main.h index ca2ee4bf..d19a2f6d 100644 --- a/src/main.h +++ b/src/main.h @@ -29,9 +29,9 @@ #define MAX_IDLE_TIME (60 * 10) /* 10 minutes of no activity */ /* Global Structures used in the program */ -extern struct config_s config; +extern struct config_s *config; extern unsigned int received_sighup; /* boolean */ -extern int reload_config (void); +extern int reload_config (int reload_logging); #endif /* __MAIN_H__ */ diff --git a/src/mypoll.c b/src/mypoll.c new file mode 100644 index 00000000..495e2c37 --- /dev/null +++ b/src/mypoll.c @@ -0,0 +1,48 @@ +#include "mypoll.h" + +#ifdef HAVE_POLL_H +int mypoll(pollfd_struct* fds, int nfds, int timeout) { + int i, ret; + for(i=0; i maxfd) maxfd = fds[i].fd; + if(fds[i].events & MYPOLL_READ) FD_SET(fds[i].fd, r); + if(fds[i].events & MYPOLL_WRITE) FD_SET(fds[i].fd, w); + } + + if(timeout >= 0) t = &tv; + if(timeout > 0) tv.tv_sec = timeout; + + ret = select(maxfd+1, r, w, 0, t); + + switch(ret) { + case -1: + case 0: + return ret; + } + + for(i=0; i +typedef struct pollfd pollfd_struct; + +#define MYPOLL_READ POLLIN +#define MYPOLL_WRITE POLLOUT + +#else + +#define SELECT_OR_POLL "select" +#include +typedef struct mypollfd { + int fd; + short events; + short revents; +} pollfd_struct; + +#define MYPOLL_READ (1<<1) +#define MYPOLL_WRITE (1<<2) +#endif + +int mypoll(pollfd_struct* fds, int nfds, int timeout); + +#endif diff --git a/src/pseudomap.c b/src/pseudomap.c new file mode 100644 index 00000000..3b62e5bb --- /dev/null +++ b/src/pseudomap.c @@ -0,0 +1,99 @@ +#include "config.h" +#include "pseudomap.h" +#include +#include +#include + +/* this data structure implements a pseudo hashmap. + tinyproxy originally used a hashmap for keeping the key/value pairs + in HTTP requests; however later it turned out that items need to be + returned in order - so we implemented an "orderedmap". + again, later it turned out that there are are special case headers, + namely Set-Cookie that can happen more than once, so a hashmap isn't the + right structure to hold the key-value pairs in HTTP headers. + it's expected that: + 1) the number of headers in a HTTP request we have to process is + not big enough to cause a noticable performance drop when we have + to iterate through our list to find the right header; and + 2) use of plain HTTP is getting exceedingly extinct by the day, so + in most usecases CONNECT method is used anyway. +*/ + +/* restrict the number of headers to 256 to prevent an attacker from + launching a denial of service attack. */ +#define MAX_SIZE 256 + +pseudomap *pseudomap_create(void) { + return sblist_new(sizeof(struct pseudomap_entry), 64); +} + +void pseudomap_destroy(pseudomap *o) { + if(!o) return; + while(sblist_getsize(o)) { + /* retrieve latest element, and "shrink" list in place, + so we don't have to constantly rearrange list items + by using sblist_delete(). */ + struct pseudomap_entry *e = sblist_get(o, sblist_getsize(o)-1); + free(e->key); + free(e->value); + --o->count; + } + sblist_free(o); +} + +int pseudomap_append(pseudomap *o, const char *key, char *value ) { + struct pseudomap_entry e; + if(sblist_getsize(o) >= MAX_SIZE) return 0; + e.key = strdup(key); + e.value = strdup(value); + if(!e.key || !e.value) goto oom; + if(!sblist_add(o, &e)) goto oom; + return 1; +oom: + free(e.key); + free(e.value); + return 0; +} + +static size_t pseudomap_find_index(pseudomap *o, const char *key) { + size_t i; + struct pseudomap_entry *e; + for(i = 0; i < sblist_getsize(o); ++i) { + e = sblist_get(o, i); + if(!strcasecmp(key, e->key)) return i; + } + return (size_t)-1; +} + +char* pseudomap_find(pseudomap *o, const char *key) { + struct pseudomap_entry *e; + size_t i = pseudomap_find_index(o, key); + if(i == (size_t)-1) return 0; + e = sblist_get(o, i); + return e->value; +} + +/* remove *all* entries that match key, to mimic behaviour of hashmap */ +int pseudomap_remove(pseudomap *o, const char *key) { + struct pseudomap_entry *e; + size_t i; + int ret = 0; + while((i = pseudomap_find_index(o, key)) != (size_t)-1) { + e = sblist_get(o, i); + free(e->key); + free(e->value); + sblist_delete(o, i); + ret = 1; + } + return ret; +} + +size_t pseudomap_next(pseudomap *o, size_t iter, char** key, char** value) { + struct pseudomap_entry *e; + if(iter >= sblist_getsize(o)) return 0; + e = sblist_get(o, iter); + *key = e->key; + *value = e->value; + return iter + 1; +} + diff --git a/src/pseudomap.h b/src/pseudomap.h new file mode 100644 index 00000000..24c67b7d --- /dev/null +++ b/src/pseudomap.h @@ -0,0 +1,22 @@ +#ifndef PSEUDOMAP_H +#define PSEUDOMAP_H + +#include +#include "sblist.h" + +struct pseudomap_entry { + char *key; + char *value; +}; + +typedef sblist pseudomap; + +pseudomap *pseudomap_create(void); +void pseudomap_destroy(pseudomap *o); +int pseudomap_append(pseudomap *o, const char *key, char *value ); +char* pseudomap_find(pseudomap *o, const char *key); +int pseudomap_remove(pseudomap *o, const char *key); +size_t pseudomap_next(pseudomap *o, size_t iter, char** key, char** value); + +#endif + diff --git a/src/reqs.c b/src/reqs.c index 0e4e5f77..2e7542cb 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -32,7 +32,8 @@ #include "buffer.h" #include "conns.h" #include "filter.h" -#include "hashmap.h" +#include "hsearch.h" +#include "pseudomap.h" #include "heap.h" #include "html-error.h" #include "log.h" @@ -42,13 +43,15 @@ #include "stats.h" #include "text.h" #include "utils.h" -#include "vector.h" +#include "sblist.h" #include "reverse-proxy.h" #include "transparent-proxy.h" #include "upstream.h" #include "connect-ports.h" #include "conf.h" #include "basicauth.h" +#include "loop.h" +#include "mypoll.h" /* * Maximum length of a HTTP line @@ -60,9 +63,9 @@ * enabled. */ #ifdef UPSTREAM_SUPPORT -# define UPSTREAM_CONFIGURED() (config.upstream_list != NULL) -# define UPSTREAM_HOST(host) upstream_get(host, config.upstream_list) -# define UPSTREAM_IS_HTTP(conn) (conn->upstream_proxy != NULL && conn->upstream_proxy->type == HTTP_TYPE) +# define UPSTREAM_CONFIGURED() (config->upstream_list != NULL) +# define UPSTREAM_HOST(host) upstream_get(host, config->upstream_list) +# define UPSTREAM_IS_HTTP(conn) (conn->upstream_proxy != NULL && conn->upstream_proxy->type == PT_HTTP) #else # define UPSTREAM_CONFIGURED() (0) # define UPSTREAM_HOST(host) (NULL) @@ -171,7 +174,7 @@ static int strip_return_port (char *host) { char *ptr1; char *ptr2; - int port; + unsigned port; ptr1 = strrchr (host, ':'); if (ptr1 == NULL) @@ -183,8 +186,11 @@ static int strip_return_port (char *host) return 0; *ptr1++ = '\0'; - if (sscanf (ptr1, "%d", &port) != 1) /* one conversion required */ - return 0; + + port = atoi(ptr1); + /* check that port string is in the valid range 1-0xffff) */ + if(strlen(ptr1) > 5 || (port & 0xffff0000)) return 0; + return port; } @@ -265,37 +271,60 @@ establish_http_connection (struct conn_s *connptr, struct request_s *request) /* host is an IPv6 address literal, so surround it with * [] */ return write_message (connptr->server_fd, - "%s %s HTTP/1.0\r\n" + "%s %s HTTP/1.%u\r\n" "Host: [%s]%s\r\n" "Connection: close\r\n", request->method, request->path, + connptr->protocol.major != 1 ? 0 : + connptr->protocol.minor, request->host, portbuff); + } else if (connptr->upstream_proxy && + connptr->upstream_proxy->type == PT_HTTP && + connptr->upstream_proxy->ua.authstr) { + return write_message (connptr->server_fd, + "%s %s HTTP/1.%u\r\n" + "Host: %s%s\r\n" + "Connection: close\r\n" + "Proxy-Authorization: Basic %s\r\n", + request->method, request->path, + connptr->protocol.major != 1 ? 0 : + connptr->protocol.minor, + request->host, portbuff, + connptr->upstream_proxy->ua.authstr); } else { return write_message (connptr->server_fd, - "%s %s HTTP/1.0\r\n" + "%s %s HTTP/1.%u\r\n" "Host: %s%s\r\n" "Connection: close\r\n", request->method, request->path, + connptr->protocol.major != 1 ? 0 : + connptr->protocol.minor, request->host, portbuff); } } /* - * These two defines are for the SSL tunnelling. + * Send the appropriate response to the client to establish a + * connection via CONNECT method. */ -#define SSL_CONNECTION_RESPONSE "HTTP/1.0 200 Connection established" -#define PROXY_AGENT "Proxy-agent: " PACKAGE "/" VERSION - -/* - * Send the appropriate response to the client to establish a SSL - * connection. - */ -static int send_ssl_response (struct conn_s *connptr) +static int send_connect_method_response (struct conn_s *connptr) { return write_message (connptr->client_fd, - "%s\r\n" - "%s\r\n" - "\r\n", SSL_CONNECTION_RESPONSE, PROXY_AGENT); + "HTTP/1.%u 200 Connection established\r\n" + "Proxy-agent: " PACKAGE "\r\n" + "\r\n", connptr->protocol.major != 1 ? 0 : + connptr->protocol.minor); +} + +/* determine whether a hostname with optional trailing colon/port is the + stathost */ +static int is_stathost (const char* host) +{ + const char *p = config->stathost; + const char *q = host; + if (!p || !q) return 0; + while (*p && *(p++) == *(q++)); + return *p == 0 && (*q == 0 || *q == ':'); } /* @@ -303,13 +332,15 @@ static int send_ssl_response (struct conn_s *connptr) * build a new request line. Finally connect to the remote server. */ static struct request_s *process_request (struct conn_s *connptr, - hashmap_t hashofheaders) + pseudomap *hashofheaders) { char *url; struct request_s *request; - int ret; + int ret, skip_trans; size_t request_len; + skip_trans = 0; + /* NULL out all the fields so frees don't cause segfaults. */ request = (struct request_s *) safecalloc (1, sizeof (struct request_s)); @@ -326,8 +357,12 @@ static struct request_s *process_request (struct conn_s *connptr, goto fail; } + /* zero-terminate the strings so they don't contain junk in error page */ + request->method[0] = url[0] = request->protocol[0] = 0; + ret = sscanf (connptr->request_line, "%[^ ] %[^ ] %[^ ]", request->method, url, request->protocol); + if (ret == 2 && !strcasecmp (request->method, "GET")) { request->protocol[0] = 0; @@ -360,8 +395,18 @@ static struct request_s *process_request (struct conn_s *connptr, goto fail; } + /* + * Check to see if they're requesting the stat host + */ + if (is_stathost (pseudomap_find (hashofheaders, "host"))) { +got_stathost: + log_message (LOG_NOTICE, "Request for the stathost."); + connptr->show_stats = TRUE; + goto fail; + } + #ifdef REVERSE_SUPPORT - if (config.reversepath_list != NULL) { + if (config->reversepath_list != NULL) { /* * Rewrite the URL based on the reverse path. After calling * reverse_rewrite_url "url" can be freed since we either @@ -369,13 +414,21 @@ static struct request_s *process_request (struct conn_s *connptr, * we'll be closing anyway. */ char *reverse_url; + int reverse_status; - reverse_url = reverse_rewrite_url (connptr, hashofheaders, url); + reverse_url = reverse_rewrite_url (connptr, hashofheaders, url, &reverse_status); if (reverse_url != NULL) { + if (reverse_status == 301) { + char buf[PATH_MAX]; + snprintf (buf, sizeof buf, "Location: %s\r\n", reverse_url); + send_http_headers (connptr, 301, "Moved Permanently", buf); + goto fail; + } safefree (url); url = reverse_url; - } else if (config.reverseonly) { + skip_trans = 1; + } else if (config->reverseonly) { log_message (LOG_ERR, "Bad request, no mapping for '%s' found", url); @@ -408,7 +461,7 @@ static struct request_s *process_request (struct conn_s *connptr, /* Verify that the port in the CONNECT method is allowed */ if (!check_allowed_connect_ports (request->port, - config.connect_ports)) + config->connect_ports)) { indicate_http_error (connptr, 403, "Access violation", "detail", @@ -424,11 +477,13 @@ static struct request_s *process_request (struct conn_s *connptr, connptr->connect_method = TRUE; } else { #ifdef TRANSPARENT_PROXY - if (!do_transparent_proxy - (connptr, hashofheaders, request, &config, &url)) { - goto fail; - } -#else + if (!skip_trans) { + if (!do_transparent_proxy + (connptr, hashofheaders, request, config, &url)) + goto fail; + } else +#endif + { indicate_http_error (connptr, 501, "Not Implemented", "detail", "Unknown method or unsupported protocol.", @@ -436,30 +491,24 @@ static struct request_s *process_request (struct conn_s *connptr, log_message (LOG_INFO, "Unknown method (%s) or protocol (%s)", request->method, url); goto fail; -#endif + } } #ifdef FILTER_ENABLE /* * Filter restricted domains/urls */ - if (config.filter) { - if (config.filter_url) - ret = filter_url (url); - else - ret = filter_domain (request->host); + if (config->filter) { + int fu = config->filter_opts & FILTER_OPT_URL; + ret = filter_run (fu ? url : request->host); if (ret) { update_stats (STAT_DENIED); - if (config.filter_url) - log_message (LOG_NOTICE, - "Proxying refused on filtered url \"%s\"", - url); - else - log_message (LOG_NOTICE, - "Proxying refused on filtered domain \"%s\"", - request->host); + log_message (LOG_NOTICE, + "Proxying refused on filtered %s \"%s\"", + fu ? "url" : "domain", + fu ? url : request->host); indicate_http_error (connptr, 403, "Filtered", "detail", @@ -469,19 +518,11 @@ static struct request_s *process_request (struct conn_s *connptr, } } #endif - - - /* - * Check to see if they're requesting the stat host - */ - if (config.stathost && strcmp (config.stathost, request->host) == 0) { - log_message (LOG_NOTICE, "Request for the stathost."); - connptr->show_stats = TRUE; - goto fail; - } + /* check whether hostname from url is the stathost */ + if (is_stathost (request->host)) + goto got_stathost; safefree (url); - return request; fail: @@ -500,7 +541,6 @@ static int pull_client_data (struct conn_s *connptr, long int length) { char *buffer; ssize_t len; - int ret; buffer = (char *) safemalloc (min (MAXBUFFSIZE, (unsigned long int) length)); @@ -521,39 +561,40 @@ static int pull_client_data (struct conn_s *connptr, long int length) length -= len; } while (length > 0); - /* - * BUG FIX: Internet Explorer will leave two bytes (carriage - * return and line feed) at the end of a POST message. These - * need to be eaten for tinyproxy to work correctly. - */ - ret = socket_nonblocking (connptr->client_fd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set the client socket " - "to non-blocking: %s", strerror(errno)); - goto ERROR_EXIT; - } + safefree (buffer); + return 0; - len = recv (connptr->client_fd, buffer, 2, MSG_PEEK); +ERROR_EXIT: + safefree (buffer); + return -1; +} - ret = socket_blocking (connptr->client_fd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set the client socket " - "to blocking: %s", strerror(errno)); - goto ERROR_EXIT; - } +/* pull chunked client data */ +static int pull_client_data_chunked (struct conn_s *connptr) { + char *buffer = 0; + ssize_t len; + long chunklen; - if (len < 0 && errno != EAGAIN) - goto ERROR_EXIT; + while(1) { + if (buffer) safefree(buffer); + len = readline (connptr->client_fd, &buffer); - if ((len == 2) && CHECK_CRLF (buffer, len)) { - ssize_t bytes_read; + if (len <= 0) + goto ERROR_EXIT; - bytes_read = read (connptr->client_fd, buffer, 2); - if (bytes_read == -1) { - log_message - (LOG_WARNING, - "Could not read two bytes from POST message"); + if (!connptr->error_variables) { + if (safe_write (connptr->server_fd, buffer, len) < 0) + goto ERROR_EXIT; } + + chunklen = strtol (buffer, (char**)0, 16); + /* prevent negative or huge values causing overflow */ + if (chunklen < 0 || chunklen > 0x0fffffff) goto ERROR_EXIT; + + if (pull_client_data (connptr, chunklen+2) < 0) + goto ERROR_EXIT; + + if(!chunklen) break; } safefree (buffer); @@ -584,7 +625,7 @@ static int add_xtinyproxy_header (struct conn_s *connptr) * can be retrieved and manipulated later. */ static int -add_header_to_connection (hashmap_t hashofheaders, char *header, size_t len) +add_header_to_connection (pseudomap *hashofheaders, char *header, size_t len) { char *sep; @@ -593,7 +634,7 @@ add_header_to_connection (hashmap_t hashofheaders, char *header, size_t len) sep = strchr (header, ':'); if (!sep) - return -1; + return 0; /* just skip invalid header, do not give error */ /* Blank out colons, spaces, and tabs. */ while (*sep == ':' || *sep == ' ' || *sep == '\t') @@ -602,7 +643,7 @@ add_header_to_connection (hashmap_t hashofheaders, char *header, size_t len) /* Calculate the new length of just the data */ len -= sep - header - 1; - return hashmap_insert (hashofheaders, header, sep, len); + return pseudomap_append (hashofheaders, header, sep); } /* @@ -615,7 +656,7 @@ add_header_to_connection (hashmap_t hashofheaders, char *header, size_t len) /* * Read all the headers from the stream */ -static int get_all_headers (int fd, hashmap_t hashofheaders) +static int get_all_headers (int fd, pseudomap *hashofheaders) { char *line = NULL; char *header = NULL; @@ -708,7 +749,7 @@ static int get_all_headers (int fd, hashmap_t hashofheaders) * Extract the headers to remove. These headers were listed in the Connection * and Proxy-Connection headers. */ -static int remove_connection_headers (hashmap_t hashofheaders) +static int remove_connection_headers (pseudomap *hashofheaders) { static const char *headers[] = { "connection", @@ -718,16 +759,17 @@ static int remove_connection_headers (hashmap_t hashofheaders) char *data; char *ptr; ssize_t len; - int i; + int i,j,df; for (i = 0; i != (sizeof (headers) / sizeof (char *)); ++i) { /* Look for the connection header. If it's not found, return. */ - len = - hashmap_entry_by_key (hashofheaders, headers[i], - (void **) &data); - if (len <= 0) + data = pseudomap_find(hashofheaders, headers[i]); + + if (!data) return 0; + len = strlen(data); + /* * Go through the data line and replace any special characters * with a NULL. @@ -742,7 +784,12 @@ static int remove_connection_headers (hashmap_t hashofheaders) */ ptr = data; while (ptr < data + len) { - hashmap_remove (hashofheaders, ptr); + df = 0; + /* check that ptr isn't one of headers to prevent + double-free (CVE-2023-49606) */ + for (j = 0; j != (sizeof (headers) / sizeof (char *)); ++j) + if(!strcasecmp(ptr, headers[j])) df = 1; + if (!df) pseudomap_remove (hashofheaders, ptr); /* Advance ptr to the next token */ ptr += strlen (ptr) + 1; @@ -751,7 +798,7 @@ static int remove_connection_headers (hashmap_t hashofheaders) } /* Now remove the connection header it self. */ - hashmap_remove (hashofheaders, headers[i]); + pseudomap_remove (hashofheaders, headers[i]); } return 0; @@ -759,23 +806,28 @@ static int remove_connection_headers (hashmap_t hashofheaders) /* * If there is a Content-Length header, then return the value; otherwise, return - * a negative number. + * -1. */ -static long get_content_length (hashmap_t hashofheaders) +static long get_content_length (pseudomap *hashofheaders) { - ssize_t len; char *data; long content_length = -1; - len = - hashmap_entry_by_key (hashofheaders, "content-length", - (void **) &data); - if (len > 0) + data = pseudomap_find (hashofheaders, "content-length"); + + if (data) content_length = atol (data); return content_length; } +static int is_chunked_transfer (pseudomap *hashofheaders) +{ + char *data; + data = pseudomap_find (hashofheaders, "transfer-encoding"); + return data ? !strcasecmp (data, "chunked") : 0; +} + /* * Search for Via header in a hash of headers and either write a new Via * header, or append our information to the end of an existing Via header. @@ -784,21 +836,20 @@ static long get_content_length (hashmap_t hashofheaders) * purposes. */ static int -write_via_header (int fd, hashmap_t hashofheaders, +write_via_header (int fd, pseudomap *hashofheaders, unsigned int major, unsigned int minor) { - ssize_t len; char hostname[512]; char *data; int ret; - if (config.disable_viaheader) { + if (config->disable_viaheader) { ret = 0; goto done; } - if (config.via_proxy_name) { - strlcpy (hostname, config.via_proxy_name, sizeof (hostname)); + if (config->via_proxy_name) { + strlcpy (hostname, config->via_proxy_name, sizeof (hostname)); } else if (gethostname (hostname, sizeof (hostname)) < 0) { strlcpy (hostname, "unknown", 512); } @@ -807,29 +858,23 @@ write_via_header (int fd, hashmap_t hashofheaders, * See if there is a "Via" header. If so, again we need to do a bit * of processing. */ - len = hashmap_entry_by_key (hashofheaders, "via", (void **) &data); - if (len > 0) { + data = pseudomap_find (hashofheaders, "via"); + if (data) { ret = write_message (fd, - "Via: %s, %hu.%hu %s (%s/%s)\r\n", - data, major, minor, hostname, PACKAGE, - VERSION); + "Via: %s, %hu.%hu %s (%s)\r\n", + data, major, minor, hostname, PACKAGE); - hashmap_remove (hashofheaders, "via"); + pseudomap_remove (hashofheaders, "via"); } else { ret = write_message (fd, - "Via: %hu.%hu %s (%s/%s)\r\n", - major, minor, hostname, PACKAGE, VERSION); + "Via: %hu.%hu %s (%s)\r\n", + major, minor, hostname, PACKAGE); } done: return ret; } -/* - * Number of buckets to use internally in the hashmap. - */ -#define HEADER_BUCKETS 256 - /* * Here we loop through all the headers the client is sending. If we * are running in anonymous mode, we will _only_ send the headers listed @@ -837,7 +882,7 @@ write_via_header (int fd, hashmap_t hashofheaders, * - rjkaes */ static int -process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) +process_client_headers (struct conn_s *connptr, pseudomap *hashofheaders) { static const char *skipheaders[] = { "host", @@ -848,7 +893,7 @@ process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) "upgrade" }; int i; - hashmap_iter iter; + size_t iter; int ret = 0; char *data, *header; @@ -871,6 +916,10 @@ process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) */ connptr->content_length.client = get_content_length (hashofheaders); + /* Check whether client sends chunked data. */ + if (connptr->content_length.client == -1 && is_chunked_transfer (hashofheaders)) + connptr->content_length.client = -2; + /* * See if there is a "Connection" header. If so, we need to do a bit * of processing. :) @@ -881,7 +930,7 @@ process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) * Delete the headers listed in the skipheaders list */ for (i = 0; i != (sizeof (skipheaders) / sizeof (char *)); i++) { - hashmap_remove (hashofheaders, skipheaders[i]); + pseudomap_remove (hashofheaders, skipheaders[i]); } /* Send, or add the Via header */ @@ -901,32 +950,27 @@ process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) /* * Output all the remaining headers to the remote machine. */ - iter = hashmap_first (hashofheaders); - if (iter >= 0) { - for (; !hashmap_is_end (hashofheaders, iter); ++iter) { - hashmap_return_entry (hashofheaders, - iter, &data, (void **) &header); - - if (!is_anonymous_enabled () - || anonymous_search (data) > 0) { - ret = - write_message (connptr->server_fd, - "%s: %s\r\n", data, header); - if (ret < 0) { - indicate_http_error (connptr, 503, - "Could not send data to remote server", - "detail", - "A network error occurred while " - "trying to write data to the " - "remote web server.", - NULL); - goto PULL_CLIENT_DATA; - } + iter = 0; + while((iter = pseudomap_next(hashofheaders, iter, &data, &header))) { + if (!is_anonymous_enabled (config) + || anonymous_search (config, data) > 0) { + ret = + write_message (connptr->server_fd, + "%s: %s\r\n", data, header); + if (ret < 0) { + indicate_http_error (connptr, 503, + "Could not send data to remote server", + "detail", + "A network error occurred while " + "trying to write data to the " + "remote web server.", + NULL); + goto PULL_CLIENT_DATA; } } } #if defined(XTINYPROXY_ENABLE) - if (config.add_xtinyproxy) + if (config->add_xtinyproxy) add_xtinyproxy_header (connptr); #endif @@ -941,7 +985,8 @@ process_client_headers (struct conn_s *connptr, hashmap_t hashofheaders) if (connptr->content_length.client > 0) { ret = pull_client_data (connptr, connptr->content_length.client); - } + } else if (connptr->content_length.client == -2) + ret = pull_client_data_chunked (connptr); return ret; } @@ -961,15 +1006,15 @@ static int process_server_headers (struct conn_s *connptr) char *response_line; - hashmap_t hashofheaders; - hashmap_iter iter; + pseudomap *hashofheaders; + size_t iter; char *data, *header; ssize_t len; int i; int ret; #ifdef REVERSE_SUPPORT - struct reversepath *reverse = config.reversepath_list; + struct reversepath *reverse = config->reversepath_list; #endif /* Get the response line from the remote server. */ @@ -991,7 +1036,7 @@ static int process_server_headers (struct conn_s *connptr) goto retry; } - hashofheaders = hashmap_create (HEADER_BUCKETS); + hashofheaders = pseudomap_create (); if (!hashofheaders) { safefree (response_line); return -1; @@ -1003,7 +1048,7 @@ static int process_server_headers (struct conn_s *connptr) if (get_all_headers (connptr->server_fd, hashofheaders) < 0) { log_message (LOG_WARNING, "Could not retrieve all the headers from the remote server."); - hashmap_delete (hashofheaders); + pseudomap_destroy (hashofheaders); safefree (response_line); indicate_http_error (connptr, 503, @@ -1022,7 +1067,7 @@ static int process_server_headers (struct conn_s *connptr) * Instead we'll free all the memory and return. */ if (connptr->protocol.major < 1) { - hashmap_delete (hashofheaders); + pseudomap_destroy (hashofheaders); safefree (response_line); return 0; } @@ -1049,7 +1094,7 @@ static int process_server_headers (struct conn_s *connptr) * Delete the headers listed in the skipheaders list */ for (i = 0; i != (sizeof (skipheaders) / sizeof (char *)); i++) { - hashmap_remove (hashofheaders, skipheaders[i]); + pseudomap_remove (hashofheaders, skipheaders[i]); } /* Send, or add the Via header */ @@ -1061,7 +1106,7 @@ static int process_server_headers (struct conn_s *connptr) #ifdef REVERSE_SUPPORT /* Write tracking cookie for the magical reverse proxy path hack */ - if (config.reversemagic && connptr->reversepath) { + if (config->reversemagic && connptr->reversepath) { ret = write_message (connptr->client_fd, "Set-Cookie: " REVERSE_COOKIE "=%s; path=/\r\n", connptr->reversepath); @@ -1070,9 +1115,8 @@ static int process_server_headers (struct conn_s *connptr) } /* Rewrite the HTTP redirect if needed */ - if (config.reversebaseurl && - hashmap_entry_by_key (hashofheaders, "location", - (void **) &header) > 0) { + if (config->reversebaseurl && + (header = pseudomap_find (hashofheaders, "location"))) { /* Look for a matching entry in the reversepath list */ while (reverse) { @@ -1088,16 +1132,16 @@ static int process_server_headers (struct conn_s *connptr) ret = write_message (connptr->client_fd, "Location: %s%s%s\r\n", - config.reversebaseurl, + config->reversebaseurl, (reverse->path + 1), (header + len)); if (ret < 0) goto ERROR_EXIT; log_message (LOG_INFO, "Rewriting HTTP redirect: %s -> %s%s%s", - header, config.reversebaseurl, + header, config->reversebaseurl, (reverse->path + 1), (header + len)); - hashmap_remove (hashofheaders, "location"); + pseudomap_remove (hashofheaders, "location"); } } #endif @@ -1105,19 +1149,15 @@ static int process_server_headers (struct conn_s *connptr) /* * All right, output all the remaining headers to the client. */ - iter = hashmap_first (hashofheaders); - if (iter >= 0) { - for (; !hashmap_is_end (hashofheaders, iter); ++iter) { - hashmap_return_entry (hashofheaders, - iter, &data, (void **) &header); - - ret = write_message (connptr->client_fd, - "%s: %s\r\n", data, header); - if (ret < 0) - goto ERROR_EXIT; - } + iter = 0; + while ((iter = pseudomap_next(hashofheaders, iter, &data, &header))) { + + ret = write_message (connptr->client_fd, + "%s: %s\r\n", data, header); + if (ret < 0) + goto ERROR_EXIT; } - hashmap_delete (hashofheaders); + pseudomap_destroy (hashofheaders); /* Write the final blank line to signify the end of the headers */ if (safe_write (connptr->client_fd, "\r\n", 2) < 0) @@ -1126,13 +1166,13 @@ static int process_server_headers (struct conn_s *connptr) return 0; ERROR_EXIT: - hashmap_delete (hashofheaders); + pseudomap_destroy (hashofheaders); return -1; } /* - * Switch the sockets into nonblocking mode and begin relaying the bytes - * between the two connections. We continue to use the buffering code + * Begin relaying the bytes between the two connections. + * We continue to use the buffering code * since we want to be able to buffer a certain amount for slower * connections (as this was the reason why I originally modified * tinyproxy oh so long ago...) @@ -1140,74 +1180,39 @@ static int process_server_headers (struct conn_s *connptr) */ static void relay_connection (struct conn_s *connptr) { - fd_set rset, wset; - struct timeval tv; - time_t last_access; int ret; - double tdiff; - int maxfd = max (connptr->client_fd, connptr->server_fd) + 1; ssize_t bytes_received; - ret = socket_nonblocking (connptr->client_fd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set the client socket " - "to non-blocking: %s", strerror(errno)); - return; - } - - ret = socket_nonblocking (connptr->server_fd); - if (ret != 0) { - log_message(LOG_ERR, "Failed to set the server socket " - "to non-blocking: %s", strerror(errno)); - return; - } - - last_access = time (NULL); - for (;;) { - FD_ZERO (&rset); - FD_ZERO (&wset); - - tv.tv_sec = - config.idletimeout - difftime (time (NULL), last_access); - tv.tv_usec = 0; + pollfd_struct fds[2] = {0}; + fds[0].fd = connptr->client_fd; + fds[1].fd = connptr->server_fd; if (buffer_size (connptr->sbuffer) > 0) - FD_SET (connptr->client_fd, &wset); + fds[0].events |= MYPOLL_WRITE; if (buffer_size (connptr->cbuffer) > 0) - FD_SET (connptr->server_fd, &wset); + fds[1].events |= MYPOLL_WRITE; if (buffer_size (connptr->sbuffer) < MAXBUFFSIZE) - FD_SET (connptr->server_fd, &rset); + fds[1].events |= MYPOLL_READ; if (buffer_size (connptr->cbuffer) < MAXBUFFSIZE) - FD_SET (connptr->client_fd, &rset); + fds[0].events |= MYPOLL_READ; - ret = select (maxfd, &rset, &wset, NULL, &tv); + ret = mypoll(fds, 2, config->idletimeout); if (ret == 0) { - tdiff = difftime (time (NULL), last_access); - if (tdiff > config.idletimeout) { - log_message (LOG_INFO, - "Idle Timeout (after select) as %g > %u.", - tdiff, config.idletimeout); + log_message (LOG_INFO, + "Idle Timeout (after " SELECT_OR_POLL ")"); return; - } else { - continue; - } } else if (ret < 0) { log_message (LOG_ERR, - "relay_connection: select() error \"%s\". " + "relay_connection: " SELECT_OR_POLL "() error \"%s\". " "Closing connection (client_fd:%d, server_fd:%d)", strerror (errno), connptr->client_fd, connptr->server_fd); return; - } else { - /* - * All right, something was actually selected so mark it. - */ - last_access = time (NULL); } - if (FD_ISSET (connptr->server_fd, &rset)) { + if (fds[1].revents & MYPOLL_READ) { bytes_received = read_buffer (connptr->server_fd, connptr->sbuffer); if (bytes_received < 0) @@ -1217,32 +1222,20 @@ static void relay_connection (struct conn_s *connptr) if (connptr->content_length.server == 0) break; } - if (FD_ISSET (connptr->client_fd, &rset) + if ((fds[0].revents & MYPOLL_READ) && read_buffer (connptr->client_fd, connptr->cbuffer) < 0) { break; } - if (FD_ISSET (connptr->server_fd, &wset) + if ((fds[1].revents & MYPOLL_WRITE) && write_buffer (connptr->server_fd, connptr->cbuffer) < 0) { break; } - if (FD_ISSET (connptr->client_fd, &wset) + if ((fds[0].revents & MYPOLL_WRITE) && write_buffer (connptr->client_fd, connptr->sbuffer) < 0) { break; } } - /* - * Here the server has closed the connection... write the - * remainder to the client and then exit. - */ - ret = socket_blocking (connptr->client_fd); - if (ret != 0) { - log_message(LOG_ERR, - "Failed to set client socket to blocking: %s", - strerror(errno)); - return; - } - while (buffer_size (connptr->sbuffer) > 0) { if (write_buffer (connptr->client_fd, connptr->sbuffer) < 0) break; @@ -1252,14 +1245,6 @@ static void relay_connection (struct conn_s *connptr) /* * Try to send any remaining data to the server if we can. */ - ret = socket_blocking (connptr->server_fd); - if (ret != 0) { - log_message(LOG_ERR, - "Failed to set server socket to blocking: %s", - strerror(errno)); - return; - } - while (buffer_size (connptr->cbuffer) > 0) { if (write_buffer (connptr->server_fd, connptr->cbuffer) < 0) break; @@ -1268,47 +1253,84 @@ static void relay_connection (struct conn_s *connptr) return; } +#ifdef UPSTREAM_SUPPORT static int connect_to_upstream_proxy(struct conn_s *connptr, struct request_s *request) { unsigned len; unsigned char buff[512]; /* won't use more than 7 + 255 */ unsigned short port; - struct hostent *host; + size_t ulen, passlen; + struct upstream *cur_upstream = connptr->upstream_proxy; + ulen = cur_upstream->ua.user ? strlen(cur_upstream->ua.user) : 0; + passlen = cur_upstream->pass ? strlen(cur_upstream->pass) : 0; + + log_message(LOG_CONN, "Established connection to %s proxy \"%s\" using file descriptor %d.", proxy_type_name(cur_upstream->type), cur_upstream->host, connptr->server_fd); - if (cur_upstream->type == SOCKS4_TYPE) { + if (cur_upstream->type == PT_SOCKS4) { buff[0] = 4; /* socks version */ buff[1] = 1; /* connect command */ port = htons(request->port); memcpy(&buff[2], &port, 2); /* dest port */ - host = gethostbyname(request->host); - memcpy(&buff[4], host->h_addr_list[0], 4); /* dest ip */ - buff[8] = 0; /* user */ - if (9 != safe_write(connptr->server_fd, buff, 9)) + memcpy(&buff[4], "\0\0\0\1" /* socks4a fake ip */ + "\0" /* user */, 5); + len = strlen(request->host); + if(len>255) + return -1; + memcpy(&buff[9], request->host, len+1); + if (9+len+1 != safe_write(connptr->server_fd, buff, 9+len+1)) return -1; if (8 != safe_read(connptr->server_fd, buff, 8)) return -1; if (buff[0]!=0 || buff[1]!=90) return -1; - } else if (cur_upstream->type == SOCKS5_TYPE) { + } else if (cur_upstream->type == PT_SOCKS5) { /* init */ + int n_methods = ulen ? 2 : 1; buff[0] = 5; /* socks version */ - buff[1] = 1; /* number of methods */ + buff[1] = n_methods; /* number of methods */ buff[2] = 0; /* no auth method */ - if (3 != safe_write(connptr->server_fd, buff, 3)) + if (ulen) buff[3] = 2; /* auth method -> username / password */ + if (2+n_methods != safe_write(connptr->server_fd, buff, 2+n_methods)) return -1; if (2 != safe_read(connptr->server_fd, buff, 2)) return -1; - if (buff[0]!=5 || buff[1]!=0) + if (buff[0] != 5 || (buff[1] != 0 && buff[1] != 2)) return -1; + + if (buff[1] == 2) { + /* authentication */ + char in[2]; + char out[515]; + char *cur = out; + size_t c; + *cur++ = 1; /* version */ + c = ulen & 0xFF; + *cur++ = c; + memcpy(cur, cur_upstream->ua.user, c); + cur += c; + c = passlen & 0xFF; + *cur++ = c; + memcpy(cur, cur_upstream->pass, c); + cur += c; + + if((cur - out) != safe_write(connptr->server_fd, out, cur - out)) + return -1; + + if(2 != safe_read(connptr->server_fd, in, 2)) + return -1; + if(in[1] != 0 || !(in[0] == 5 || in[0] == 1)) { + return -1; + } + } /* connect */ buff[0] = 5; /* socks version */ buff[1] = 1; /* connect */ @@ -1348,7 +1370,7 @@ connect_to_upstream_proxy(struct conn_s *connptr, struct request_s *request) return establish_http_connection(connptr, request); } - +#endif /* * Establish a connection to the upstream proxy server. @@ -1372,7 +1394,7 @@ connect_to_upstream (struct conn_s *connptr, struct request_s *request) log_message (LOG_WARNING, "No upstream proxy defined for %s.", request->host); - indicate_http_error (connptr, 404, + indicate_http_error (connptr, 502, "Unable to connect to upstream proxy."); return -1; } @@ -1384,7 +1406,7 @@ connect_to_upstream (struct conn_s *connptr, struct request_s *request) if (connptr->server_fd < 0) { log_message (LOG_WARNING, "Could not connect to upstream proxy."); - indicate_http_error (connptr, 404, + indicate_http_error (connptr, 502, "Unable to connect to upstream proxy", "detail", "A network error occurred while trying to " @@ -1393,7 +1415,7 @@ connect_to_upstream (struct conn_s *connptr, struct request_s *request) return -1; } - if (cur_upstream->type != HTTP_TYPE) + if (cur_upstream->type != PT_HTTP) return connect_to_upstream_proxy(connptr, request); log_message (LOG_CONN, @@ -1435,41 +1457,42 @@ connect_to_upstream (struct conn_s *connptr, struct request_s *request) #endif } +/* this function "drains" remaining bytes in the read pipe from + the client. it's usually only called on error before displaying + an error code/page. */ static int get_request_entity(struct conn_s *connptr) { int ret; - fd_set rset; - struct timeval tv; + pollfd_struct fds[1] = {0}; - FD_ZERO (&rset); - FD_SET (connptr->client_fd, &rset); - tv.tv_sec = 0; - tv.tv_usec = 0; - ret = select (connptr->client_fd + 1, &rset, NULL, NULL, &tv); + fds[0].fd = connptr->client_fd; + fds[0].events |= MYPOLL_READ; + + ret = mypoll(fds, 1, config->idletimeout); if (ret == -1) { log_message (LOG_ERR, - "Error calling select on client fd %d: %s", + "Error calling " SELECT_OR_POLL " on client fd %d: %s", connptr->client_fd, strerror(errno)); } else if (ret == 0) { log_message (LOG_INFO, "no entity"); - } else if (ret == 1 && FD_ISSET (connptr->client_fd, &rset)) { + } else if (ret == 1 && (fds[0].revents & MYPOLL_READ)) { ssize_t nread; nread = read_buffer (connptr->client_fd, connptr->cbuffer); if (nread < 0) { log_message (LOG_ERR, - "Error reading readable client_fd %d", - connptr->client_fd); + "Error reading readable client_fd %d (%s)", + connptr->client_fd, strerror(errno)); ret = -1; } else { log_message (LOG_INFO, - "Read request entity of %d bytes", - nread); + "Read request entity of %ld bytes", + (long) nread); ret = 0; } } else { - log_message (LOG_ERR, "strange situation after select: " + log_message (LOG_ERR, "strange situation after " SELECT_OR_POLL ": " "ret = %d, but client_fd (%d) is not readable...", ret, connptr->client_fd); ret = -1; @@ -1478,67 +1501,116 @@ get_request_entity(struct conn_s *connptr) return ret; } +static void handle_connection_failure(struct conn_s *connptr, int got_headers) +{ + /* + * First, get the body if there is one. + * If we don't read all there is from the socket first, + * it is still marked for reading and we won't be able + * to send our data properly. + */ + if (!got_headers && get_request_entity (connptr) < 0) { + log_message (LOG_WARNING, + "Could not retrieve request entity"); + indicate_http_error (connptr, 400, "Bad Request", + "detail", + "Could not retrieve the request entity " + "the client.", NULL); + update_stats (STAT_BADCONN); + } + + if (connptr->error_variables) { + send_http_error_message (connptr); + } else if (connptr->show_stats) { + showstats (connptr); + } +} + +static void auth_error(struct conn_s *connptr, int code) { + const char *tit = code == 401 ? "Unauthorized" : "Proxy Authentication Required"; + const char *msg = code == 401 ? + "The administrator of this proxy has not configured it to service requests from you." : + "This proxy requires authentication."; + + update_stats (STAT_DENIED); + log_message (LOG_INFO, + "Failed auth attempt (file descriptor: %d), ip %s", + connptr->client_fd, + connptr->client_ip_addr); + indicate_http_error (connptr, code, tit, "detail", msg, NULL); +} /* - * This is the main drive for each connection. As you can tell, for the - * first few steps we are using a blocking socket. If you remember the - * older tinyproxy code, this use to be a very confusing state machine. - * Well, no more! :) The sockets are only switched into nonblocking mode - * when we start the relay portion. This makes most of the original - * tinyproxy code, which was confusing, redundant. Hail progress. - * - rjkaes + * This is the main drive for each connection. + * this function is called directly from child_thread() with the newly + * received fd from accept(). */ -void handle_connection (int fd) +void handle_connection (struct conn_s *connptr, union sockaddr_union* addr) { - ssize_t i; - struct conn_s *connptr; + +#define HC_FAIL() \ + do {handle_connection_failure(connptr, got_headers); goto done;} \ + while(0) + + int got_headers = 0, fd = connptr->client_fd; + size_t i; struct request_s *request = NULL; - hashmap_t hashofheaders = NULL; + pseudomap *hashofheaders = NULL; char sock_ipaddr[IP_LENGTH]; char peer_ipaddr[IP_LENGTH]; - char peer_string[HOSTNAME_LENGTH]; - getpeer_information (fd, peer_ipaddr, peer_string); + getpeer_information (addr, peer_ipaddr, sizeof(peer_ipaddr)); - if (config.bindsame) + if (config->bindsame) getsock_ip (fd, sock_ipaddr); - log_message (LOG_CONN, config.bindsame ? - "Connect (file descriptor %d): %s [%s] at [%s]" : - "Connect (file descriptor %d): %s [%s]", - fd, peer_string, peer_ipaddr, sock_ipaddr); + log_message (LOG_CONN, config->bindsame ? + "Connect (file descriptor %d): %s at [%s]" : + "Connect (file descriptor %d): %s", + fd, peer_ipaddr, sock_ipaddr); - connptr = initialize_conn (fd, peer_ipaddr, peer_string, - config.bindsame ? sock_ipaddr : NULL); - if (!connptr) { + if(!conn_init_contents (connptr, peer_ipaddr, + config->bindsame ? sock_ipaddr : NULL)) { close (fd); return; } - if (check_acl (peer_ipaddr, peer_string, config.access_list) <= 0) { + set_socket_timeout(fd); + + if (connection_loops (addr)) { + log_message (LOG_CONN, + "Prevented endless loop (file descriptor %d): %s", + fd, peer_ipaddr); + + indicate_http_error(connptr, 400, "Bad Request", + "detail", + "You tried to connect to the " + "machine the proxy is running on", + NULL); + HC_FAIL(); + } + + + if (check_acl (peer_ipaddr, addr, config->access_list) <= 0) { update_stats (STAT_DENIED); indicate_http_error (connptr, 403, "Access denied", "detail", "The administrator of this proxy has not configured " "it to service requests from your host.", NULL); - goto fail; + HC_FAIL(); } if (read_request_line (connptr) < 0) { update_stats (STAT_BADCONN); - indicate_http_error (connptr, 408, "Timeout", - "detail", - "Server timeout waiting for the HTTP request " - "from the client.", NULL); - goto fail; + goto done; } /* * The "hashofheaders" store the client's headers. */ - hashofheaders = hashmap_create (HEADER_BUCKETS); + hashofheaders = pseudomap_create (); if (hashofheaders == NULL) { update_stats (STAT_BADCONN); indicate_http_error (connptr, 503, "Internal error", @@ -1546,7 +1618,7 @@ void handle_connection (int fd) "An internal server error occurred while processing " "your request. Please contact the administrator.", NULL); - goto fail; + HC_FAIL(); } /* @@ -1560,52 +1632,48 @@ void handle_connection (int fd) "Could not retrieve all the headers from " "the client.", NULL); update_stats (STAT_BADCONN); - goto fail; + HC_FAIL(); } + got_headers = 1; - if (config.basicauth_list != NULL) { - ssize_t len; + if (config->basicauth_list != NULL) { char *authstring; - int failure = 1; - len = hashmap_entry_by_key (hashofheaders, "proxy-authorization", - (void **) &authstring); + int failure = 1, stathost_connect = 0; + authstring = pseudomap_find (hashofheaders, "proxy-authorization"); + + if (!authstring && config->stathost) { + authstring = pseudomap_find (hashofheaders, "host"); + if (authstring && is_stathost(authstring)) { + authstring = pseudomap_find (hashofheaders, "authorization"); + stathost_connect = 1; + } else authstring = 0; + } - if (len == 0) { - update_stats (STAT_DENIED); - indicate_http_error (connptr, 407, "Proxy Authentication Required", - "detail", - "This proxy requires authentication.", - NULL); - goto fail; + if (!authstring) { + auth_error(connptr, stathost_connect ? 401 : 407); + HC_FAIL(); } if ( /* currently only "basic" auth supported */ (strncmp(authstring, "Basic ", 6) == 0 || strncmp(authstring, "basic ", 6) == 0) && - basicauth_check (config.basicauth_list, authstring + 6) == 1) + basicauth_check (config->basicauth_list, authstring + 6) == 1) failure = 0; if(failure) { - update_stats (STAT_DENIED); - indicate_http_error (connptr, 401, "Unauthorized", - "detail", - "The administrator of this proxy has not configured " - "it to service requests from you.", - NULL); - goto fail; + auth_error(connptr, stathost_connect ? 401 : 407); + HC_FAIL(); } - hashmap_remove (hashofheaders, "proxy-authorization"); + pseudomap_remove (hashofheaders, "proxy-authorization"); } /* * Add any user-specified headers (AddHeader directive) to the * outgoing HTTP request. */ - for (i = 0; i < vector_length (config.add_headers); i++) { - http_header_t *header = (http_header_t *) - vector_getentry (config.add_headers, i, NULL); + if (config->add_headers) + for (i = 0; i < sblist_getsize (config->add_headers); i++) { + http_header_t *header = sblist_get (config->add_headers, i); - hashmap_insert (hashofheaders, - header->name, - header->value, strlen (header->value) + 1); + pseudomap_append (hashofheaders, header->name, header->value); } request = process_request (connptr, hashofheaders); @@ -1613,13 +1681,13 @@ void handle_connection (int fd) if (!connptr->show_stats) { update_stats (STAT_BADCONN); } - goto fail; + HC_FAIL(); } connptr->upstream_proxy = UPSTREAM_HOST (request->host); if (connptr->upstream_proxy != NULL) { if (connect_to_upstream (connptr, request) < 0) { - goto fail; + HC_FAIL(); } } else { connptr->server_fd = opensock (request->host, request->port, @@ -1630,7 +1698,7 @@ void handle_connection (int fd) PACKAGE_NAME " " "was unable to connect to the remote web server.", "error", strerror (errno), NULL); - goto fail; + HC_FAIL(); } log_message (LOG_CONN, @@ -1644,21 +1712,33 @@ void handle_connection (int fd) if (process_client_headers (connptr, hashofheaders) < 0) { update_stats (STAT_BADCONN); - goto fail; + log_message (LOG_INFO, + "process_client_headers failed: %s. host \"%s\" using " + "file descriptor %d.", strerror(errno), + request->host, + connptr->server_fd); + + HC_FAIL(); } if (!connptr->connect_method || UPSTREAM_IS_HTTP(connptr)) { if (process_server_headers (connptr) < 0) { update_stats (STAT_BADCONN); - goto fail; + log_message (LOG_INFO, + "process_server_headers failed: %s. host \"%s\" using " + "file descriptor %d.", strerror(errno), + request->host, + connptr->server_fd); + + HC_FAIL(); } } else { - if (send_ssl_response (connptr) < 0) { + if (send_connect_method_response (connptr) < 0) { log_message (LOG_ERR, - "handle_connection: Could not send SSL greeting " - "to client."); + "handle_connection: Could not send CONNECT" + " method greeting to client."); update_stats (STAT_BADCONN); - goto fail; + HC_FAIL(); } } @@ -1669,34 +1749,10 @@ void handle_connection (int fd) "and remote client (fd:%d)", connptr->client_fd, connptr->server_fd); - goto done; - -fail: - /* - * First, get the body if there is one. - * If we don't read all there is from the socket first, - * it is still marked for reading and we won't be able - * to send our data properly. - */ - if (get_request_entity (connptr) < 0) { - log_message (LOG_WARNING, - "Could not retrieve request entity"); - indicate_http_error (connptr, 400, "Bad Request", - "detail", - "Could not retrieve the request entity " - "the client.", NULL); - update_stats (STAT_BADCONN); - } - - if (connptr->error_variables) { - send_http_error_message (connptr); - } else if (connptr->show_stats) { - showstats (connptr); - } - done: free_request_struct (request); - hashmap_delete (hashofheaders); - destroy_conn (connptr); + pseudomap_destroy (hashofheaders); + conn_destroy_contents (connptr); return; +#undef HC_FAIL } diff --git a/src/reqs.h b/src/reqs.h index 73dd030a..4a0374ff 100644 --- a/src/reqs.h +++ b/src/reqs.h @@ -23,6 +23,8 @@ #define _TINYPROXY_REQS_H_ #include "common.h" +#include "sock.h" +#include "conns.h" /* * Port constants for HTTP (80) and SSL (443) @@ -43,6 +45,6 @@ struct request_s { char *path; }; -extern void handle_connection (int fd); +extern void handle_connection (struct conn_s *, union sockaddr_union* addr); #endif diff --git a/src/reverse-proxy.c b/src/reverse-proxy.c index 02647874..98b0963e 100644 --- a/src/reverse-proxy.c +++ b/src/reverse-proxy.c @@ -34,6 +34,7 @@ void reversepath_add (const char *path, const char *url, struct reversepath **reversepath_list) { struct reversepath *reverse; + size_t l; if (url == NULL) { log_message (LOG_WARNING, @@ -65,8 +66,17 @@ void reversepath_add (const char *path, const char *url, if (!path) reverse->path = safestrdup ("/"); - else - reverse->path = safestrdup (path); + else { + l = strlen (path); + if (l && path[l-1] == '/') + reverse->path = safestrdup (path); + else { + reverse->path = safemalloc (l + 2); + memcpy (reverse->path, path, l); + reverse->path[l] = '/'; + reverse->path[l+1] = 0; + } + } reverse->url = safestrdup (url); @@ -83,10 +93,16 @@ void reversepath_add (const char *path, const char *url, */ struct reversepath *reversepath_get (char *url, struct reversepath *reverse) { + size_t l, lu, lp; while (reverse) { - if (strstr (url, reverse->path) == url) + lu = strlen (url); + lp = strlen (reverse->path); + if (( + (l = lu) == lp-1 || + (l = lp) <= lu + ) && + !memcmp(url, reverse->path, l)) return reverse; - reverse = reverse->next; } @@ -111,35 +127,41 @@ void free_reversepath_list (struct reversepath *reverse) /* * Rewrite the URL for reverse proxying. */ -char *reverse_rewrite_url (struct conn_s *connptr, hashmap_t hashofheaders, - char *url) +char *reverse_rewrite_url (struct conn_s *connptr, pseudomap *hashofheaders, + char *url, int *status) { char *rewrite_url = NULL; char *cookie = NULL; char *cookieval; struct reversepath *reverse = NULL; + *status = 0; + /* Reverse requests always start with a slash */ if (*url == '/') { /* First try locating the reverse mapping by request url */ - reverse = reversepath_get (url, config.reversepath_list); + reverse = reversepath_get (url, config->reversepath_list); if (reverse) { - rewrite_url = (char *) - safemalloc (strlen (url) + strlen (reverse->url) + - 1); - strcpy (rewrite_url, reverse->url); - strcat (rewrite_url, url + strlen (reverse->path)); - } else if (config.reversemagic - && hashmap_entry_by_key (hashofheaders, - "cookie", - (void **) &cookie) > 0) { + size_t lu = strlen (url); + size_t lrp = strlen (reverse->path); + if (lrp > lu) { + rewrite_url = safestrdup (reverse->path); + *status = 301; + } else { + rewrite_url = safemalloc ( + strlen (reverse->url) + lu + 1); + sprintf (rewrite_url, "%s%s", reverse->url, url + lrp); + } + } else if (config->reversemagic + && (cookie = pseudomap_find (hashofheaders, + "cookie"))) { /* No match - try the magical tracking cookie next */ if ((cookieval = strstr (cookie, REVERSE_COOKIE "=")) && (reverse = reversepath_get (cookieval + strlen (REVERSE_COOKIE) + 1, - config.reversepath_list))) + config->reversepath_list))) { rewrite_url = (char *) safemalloc @@ -163,7 +185,7 @@ char *reverse_rewrite_url (struct conn_s *connptr, hashmap_t hashofheaders, log_message (LOG_CONN, "Rewriting URL: %s -> %s", url, rewrite_url); /* Store reverse path so that the magical tracking cookie can be set */ - if (config.reversemagic && reverse) + if (config->reversemagic && reverse) connptr->reversepath = safestrdup (reverse->path); return rewrite_url; diff --git a/src/reverse-proxy.h b/src/reverse-proxy.h index 64b4acd9..7ded66d4 100644 --- a/src/reverse-proxy.h +++ b/src/reverse-proxy.h @@ -22,6 +22,7 @@ #define TINYPROXY_REVERSE_PROXY_H #include "conns.h" +#include "pseudomap.h" struct reversepath { struct reversepath *next; @@ -37,6 +38,7 @@ extern struct reversepath *reversepath_get (char *url, struct reversepath *reverse); void free_reversepath_list (struct reversepath *reverse); extern char *reverse_rewrite_url (struct conn_s *connptr, - hashmap_t hashofheaders, char *url); + pseudomap *hashofheaders, char *url, + int *status); #endif diff --git a/src/sblist.c b/src/sblist.c new file mode 100644 index 00000000..4ddc4aa5 --- /dev/null +++ b/src/sblist.c @@ -0,0 +1,80 @@ +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#include "sblist.h" +#include +#include +#include +#define MY_PAGE_SIZE 4096 + +sblist* sblist_new(size_t itemsize, size_t blockitems) { + sblist* ret = (sblist*) malloc(sizeof(sblist)); + sblist_init(ret, itemsize, blockitems); + return ret; +} + +static void sblist_clear(sblist* l) { + l->items = NULL; + l->capa = 0; + l->count = 0; +} + +void sblist_init(sblist* l, size_t itemsize, size_t blockitems) { + if(l) { + l->blockitems = blockitems ? blockitems : MY_PAGE_SIZE / itemsize; + l->itemsize = itemsize; + sblist_clear(l); + } +} + +void sblist_free_items(sblist* l) { + if(l) { + if(l->items) free(l->items); + sblist_clear(l); + } +} + +void sblist_free(sblist* l) { + if(l) { + sblist_free_items(l); + free(l); + } +} + +char* sblist_item_from_index(sblist* l, size_t idx) { + return l->items + (idx * l->itemsize); +} + +void* sblist_get(sblist* l, size_t item) { + if(item < l->count) return (void*) sblist_item_from_index(l, item); + return NULL; +} + +int sblist_set(sblist* l, void* item, size_t pos) { + if(pos >= l->count) return 0; + memcpy(sblist_item_from_index(l, pos), item, l->itemsize); + return 1; +} + +int sblist_grow_if_needed(sblist* l) { + char* temp; + if(l->count == l->capa) { + temp = realloc(l->items, (l->capa + l->blockitems) * l->itemsize); + if(!temp) return 0; + l->capa += l->blockitems; + l->items = temp; + } + return 1; +} + +int sblist_add(sblist* l, void* item) { + if(!sblist_grow_if_needed(l)) return 0; + l->count++; + return sblist_set(l, item, l->count - 1); +} + +void sblist_delete(sblist* l, size_t item) { + if (l->count && item < l->count) { + memmove(sblist_item_from_index(l, item), sblist_item_from_index(l, item + 1), (sblist_getsize(l) - (item + 1)) * l->itemsize); + l->count--; + } +} diff --git a/src/sblist.h b/src/sblist.h new file mode 100644 index 00000000..02c33d79 --- /dev/null +++ b/src/sblist.h @@ -0,0 +1,92 @@ +#ifndef SBLIST_H +#define SBLIST_H + +/* this file is part of libulz, as of commit 8ab361a27743aaf025323ee43b8b8876dc054fdd + modified for direct inclusion in tinyproxy, and for this purpose released under + the license of tinyproxy. */ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include +/* + * simple buffer list. + * + * this thing here is basically a generic dynamic array + * will realloc after every blockitems inserts + * can store items of any size. + * + * so think of it as a by-value list, as opposed to a typical by-ref list. + * you typically use it by having some struct on the stack, and pass a pointer + * to sblist_add, which will copy the contents into its internal memory. + * + */ + +typedef struct { + size_t itemsize; + size_t blockitems; + size_t count; + size_t capa; + char* items; +} sblist; + +#define sblist_getsize(X) ((X)->count) +#define sblist_get_count(X) ((X)->count) +#define sblist_empty(X) ((X)->count == 0) + +/* for dynamic style */ +sblist* sblist_new(size_t itemsize, size_t blockitems); +void sblist_free(sblist* l); + +/*for static style*/ +void sblist_init(sblist* l, size_t itemsize, size_t blockitems); +void sblist_free_items(sblist* l); + +/* accessors */ +void* sblist_get(sblist* l, size_t item); +/* returns 1 on success, 0 on OOM */ +int sblist_add(sblist* l, void* item); +int sblist_set(sblist* l, void* item, size_t pos); +void sblist_delete(sblist* l, size_t item); +char* sblist_item_from_index(sblist* l, size_t idx); +int sblist_grow_if_needed(sblist* l); +int sblist_insert(sblist* l, void* item, size_t pos); +/* same as sblist_add, but returns list index of new item, or -1 */ +size_t sblist_addi(sblist* l, void* item); +void sblist_sort(sblist *l, int (*compar)(const void *, const void *)); +/* insert element into presorted list, returns listindex of new entry or -1*/ +size_t sblist_insert_sorted(sblist* l, void* o, int (*compar)(const void *, const void *)); + +#ifndef __COUNTER__ +#define __COUNTER__ __LINE__ +#endif + +#define __sblist_concat_impl( x, y ) x##y +#define __sblist_macro_concat( x, y ) __sblist_concat_impl( x, y ) +#define __sblist_iterator_name __sblist_macro_concat(sblist_iterator, __COUNTER__) + +/* use with custom iterator variable */ +#define sblist_iter_counter(LIST, ITER, PTR) \ + for(size_t ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++) + +/* use with custom iterator variable, which is predeclared */ +#define sblist_iter_counter2(LIST, ITER, PTR) \ + for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++) + +/* use with custom iterator variable, which is predeclared and signed */ +/* useful for a loop which can delete items from the list, and then decrease the iterator var. */ +#define sblist_iter_counter2s(LIST, ITER, PTR) \ + for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < (ssize_t) sblist_getsize(LIST); ITER++) + + +/* uses "magic" iterator variable */ +#define sblist_iter(LIST, PTR) sblist_iter_counter(LIST, __sblist_iterator_name, PTR) + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/sock.c b/src/sock.c index eef606a6..ec8ad4c8 100644 --- a/src/sock.c +++ b/src/sock.c @@ -33,6 +33,29 @@ #include "sock.h" #include "text.h" #include "conf.h" +#include "loop.h" +#include "sblist.h" + +/* + * Return a human readable error for getaddrinfo() and getnameinfo(). + */ +static const char * get_gai_error (int n) +{ + if (n == EAI_SYSTEM) + return strerror (errno); + else + return gai_strerror (n); +} + +static const char * family_string (int af) +{ + switch(af) { + case AF_UNSPEC: return "AF_UNSPEC"; + case AF_INET: return "AF_INET"; + case AF_INET6: return "AF_INET6"; + } + return "unknown"; +} /* * Bind the given socket to the supplied address. The socket is @@ -43,6 +66,7 @@ static int bind_socket (int sockfd, const char *addr, int family) { struct addrinfo hints, *res, *ressave; + int n; assert (sockfd >= 0); assert (addr != NULL && strlen (addr) != 0); @@ -51,9 +75,13 @@ bind_socket (int sockfd, const char *addr, int family) hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; - /* The local port it not important */ - if (getaddrinfo (addr, NULL, &hints, &res) != 0) + /* The local port is not important */ + n = getaddrinfo (addr, NULL, &hints, &res); + if (n != 0) { + log_message (LOG_INFO, + "bind_socket: getaddrinfo failed for %s: %s (af: %s)", addr, get_gai_error (n), family_string(family)); return -1; + } ressave = res; @@ -70,6 +98,36 @@ bind_socket (int sockfd, const char *addr, int family) return sockfd; } +/** + * Try binding the given socket to supplied addresses, stopping when one succeeds. + */ +static int +bind_socket_list (int sockfd, sblist *addresses, int family) +{ + size_t nb_addresses = sblist_getsize(addresses); + size_t i; + + for (i = 0; i < nb_addresses; i++) { + const char *address = *(const char **)sblist_get(addresses, i); + if (bind_socket(sockfd, address, family) >= 0) { + log_message(LOG_INFO, "Bound to %s", address); + return 0; + } + } + + return -1; +} + +void set_socket_timeout(int fd) { + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = config->idletimeout; + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (void*) &tv, sizeof(tv)); + tv.tv_usec = 0; + tv.tv_sec = config->idletimeout; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (void*) &tv, sizeof(tv)); +} + /* * Open a connection to a remote host. It's been re-written to use * the getaddrinfo() library function, which allows for a protocol @@ -96,7 +154,7 @@ int opensock (const char *host, int port, const char *bind_to) n = getaddrinfo (host, portstr, &hints, &res); if (n != 0) { log_message (LOG_ERR, - "opensock: Could not retrieve info for %s", host); + "opensock: Could not retrieve address info for %s:%d: %s", host, port, get_gai_error (n)); return -1; } @@ -117,16 +175,27 @@ int opensock (const char *host, int port, const char *bind_to) close (sockfd); continue; /* can't bind, so try again */ } - } else if (config.bind_address) { - if (bind_socket (sockfd, config.bind_address, - res->ai_family) < 0) { + } else if (config->bind_addrs) { + if (bind_socket_list (sockfd, config->bind_addrs, + res->ai_family) < 0) { close (sockfd); continue; /* can't bind, so try again */ } } - if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) + set_socket_timeout(sockfd); + + if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) { + union sockaddr_union *p = (void*) res->ai_addr, u; + int af = res->ai_addr->sa_family; + unsigned dport = ntohs(af == AF_INET ? p->v4.sin_port : p->v6.sin6_port); + socklen_t slen = sizeof u; + if (dport == config->port) { + getsockname(sockfd, (void*)&u, &slen); + loop_records_add(&u); + } break; /* success */ + } close (sockfd); } while ((res = res->ai_next) != NULL); @@ -134,41 +203,15 @@ int opensock (const char *host, int port, const char *bind_to) freeaddrinfo (ressave); if (res == NULL) { log_message (LOG_ERR, - "opensock: Could not establish a connection to %s", - host); + "opensock: Could not establish a connection to %s:%d", + host, + port); return -1; } return sockfd; } -/* - * Set the socket to non blocking -rjkaes - */ -int socket_nonblocking (int sock) -{ - int flags; - - assert (sock >= 0); - - flags = fcntl (sock, F_GETFL, 0); - return fcntl (sock, F_SETFL, flags | O_NONBLOCK); -} - -/* - * Set the socket to blocking -rjkaes - */ -int socket_blocking (int sock) -{ - int flags; - - assert (sock >= 0); - - flags = fcntl (sock, F_GETFL, 0); - return fcntl (sock, F_SETFL, flags & ~O_NONBLOCK); -} - - /** * Try to listen on one socket based on the addrinfo * as returned from getaddrinfo. @@ -186,8 +229,7 @@ static int listen_on_one_socket(struct addrinfo *ad) ret = getnameinfo(ad->ai_addr, ad->ai_addrlen, numerichost, NI_MAXHOST, NULL, 0, flags); if (ret != 0) { - log_message(LOG_ERR, "error calling getnameinfo: %s", - gai_strerror(errno)); + log_message(LOG_ERR, "getnameinfo failed: %s", get_gai_error (ret)); return -1; } @@ -251,11 +293,12 @@ static int listen_on_one_socket(struct addrinfo *ad) * Upon success, the listen-fds are added to the listen_fds list * and 0 is returned. Upon error, -1 is returned. */ -int listen_sock (const char *addr, uint16_t port, vector_t listen_fds) +int listen_sock (const char *addr, uint16_t port, sblist* listen_fds) { struct addrinfo hints, *result, *rp; char portstr[6]; int ret = -1; + int n; assert (port > 0); assert (listen_fds != NULL); @@ -270,10 +313,13 @@ int listen_sock (const char *addr, uint16_t port, vector_t listen_fds) snprintf (portstr, sizeof (portstr), "%d", port); - if (getaddrinfo (addr, portstr, &hints, &result) != 0) { + n = getaddrinfo (addr, portstr, &hints, &result); + if (n != 0) { log_message (LOG_ERR, - "Unable to getaddrinfo() because of %s", - strerror (errno)); + "Unable to getaddrinfo() for %s:%d because of %s", + addr, + port, + get_gai_error (n)); return -1; } @@ -285,7 +331,7 @@ int listen_sock (const char *addr, uint16_t port, vector_t listen_fds) continue; } - vector_append (listen_fds, &listenfd, sizeof(int)); + sblist_add (listen_fds, &listenfd); /* success */ ret = 0; @@ -334,27 +380,9 @@ int getsock_ip (int fd, char *ipaddr) /* * Return the peer's socket information. */ -int getpeer_information (int fd, char *ipaddr, char *string_addr) +void getpeer_information (union sockaddr_union* addr, char *ipaddr, size_t ipaddr_len) { - struct sockaddr_storage sa; - socklen_t salen = sizeof sa; - - assert (fd >= 0); - assert (ipaddr != NULL); - assert (string_addr != NULL); - - /* Set the strings to default values */ - ipaddr[0] = '\0'; - strlcpy (string_addr, "[unknown]", HOSTNAME_LENGTH); - - /* Look up the IP address */ - if (getpeername (fd, (struct sockaddr *) &sa, &salen) != 0) - return -1; - - if (get_ip_string ((struct sockaddr *) &sa, ipaddr, IP_LENGTH) == NULL) - return -1; - - /* Get the full host name */ - return getnameinfo ((struct sockaddr *) &sa, salen, - string_addr, HOSTNAME_LENGTH, NULL, 0, 0); + int af = addr->v4.sin_family; + void *ipdata = af == AF_INET ? (void*)&addr->v4.sin_addr : (void*)&addr->v6.sin6_addr; + inet_ntop(af, ipdata, ipaddr, ipaddr_len); } diff --git a/src/sock.h b/src/sock.h index f1225eab..9763490d 100644 --- a/src/sock.h +++ b/src/sock.h @@ -28,15 +28,34 @@ #define MAXLINE (1024 * 4) -#include "vector.h" +#include "common.h" +#include "sblist.h" + +#define SOCKADDR_UNION_AF(PTR) (PTR)->v4.sin_family + +#define SOCKADDR_UNION_LENGTH(PTR) ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? sizeof((PTR)->v4) : ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? sizeof((PTR)->v6) : 0 ) ) + +#define SOCKADDR_UNION_ADDRESS(PTR) ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? (void*) &(PTR)->v4.sin_addr : ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? (void*) &(PTR)->v6.sin6_addr : (void*) 0 ) ) + +#define SOCKADDR_UNION_PORT(PTR) ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? (PTR)->v4.sin_port : ( \ + ( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? (PTR)->v6.sin6_port : 0 ) ) + +union sockaddr_union { + struct sockaddr_in v4; + struct sockaddr_in6 v6; +}; extern int opensock (const char *host, int port, const char *bind_to); -extern int listen_sock (const char *addr, uint16_t port, vector_t listen_fds); +extern int listen_sock (const char *addr, uint16_t port, sblist* listen_fds); -extern int socket_nonblocking (int sock); -extern int socket_blocking (int sock); +extern void set_socket_timeout(int fd); extern int getsock_ip (int fd, char *ipaddr); -extern int getpeer_information (int fd, char *ipaddr, char *string_addr); +extern void getpeer_information (union sockaddr_union *addr, char *ipaddr, size_t ipaddr_len); #endif diff --git a/src/stats.c b/src/stats.c index c7b44232..19607336 100644 --- a/src/stats.c +++ b/src/stats.c @@ -33,6 +33,7 @@ #include "stats.h" #include "utils.h" #include "conf.h" +#include struct stat_s { unsigned long int num_reqs; @@ -42,18 +43,16 @@ struct stat_s { unsigned long int num_denied; }; -static struct stat_s *stats; +static struct stat_s stats_buf, *stats; +static pthread_mutex_t stats_update_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t stats_file_lock = PTHREAD_MUTEX_INITIALIZER; /* * Initialize the statistics information to zero. */ void init_stats (void) { - stats = (struct stat_s *) malloc_shared_memory (sizeof (struct stat_s)); - if (stats == MAP_FAILED) - return; - - memset (stats, 0, sizeof (struct stat_s)); + stats = &stats_buf; } /* @@ -72,10 +71,15 @@ showstats (struct conn_s *connptr) snprintf (denied, sizeof (denied), "%lu", stats->num_denied); snprintf (refused, sizeof (refused), "%lu", stats->num_refused); - if (!config.statpage || (!(statfile = fopen (config.statpage, "r")))) { + pthread_mutex_lock(&stats_file_lock); + + if (!config->statpage || (!(statfile = fopen (config->statpage, "r")))) { message_buffer = (char *) safemalloc (MAXBUFFSIZE); - if (!message_buffer) + if (!message_buffer) { +err_minus_one: + pthread_mutex_unlock(&stats_file_lock); return -1; + } snprintf (message_buffer, MAXBUFFSIZE, @@ -83,9 +87,9 @@ showstats (struct conn_s *connptr) "\n" "\n" - "%s version %s run-time statistics\n" + "%s run-time statistics\n" "\n" - "

%s version %s run-time statistics

\n" + "

%s run-time statistics

\n" "

\n" "Number of open connections: %lu
\n" "Number of requests: %lu
\n" @@ -94,33 +98,34 @@ showstats (struct conn_s *connptr) "Number of refused connections due to high load: %lu\n" "

\n" "
\n" - "

Generated by %s version %s.

\n" "\n" + "

Generated by %s.

\n" "\n" "\n", - PACKAGE, VERSION, PACKAGE, VERSION, + PACKAGE, PACKAGE, stats->num_open, stats->num_reqs, stats->num_badcons, stats->num_denied, - stats->num_refused, PACKAGE, VERSION); + stats->num_refused, PACKAGE); if (send_http_message (connptr, 200, "OK", message_buffer) < 0) { safefree (message_buffer); - return -1; + goto err_minus_one; } safefree (message_buffer); + pthread_mutex_unlock(&stats_file_lock); return 0; } - add_error_variable (connptr, "opens", opens); add_error_variable (connptr, "reqs", reqs); add_error_variable (connptr, "badconns", badconns); add_error_variable (connptr, "deniedconns", denied); add_error_variable (connptr, "refusedconns", refused); add_standard_vars (connptr); - send_http_headers (connptr, 200, "Statistic requested"); + send_http_headers (connptr, 200, "Statistic requested", ""); send_html_file (statfile, connptr); fclose (statfile); + pthread_mutex_unlock(&stats_file_lock); return 0; } @@ -131,6 +136,9 @@ showstats (struct conn_s *connptr) */ int update_stats (status_t update_level) { + int ret = 0; + + pthread_mutex_lock(&stats_update_lock); switch (update_level) { case STAT_BADCONN: ++stats->num_badcons; @@ -149,8 +157,9 @@ int update_stats (status_t update_level) ++stats->num_denied; break; default: - return -1; + ret = -1; } + pthread_mutex_unlock(&stats_update_lock); - return 0; + return ret; } diff --git a/src/text.c b/src/text.c index 6bd46d22..e4bae39d 100644 --- a/src/text.c +++ b/src/text.c @@ -47,30 +47,6 @@ size_t strlcpy (char *dst, const char *src, size_t size) } #endif -#ifndef HAVE_STRLCAT -/* - * Function API taken from OpenBSD. Like strncat(), but does not 0 fill the - * buffer, and always NULL terminates the buffer. size is the length of the - * buffer, which should be one more than the maximum resulting string - * length. - */ -size_t strlcat (char *dst, const char *src, size_t size) -{ - size_t len1 = strlen (dst); - size_t len2 = strlen (src); - size_t ret = len1 + len2; - - if (len1 + len2 >= size) - len2 = size - len1 - 1; - if (len2 > 0) { - memcpy (dst + len1, src, len2); - dst[len1 + len2] = '\0'; - } - - return ret; -} -#endif - /* * Removes any new-line or carriage-return characters from the end of the * string. This function is named after the same function in Perl. diff --git a/src/text.h b/src/text.h index 0beb9b11..cfe631ab 100644 --- a/src/text.h +++ b/src/text.h @@ -21,10 +21,6 @@ #ifndef TINYPROXY_TEXT_H #define TINYPROXY_TEXT_H -#ifndef HAVE_STRLCAT -extern size_t strlcat (char *dst, const char *src, size_t size); -#endif /* HAVE_STRLCAT */ - #ifndef HAVE_STRLCPY extern size_t strlcpy (char *dst, const char *src, size_t size); #endif /* HAVE_STRLCPY */ diff --git a/src/transparent-proxy.c b/src/transparent-proxy.c index 5d0c8f6c..d0ba2c86 100644 --- a/src/transparent-proxy.c +++ b/src/transparent-proxy.c @@ -45,7 +45,7 @@ static int build_url (char **url, const char *host, int port, const char *path) assert (path != NULL); len = strlen (host) + strlen (path) + 14; - *url = (char *) safemalloc (len); + *url = (char *) saferealloc (*url, len); if (*url == NULL) return -1; @@ -53,22 +53,27 @@ static int build_url (char **url, const char *host, int port, const char *path) } int -do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, +do_transparent_proxy (struct conn_s *connptr, pseudomap *hashofheaders, struct request_s *request, struct config_s *conf, char **url) { socklen_t length; char *data; size_t ulen = strlen (*url); - ssize_t i; + size_t i; - length = hashmap_entry_by_key (hashofheaders, "host", (void **) &data); - if (length <= 0) { - struct sockaddr_in dest_addr; + data = pseudomap_find (hashofheaders, "host"); + if (!data) { + union sockaddr_union dest_addr; + const void *dest_inaddr; + char namebuf[INET6_ADDRSTRLEN+1]; + int af; + length = sizeof(dest_addr); if (getsockname - (connptr->client_fd, (struct sockaddr *) &dest_addr, - &length) < 0) { + (connptr->client_fd, (void *) &dest_addr, + &length) < 0 || length > sizeof(dest_addr)) { + addr_err:; log_message (LOG_ERR, "process_request: cannot get destination IP for %d", connptr->client_fd); @@ -78,10 +83,14 @@ do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, return 0; } - request->host = (char *) safemalloc (17); - strlcpy (request->host, inet_ntoa (dest_addr.sin_addr), 17); + af = SOCKADDR_UNION_AF(&dest_addr); + dest_inaddr = SOCKADDR_UNION_ADDRESS(&dest_addr); - request->port = ntohs (dest_addr.sin_port); + if (!inet_ntop(af, dest_inaddr, namebuf, sizeof namebuf)) + goto addr_err; + + request->host = safestrdup (namebuf); + request->port = ntohs (SOCKADDR_UNION_PORT(&dest_addr)); request->path = (char *) safemalloc (ulen + 1); strlcpy (request->path, *url, ulen + 1); @@ -91,6 +100,7 @@ do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, "process_request: trans IP %s %s for %d", request->method, *url, connptr->client_fd); } else { + length = strlen (data); request->host = (char *) safemalloc (length + 1); if (sscanf (data, "%[^:]:%hu", request->host, &request->port) != 2) { @@ -111,12 +121,12 @@ do_transparent_proxy (struct conn_s *connptr, hashmap_t hashofheaders, return 1; } - for (i = 0; i < vector_length(conf->listen_addrs); i++) { - const char *addr; + for (i = 0; i < sblist_getsize(conf->listen_addrs); i++) { + char **addr; - addr = (char *)vector_getentry(conf->listen_addrs, i, NULL); + addr = sblist_get(conf->listen_addrs, i); - if (addr && strcmp(request->host, addr) == 0) { + if (addr && *addr && strcmp(request->host, *addr) == 0) { log_message(LOG_ERR, "transparent: destination IP %s is local " "on socket fd %d", diff --git a/src/transparent-proxy.h b/src/transparent-proxy.h index 4fc3a4d6..1167aed2 100644 --- a/src/transparent-proxy.h +++ b/src/transparent-proxy.h @@ -26,11 +26,11 @@ #ifdef TRANSPARENT_PROXY #include "conns.h" -#include "hashmap.h" +#include "pseudomap.h" #include "reqs.h" extern int do_transparent_proxy (struct conn_s *connptr, - hashmap_t hashofheaders, + pseudomap *hashofheaders, struct request_s *request, struct config_s *config, char **url); diff --git a/src/upstream.c b/src/upstream.c index 91bf4572..52dad808 100644 --- a/src/upstream.c +++ b/src/upstream.c @@ -27,43 +27,79 @@ #include "upstream.h" #include "heap.h" #include "log.h" +#include "base64.h" +#include "basicauth.h" #ifdef UPSTREAM_SUPPORT const char * proxy_type_name(proxy_type type) { switch(type) { - case HTTP_TYPE: return "http"; - case SOCKS4_TYPE: return "socks4"; - case SOCKS5_TYPE: return "socks5"; + case PT_NONE: return "none"; + case PT_HTTP: return "http"; + case PT_SOCKS4: return "socks4"; + case PT_SOCKS5: return "socks5"; default: return "unknown"; } } + +const char* upstream_build_error_string(enum upstream_build_error ube) { + static const char *emap[] = { + [UBE_SUCCESS] = "", + [UBE_OOM] = "Unable to allocate memory in upstream_build()", + [UBE_USERLEN] = "User / pass in upstream config too long", + [UBE_EDOMAIN] = "Nonsense upstream none rule: empty domain", + [UBE_INVHOST] = "Nonsense upstream rule: invalid host or port", + [UBE_INVPARAMS] = "Nonsense upstream rule: invalid parameters", + [UBE_NETMASK] = "Nonsense upstream rule: failed to parse netmask", + }; + return emap[ube]; +} + /** * Construct an upstream struct from input data. */ -static struct upstream *upstream_build (const char *host, int port, const char *domain, - proxy_type type) +static struct upstream *upstream_build (const char *host, int port, char *domain, + const char *user, const char *pass, + proxy_type type, enum upstream_build_error *ube) { - char *ptr; struct upstream *up; + *ube = UBE_SUCCESS; up = (struct upstream *) safemalloc (sizeof (struct upstream)); if (!up) { - log_message (LOG_ERR, - "Unable to allocate memory in upstream_build()"); + *ube = UBE_OOM; return NULL; } up->type = type; - up->host = up->domain = NULL; - up->ip = up->mask = 0; + up->target.type = HST_NONE; + up->host = up->ua.user = up->pass = NULL; + if (user) { + if (type == PT_HTTP) { + char b[BASE64ENC_BYTES((256+2)-1) + 1]; + ssize_t ret; + ret = basicauth_string(user, pass, b, sizeof b); + if (ret == 0) { + *ube = UBE_USERLEN; + return NULL; + } + up->ua.authstr = safestrdup (b); + } else { + up->ua.user = safestrdup (user); + up->pass = safestrdup (pass); + } + } if (domain == NULL) { - if (!host || host[0] == '\0' || port < 1) { - log_message (LOG_WARNING, - "Nonsense upstream rule: invalid host or port"); + if (type == PT_NONE) { + e_nonedomain:; + *ube = UBE_EDOMAIN; + goto fail; + } + if (!host || !host[0] || port < 1) { + *ube = UBE_INVHOST; goto fail; } @@ -72,57 +108,39 @@ static struct upstream *upstream_build (const char *host, int port, const char * log_message (LOG_INFO, "Added upstream %s %s:%d for [default]", proxy_type_name(type), host, port); - } else if (host == NULL) { - if (!domain || domain[0] == '\0') { - log_message (LOG_WARNING, - "Nonsense no-upstream rule: empty domain"); - goto fail; - } - - ptr = strchr (domain, '/'); - if (ptr) { - struct in_addr addrstruct; - - *ptr = '\0'; - if (inet_aton (domain, &addrstruct) != 0) { - up->ip = ntohl (addrstruct.s_addr); - *ptr++ = '/'; - - if (strchr (ptr, '.')) { - if (inet_aton (ptr, &addrstruct) != 0) - up->mask = - ntohl (addrstruct.s_addr); - } else { - up->mask = - ~((1 << (32 - atoi (ptr))) - 1); - } - } + } else { + if (type == PT_NONE) { + if (!domain[0]) goto e_nonedomain; } else { - up->domain = safestrdup (domain); + if (!host || !host[0] || !domain[0]) { + *ube = UBE_INVPARAMS; + goto fail; + } + up->host = safestrdup (host); + up->port = port; } - log_message (LOG_INFO, "Added no-upstream for %s", domain); - } else { - if (!host || host[0] == '\0' || port < 1 || !domain - || domain == '\0') { - log_message (LOG_WARNING, - "Nonsense upstream rule: invalid parameters"); + if (hostspec_parse(domain, &up->target) + || up->target.type == HST_NONE) { + *ube = UBE_NETMASK; goto fail; } - up->host = safestrdup (host); - up->port = port; - up->domain = safestrdup (domain); - - log_message (LOG_INFO, "Added upstream %s %s:%d for %s", - proxy_type_name(type), host, port, domain); + if (type == PT_NONE) + log_message (LOG_INFO, "Added upstream none for %s", domain); + else + log_message (LOG_INFO, "Added upstream %s %s:%d for %s", + proxy_type_name(type), host, port, domain); } return up; fail: + safefree (up->ua.user); + safefree (up->pass); safefree (up->host); - safefree (up->domain); + if(up->target.type == HST_STRING) + safefree (up->target.address.string); safefree (up); return NULL; @@ -131,21 +149,24 @@ static struct upstream *upstream_build (const char *host, int port, const char * /* * Add an entry to the upstream list */ -void upstream_add (const char *host, int port, const char *domain, +enum upstream_build_error upstream_add ( + const char *host, int port, char *domain, + const char *user, const char *pass, proxy_type type, struct upstream **upstream_list) { struct upstream *up; + enum upstream_build_error ube; - up = upstream_build (host, port, domain, type); + up = upstream_build (host, port, domain, user, pass, type, &ube); if (up == NULL) { - return; + return ube; } - if (!up->domain && !up->ip) { /* always add default to end */ + if (up->target.type == HST_NONE) { /* always add default to end */ struct upstream *tmp = *upstream_list; while (tmp) { - if (!tmp->domain && !tmp->ip) { + if (tmp->target.type == HST_NONE) { log_message (LOG_WARNING, "Duplicate default upstream"); goto upstream_cleanup; @@ -154,7 +175,7 @@ void upstream_add (const char *host, int port, const char *domain, if (!tmp->next) { up->next = NULL; tmp->next = up; - return; + return ube; } tmp = tmp->next; @@ -164,14 +185,15 @@ void upstream_add (const char *host, int port, const char *domain, up->next = *upstream_list; *upstream_list = up; - return; + return ube; upstream_cleanup: safefree (up->host); - safefree (up->domain); + if(up->target.type == HST_STRING) + safefree (up->target.address.string); safefree (up); - return; + return ube; } /* @@ -179,39 +201,17 @@ void upstream_add (const char *host, int port, const char *domain, */ struct upstream *upstream_get (char *host, struct upstream *up) { - in_addr_t my_ip = INADDR_NONE; - while (up) { - if (up->domain) { - if (strcasecmp (host, up->domain) == 0) - break; /* exact match */ - - if (up->domain[0] == '.') { - char *dot = strchr (host, '.'); + if (up->target.type == HST_NONE) + break; - if (!dot && !up->domain[1]) - break; /* local host matches "." */ - - while (dot && strcasecmp (dot, up->domain)) - dot = strchr (dot + 1, '.'); - - if (dot) - break; /* subdomain match */ - } - } else if (up->ip) { - if (my_ip == INADDR_NONE) - my_ip = ntohl (inet_addr (host)); - - if ((my_ip & up->mask) == up->ip) - break; - } else { - break; /* No domain or IP, default upstream */ - } + if (hostspec_match(host, &up->target)) + break; up = up->next; } - if (up && (!up->host || !up->port)) + if (up && (!up->host)) up = NULL; if (up) @@ -228,7 +228,8 @@ void free_upstream_list (struct upstream *up) while (up) { struct upstream *tmp = up; up = up->next; - safefree (tmp->domain); + if(tmp->target.type == HST_STRING) + safefree (tmp->target.address.string); safefree (tmp->host); safefree (tmp); } diff --git a/src/upstream.h b/src/upstream.h index 78552149..9a2314d9 100644 --- a/src/upstream.h +++ b/src/upstream.h @@ -26,27 +26,51 @@ #define _TINYPROXY_UPSTREAM_H_ #include "common.h" +#include "hostspec.h" + +enum upstream_build_error { + UBE_SUCCESS = 0, + UBE_OOM, + UBE_USERLEN, + UBE_EDOMAIN, + UBE_INVHOST, + UBE_INVPARAMS, + UBE_NETMASK, +}; /* * Even if upstream support is not compiled into tinyproxy, this * structure still needs to be defined. */ -typedef enum {HTTP_TYPE, SOCKS4_TYPE, SOCKS5_TYPE} proxy_type; +typedef enum proxy_type { + PT_NONE = 0, + PT_HTTP, + PT_SOCKS4, + PT_SOCKS5 +} proxy_type; + struct upstream { struct upstream *next; - char *domain; /* optional */ char *host; + union { + char *user; + char *authstr; + } ua; + char *pass; int port; - in_addr_t ip, mask; + struct hostspec target; proxy_type type; }; #ifdef UPSTREAM_SUPPORT const char *proxy_type_name(proxy_type type); -extern void upstream_add (const char *host, int port, const char *domain, +extern enum upstream_build_error upstream_add ( + const char *host, int port, char *domain, + const char *user, const char *pass, proxy_type type, struct upstream **upstream_list); extern struct upstream *upstream_get (char *host, struct upstream *up); extern void free_upstream_list (struct upstream *up); +extern const char* upstream_build_error_string(enum upstream_build_error); #endif /* UPSTREAM_SUPPORT */ #endif /* _TINYPROXY_UPSTREAM_H_ */ diff --git a/src/utils.c b/src/utils.c index ef2e6732..69faef6f 100644 --- a/src/utils.c +++ b/src/utils.c @@ -39,7 +39,7 @@ send_http_message (struct conn_s *connptr, int http_code, const char *error_title, const char *message) { static const char *headers[] = { - "Server: " PACKAGE "/" VERSION, + "Server: " PACKAGE, "Content-type: text/html", "Connection: close" }; diff --git a/src/vector.c b/src/vector.c deleted file mode 100644 index cf9fc75e..00000000 --- a/src/vector.c +++ /dev/null @@ -1,214 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2002 Robert James Kaes - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* A vector implementation. The vector can be of an arbitrary length, and - * the data for each entry is an lump of data (the size is stored in the - * vector.) - */ - -#include "main.h" - -#include "heap.h" -#include "vector.h" - -/* - * These structures are the storage for the "vector". Entries are - * stored in struct vectorentry_s (the data and the length), and the - * "vector" structure is implemented as a linked-list. The struct - * vector_s stores a pointer to the first vector (vector[0]) and a - * count of the number of entries (or how long the vector is.) - */ -struct vectorentry_s { - void *data; - size_t len; - - struct vectorentry_s *next; -}; - -struct vector_s { - size_t num_entries; - struct vectorentry_s *head; - struct vectorentry_s *tail; -}; - -/* - * Create an vector. The vector initially has no elements and no - * storage has been allocated for the entries. - * - * A NULL is returned if memory could not be allocated for the - * vector. - */ -vector_t vector_create (void) -{ - vector_t vector; - - vector = (vector_t) safemalloc (sizeof (struct vector_s)); - if (!vector) - return NULL; - - vector->num_entries = 0; - vector->head = vector->tail = NULL; - - return vector; -} - -/* - * Deletes an vector. All the entries when this function is run. - * - * Returns: 0 on success - * negative if a NULL vector is supplied - */ -int vector_delete (vector_t vector) -{ - struct vectorentry_s *ptr, *next; - - if (!vector) - return -EINVAL; - - ptr = vector->head; - while (ptr) { - next = ptr->next; - safefree (ptr->data); - safefree (ptr); - - ptr = next; - } - - safefree (vector); - - return 0; -} - -/* - * Appends an entry into the vector. The entry is an arbitrary - * collection of bytes of _len_ octets. The data is copied into the - * vector, so the original data must be freed to avoid a memory leak. - * The "data" must be non-NULL and the "len" must be greater than zero. - * "pos" is either 0 to prepend the data, or 1 to append the data. - * - * Returns: 0 on success - * negative number if there are errors - */ - -typedef enum { - INSERT_PREPEND, - INSERT_APPEND -} vector_pos_t; - -static int -vector_insert (vector_t vector, - void *data, - size_t len, - vector_pos_t pos) -{ - struct vectorentry_s *entry; - - if (!vector || !data || len <= 0 || - (pos != INSERT_PREPEND && pos != INSERT_APPEND)) - return -EINVAL; - - entry = - (struct vectorentry_s *) safemalloc (sizeof (struct vectorentry_s)); - if (!entry) - return -ENOMEM; - - entry->data = safemalloc (len); - if (!entry->data) { - safefree (entry); - return -ENOMEM; - } - - memcpy (entry->data, data, len); - entry->len = len; - entry->next = NULL; - - /* If there is no head or tail, create them */ - if (!vector->head && !vector->tail) - vector->head = vector->tail = entry; - else if (pos == INSERT_PREPEND) { - /* prepend the entry */ - entry->next = vector->head; - vector->head = entry; - } else { - /* append the entry */ - vector->tail->next = entry; - vector->tail = entry; - } - - vector->num_entries++; - - return 0; -} - -/* - * The following two function are used to make the API clearer. As you - * can see they simply call the vector_insert() function with appropriate - * arguments. - */ -int vector_append (vector_t vector, void *data, size_t len) -{ - return vector_insert (vector, data, len, INSERT_APPEND); -} - -int vector_prepend (vector_t vector, void *data, size_t len) -{ - return vector_insert (vector, data, len, INSERT_PREPEND); -} - -/* - * A pointer to the data at position "pos" (zero based) is returned. - * If the vector is out of bound, data is set to NULL. - * - * Returns: negative upon an error - * length of data if position is valid - */ -void *vector_getentry (vector_t vector, size_t pos, size_t * size) -{ - struct vectorentry_s *ptr; - size_t loc; - - if (!vector || pos >= vector->num_entries) - return NULL; - - loc = 0; - ptr = vector->head; - - while (loc != pos) { - ptr = ptr->next; - loc++; - } - - if (size) - *size = ptr->len; - - return ptr->data; -} - -/* - * Returns the number of entries (or the length) of the vector. - * - * Returns: negative if vector is not valid - * positive length of vector otherwise - */ -ssize_t vector_length (vector_t vector) -{ - if (!vector) - return -EINVAL; - - return vector->num_entries; -} diff --git a/src/vector.h b/src/vector.h deleted file mode 100644 index ef8f9535..00000000 --- a/src/vector.h +++ /dev/null @@ -1,75 +0,0 @@ -/* tinyproxy - A fast light-weight HTTP proxy - * Copyright (C) 2002 Robert James Kaes - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* See 'vector.c' for detailed information. */ - -#ifndef _VECTOR_H -#define _VECTOR_H - -/* - * We're using a typedef here to "hide" the implementation details of the - * vector. Sure, it's a pointer, but the struct is hidden in the C file. - * So, just use the vector_t like it's a cookie. :) - */ -typedef struct vector_s *vector_t; - -/* - * vector_create() takes no arguments. - * vector_delete() is self explanatory. - */ -extern vector_t vector_create (void); -extern int vector_delete (vector_t vector); - -/* - * When you insert a piece of data into the vector, the data will be - * duplicated, so you must free your copy if it was created on the heap. - * The data must be non-NULL and the length must be greater than zero. - * - * Returns: negative on error - * 0 upon successful insert. - */ -extern int vector_append (vector_t vector, void *data, size_t len); -extern int vector_prepend (vector_t vector, void *data, size_t len); - -/* - * A pointer to the data at position "pos" (zero based) is returned and the - * size pointer contains the length of the data stored. - * - * The pointer points to the actual data in the vector, so you have - * the power to modify the data, but do it responsibly since the - * library doesn't take any steps to prevent you from messing up the - * vector. (A better rule is, don't modify the data since you'll - * likely mess up the "length" parameter of the data.) However, DON'T - * try to realloc or free the data; doing so will break the vector. - * - * If "size" is NULL the size of the data is not returned. - * - * Returns: NULL on error - * valid pointer to data - */ -extern void *vector_getentry (vector_t vector, size_t pos, size_t * size); - -/* - * Returns the number of enteries (or the length) of the vector. - * - * Returns: negative if vector is not valid - * positive length of vector otherwise - */ -extern ssize_t vector_length (vector_t vector); - -#endif /* _VECTOR_H */ diff --git a/tests/scripts/run_tests.sh b/tests/scripts/run_tests.sh index dd95400c..7b76002b 100755 --- a/tests/scripts/run_tests.sh +++ b/tests/scripts/run_tests.sh @@ -18,7 +18,7 @@ # this program; if not, see . -SCRIPTS_DIR=$(pwd)/$(dirname $0) +SCRIPTS_DIR=$(cd "$(dirname "$0")" && pwd) BASEDIR=$SCRIPTS_DIR/../.. TESTS_DIR=$SCRIPTS_DIR/.. TESTENV_DIR=$TESTS_DIR/env @@ -30,6 +30,7 @@ TINYPROXY_USER=$(id -un) TINYPROXY_PID_DIR=$TESTENV_DIR/var/run/tinyproxy TINYPROXY_PID_FILE=$TINYPROXY_PID_DIR/tinyproxy.pid TINYPROXY_LOG_DIR=$LOG_DIR/tinyproxy +TINYPROXY_LOG_FILE=$TINYPROXY_LOG_DIR/tinyproxy.log TINYPROXY_DATA_DIR=$TESTENV_DIR/usr/share/tinyproxy TINYPROXY_CONF_DIR=$TESTENV_DIR/etc/tinyproxy TINYPROXY_CONF_FILE=$TINYPROXY_CONF_DIR/tinyproxy.conf @@ -50,27 +51,27 @@ WEBCLIENT_LOG=$LOG_DIR/webclient.log WEBCLIENT_BIN=$SCRIPTS_DIR/webclient.pl provision_initial() { - if test -e $TESTENV_DIR ; then + if test -e "$TESTENV_DIR" ; then TESTENV_DIR_OLD=$TESTENV_DIR.old - if test -e $TESTENV_DIR_OLD ; then - rm -rf $TESTENV_DIR_OLD + if test -e "$TESTENV_DIR_OLD" ; then + rm -rf "$TESTENV_DIR_OLD" fi - mv $TESTENV_DIR $TESTENV_DIR.old + mv "$TESTENV_DIR" "$TESTENV_DIR".old fi - mkdir -p $LOG_DIR + mkdir -p "$LOG_DIR" } provision_tinyproxy() { - mkdir -p $TINYPROXY_DATA_DIR - cp $BASEDIR/data/templates/default.html $TINYPROXY_DATA_DIR - cp $BASEDIR/data/templates/debug.html $TINYPROXY_DATA_DIR - cp $BASEDIR/data/templates/stats.html $TINYPROXY_DATA_DIR - mkdir -p $TINYPROXY_PID_DIR - mkdir -p $TINYPROXY_LOG_DIR - mkdir -p $TINYPROXY_CONF_DIR - - cat >>$TINYPROXY_CONF_FILE<>"$TINYPROXY_CONF_FILE"< "$TINYPROXY_FILTER_FILE" +.*\.google-analytics\.com$ +EOF } start_tinyproxy() { - echo -n "starting tinyproxy..." - $VALGRIND $TINYPROXY_BIN -c $TINYPROXY_CONF_FILE 2> $TINYPROXY_STDERR_LOG + printf "starting tinyproxy..." + $VALGRIND "$TINYPROXY_BIN" -c "$TINYPROXY_CONF_FILE" 2> "$TINYPROXY_STDERR_LOG" echo " done (listening on $TINYPROXY_IP:$TINYPROXY_PORT)" } +reload_config() { + printf "signaling tinyproxy to reload config..." + pid=$(cat "$TINYPROXY_PID_FILE") + #1: SIGHUP + kill -1 "$pid" && echo "ok" || echo "fail" +} + stop_tinyproxy() { - echo -n "killing tinyproxy..." - kill $(cat $TINYPROXY_PID_FILE) - if test "x$?" = "x0" ; then + printf "killing tinyproxy..." + pid=$(cat "$TINYPROXY_PID_FILE") + kill "$pid" + if test "$?" = "0" ; then echo " ok" else - echo " error" + echo " error killing pid $pid" + pgrep tinyproxy + echo "### printing logfile" + cat "$TINYPROXY_LOG_FILE" + echo "### printing stderr logfile" + cat "$TINYPROXY_STDERR_LOG" fi } provision_webserver() { - mkdir -p $WEBSERVER_PID_DIR - mkdir -p $WEBSERVER_LOG_DIR + mkdir -p "$WEBSERVER_PID_DIR" + mkdir -p "$WEBSERVER_LOG_DIR" } start_webserver() { - echo -n "starting web server..." - $WEBSERVER_BIN --port $WEBSERVER_PORT --log-dir $WEBSERVER_LOG_DIR --pid-file $WEBSERVER_PID_FILE - echo " done (listening on $WEBSERVER_IP:$WEBSERVER_PORT)" + printf "starting web server..." + "$WEBSERVER_BIN" --port "$WEBSERVER_PORT" --log-dir "$WEBSERVER_LOG_DIR" --pid-file "$WEBSERVER_PID_FILE" + echo " done. listening on $WEBSERVER_IP:$WEBSERVER_PORT" } stop_webserver() { - echo -n "killing webserver..." - kill $(cat $WEBSERVER_PID_FILE) - if test "x$?" = "x0" ; then + printf "killing webserver..." + kill "$(cat "$WEBSERVER_PID_FILE")" + if test "$?" = "0" ; then echo " ok" else echo " error" - fi + fi } wait_for_some_seconds() { - SECONDS=$1 - if test "x$SECONDS" = "x" ; then - SECONDS=1 + seconds=$1 + if test "$seconds" = "" ; then + seconds=1 fi - echo -n "waiting for $SECONDS seconds." + printf 'waiting for %s seconds.' "$seconds" - for COUNT in $(seq 1 $SECONDS) ; do + for COUNT in $(seq 1 "$seconds") ; do sleep 1 - echo -n "." + printf ' %s ' "$COUNT" done - echo " done" + echo ' done' } run_basic_webclient_request() { - $WEBCLIENT_BIN $1 $2 >> $WEBCLIENT_LOG 2>&1 + "$WEBCLIENT_BIN" "$1" "$2" > "$WEBCLIENT_LOG" 2>&1 WEBCLIENT_EXIT_CODE=$? - if test "x$WEBCLIENT_EXIT_CODE" = "x0" ; then + if test "$WEBCLIENT_EXIT_CODE" = "0" ; then echo " ok" else - echo "ERROR ($WEBCLIENT_EXIT_CODE)" + echo "ERROR: $WEBCLIENT_EXIT_CODE" echo "webclient output:" - cat $WEBCLIENT_LOG + cat "$WEBCLIENT_LOG" + echo "######################################" fi return $WEBCLIENT_EXIT_CODE } +run_failure_webclient_request() { + ec=$1 + expected_error=$(($1 - 399)) + shift + "$WEBCLIENT_BIN" "$1" "$2" "$3" "$4" > "$WEBCLIENT_LOG" 2>&1 + WEBCLIENT_EXIT_CODE=$? + if test "$WEBCLIENT_EXIT_CODE" = "$expected_error" ; then + echo " ok, got expected error code $ec" + return 0 + else + echo "ERROR: $WEBCLIENT_EXIT_CODE" + echo "webclient output:" + cat "$WEBCLIENT_LOG" + echo "######################################" + fi + + return 1 +} + # "main" provision_initial @@ -176,28 +216,54 @@ provision_webserver start_webserver start_tinyproxy -wait_for_some_seconds 3 +wait_for_some_seconds 1 FAILED=0 -echo -n "checking direct connection to web server..." +basic_test() { +printf "checking direct connection to web server..." run_basic_webclient_request "$WEBSERVER_IP:$WEBSERVER_PORT" / -test "x$?" = "x0" || FAILED=$((FAILED + 1)) +test "$?" = "0" || FAILED=$((FAILED + 1)) -echo -n "testing connection through tinyproxy..." +printf "testing connection through tinyproxy..." run_basic_webclient_request "$TINYPROXY_IP:$TINYPROXY_PORT" "http://$WEBSERVER_IP:$WEBSERVER_PORT/" -test "x$?" = "x0" || FAILED=$((FAILED + 1)) +test "$?" = "0" || FAILED=$((FAILED + 1)) -echo -n "requesting statspage via stathost url..." +printf "requesting statspage via stathost url..." run_basic_webclient_request "$TINYPROXY_IP:$TINYPROXY_PORT" "http://$TINYPROXY_STATHOST_IP" -test "x$?" = "x0" || FAILED=$((FAILED + 1)) +test "$?" = "0" || FAILED=$((FAILED + 1)) +} + +ext_test() { +printf "checking bogus request..." +run_failure_webclient_request 400 --method="BIG FART" "$TINYPROXY_IP:$TINYPROXY_PORT" "http://$WEBSERVER_IP:$WEBSERVER_PORT" +test "$?" = "0" || FAILED=$((FAILED + 1)) + +printf "testing connection to filtered domain..." +run_failure_webclient_request 403 "$TINYPROXY_IP:$TINYPROXY_PORT" "http://badgoy.google-analytics.com/" +test "$?" = "0" || FAILED=$((FAILED + 1)) + +printf "requesting connect method to denied port..." +run_failure_webclient_request 403 --method=CONNECT "$TINYPROXY_IP:$TINYPROXY_PORT" "localhost:12345" +test "$?" = "0" || FAILED=$((FAILED + 1)) + +printf "testing unavailable backend..." +run_failure_webclient_request 502 "$TINYPROXY_IP:$TINYPROXY_PORT" "http://bogus.invalid" +test "$?" = "0" || FAILED=$((FAILED + 1)) +} + +basic_test +reload_config +basic_test +ext_test echo "$FAILED errors" -if test "x$TINYPROXY_TESTS_WAIT" = "xyes"; then +if test "$TINYPROXY_TESTS_WAIT" = "yes"; then echo "You can continue using the webserver and tinyproxy." - echo -n "hit to stop the servers and exit: " - read READ + rintf "hit to stop the servers and exit: " + read -r READ + echo "$READ" > /dev/null fi stop_tinyproxy diff --git a/tests/scripts/run_tests_valgrind.sh b/tests/scripts/run_tests_valgrind.sh index b93371f0..1ebb4f71 100755 --- a/tests/scripts/run_tests_valgrind.sh +++ b/tests/scripts/run_tests_valgrind.sh @@ -18,11 +18,10 @@ # this program; if not, see . -SCRIPTS_DIR=$(dirname $0) -BASEDIR=$SCRIPTS_DIR/../.. +SCRIPTS_DIR=$(dirname "$0") TESTS_DIR=$SCRIPTS_DIR/.. TESTENV_DIR=$TESTS_DIR/env LOG_DIR=$TESTENV_DIR/var/log -VALGRIND="valgrind -q --tool=memcheck --leak-check=full --log-file=$LOG_DIR/valgrind.log" $SCRIPTS_DIR/run_tests.sh +VALGRIND="valgrind --track-origins=yes --show-leak-kinds=all --tool=memcheck --leak-check=full --log-file=$LOG_DIR/valgrind.log" "$SCRIPTS_DIR"/run_tests.sh diff --git a/tests/scripts/webclient.pl b/tests/scripts/webclient.pl index 25c0fb79..94df08b2 100755 --- a/tests/scripts/webclient.pl +++ b/tests/scripts/webclient.pl @@ -26,9 +26,8 @@ my $EOL = "\015\012"; -my $VERSION = "0.1"; my $NAME = "Tinyproxy-Web-Client"; -my $user_agent = "$NAME/$VERSION"; +my $user_agent = "$NAME"; my $user_agent_header = "User-Agent: $user_agent$EOL"; my $http_version = "1.0"; my $method = "GET"; @@ -122,11 +121,16 @@ ($$$$$$) print $remote $request; + $_ = <$remote>; + print; # /* HTTP/1.0 400 Bad Request */ + my($errn) = ($_ =~ /HTTP\/\d\.\d (\d{3})/); + while (<$remote>) { print; } close $remote; + exit($errn - 399) if($errn > 399); } exit(0); diff --git a/tests/scripts/webserver.pl b/tests/scripts/webserver.pl index aa5eb137..2a6860fb 100755 --- a/tests/scripts/webserver.pl +++ b/tests/scripts/webserver.pl @@ -31,9 +31,8 @@ use Pod::Usage; use Fcntl ':flock'; # import LOCK_* constants -my $VERSION = "0.1"; my $NAME = "Tinyproxy-Test-Web-Server"; -my $server_header = "Server: $NAME/$VERSION"; +my $server_header = "Server: $NAME"; my $EOL = "\015\012";