Compare commits

...

80 Commits

Author SHA1 Message Date
rofl0r
cea0ebe657 tinyproxy.conf.5: explain what a site_spec looks like 2025-01-06 19:25:57 +00:00
rofl0r
05f6e4e000 basic auth: fix error status 401 vs 407
if tinyproxy serves as a HTTP server (i.e. when serving stats),
use error code 401, else error code 407.

fixes #532
2024-11-04 12:57:04 +01:00
rofl0r
c04ba4711a reqs: don't compile upstream code if feature disabled
fixes warning about implicit function declaration which is by default
treated as an error starting with GCC14.

closes #560
2024-10-19 09:26:37 +00:00
Robert Grumann
73da8a35a3
conf: add BasicAuthRealm feature (#547)
makes BasicAuth realm string editable in config file.

closes #235
2024-07-14 13:38:25 +02:00
James McKinney
d652ed8538
Omit the version number from headers and HTML responses (#543)
Omit the version number from headers, HTML responses, and templates
2024-06-20 10:51:29 +02:00
rofl0r
72b93f6d4b CI: update release workflow to non-deprecated actions
github continues to deprecate actions and idioms in their CI system.
hopefully these changes will last for a while and maintaining a simple
CI task doesn't turn into a neverending story.
2024-06-16 13:12:25 +00:00
Mohamed Akram
942d0c6b03 Use appropriate installation path variables 2024-06-02 20:42:49 +02:00
rofl0r
dd49e975a0 release 1.11.2 2024-05-08 18:22:55 +00:00
rofl0r
e69788b761
Add SECURITY.md
given the catastrophic way TALOS Intelligence "communicated" with upstream
(i.e. by probably sending a single mail to an unused email address),
it's probably best to explicitly document how to approach upstream
when a security issue is discovered.
2024-05-05 20:56:17 +02:00
rofl0r
12a8484265 fix potential UAF in header handling (CVE-2023-49606)
https://talosintelligence.com/vulnerability_reports/TALOS-2023-1889

this bug was brought to my attention today by the debian tinyproxy
package maintainer. the above link states that the issue was known
since last year and that maintainers have been contacted, but if
that is even true then it probably was done via a private email
to a potentially outdated email address of one of the maintainers,
not through the channels described clearly on the tinyproxy homepage:

> Feel free to report a new bug or suggest features via github issues.
> Tinyproxy developers hang out in #tinyproxy on irc.libera.chat.

no github issue was filed, and nobody mentioned a vulnerability on
the mentioned IRC chat. if the issue had been reported on github or
IRC, the bug would have been fixed within a day.
2024-05-05 10:37:29 +00:00
rofl0r
92289d5a4c main: print filename of config file used on (re)load 2024-05-01 23:48:37 +00:00
strongleong
c4df45b7e4 BasicAuth: Added logging for failed login attemps
closes #514
2023-11-12 15:05:38 +00:00
Victor Kislov
84285b640d
BasicAuth: Accept special chars in username and password (#516)
Co-authored-by: Victor Kislov <victork@primis.tech>
2023-11-02 19:24:42 +00:00
rofl0r
c834073968 fix CI by running apt update 2023-10-16 00:06:15 +01:00
rofl0r
1289d8afc8 conf: use case-independent match for Filtertype parameter 2023-10-13 19:54:26 +00:00
rofl0r
2935519eb7 fix omission to reset socklen parameter for accept()
since accept() uses the socklen parameter as in/out, after processing
an IPv4 the socklen fed to it waiting for the next client was only
the length of sockaddr_in, so if a connection from an IPv6 came in
the client sockaddr was only partially filled in.
this caused wrongly printed ipv6 addresses in log, and failure to
match them correctly against the acl.

closes #495
2023-06-07 18:57:05 +00:00
rofl0r
d7c20e663f tinyproxy.conf.5: document syntax for upstream IPv6 addresses
follow-up to 2bec15ee40
2023-05-25 19:42:02 +00:00
ivanwick
1e615e66a9
tinyproxy.conf.5: document config strings that require double quotes (#493)
* tinyproxy.conf.5: document config strings that require double quotes

String config values matched by the STR regex must be enclosed in double
quotes

Edit descriptions for brevity

conf.c: move boolean arguments comment before BOOL group

addresses #491

* Revert conf.c: move boolean arguments comment before BOOL group
2023-05-24 15:06:15 +01:00
Mario-Klebsch
2bec15ee40
Allow configuring IPv6 address for upstream proxy (#492)
* Added support to configure IPv6 upstream proxy servers using bracket syntax.
* Added regular expression for IPv6 scope identifier to re for IPv6 address.
2023-05-23 14:04:48 +01:00
rofl0r
ef60434b39 docs: typo fix
closes #487
2023-03-20 20:36:01 +00:00
rofl0r
31339cb161 tinyproxy.conf.5: update text for bind directive
the existing text was sort of misleading as it was written in a
pre-HTTPS era.

addressing #475
2023-02-01 15:46:58 +00:00
rofl0r
470cc0863d conf: fix potential crash with invalid input data
closes #474
2023-02-01 13:32:45 +00:00
rofl0r
6ffd9af2c7 hsearch: fix potential UB (pointer arithmetics on nullptr)
closes #471
addresses #470
2023-02-01 13:16:58 +00:00
rofl0r
3764b85514 prevent junk from showing up in error page in invalid requests
fixes #457
2022-09-08 15:18:04 +00:00
rofl0r
84f203fb1c fix reversepath directive using https url giving misleading error
it's not possible to use a https url in a ReversePath directive, without
removing the security provided by https, and would require adding a
dependency on a TLS library like openssl and a lot of code complexity
to fetch the requested resource via https and relay it back to the client.

in case the reversepath directive kicked in, but the protocol wasn't
recognized, and support for transparent proxying built-in, the code
wrongfully tried to turn the request into a trans request, leading
to a bogus rewritten url like http://localhost:8888https://www.endpoint.com
and an error message that we're trying to connect to the machine the
proxy runs on.

now instead use the generic code that signals an invalid protocol/url
was used.

closes #419
2022-08-20 14:43:24 +00:00
rofl0r
121be4a74e echo http protocol version on CONNECT request response
while at it, the function doing it was renamed from the misleading
ssl name to what it actually does.
also inlined the strings that were previously defined as macros.

addressing #152
2022-07-15 23:43:13 +00:00
rofl0r
8b373f804e update html documentation, add quickstart section 2022-05-30 19:10:27 +00:00
rofl0r
90adf28663 release 1.11.1 2022-05-27 14:08:03 +00:00
rofl0r
ea75e79609 CI: add github workflow to build release tarball 2022-05-27 14:06:16 +00:00
rofl0r
d6ee3835f0 configure: use release tarball provided gperf file 2022-05-27 14:06:16 +00:00
rofl0r
d9e38babb7 add gperf generated files to make dist 2022-05-27 14:06:16 +00:00
rofl0r
77cd87efef print error message if errorfile is configured but can't be opened
also, don't use fopen(NULL) if usage of errorfiles is not configured.
2022-05-27 12:48:41 +00:00
rofl0r
7d1e86ccae don't try to send 408 error to closed client socket
read_request_line() is exercised on the client's fd, and it fails
when the client closed the connection. therefore it's wrong
to send an error message to the client in this situation.
additionally, the error message states that the server closed
the connection.

might fix #383
2022-05-02 14:50:42 +00:00
rofl0r
235b1c10a7 implement filtertype keyword and fnmatch-based filtering
as suggested in #212, it seems the majority of people don't understand
that input was expected to be in regex format and people were using
filter lists containing plain hostnames, e.g. `www.google.com`.

apart from that, using fnmatch() for matching is actually a lot less
computationally expensive and allows to use big blacklists without
incurring a huge performance hit.

the config file now understands a new option `FilterType` which can
be one of `bre`, `ere` and `fnmatch`.
The `FilterExtended` option was deprecated in favor of it.
It still works, but will be removed in the release after the next.
2022-05-02 13:13:40 +00:00
Tristan Stenner
26db3f6cc9 Allow "XTinyProxy No" with xtinyproxy disabled 2022-05-02 08:32:54 +01:00
dr|z3d
14d31ed63f Prettify stats.html 2022-04-22 06:13:38 +00:00
rofl0r
c63028d675 bind_socket(): improve log output
bind_socket most often fails due to having wrong address family,
so it's helpful to see which one was used.
2022-03-01 15:17:32 +00:00
rofl0r
9718be09c1 reload_logging: fix returning possibly uninitialized value
introduced in 17d3733be3

may fix #422
2022-02-16 02:28:11 +00:00
Malte S. Stretz
479df8ecec Add test case for unavailable upstream 2022-02-13 21:46:03 +00:00
Malte S. Stretz
1576ee279f Return 5xx when upstream is unreachable
Currently a 404 is returned for a misconfigured or unavailable upstream
server.  Since that's a server error it should be a 5xx instead; a 404
is confusing when used as a forward proxy and might even be harmful when
used as a reverse proxy.

It is debatable if another 5xx code might be better; the misconfigured
situation might better be a 500 whereas the connection issue could be
a 503 instead (as used eg. in haproxy).
2022-02-13 21:46:03 +00:00
rofl0r
eced6822f8 properly deal with client sending chunked data
this fixes OPTIONS requests sent from apache SVN client using their
native HTTP proxy support.

closes #421

tested with `svn info http://svnmir.bme.freebsd.org/ports/`
2022-02-13 21:11:37 +00:00
rofl0r
17d3733be3 main: fix logging being disabled after reload conf fails
fixes #417
2022-01-26 12:13:22 +00:00
rofl0r
79d0b0fa79 fix timeout not being applied to outgoing connections
the fix in 0b9a74c290 was incomplete, as it
applied the socket timeout only to the socket received from accept(), but
not to sockets created for outgoing connections.
2022-01-20 20:25:42 +00:00
rofl0r
d3d8943fe4 update website footer re: freenode 2021-09-22 12:14:58 +00:00
rofl0r
f0c291e1da README.md: freenode is dead, long live libera
since freenode.net doesn't even serve a website anymore, it's safe to
finally announce the switch.
2021-09-22 12:07:33 +00:00
Aaron M. Ucko
207f790314 Fix FTBFS on hurd-i386: run_tests.sh duplicates build path. 2021-09-22 13:05:12 +01:00
rofl0r
c1023f6821 fix regression failing to parse dotted netmask in upstream
introduced in 979c737f9b.
when refactoring the "site-spec" parsing code i failed to realize that
the code dealing with acl allow/deny directives didn't provide the
option to specify netmasks in dotted ipv4 notation, unlike the code
in the upstream parser. since both scenarios now use the same parsing,
both dotted notation and CIDR slash-notation are possible.

while at it, removed the len parameter from fill_netmask_array() which
provided the illusion the array length could be of variable size.

fixes #394
2021-08-23 14:00:05 +00:00
rofl0r
39d7bf6c70 improve error message for "Error reading readable client_fd"
maybe this helps to track down the cause of #383.
2021-07-23 20:17:18 +01:00
rofl0r
e91e48dd60
add an issue template (#387)
as it's unproductive to be getting the same bug report for old tinyproxy versions over and over, and people not even stating which version they're using, this new issue template makes people
aware of what information to include when filing an issue request.
2021-07-21 14:29:20 +01:00
rofl0r
563978a3ea socks4 upstream: add safety check for hostname length 2021-06-25 02:55:22 +01:00
rofl0r
7ea9f80d3f fix segfault in socks4 upstream with unresolvable hostname
using a socks4 tor upstream with an .onion url resulted in
gethostbyname() returning NULL and a subsequent segfault.
not only did the code not check the return value of gethostbyname(),
that resolver API itself isn't threadsafe.

as pure SOCKS4 supports only IPv4 addresses, and the main SOCKS4
user to this date is tor, we just use SOCKS4a unconditionally and
pass the hostname to the proxy without trying to do any local name
resolving.

i suspect in 2021 almost all SOCKS4 proxy servers in existence use
SOCKS4a extension, but should i be wrong on this, i prefer issue
reports to show up and implement plain SOCKS4 fallback only when
i see it is actually used in practice.
2021-06-25 02:43:00 +01:00
rofl0r
bc81b4d9e8 put an end to LINE_MAX issues
for some reason, getting this macro is really hard across platforms,
requiring either different feature test macros or even the right order
of included headers, and its usage caused several build failures in the
past. fix it once and for all by just using 1024 as max line length if
the macro can't be retrieved.

closes #382
2021-06-24 22:55:33 +01:00
rofl0r
558e3f748d configure.ac: remove unneeded "defs" linker flag
the flag was added in 753010f571 without
explanation, and according to my research it is used to make the linker
report undefined symbols when linking a shared library.
since we don't build any shared libs, this isn't needed at all, but
reportedly causes issues with cygwin (#382).
2021-06-23 16:59:55 +01:00
Alex Wied
7168a42624 Include limits.h to fix build on OSX 2021-05-13 01:15:13 +01:00
Michael Adam
60bf8b9e4d github actions: don't run "make test" on macos it currenctly fails
Signed-off-by: Michael Adam <obnox@samba.org>
2021-05-13 01:02:43 +01:00
Michael Adam
2db9a2a00f github actions: add macos tests
Signed-off-by: Michael Adam <obnox@samba.org>
2021-05-13 01:38:10 +02:00
Michael Adam
132a55cefb Fix github actions
Signed-off-by: Michael Adam <obnox@samba.org>
2021-05-12 22:50:15 +02:00
Michael Adam
e81d7767d3 Add github actions workflow for new CI
Signed-off-by: Michael Adam <obnox@samba.org>
2021-05-12 22:39:30 +02:00
rofl0r
7af5b1a553 manpage: improve FilterDefaultDeny paragraph 2021-05-10 00:25:50 +01:00
rofl0r
aeb7b19c53 conf: do not warn about missing user directive unless root
there's no point in printing a warning if the program is already started
as a restricted user.
2021-05-10 00:03:43 +01:00
rofl0r
9d815f69a4 filter: hard error when filter file doesn't exist 2021-05-09 23:41:49 +01:00
rofl0r
cc47fbf1f7 manpage: URL-based filtering is no longer recommended 2021-05-09 17:53:52 +01:00
rofl0r
a869e71ac3 add support for outgoing connections with HTTP/1.1
since there are numerous changes in HTTP/1.1, the proxyserver will
stick to using HTTP/1.0 for internal usage, however when a connection
is requested with HTTP/1.x from now on we will duplicate the minor revision
the client requested, because apparently some servers refuse to accept
HTTP/1.0

addresses #152.
2021-04-16 14:51:01 +01:00
rofl0r
979c737f9b make upstream site-spec ipv6 compatible, refactor acl code
the acl.c code parsing a site-spec has been factored out into a
new TU: hostspec. it was superior to the parsing code in
upstream.c in that it properly deals with both ipv4 and ipv6.

both upstream and acl now use the new code for parsing, and upstream
also for checking for a match.
acl.c still uses the old matching code as it has a lot of special case
code for specifications containing a hostname, and in case such
a spec is encountered, tries to do reverse name lookup to see if
a numeric ip matches that spec.

removing that code could break existing usecases, however since
that was never implemented for upstream nobody will miss it there.
2021-04-16 14:46:02 +01:00
rofl0r
2529597ea0 reverse: redirect if path without trailing slash is detected
if for example:

ReversePath = "/foo/"

and user requests "http://tinyproxy/foo" the common behaviour for HTTP
servers is to send a http 301 redirect to the correct url.
we now do the same.
2021-04-16 14:41:40 +01:00
rofl0r
bc87de3482 Release 1.11.0 2021-04-16 13:47:21 +01:00
rofl0r
11a4f6c5cf reverse: ensure paths always end with a slash 2021-03-28 20:36:55 +01:00
rofl0r
64badd6b37 htab: prevent filling up of table with tombstones
as pointed out by @craigbarnes [0], using the latest fix for
the tombstone issue, it's possible to provoke a situation
that causes an endless loop when all free slots in the table
are filled up with tombstones and htab_find() is called.

therefore we need to account for those as well when deciding
if there's a need to call resize() so there's never more than
75% of the table used by either dead or live items.
the resize() serves as a rehash which gets rid of all deleted
entries, and it might cause the table size to shrink if
htab_insert() is called after a lot of items have been removed.

[0]: https://github.com/rofl0r/htab/issues/1#issuecomment-800094442

testcase:

    #include <assert.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include "hsearch.h"

    #define HTAB_OOM_TEST
    #include "hsearch.c"

    static char *xstrdup(const char *str)
    {
        char *dup = strdup(str);
        assert(dup);
        return dup;
    }

    void utoa(unsigned number, char* buffer) {
            int lentest, len = 0, i, start = 0;

            lentest = number;
            do {
                    len++;
                    lentest /= 10;
            } while(lentest);
            buffer[start+len] = 0;
            do {
                    i = number % 10;
                    buffer[start+len - 1] = '0' + i;
                    number -= i;
                    len -= 1;
                    number /= 10;
            } while (number);
    }

    #define TESTSIZE 8
    #define KEEP 1

    static char* notorious[TESTSIZE];

    static void prep() {
    	srand(0);
    	char buf[16];
    	size_t filled = 0;
    	while(filled < TESTSIZE) {
    		utoa(rand(), buf);
    		size_t idx = keyhash(buf) & (TESTSIZE-1);
    		if(!notorious[idx]) {
    			notorious[idx] = xstrdup(buf);
    			++filled;
    		}
    	}
    }

    int main(void)
    {
    	struct htab *h = htab_create(TESTSIZE);
    	size_t i;
    	assert(h);

    	prep();
    	for(i=0; i<TESTSIZE; ++i) {
    		char *key = notorious[i];
    		printf("[%zu] = \"%s\"\n", i, key);
    		int r = htab_insert(h, key, HTV_N(42));
    		if(!r == 1) {
    			printf("element %zu couldn't be inserted\n", i);
    			break;
    		}
    		assert(r == 1);
    		// Ensure newly inserted entry can be found
    		assert(htab_find(h, key));
    		if(i >= KEEP) htab_delete(h, key);
    	}

    	htab_find(h, "looooop");

    	return 0;
    }
2021-03-28 20:33:17 +01:00
rofl0r
48860bbe26 refactor html-error so send_http_headers() can take extra arg
we already required an extra argument inside the headers sent
for 401 and 407 error responses, move those to sent_http_error_message()
and refactor send_http_headers() to always take the extra argument.
in calling sites where the extra arg isn't needed, use "".
2021-03-28 20:24:23 +01:00
rofl0r
c4231e58bf orderedmap: fix memory leak when using orderedmap_remove()
closes #351
2021-03-14 16:06:10 +00:00
rofl0r
38934921c4 htab_delete(): fix failure to set tombstone
we can't just set an item's key to zero and be done with a deletion,
because this will break the item search chain.
a deleted item requires a special marker, also known as tombstone.
when searching for an item, all slots with a tombstone need to treated
as if they were in use, but when inserting an item such a slot needs
to be filled with the new item.

a common procedure is to rehash the table when the number of deleted
items crosses a certain threshold, though for simplicity we leave this
task to the resize() function which does the same thing anyway when
the hashtable grows.

this allows to fix the issue quite elegantly and with almost no
additional overhead, so we don't penalize applications that do very
few deletions.
2021-03-14 01:57:21 +00:00
rofl0r
4147e917d6 configure: check whether gperf is compatible
closes #337
2021-02-13 09:53:55 +00:00
rofl0r
adad565c03 http-message: fix UB passing long to format string expecting int 2020-10-19 20:33:04 +01:00
rofl0r
db5c0e99b4 reqs: fix UB passing ssize_t to format string expecting int 2020-10-19 20:30:10 +01:00
rofl0r
8ebbd50cb2 log: replace non-mt-safe localtime() with localtime_r() 2020-10-19 20:26:12 +01:00
rofl0r
732bdd0f56 replace usage of non-threadsafe gmtime() with gmtime_r()
the latter is a standard POSIX function too.
2020-10-19 20:21:26 +01:00
Anton Khirnov
3bb14e0440 Allow multiple Bind directives.
Try all the addresses specified with Bind in order. This is necessary
e.g. for maintaining IPv4+6 connectivity while still being restricted to
one interface.
2020-10-19 20:08:31 +01:00
Anton Khirnov
2b49ef0e0f sock: add missing format specifier to log_message() 2020-10-19 20:08:31 +01:00
rofl0r
f7c616d2b9 log.c: fix format string args 2020-10-19 20:08:31 +01:00
rofl0r
cc0a7eb9a2 html-error: move common.h inclusion back to top
this seems to cause an implicit declaration of snprintf() thanks to
feature test macro hell.
2020-10-19 20:08:31 +01:00
50 changed files with 1144 additions and 580 deletions

View File

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

36
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,36 @@
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
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

40
.github/workflows/release_tarball.yml vendored Normal file
View File

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

View File

@ -89,4 +89,4 @@ and create a [pull request](https://github.com/tinyproxy/tinyproxy/pulls).
You can meet developers and users to discuss development, You can meet developers and users to discuss development,
patches and deployment issues in the `#tinyproxy` IRC channel on patches and deployment issues in the `#tinyproxy` IRC channel on
Freenode (`irc.freenode.net`). libera (`irc.libera.chat`).

28
SECURITY.md Normal file
View File

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

View File

@ -1 +1 @@
1.11.0-rc1 1.11.2

View File

@ -170,20 +170,12 @@ if test x"$debug_enabled" != x"yes" ; then
CFLAGS="-DNDEBUG $CFLAGS" CFLAGS="-DNDEBUG $CFLAGS"
fi 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
dnl Substitute the variables into the various Makefiles dnl Substitute the variables into the various Makefiles
dnl 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(CFLAGS)
AC_SUBST(LDFLAGS) AC_SUBST(LDFLAGS)
AC_SUBST(CPPFLAGS) AC_SUBST(CPPFLAGS)
@ -205,13 +197,21 @@ fi #manpage_support_enabled
AM_CONDITIONAL(HAVE_POD2MAN, test "x$POD2MAN" != "x" -a "x$POD2MAN" != "xno") AM_CONDITIONAL(HAVE_POD2MAN, test "x$POD2MAN" != "x" -a "x$POD2MAN" != "xno")
AC_PATH_PROG(GPERF, gperf, no) AC_PATH_PROG(GPERF, gperf, no)
AM_CONDITIONAL(HAVE_GPERF, test "x$GPERF" != "x" -a "x$GPERF" != "xno")
AH_TEMPLATE([HAVE_GPERF], AH_TEMPLATE([HAVE_GPERF],
[Whether you have gperf installed for faster config parsing.]) [Whether you have gperf installed for faster config parsing.])
tmp_gperf=false
if test "x$GPERF" != "x" -a "x$GPERF" != "xno" ; then 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) AC_DEFINE(HAVE_GPERF)
tmp_gperf=true
else
AS_ECHO("no")
fi
fi fi
AM_CONDITIONAL(HAVE_GPERF, $tmp_gperf)
AC_CONFIG_FILES([ AC_CONFIG_FILES([
Makefile Makefile
@ -223,7 +223,6 @@ docs/Makefile
docs/man5/Makefile docs/man5/Makefile
docs/man5/tinyproxy.conf.txt docs/man5/tinyproxy.conf.txt
docs/man8/Makefile docs/man8/Makefile
docs/man8/tinyproxy.txt
m4macros/Makefile m4macros/Makefile
tests/Makefile tests/Makefile
tests/scripts/Makefile tests/scripts/Makefile
@ -248,3 +247,7 @@ if test "x$POD2MAN" = "xno" ; then
touch docs/man8/tinyproxy.8 touch docs/man8/tinyproxy.8
fi fi
fi fi
if test "x$HAVE_GPERF" = "xno" && test -e src/conf-tokens-gperf.inc ; then
touch src/conf-tokens-gperf.inc
fi

View File

@ -30,9 +30,6 @@
<dt>clienthost</dt> <dt>clienthost</dt>
<dd>{clienthost}</dd> <dd>{clienthost}</dd>
<dt>version</dt>
<dd>{version}</dd>
<dt>package</dt> <dt>package</dt>
<dd>{package}</dd> <dd>{package}</dd>
@ -49,7 +46,7 @@
<hr /> <hr />
<p><em>Generated by <a href="{website}">{package}</a> version {version}.</em></p> <p><em>Generated by <a href="{website}">{package}</a>.</em></p>
</body> </body>

View File

@ -16,7 +16,7 @@
<hr /> <hr />
<p><em>Generated by <a href="{website}">{package}</a> version {version}.</em></p> <p><em>Generated by <a href="{website}">{package}</a>.</em></p>
</body> </body>

View File

@ -1,69 +1,95 @@
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <html lang="en">
<head>
<title>Stats [{package}]</title>
<meta charset="UTF-8" />
<style type="text/css">
body {
color: #eee;
background: #110d0d;
text-align: center;
font: 12pt/1.6 Open Sans, Segoe UI, sans-serif;
}
#container {
position: absolute;
top: 0;
left: 0;
margin: 0;
width: 100%;
height: 100%;
display: table;
}
#inner {
width: 100%;
display: table-cell;
vertical-align: middle;
}
table {
width: auto;
margin: auto;
height: auto;
background: #222020;
border: 1px solid #777373;
border-spacing: 3px;
}
th,
td {
padding: 6px 18px;
}
th {
font-weight: 700;
background: linear-gradient(to bottom, #777373, #555151);
}
.odd {
background: #444040;
}
.even {
background: #555151;
}
.center {
text-align: center;
}
.right {
text-align: right;
font-weight: 600;
}
</style>
</head>
<head> <body>
<title>{package} version {version} run-time statistics</title> <div id="container">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <div id="inner">
<table>
<tr>
<th colspan="2">{package} statistics</th>
</tr>
<tr class="odd">
<td class="right">Open connections</td>
<td class="center">{opens}</td>
</tr>
<style type="text/css" media="screen"> <tr class="even">
<!--/*--><![CDATA[<!--*/ <td class="right">Bad connections</td>
<td class="center">{badconns}</td>
</tr>
th, td <tr class="odd">
{ <td class="right">Denied connections</td>
text-align: left; <td class="center">{deniedconns}</td>
padding: 0.5em; </tr>
border: 1px solid gray;
}
/*]]>*/--> <tr class="even">
</style> <td class="right">Refused (high load)</td>
<td class="center">{refusedconns}</td>
</head> </tr>
<body>
<h1>{package} version {version} run-time statistics</h1>
<table>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
<tr>
<td>Number of open connections</td>
<td>{opens}</td>
</tr>
<tr>
<td>Number of requests</td>
<td>{reqs}</td>
</tr>
<tr>
<td>Number of bad connections</td>
<td>{badconns}</td>
</tr>
<tr>
<td>Number of denied connections</td>
<td>{deniedconns}</td>
</tr>
<tr>
<td>Number of refused connections due to high load</td>
<td>{refusedconns}</td>
</tr>
</table>
<hr />
<p><em>Generated by <a href="{website}">{package}</a> version {version}.</em></p>
</body>
<tr class="odd">
<td class="right">Total requests</td>
<td class="center">{reqs}</td>
</tr>
</table>
</div>
</div>
</body>
</html> </html>

View File

@ -22,8 +22,8 @@ configuration file.
The Tinyproxy configuration file contains key-value pairs, one per The Tinyproxy configuration file contains key-value pairs, one per
line. Lines starting with `#` and empty lines are comments and are line. Lines starting with `#` and empty lines are comments and are
ignored. Keywords are case-insensitive, whereas values are ignored. Keywords are case-insensitive, whereas values are
case-sensitive. Values may be enclosed in double-quotes (") if they case-sensitive. Some string values must be enclosed in double
contain spaces. quotes (") as noted below.
The possible keywords and their descriptions are as follows: The possible keywords and their descriptions are as follows:
@ -57,7 +57,9 @@ only on one specific address.
=item B<Bind> =item B<Bind>
This allows you to specify which address Tinyproxy will bind This allows you to specify which address Tinyproxy will bind
to for outgoing connections to web servers or upstream proxies. to for outgoing connections.
This parameter may be specified multiple times, then Tinyproxy
will try all the specified addresses in order.
=item B<BindSame> =item B<BindSame>
@ -74,29 +76,29 @@ allowed to have before it is closed by Tinyproxy.
This parameter controls which HTML file Tinyproxy returns when a This parameter controls which HTML file Tinyproxy returns when a
given HTTP error occurs. It takes two arguments, the error number given HTTP error occurs. It takes two arguments, the error number
and the location of the HTML error file. and the location of the HTML error file. Enclose the file location
in double quotes.
=item B<DefaultErrorFile> =item B<DefaultErrorFile>
This parameter controls the HTML template file returned when an The HTML template file returned when an error occurs for which no
error occurs for which no specific error file has been set. specific error file has been set. Enclose in double quotes.
=item B<StatHost> =item B<StatHost>
This configures the host name or IP address that is treated The host name or IP address that is treated as the `stat host`.
as the `stat host`: Whenever a request for this host is received, Enclose in double quotes. Whenever Tinyproxy receives a request for
Tinyproxy will return an internal statistics page instead of the `stat host` it returns an internal statistics page instead of
forwarding the request to that host. The template for this forwarding the request to that host. The template for this page can be
page can be configured with the `StatFile` configuration option. configured with the `StatFile` configuration option. The default value
The default value of `StatHost` is `@TINYPROXY_STATHOST@`. of `StatHost` is `@TINYPROXY_STATHOST@`.
=item B<StatFile> =item B<StatFile>
This configures the HTML file that Tinyproxy sends when The HTML file that Tinyproxy sends in response to a request for the
a request for the stathost is received. If this parameter is `stat host`. Enclose in double quotes. If this parameter is not set,
not set, Tinyproxy returns a hard-coded basic statistics page. Tinyproxy returns a hard-coded basic statistics page. See the STATHOST
See the STATHOST section in the L<tinyproxy(8)> manual page section in the L<tinyproxy(8)> manual page for details.
for details.
Note that the StatFile and the error files configured with ErrorFile Note that the StatFile and the error files configured with ErrorFile
and DefaultErrorFile are template files that can contain a few and DefaultErrorFile are template files that can contain a few
@ -107,9 +109,9 @@ manual page contains a description of all template variables.
=item B<LogFile> =item B<LogFile>
This controls the location of the file to which Tinyproxy The location of the file to which Tinyproxy writes its debug output.
writes its debug output. Alternatively, Tinyproxy can log Enclose in double quotes. Alternatively, Tinyproxy can log to syslog
to syslog -- see the Syslog option. -- see the Syslog option.
=item B<Syslog> =item B<Syslog>
@ -142,8 +144,8 @@ and below would be suppressed. Allowed values are:
=item B<PidFile> =item B<PidFile>
This option controls the location of the file where the main The location of the file where the main Tinyproxy process stores its
Tinyproxy process stores its process ID for signaling purposes. process ID for signaling purposes. Enclose in double quotes.
=item B<XTinyproxy> =item B<XTinyproxy>
@ -171,12 +173,20 @@ turns on the upstream proxy for the sites matching `site_spec`.
`type` can be one of `http`, `socks4`, `socks5`, `none`. `type` can be one of `http`, `socks4`, `socks5`, `none`.
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.
=item * I<upstream none "site_spec"> =item * I<upstream none "site_spec">
turns off upstream support for sites matching `site_spec`, that means the turns off upstream support for sites matching `site_spec`, that means the
connection is done directly. connection is done directly.
=back =back
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.
The site can be specified in various forms as a hostname, domain The site can be specified in various forms as a hostname, domain
name or as an IP range: name or as an IP range:
@ -233,6 +243,14 @@ access is only granted for authenticated users.
BasicAuth user password BasicAuth user password
=item B<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.
=item B<AddHeader> =item B<AddHeader>
Configure one or more HTTP request headers to be added to outgoing Configure one or more HTTP request headers to be added to outgoing
@ -248,7 +266,8 @@ RFC 2616 requires proxies to add a `Via` header to the HTTP
requests, but using the real host name can be a security requests, but using the real host name can be a security
concern. If the `ViaProxyname` option is present, then its concern. If the `ViaProxyname` option is present, then its
string value will be used as the host name in the Via header. string value will be used as the host name in the Via header.
Otherwise, the server's host name will be used. Otherwise, the server's host name will be used. Enclose in double
quotes.
=item B<DisableViaHeader> =item B<DisableViaHeader>
@ -265,7 +284,7 @@ domains. This option specifies the location of the file
containing the filter rules, one rule per line. containing the filter rules, one rule per line.
Rules are specified as POSIX basic regular expressions (BRE), unless Rules are specified as POSIX basic regular expressions (BRE), unless
FilterExtended is activated. another FilterType is specified.
Comment lines start with a `#` character. Comment lines start with a `#` character.
Example filter file contents: Example filter file contents:
@ -285,14 +304,34 @@ Example filter file contents:
# filter any domain that starts with adserver # filter any domain that starts with adserver
^adserver ^adserver
=item B<FilterType>
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<FilterURLs> =item B<FilterURLs>
If this boolean option is set to `Yes` or `On`, filtering is If this boolean option is set to `Yes` or `On`, filtering is
performed for URLs rather than for domains. The default is to performed for URLs rather than for domains. The default is to
filter based on domains. 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<FilterExtended> =item B<FilterExtended>
Deprecated. Use `FilterType ere` instead.
If this boolean option is set to `Yes`, then extended POSIX If this boolean option is set to `Yes`, then extended POSIX
regular expressions are used for matching the filter rules. regular expressions are used for matching the filter rules.
The default is to use basic POSIX regular expressions. The default is to use basic POSIX regular expressions.
@ -301,7 +340,11 @@ The default is to use basic POSIX regular expressions.
If this boolean option is set to `Yes`, then the filter rules If this boolean option is set to `Yes`, then the filter rules
are matched in a case sensitive manner. The default is to are matched in a case sensitive manner. The default is to
match case-insensitively. 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<FilterDefaultDeny> =item B<FilterDefaultDeny>
@ -309,6 +352,8 @@ The default filtering policy is to allow everything that is
not matched by a filtering rule. Setting `FilterDefaultDeny` not matched by a filtering rule. Setting `FilterDefaultDeny`
to `Yes` changes the policy do deny everything but the domains to `Yes` changes the policy do deny everything but the domains
or URLs matched by the filtering rules. 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<Anonymous> =item B<Anonymous>
@ -316,7 +361,7 @@ If an `Anonymous` keyword is present, then anonymous proxying
is enabled. The headers listed with `Anonymous` are allowed is enabled. The headers listed with `Anonymous` are allowed
through, while all others are denied. If no Anonymous keyword through, while all others are denied. If no Anonymous keyword
is present, then all headers are allowed through. You must is present, then all headers are allowed through. You must
include quotes around the headers. include double quotes around the headers.
Most sites require cookies to be enabled for them to work correctly, so 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. you will need to allow cookies through if you access those sites.
@ -387,7 +432,7 @@ This manpage was written by the Tinyproxy project team.
=head1 COPYRIGHT =head1 COPYRIGHT
Copyright (c) 1998-2020 the Tinyproxy authors. Copyright (c) 1998-2024 the Tinyproxy authors.
This program is distributed under the terms of the GNU General Public This program is distributed under the terms of the GNU General Public
License version 2 or above. See the COPYING file for additional License version 2 or above. See the COPYING file for additional

View File

@ -9,6 +9,17 @@ M_NAME=TINYPROXY
man_MANS = \ man_MANS = \
$(MAN8_FILES:.txt=.8) $(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: .txt.8:
if HAVE_POD2MAN if HAVE_POD2MAN
$(AM_V_GEN) $(POD2MAN) --center="Tinyproxy manual" \ $(AM_V_GEN) $(POD2MAN) --center="Tinyproxy manual" \

View File

@ -156,7 +156,11 @@ configuration variable `StatFile`.
=head1 FILES =head1 FILES
`/etc/tinyproxy/tinyproxy.conf`, `/var/run/tinyproxy/tinyproxy.pid`, `/var/log/tinyproxy/tinyproxy.log` F<@sysconfdir@/tinyproxy/tinyproxy.conf>
F<@runstatedir@/tinyproxy/tinyproxy.pid>
F<@localstatedir@/log/tinyproxy/tinyproxy.log>
=head1 BUGS =head1 BUGS

View File

@ -3,7 +3,7 @@
<ul> <ul>
<li>Feel free to report a new bug or suggest features via github issues.</li> <li>Feel free to report a new bug or suggest features via github issues.</li>
<li>Tinyproxy developers hang out in #tinyproxy on irc.freenode.net.</li> <li>Tinyproxy developers hang out in #tinyproxy on irc.libera.chat.</li>
</ul> </ul>
</section> </section>
</div> </div>

View File

@ -41,25 +41,27 @@
<p>Tinyproxy requires only a <strong>minimal POSIX environment</strong> to build and operate. It can use additional libraries to add functionality though.</p> <p>Tinyproxy requires only a <strong>minimal POSIX environment</strong> to build and operate. It can use additional libraries to add functionality though.</p>
<p>Tinyproxy allows forwarding of <strong>HTTPS connections</strong> without modifying traffic in any way through the <code>CONNECT</code> method (see the <code>ConnectPort</code> directive).</p> <p>Tinyproxy allows forwarding of <strong>HTTPS connections</strong> without modifying traffic in any way through the <code>CONNECT</code> method (see the <code>ConnectPort</code> directive, which you should disable, unless you want to restrict the users).</p>
<p>Tinyproxy supports being configured as a <strong>transparent proxy</strong>, so that a proxy can be used without requiring any client-side configuration. You can also use it as a <strong>reverse proxy</strong> front-end to your websites.</p> <p>Tinyproxy supports being configured as a <strong>transparent proxy</strong>, so that a proxy can be used without requiring any client-side configuration. You can also use it as a <strong>reverse proxy</strong> front-end to your websites.</p>
<p>Using the <code>AddHeader</code> directive, you can <strong>add/insert HTTP headers</strong> to outgoing traffic.</p> <p>Using the <code>AddHeader</code> directive, you can <strong>add/insert HTTP headers</strong> to outgoing traffic (HTTP only).</p>
<p>If you're looking to build a custom web proxy, Tinyproxy is <strong>easy to modify</strong> 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.</p> <p>If you're looking to build a custom web proxy, Tinyproxy is <strong>easy to modify</strong> 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.</p>
<p>Tinyproxy has <strong>privacy features</strong> 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).</p> <p>Tinyproxy has <strong>privacy features</strong> 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.</p>
<p>Using the <strong>remote monitoring</strong> facility, you can access proxy statistics from afar, letting you know exactly how busy the proxy is.</p> <p>Using the <strong>remote monitoring</strong> facility, you can access proxy statistics from afar, letting you know exactly how busy the proxy is.</p>
<p>You can configure Tinyproxy to <strong>control access</strong> 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.</p> <p>You can configure Tinyproxy to <strong>control access</strong> 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.</p>
<p>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. Furthermore, it was designed with an eye towards preventing buffer overflows. The simplicity of the code ensures it remains easy to spot such bugs.</p> <p>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 <b>recommended</b> 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.</p>
<h2> <h2>
<a id="downloads" class="anchor" href="#downloads" aria-hidden="true"><span class="octicon octicon-link"></span></a>Downloads</h2> <a id="downloads" class="anchor" href="#downloads" aria-hidden="true"><span class="octicon octicon-link"></span></a>Downloads</h2>
<p>Note that many distributions ship horribly outdated versions of tinyproxy, therefore it is recommended to compile it from source.</p>
<ul> <ul>
<li>On Red Hat Enterprise Linux, or its derivatives such as CentOS, install Tinyproxy from the EPEL repository by running yum install tinyproxy.</li> <li>On Red Hat Enterprise Linux, or its derivatives such as CentOS, install Tinyproxy from the EPEL repository by running yum install tinyproxy.</li>
<li>On Fedora, install Tinyproxy by running yum install tinyproxy.</li> <li>On Fedora, install Tinyproxy by running yum install tinyproxy.</li>
@ -70,7 +72,7 @@
<li>Mac OS X users can check MacPorts to see if the Tinyproxy port there is recent enough.</li> <li>Mac OS X users can check MacPorts to see if the Tinyproxy port there is recent enough.</li>
</ul> </ul>
<p>If you feel that the Tinyproxy binary package in your operating system is not recent, please contact the package maintainer for that particular operating system. If this fails, you can always compile the latest stable version from source code.</p> <p>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.</p>
<p>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 <a href="https://github.com/tinyproxy/tinyproxy/releases">releases page</a>. 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.</p> <p>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 <a href="https://github.com/tinyproxy/tinyproxy/releases">releases page</a>. 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.</p>
@ -78,5 +80,19 @@
<p>git clone <a href="https://github.com/tinyproxy/tinyproxy.git">https://github.com/tinyproxy/tinyproxy.git</a></p> <p>git clone <a href="https://github.com/tinyproxy/tinyproxy.git">https://github.com/tinyproxy/tinyproxy.git</a></p>
<h2>
<a id="quickstart" class="anchor" href="#quickstart" aria-hidden="true"><span class="octicon octicon-link"></span></a>Quickstart</h2>
<p>The quickest way to get started is using a minimal config file like the below:</p>
<pre><code>
Port 8888
Listen 127.0.0.1
Timeout 600
Allow 127.0.0.1
</code></pre>
<p>And then simply run <code>tinyproxy -d -c tinyproxy.conf</code> as your current user. This starts tinyproxy in foreground mode with <code>tinyproxy.conf</code> 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 <code>http_proxy=127.0.0.1:8888 curl example.com</code>.</p>
<h2> <h2>
<a id="documentation" class="anchor" href="#documentation" aria-hidden="true"><span class="octicon octicon-link"></span></a>Documentation</h2> <a id="documentation" class="anchor" href="#documentation" aria-hidden="true"><span class="octicon octicon-link"></span></a>Documentation</h2>

View File

@ -12,6 +12,7 @@ edit = sed \
-e 's|@datarootdir[@]|$(datarootdir)|g' \ -e 's|@datarootdir[@]|$(datarootdir)|g' \
-e 's|@pkgsysconfdir[@]|$(pkgsysconfdir)|g' \ -e 's|@pkgsysconfdir[@]|$(pkgsysconfdir)|g' \
-e 's|@localstatedir[@]|$(localstatedir)|g' \ -e 's|@localstatedir[@]|$(localstatedir)|g' \
-e 's|@runstatedir[@]|$(runstatedir)|g' \
-e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \ -e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \
-e 's|@prefix[@]|$(prefix)|g' \ -e 's|@prefix[@]|$(prefix)|g' \
-e 's|@TINYPROXY_STATHOST[@]|$(TINYPROXY_STATHOST)|g' -e 's|@TINYPROXY_STATHOST[@]|$(TINYPROXY_STATHOST)|g'

View File

@ -3,7 +3,7 @@
## ##
## This example tinyproxy.conf file contains example settings ## This example tinyproxy.conf file contains example settings
## with explanations in comments. For decriptions of all ## 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 # /usr/share/tinyproxy
# /etc/tinyproxy # /etc/tinyproxy
# #
#ErrorFile 404 "@pkgdatadir@/404.html"
#ErrorFile 400 "@pkgdatadir@/400.html" #ErrorFile 400 "@pkgdatadir@/400.html"
#ErrorFile 502 "@pkgdatadir@/502.html"
#ErrorFile 503 "@pkgdatadir@/503.html" #ErrorFile 503 "@pkgdatadir@/503.html"
#ErrorFile 403 "@pkgdatadir@/403.html" #ErrorFile 403 "@pkgdatadir@/403.html"
#ErrorFile 408 "@pkgdatadir@/408.html" #ErrorFile 408 "@pkgdatadir@/408.html"
@ -124,7 +124,7 @@ LogLevel Info
# can be used for signalling purposes. # can be used for signalling purposes.
# If not specified, no pidfile will be written. # 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 # XTinyproxy: Tell Tinyproxy to include the X-Tinyproxy header, which
@ -205,6 +205,13 @@ Allow ::1
# users. # users.
#BasicAuth user password #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 # AddHeader: Adds the specified headers to outgoing HTTP requests that
# Tinyproxy makes. Note that this option will not work for HTTPS # Tinyproxy makes. Note that this option will not work for HTTPS
@ -240,10 +247,9 @@ ViaProxyName "tinyproxy"
#FilterURLs On #FilterURLs On
# #
# FilterExtended: Use POSIX Extended regular expressions rather than # FilterType: Use bre (default), ere, or fnmatch for filtering.
# basic.
# #
#FilterExtended On #FilterType fnmatch
# #
# FilterCaseSensitive: Use case sensitive regular expressions. # FilterCaseSensitive: Use case sensitive regular expressions.
@ -321,6 +327,3 @@ ViaProxyName "tinyproxy"
# If not set then no rewriting occurs. # If not set then no rewriting occurs.
# #
#ReverseBaseURL "http://localhost:8888/" #ReverseBaseURL "http://localhost:8888/"

View File

@ -24,6 +24,7 @@ AM_CPPFLAGS = \
-DLOCALSTATEDIR=\"${localstatedir}\" -DLOCALSTATEDIR=\"${localstatedir}\"
tinyproxy_SOURCES = \ tinyproxy_SOURCES = \
hostspec.c hostspec.h \
acl.c acl.h \ acl.c acl.h \
anonymous.c anonymous.h \ anonymous.c anonymous.h \
buffer.c buffer.h \ buffer.c buffer.h \
@ -66,5 +67,5 @@ conf-tokens-gperf.inc: conf-tokens.gperf
$(GPERF) $< > $@ $(GPERF) $< > $@
endif endif
EXTRA_DIST = conf-tokens.gperf EXTRA_DIST = conf-tokens.gperf conf-tokens-gperf.inc

149
src/acl.c
View File

@ -29,16 +29,7 @@
#include "network.h" #include "network.h"
#include "sock.h" #include "sock.h"
#include "sblist.h" #include "sblist.h"
#include "hostspec.h"
#include <limits.h>
/* Define how long an IPv6 address is in bytes (128 bits, 16 bytes) */
#define IPV6_LEN 16
enum acl_type {
ACL_STRING,
ACL_NUMERIC
};
/* /*
* Hold the information about a particular access control. We store * Hold the information about a particular access control. We store
@ -47,66 +38,9 @@ enum acl_type {
*/ */
struct acl_s { struct acl_s {
acl_access_t access; acl_access_t access;
enum acl_type type; struct hostspec h;
union {
char *string;
struct {
unsigned char network[IPV6_LEN];
unsigned char mask[IPV6_LEN];
} ip;
} address;
}; };
/*
* 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. * If the access list has not been set up, create it.
@ -138,7 +72,6 @@ int
insert_acl (char *location, acl_access_t access_type, acl_list_t *access_list) insert_acl (char *location, acl_access_t access_type, acl_list_t *access_list)
{ {
struct acl_s acl; struct acl_s acl;
char *mask, ip_dst[IPV6_LEN];
assert (location != NULL); assert (location != NULL);
@ -150,55 +83,11 @@ insert_acl (char *location, acl_access_t access_type, acl_list_t *access_list)
*/ */
memset (&acl, 0, sizeof (struct acl_s)); memset (&acl, 0, sizeof (struct acl_s));
acl.access = access_type; acl.access = access_type;
if(hostspec_parse(location, &acl.h) || acl.h.type == HST_NONE)
if ((mask = strrchr(location, '/'))) return -1;
*(mask++) = 0;
/*
* 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);
if(!mask) memset (acl.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, &(acl.address.ip.mask[0]), IPV6_LEN)
< 0)
goto err;
for (i = 0; i < IPV6_LEN; i++)
acl.address.ip.network[i] = ip_dst[i] &
acl.address.ip.mask[i];
}
} else {
/* either bogus IP or hostname */
/* bogus ipv6 ? */
if (mask || strchr (location, ':'))
goto err;
/* In all likelihood a string */
acl.type = ACL_STRING;
acl.address.string = safestrdup (location);
if (!acl.address.string)
goto err;
}
if(!sblist_add(*access_list, &acl)) return -1; if(!sblist_add(*access_list, &acl)) return -1;
return 0; return 0;
err:;
/* restore mask for proper error message */
if(mask) *(--mask) = '/';
return -1;
} }
/* /*
@ -219,7 +108,7 @@ acl_string_processing (struct acl_s *acl, const char *ip_address,
size_t test_length, match_length; size_t test_length, match_length;
char ipbuf[512]; char ipbuf[512];
assert (acl && acl->type == ACL_STRING); assert (acl && acl->h.type == HST_STRING);
assert (ip_address && strlen (ip_address) > 0); assert (ip_address && strlen (ip_address) > 0);
/* /*
@ -227,11 +116,11 @@ acl_string_processing (struct acl_s *acl, const char *ip_address,
* do a string based test only; otherwise, we can do a reverse * do a string based test only; otherwise, we can do a reverse
* lookup test as well. * lookup test as well.
*/ */
if (acl->address.string[0] != '.') { if (acl->h.address.string[0] != '.') {
memset (&hints, 0, sizeof (struct addrinfo)); memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM; 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; goto STRING_TEST;
ressave = res; ressave = res;
@ -265,7 +154,7 @@ STRING_TEST:
} }
test_length = strlen (string_addr); test_length = strlen (string_addr);
match_length = strlen (acl->address.string); match_length = strlen (acl->h.address.string);
/* /*
* If the string length is shorter than AC string, return a -1 so * If the string length is shorter than AC string, return a -1 so
@ -276,7 +165,7 @@ STRING_TEST:
if (strcasecmp if (strcasecmp
(string_addr + (test_length - match_length), (string_addr + (test_length - match_length),
acl->address.string) == 0) { acl->h.address.string) == 0) {
if (acl->access == ACL_DENY) if (acl->access == ACL_DENY)
return 0; return 0;
else else
@ -300,11 +189,11 @@ static int check_numeric_acl (const struct acl_s *acl, uint8_t addr[IPV6_LEN])
uint8_t x, y; uint8_t x, y;
int i; int i;
assert (acl && acl->type == ACL_NUMERIC); assert (acl && acl->h.type == HST_NUMERIC);
for (i = 0; i != IPV6_LEN; ++i) { for (i = 0; i != IPV6_LEN; ++i) {
x = addr[i] & acl->address.ip.mask[i]; x = addr[i] & acl->h.address.ip.mask[i];
y = acl->address.ip.network[i]; y = acl->h.address.ip.network[i];
/* If x and y don't match, the IP addresses don't match */ /* If x and y don't match, the IP addresses don't match */
if (x != y) if (x != y)
@ -345,12 +234,12 @@ int check_acl (const char *ip, union sockaddr_union *addr, acl_list_t access_lis
for (i = 0; i < sblist_getsize (access_list); ++i) { for (i = 0; i < sblist_getsize (access_list); ++i) {
acl = sblist_get (access_list, i); acl = sblist_get (access_list, i);
switch (acl->type) { switch (acl->h.type) {
case ACL_STRING: case HST_STRING:
perm = acl_string_processing (acl, ip, addr, string_addr); perm = acl_string_processing (acl, ip, addr, string_addr);
break; break;
case ACL_NUMERIC: case HST_NUMERIC:
if (ip[0] == '\0') if (ip[0] == '\0')
continue; continue;
@ -358,6 +247,10 @@ int check_acl (const char *ip, union sockaddr_union *addr, acl_list_t access_lis
? check_numeric_acl (acl, numeric_addr) ? check_numeric_acl (acl, numeric_addr)
: -1; : -1;
break; break;
case HST_NONE:
perm = -1;
break;
} }
/* /*
@ -394,8 +287,8 @@ void flush_access_list (acl_list_t access_list)
*/ */
for (i = 0; i < sblist_getsize (access_list); ++i) { for (i = 0; i < sblist_getsize (access_list); ++i) {
acl = sblist_get (access_list, i); acl = sblist_get (access_list, i);
if (acl->type == ACL_STRING) { if (acl->h.type == HST_STRING) {
safefree (acl->address.string); safefree (acl->h.address.string);
} }
} }

View File

@ -81,7 +81,7 @@ void child_main_loop (void)
int connfd; int connfd;
union sockaddr_union cliaddr_storage; union sockaddr_union cliaddr_storage;
struct sockaddr *cliaddr = (void*) &cliaddr_storage; struct sockaddr *cliaddr = (void*) &cliaddr_storage;
socklen_t clilen = sizeof(cliaddr_storage); socklen_t clilen;
int nfds = sblist_getsize(listen_fds); int nfds = sblist_getsize(listen_fds);
pollfd_struct *fds = safecalloc(nfds, sizeof *fds); pollfd_struct *fds = safecalloc(nfds, sizeof *fds);
ssize_t i; ssize_t i;
@ -167,6 +167,7 @@ void child_main_loop (void)
* Continue handling this connection. * Continue handling this connection.
*/ */
clilen = sizeof(cliaddr_storage);
connfd = accept (listenfd, cliaddr, &clilen); connfd = accept (listenfd, cliaddr, &clilen);

View File

@ -68,6 +68,7 @@
# include <arpa/inet.h> # include <arpa/inet.h>
# include <grp.h> # include <grp.h>
# include <pwd.h> # include <pwd.h>
# include <limits.h>
/* rest - some oddball headers */ /* rest - some oddball headers */
#ifdef HAVE_VALUES_H #ifdef HAVE_VALUES_H

View File

@ -34,6 +34,7 @@ config_directive_find (register const char *str, register size_t len)
{"defaulterrorfile", CD_defaulterrorfile}, {"defaulterrorfile", CD_defaulterrorfile},
{"startservers", CD_startservers}, {"startservers", CD_startservers},
{"filtercasesensitive", CD_filtercasesensitive}, {"filtercasesensitive", CD_filtercasesensitive},
{"filtertype", CD_filtertype},
{"filterurls", CD_filterurls}, {"filterurls", CD_filterurls},
{"filter", CD_filter}, {"filter", CD_filter},
{"reversemagic", CD_reversemagic}, {"reversemagic", CD_reversemagic},
@ -56,6 +57,7 @@ config_directive_find (register const char *str, register size_t len)
{"connectport", CD_connectport}, {"connectport", CD_connectport},
{"logfile", CD_logfile}, {"logfile", CD_logfile},
{"basicauth", CD_basicauth}, {"basicauth", CD_basicauth},
{"basicauthrealm", CD_basicauthrealm},
{"addheader", CD_addheader}, {"addheader", CD_addheader},
{"maxrequestsperchild", CD_maxrequestsperchild} {"maxrequestsperchild", CD_maxrequestsperchild}
}; };

View File

@ -44,6 +44,7 @@ allow, CD_allow
deny, CD_deny deny, CD_deny
bind, CD_bind bind, CD_bind
basicauth, CD_basicauth basicauth, CD_basicauth
basicauthrealm, CD_basicauthrealm
errorfile, CD_errorfile errorfile, CD_errorfile
addheader, CD_addheader addheader, CD_addheader
filter, CD_filter filter, CD_filter
@ -51,6 +52,7 @@ filterurls, CD_filterurls
filterextended, CD_filterextended filterextended, CD_filterextended
filterdefaultdeny, CD_filterdefaultdeny filterdefaultdeny, CD_filterdefaultdeny
filtercasesensitive, CD_filtercasesensitive filtercasesensitive, CD_filtercasesensitive
filtertype, CD_filtertype
reversebaseurl, CD_reversebaseurl reversebaseurl, CD_reversebaseurl
reverseonly, CD_reverseonly reverseonly, CD_reverseonly
reversemagic, CD_reversemagic reversemagic, CD_reversemagic

View File

@ -29,10 +29,12 @@ CD_allow,
CD_deny, CD_deny,
CD_bind, CD_bind,
CD_basicauth, CD_basicauth,
CD_basicauthrealm,
CD_errorfile, CD_errorfile,
CD_addheader, CD_addheader,
CD_filter, CD_filter,
CD_filterurls, CD_filterurls,
CD_filtertype,
CD_filterextended, CD_filterextended,
CD_filterdefaultdeny, CD_filterdefaultdeny,
CD_filtercasesensitive, CD_filtercasesensitive,

View File

@ -40,6 +40,12 @@
#include "basicauth.h" #include "basicauth.h"
#include "conf-tokens.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 * The configuration directives are defined in the structure below. Each
* directive requires a regular expression to match against, and a * directive requires a regular expression to match against, and a
@ -60,9 +66,10 @@
#define PASSWORD "([^@]*)" #define PASSWORD "([^@]*)"
#define IP "((([0-9]{1,3})\\.){3}[0-9]{1,3})" #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 "(" \ #define IPV6 "(" \
"(([0-9a-f:]{2,39}))|" \ "([0-9a-f:]{2,39})" IPV6SCOPE "|" \
"(([0-9a-f:]{0,29}:" IP "))" \ "([0-9a-f:]{0,29}:" IP ")" IPV6SCOPE \
")" ")"
#define IPV6MASK "(" IPV6 "(/" DIGIT "+)?)" #define IPV6MASK "(" IPV6 "(/" DIGIT "+)?)"
@ -74,7 +81,7 @@
* number. Given the usual structure of the configuration file, sixteen * number. Given the usual structure of the configuration file, sixteen
* substring matches should be plenty. * substring matches should be plenty.
*/ */
#define RE_MAX_MATCHES 24 #define RE_MAX_MATCHES 33
#define CP_WARN(FMT, ...) \ #define CP_WARN(FMT, ...) \
log_message (LOG_WARNING, "line %lu: " FMT, lineno, __VA_ARGS__) log_message (LOG_WARNING, "line %lu: " FMT, lineno, __VA_ARGS__)
@ -115,6 +122,7 @@ static HANDLE_FUNC (handle_disabled_feature)
static HANDLE_FUNC (handle_allow); static HANDLE_FUNC (handle_allow);
static HANDLE_FUNC (handle_basicauth); static HANDLE_FUNC (handle_basicauth);
static HANDLE_FUNC (handle_basicauthrealm);
static HANDLE_FUNC (handle_anonymous); static HANDLE_FUNC (handle_anonymous);
static HANDLE_FUNC (handle_bind); static HANDLE_FUNC (handle_bind);
static HANDLE_FUNC (handle_bindsame); static HANDLE_FUNC (handle_bindsame);
@ -129,6 +137,7 @@ static HANDLE_FUNC (handle_filtercasesensitive);
static HANDLE_FUNC (handle_filterdefaultdeny); static HANDLE_FUNC (handle_filterdefaultdeny);
static HANDLE_FUNC (handle_filterextended); static HANDLE_FUNC (handle_filterextended);
static HANDLE_FUNC (handle_filterurls); static HANDLE_FUNC (handle_filterurls);
static HANDLE_FUNC (handle_filtertype);
#endif #endif
static HANDLE_FUNC (handle_group); static HANDLE_FUNC (handle_group);
static HANDLE_FUNC (handle_listen); static HANDLE_FUNC (handle_listen);
@ -185,6 +194,7 @@ struct {
regex_t *cre; regex_t *cre;
} directives[] = { } directives[] = {
/* string arguments */ /* string arguments */
STDCONF (basicauthrealm, STR, handle_basicauthrealm),
STDCONF (logfile, STR, handle_logfile), STDCONF (logfile, STR, handle_logfile),
STDCONF (pidfile, STR, handle_pidfile), STDCONF (pidfile, STR, handle_pidfile),
STDCONF (anonymous, STR, handle_anonymous), STDCONF (anonymous, STR, handle_anonymous),
@ -217,7 +227,7 @@ struct {
handle_deny), handle_deny),
STDCONF (bind, "(" IP "|" IPV6 ")", handle_bind), STDCONF (bind, "(" IP "|" IPV6 ")", handle_bind),
/* other */ /* other */
STDCONF (basicauth, ALNUM WS ALNUM, handle_basicauth), STDCONF (basicauth, USERNAME WS PASSWORD, handle_basicauth),
STDCONF (errorfile, INT WS STR, handle_errorfile), STDCONF (errorfile, INT WS STR, handle_errorfile),
STDCONF (addheader, STR WS STR, handle_addheader), STDCONF (addheader, STR WS STR, handle_addheader),
@ -228,6 +238,7 @@ struct {
STDCONF (filterextended, BOOL, handle_filterextended), STDCONF (filterextended, BOOL, handle_filterextended),
STDCONF (filterdefaultdeny, BOOL, handle_filterdefaultdeny), STDCONF (filterdefaultdeny, BOOL, handle_filterdefaultdeny),
STDCONF (filtercasesensitive, BOOL, handle_filtercasesensitive), STDCONF (filtercasesensitive, BOOL, handle_filtercasesensitive),
STDCONF (filtertype, "(bre|ere|fnmatch)", handle_filtertype),
#endif #endif
#ifdef REVERSE_SUPPORT #ifdef REVERSE_SUPPORT
/* Reverse proxy arguments */ /* Reverse proxy arguments */
@ -241,7 +252,7 @@ struct {
"(" "(none)" WS STR ")|" \ "(" "(none)" WS STR ")|" \
"(" "(http|socks4|socks5)" WS \ "(" "(http|socks4|socks5)" WS \
"(" USERNAME /*username*/ ":" PASSWORD /*password*/ "@" ")?" "(" USERNAME /*username*/ ":" PASSWORD /*password*/ "@" ")?"
"(" IP "|" ALNUM ")" "(" IP "|" "\\[(" IPV6 ")\\]" "|" ALNUM ")"
":" INT "(" WS STR ")?" ")", handle_upstream), ":" INT "(" WS STR ")?" ")", handle_upstream),
#endif #endif
/* loglevel */ /* loglevel */
@ -285,12 +296,14 @@ void free_config (struct config_s *conf)
char *k; char *k;
htab_value *v; htab_value *v;
size_t it; size_t it;
safefree (conf->basicauth_realm);
safefree (conf->logf_name); safefree (conf->logf_name);
safefree (conf->stathost); safefree (conf->stathost);
safefree (conf->user); safefree (conf->user);
safefree (conf->group); safefree (conf->group);
stringlist_free(conf->basicauth_list); stringlist_free(conf->basicauth_list);
stringlist_free(conf->listen_addrs); stringlist_free(conf->listen_addrs);
stringlist_free(conf->bind_addrs);
#ifdef FILTER_ENABLE #ifdef FILTER_ENABLE
safefree (conf->filter); safefree (conf->filter);
#endif /* FILTER_ENABLE */ #endif /* FILTER_ENABLE */
@ -302,7 +315,6 @@ void free_config (struct config_s *conf)
free_upstream_list (conf->upstream_list); free_upstream_list (conf->upstream_list);
#endif /* UPSTREAM_SUPPORT */ #endif /* UPSTREAM_SUPPORT */
safefree (conf->pidpath); safefree (conf->pidpath);
safefree (conf->bind_address);
safefree (conf->via_proxy_name); safefree (conf->via_proxy_name);
if (conf->errorpages) { if (conf->errorpages) {
it = 0; it = 0;
@ -409,7 +421,7 @@ static int check_match (struct config_s *conf, const char *line,
*/ */
static int config_parse (struct config_s *conf, FILE * f) static int config_parse (struct config_s *conf, FILE * f)
{ {
char buffer[LINE_MAX], *p, *q, c; char buffer[TP_LINE_MAX], *p, *q, c;
const struct config_directive_entry *e; const struct config_directive_entry *e;
unsigned long lineno = 1; unsigned long lineno = 1;
@ -419,7 +431,7 @@ static int config_parse (struct config_s *conf, FILE * f)
while(isspace(*p))p++; while(isspace(*p))p++;
if(!*p) continue; if(!*p) continue;
q = p; q = p;
while(!isspace(*q))q++; while(*q && !isspace(*q))q++;
c = *q; c = *q;
*q = 0; *q = 0;
e = config_directive_find(p, strlen(p)); e = config_directive_find(p, strlen(p));
@ -472,6 +484,7 @@ static void initialize_config_defaults (struct config_s *conf)
* (FIXME: Should have a better API for all this) * (FIXME: Should have a better API for all this)
*/ */
conf->errorpages = NULL; conf->errorpages = NULL;
conf->basicauth_realm = safestrdup (PACKAGE_NAME);
conf->stathost = safestrdup (TINYPROXY_STATHOST); conf->stathost = safestrdup (TINYPROXY_STATHOST);
conf->idletimeout = MAX_IDLE_TIME; conf->idletimeout = MAX_IDLE_TIME;
conf->logf_name = NULL; conf->logf_name = NULL;
@ -505,7 +518,7 @@ int reload_config_file (const char *config_fname, struct config_s *conf)
goto done; goto done;
} }
if (!conf->user) { if (!conf->user && !geteuid()) {
log_message (LOG_WARNING, "You SHOULD set a UserName in the " log_message (LOG_WARNING, "You SHOULD set a UserName in the "
"config file. Using current user instead."); "config file. Using current user instead.");
} }
@ -625,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) static HANDLE_FUNC (handle_logfile)
{ {
return set_string_arg (&conf->logf_name, line, &match[2]); return set_string_arg (&conf->logf_name, line, &match[2]);
@ -701,6 +719,8 @@ static HANDLE_FUNC (handle_xtinyproxy)
#ifdef XTINYPROXY_ENABLE #ifdef XTINYPROXY_ENABLE
return set_bool_arg (&conf->add_xtinyproxy, line, &match[2]); return set_bool_arg (&conf->add_xtinyproxy, line, &match[2]);
#else #else
if(!get_bool_arg(line, &match[2]))
return 0;
fprintf (stderr, fprintf (stderr,
"XTinyproxy NOT Enabled! Recompile with --enable-xtinyproxy\n"); "XTinyproxy NOT Enabled! Recompile with --enable-xtinyproxy\n");
return 1; return 1;
@ -796,12 +816,27 @@ static HANDLE_FUNC (handle_deny)
static HANDLE_FUNC (handle_bind) static HANDLE_FUNC (handle_bind)
{ {
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, log_message (LOG_INFO,
"Outgoing connections bound to IP %s", conf->bind_address); "Added bind address [%s] for outgoing connections.", arg);
return 0; return 0;
} }
@ -929,6 +964,11 @@ static HANDLE_FUNC (handle_basicauth)
} }
#ifdef FILTER_ENABLE #ifdef FILTER_ENABLE
static void warn_deprecated(const char *arg, unsigned long lineno) {
CP_WARN ("deprecated option %s", arg);
}
static HANDLE_FUNC (handle_filter) static HANDLE_FUNC (handle_filter)
{ {
return set_string_arg (&conf->filter, line, &match[2]); return set_string_arg (&conf->filter, line, &match[2]);
@ -936,26 +976,53 @@ static HANDLE_FUNC (handle_filter)
static HANDLE_FUNC (handle_filterurls) 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) 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) static HANDLE_FUNC (handle_filterdefaultdeny)
{ {
assert (match[2].rm_so != -1); assert (match[2].rm_so != -1);
conf->filter_opts |=
if (get_bool_arg (line, &match[2])) get_bool_arg (line, &match[2]) * FILTER_OPT_DEFAULT_DENY;
filter_set_default_policy (FILTER_DEFAULT_DENY);
return 0; return 0;
} }
static HANDLE_FUNC (handle_filtercasesensitive) 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;i<sizeof(ftmap)/sizeof(ftmap[0]);++i)
if(!strcasecmp(ftmap[i].type, type))
conf->filter_opts |= ftmap[i].flag;
safefree (type);
return 0;
} }
#endif #endif
@ -1057,10 +1124,13 @@ static HANDLE_FUNC (handle_upstream)
pass = get_string_arg (line, &match[mi]); pass = get_string_arg (line, &match[mi]);
mi++; mi++;
ip = get_string_arg (line, &match[mi]); 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) if (!ip)
return -1; return -1;
mi += 5; mi += 16;
port = (int) get_long_arg (line, &match[mi]); port = (int) get_long_arg (line, &match[mi]);
mi += 3; mi += 3;

View File

@ -39,6 +39,7 @@ typedef struct {
*/ */
struct config_s { struct config_s {
sblist *basicauth_list; sblist *basicauth_list;
char *basicauth_realm;
char *logf_name; char *logf_name;
unsigned int syslog; /* boolean */ unsigned int syslog; /* boolean */
unsigned int port; unsigned int port;
@ -50,9 +51,7 @@ struct config_s {
sblist *listen_addrs; sblist *listen_addrs;
#ifdef FILTER_ENABLE #ifdef FILTER_ENABLE
char *filter; char *filter;
unsigned int filter_url; /* boolean */ unsigned int filter_opts; /* enum filter_options */
unsigned int filter_extended; /* boolean */
unsigned int filter_casesensitive; /* boolean */
#endif /* FILTER_ENABLE */ #endif /* FILTER_ENABLE */
#ifdef XTINYPROXY_ENABLE #ifdef XTINYPROXY_ENABLE
unsigned int add_xtinyproxy; /* boolean */ unsigned int add_xtinyproxy; /* boolean */
@ -68,7 +67,7 @@ struct config_s {
#endif /* UPSTREAM_SUPPORT */ #endif /* UPSTREAM_SUPPORT */
char *pidpath; char *pidpath;
unsigned int idletimeout; unsigned int idletimeout;
char *bind_address; sblist *bind_addrs;
unsigned int bindsame; unsigned int bindsame;
/* /*

View File

@ -25,6 +25,7 @@
#include "main.h" #include "main.h"
#include <regex.h> #include <regex.h>
#include <fnmatch.h>
#include "filter.h" #include "filter.h"
#include "heap.h" #include "heap.h"
#include "log.h" #include "log.h"
@ -37,15 +38,17 @@
static int err; static int err;
struct filter_list { struct filter_list {
regex_t cpatb; union {
regex_t cpatb;
char *pattern;
} u;
}; };
static sblist *fl = NULL; static sblist *fl = NULL;
static int already_init = 0; 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) void filter_init (void)
{ {
@ -61,14 +64,13 @@ void filter_init (void)
fd = fopen (config->filter, "r"); fd = fopen (config->filter, "r");
if (!fd) { if (!fd) {
return; perror ("filter file");
exit (EX_DATAERR);
} }
cflags = REG_NEWLINE | REG_NOSUB; cflags = REG_NEWLINE | REG_NOSUB;
if (config->filter_extended) cflags |= (REG_EXTENDED * !!(config->filter_opts & FILTER_OPT_TYPE_ERE));
cflags |= REG_EXTENDED; cflags |= (REG_ICASE * !(config->filter_opts & FILTER_OPT_CASESENSITIVE));
if (!config->filter_casesensitive)
cflags |= REG_ICASE;
while (fgets (buf, FILTER_BUFFER_LEN, fd)) { while (fgets (buf, FILTER_BUFFER_LEN, fd)) {
++lineno; ++lineno;
@ -106,13 +108,19 @@ void filter_init (void)
if (!fl) fl = sblist_new(sizeof(struct filter_list), if (!fl) fl = sblist_new(sizeof(struct filter_list),
4096/sizeof(struct filter_list)); 4096/sizeof(struct filter_list));
err = regcomp (&fe.cpatb, s, cflags); if (config->filter_opts & FILTER_OPT_TYPE_FNMATCH) {
if (err != 0) { fe.u.pattern = safestrdup(s);
if (err == REG_ESPACE) goto oom; if (!fe.u.pattern) goto oom;
fprintf (stderr, } else {
"Bad regex in %s: line %d - %s\n",
config->filter, lineno, s); err = regcomp (&fe.u.cpatb, s, cflags);
exit (EX_DATAERR); 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);
}
} }
if (!sblist_add(fl, &fe)) { if (!sblist_add(fl, &fe)) {
oom:; oom:;
@ -141,7 +149,10 @@ void filter_destroy (void)
if (fl) { if (fl) {
for (i = 0; i < sblist_getsize(fl); ++i) { for (i = 0; i < sblist_getsize(fl); ++i) {
p = sblist_get(fl, i); p = sblist_get(fl, i);
regfree (&p->cpatb); if (config->filter_opts & FILTER_OPT_TYPE_FNMATCH)
safefree(p->u.pattern);
else
regfree (&p->u.cpatb);
} }
sblist_free(fl); sblist_free(fl);
} }
@ -174,11 +185,14 @@ int filter_run (const char *str)
for (i = 0; i < sblist_getsize(fl); ++i) { for (i = 0; i < sblist_getsize(fl); ++i) {
p = sblist_get(fl, i); p = sblist_get(fl, i);
result = if (config->filter_opts & FILTER_OPT_TYPE_FNMATCH)
regexec (&p->cpatb, str, (size_t) 0, (regmatch_t *) 0, 0); 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 (result == 0) {
if (default_policy == FILTER_DEFAULT_ALLOW) if (!(config->filter_opts & FILTER_OPT_DEFAULT_DENY))
return 1; return 1;
else else
return 0; return 0;
@ -186,16 +200,8 @@ int filter_run (const char *str)
} }
COMMON_EXIT: COMMON_EXIT:
if (default_policy == FILTER_DEFAULT_ALLOW) if (!(config->filter_opts & FILTER_OPT_DEFAULT_DENY))
return 0; return 0;
else else
return 1; return 1;
} }
/*
* Set the default filtering policy
*/
void filter_set_default_policy (filter_policy_t policy)
{
default_policy = policy;
}

View File

@ -21,16 +21,22 @@
#ifndef _TINYPROXY_FILTER_H_ #ifndef _TINYPROXY_FILTER_H_
#define _TINYPROXY_FILTER_H_ #define _TINYPROXY_FILTER_H_
typedef enum { enum filter_options {
FILTER_DEFAULT_ALLOW, FILTER_OPT_CASESENSITIVE = 1 << 0,
FILTER_DEFAULT_DENY FILTER_OPT_URL = 1 << 1,
} filter_policy_t; 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_init (void);
extern void filter_destroy (void); extern void filter_destroy (void);
extern void filter_reload (void); extern void filter_reload (void);
extern int filter_run (const char *str); extern int filter_run (const char *str);
extern void filter_set_default_policy (filter_policy_t policy);
#endif #endif

179
src/hostspec.c Normal file
View File

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

26
src/hostspec.h Normal file
View File

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

View File

@ -49,6 +49,7 @@ struct htab {
size_t mask; size_t mask;
size_t used; size_t used;
size_t seed; size_t seed;
size_t dead;
}; };
#define MINSIZE 8 #define MINSIZE 8
@ -79,9 +80,10 @@ static int resize(struct htab *htab, size_t nel)
{ {
size_t newsize; size_t newsize;
size_t i, j; size_t i, j;
size_t oldmask = htab->mask;
struct elem *e, *newe; struct elem *e, *newe;
struct elem *oldtab = htab->elems; struct elem *oldtab = htab->elems;
struct elem *oldend = htab->elems + htab->mask + 1; struct elem *oldend;
if (nel > MAXSIZE) if (nel > MAXSIZE)
nel = MAXSIZE; nel = MAXSIZE;
@ -94,6 +96,8 @@ static int resize(struct htab *htab, size_t nel)
htab->mask = newsize - 1; htab->mask = newsize - 1;
if (!oldtab) if (!oldtab)
return 1; return 1;
oldend = oldtab + oldmask + 1;
for (e = oldtab; e < oldend; e++) for (e = oldtab; e < oldend; e++)
if (e->item.key) { if (e->item.key) {
for (i=e->hash,j=1; ; i+=j++) { for (i=e->hash,j=1; ; i+=j++) {
@ -107,14 +111,14 @@ static int resize(struct htab *htab, size_t nel)
return 1; return 1;
} }
static struct elem *lookup(struct htab *htab, const char *key, size_t hash) static struct elem *lookup(struct htab *htab, const char *key, size_t hash, size_t dead)
{ {
size_t i, j; size_t i, j;
struct elem *e; struct elem *e;
for (i=hash,j=1; ; i+=j++) { for (i=hash,j=1; ; i+=j++) {
e = htab->elems + (i & htab->mask); e = htab->elems + (i & htab->mask);
if (!e->item.key || if ((!e->item.key && (!e->hash || e->hash == dead)) ||
(e->hash==hash && STRCMP(e->item.key, key)==0)) (e->hash==hash && STRCMP(e->item.key, key)==0))
break; break;
} }
@ -138,50 +142,67 @@ void htab_destroy(struct htab *htab)
free(htab); free(htab);
} }
static htab_entry *htab_find_item(struct htab *htab, const char* key) static struct elem *htab_find_elem(struct htab *htab, const char* key)
{ {
size_t hash = keyhash(key, htab->seed); size_t hash = keyhash(key, htab->seed);
struct elem *e = lookup(htab, key, hash); struct elem *e = lookup(htab, key, hash, 0);
if (e->item.key) { if (e->item.key) {
return &e->item; return e;
} }
return 0; return 0;
} }
htab_value* htab_find(struct htab *htab, const char* key) htab_value* htab_find(struct htab *htab, const char* key)
{ {
htab_entry *i = htab_find_item(htab, key); struct elem *e = htab_find_elem(htab, key);
if(i) return &i->data; if(!e) return 0;
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) int htab_delete(struct htab *htab, const char* key)
{ {
htab_entry *i = htab_find_item(htab, key); struct elem *e = htab_find_elem(htab, key);
if(!i) return 0; if(!e) return 0;
i->key = 0; e->item.key = 0;
e->hash = 0xdeadc0de;
--htab->used;
++htab->dead;
return 1; return 1;
} }
int htab_insert(struct htab *htab, char* key, htab_value value) int htab_insert(struct htab *htab, char* key, htab_value value)
{ {
size_t hash = keyhash(key, htab->seed); size_t hash = keyhash(key, htab->seed), oh;
struct elem *e = lookup(htab, key, hash); struct elem *e = lookup(htab, key, hash, 0xdeadc0de);
if(e->item.key) { if(e->item.key) {
/* it's not allowed to overwrite existing data */ /* it's not allowed to overwrite existing data */
return 0; return 0;
} }
oh = e->hash; /* save old hash in case it's tombstone marker */
e->item.key = key; e->item.key = key;
e->item.data = value; e->item.data = value;
e->hash = hash; e->hash = hash;
if (++htab->used > htab->mask - htab->mask/4) { if (++htab->used + htab->dead > htab->mask - htab->mask/4) {
if (!resize(htab, 2*htab->used)) { if (!resize(htab, 2*htab->used)) {
htab->used--; htab->used--;
e->item.key = 0; e->item.key = 0;
e->hash = oh;
return 0; return 0;
} }
htab->dead = 0;
} else if (oh == 0xdeadc0de) {
/* re-used tomb */
--htab->dead;
} }
return 1; return 1;
} }

View File

@ -14,6 +14,8 @@ typedef union htab_value {
struct htab * htab_create(size_t); struct htab * htab_create(size_t);
void htab_destroy(struct htab *); void htab_destroy(struct htab *);
htab_value* htab_find(struct htab *, const char* key); 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_insert(struct htab *, char*, htab_value);
int htab_delete(struct htab *htab, const char* key); int htab_delete(struct htab *htab, const char* key);
size_t htab_next(struct htab *, size_t iterator, char** key, htab_value **v); size_t htab_next(struct htab *, size_t iterator, char** key, htab_value **v);

View File

@ -20,10 +20,9 @@
* HTML error pages with variable substitution. * HTML error pages with variable substitution.
*/ */
#include <regex.h> #include "common.h"
#include "main.h" #include "main.h"
#include "common.h"
#include "buffer.h" #include "buffer.h"
#include "conns.h" #include "conns.h"
#include "heap.h" #include "heap.h"
@ -31,6 +30,9 @@
#include "network.h" #include "network.h"
#include "utils.h" #include "utils.h"
#include "conf.h" #include "conf.h"
#include "log.h"
#include <regex.h>
/* /*
* Add an error number -> filename mapping to the errorpages list. * Add an error number -> filename mapping to the errorpages list.
@ -132,30 +134,21 @@ send_html_file (FILE *infile, struct conn_s *connptr)
return 1; 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[] = const char headers[] =
"HTTP/1.0 %d %s\r\n" "HTTP/1.%u %d %s\r\n"
"Server: %s/%s\r\n" "Server: %s\r\n"
"Content-Type: text/html\r\n" "Content-Type: text/html\r\n"
"%s" "%s"
"Connection: close\r\n" "\r\n"; "Connection: close\r\n" "\r\n";
const char p_auth_str[] =
"Proxy-Authenticate: Basic realm=\""
PACKAGE_NAME "\"\r\n";
const char w_auth_str[] =
"WWW-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 ? p_auth_str : (code == 401 ? w_auth_str : "");
return (write_message (connptr->client_fd, headers, return (write_message (connptr->client_fd, headers,
code, message, PACKAGE, VERSION, connptr->protocol.major != 1 ? 0 : connptr->protocol.minor,
add)); code, message, PACKAGE,
extra));
} }
/* /*
@ -176,20 +169,47 @@ int send_http_error_message (struct conn_s *connptr)
"<h1>%s</h1>\n" "<h1>%s</h1>\n"
"<p>%s</p>\n" "<p>%s</p>\n"
"<hr />\n" "<hr />\n"
"<p><em>Generated by %s version %s.</em></p>\n" "</body>\n" "<p><em>Generated by %s.</em></p>\n" "</body>\n"
"</html>\n"; "</html>\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, 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); error_file = get_html_file (connptr->error_number);
if (!(infile = fopen (error_file, "r"))) { if (!error_file || !(infile = fopen (error_file, "r"))) {
char *detail = lookup_variable (connptr->error_variables, "detail"); 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, return (write_message (connptr->client_fd, fallback_error,
connptr->error_number, connptr->error_number,
connptr->error_string, connptr->error_string,
connptr->error_string, connptr->error_string,
detail, PACKAGE, VERSION)); detail, PACKAGE));
} }
ret = send_html_file (infile, connptr); ret = send_html_file (infile, connptr);
@ -243,6 +263,7 @@ int add_standard_vars (struct conn_s *connptr)
char errnobuf[16]; char errnobuf[16];
char timebuf[30]; char timebuf[30];
time_t global_time; time_t global_time;
struct tm tm_buf;
snprintf (errnobuf, sizeof errnobuf, "%d", connptr->error_number); snprintf (errnobuf, sizeof errnobuf, "%d", connptr->error_number);
ADD_VAR_RET ("errno", errnobuf); ADD_VAR_RET ("errno", errnobuf);
@ -258,7 +279,7 @@ int add_standard_vars (struct conn_s *connptr)
global_time = time (NULL); global_time = time (NULL);
strftime (timebuf, sizeof (timebuf), "%a, %d %b %Y %H:%M:%S GMT", 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, "date", timebuf);
add_error_variable (connptr, "website", add_error_variable (connptr, "website",

View File

@ -33,7 +33,7 @@ extern int add_error_variable (struct conn_s *connptr, const char *key,
const char *val); const char *val);
extern int send_html_file (FILE * infile, struct conn_s *connptr); extern int send_html_file (FILE * infile, struct conn_s *connptr);
extern int send_http_headers (struct conn_s *connptr, int code, 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); extern int add_standard_vars (struct conn_s *connptr);
#endif /* !TINYPROXY_HTML_ERROR_H */ #endif /* !TINYPROXY_HTML_ERROR_H */

View File

@ -232,6 +232,7 @@ int http_message_send (http_message_t msg, int fd)
char timebuf[30]; char timebuf[30];
time_t global_time; time_t global_time;
unsigned int i; unsigned int i;
struct tm tm_buf;
assert (is_http_message_valid (msg)); assert (is_http_message_valid (msg));
@ -254,11 +255,11 @@ int http_message_send (http_message_t msg, int fd)
/* Output the date */ /* Output the date */
global_time = time (NULL); global_time = time (NULL);
strftime (timebuf, sizeof (timebuf), "%a, %d %b %Y %H:%M:%S GMT", 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); write_message (fd, "Date: %s\r\n", timebuf);
/* Output the content-length */ /* 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 */ /* Write the separator between the headers and body */
safe_write (fd, "\r\n", 2); safe_write (fd, "\r\n", 2);

View File

@ -109,6 +109,7 @@ void log_message (int level, const char *fmt, ...)
{ {
va_list args; va_list args;
struct timespec nowtime; struct timespec nowtime;
struct tm tm_buf;
char time_string[TIME_LENGTH]; char time_string[TIME_LENGTH];
char str[STRING_LENGTH]; char str[STRING_LENGTH];
@ -177,11 +178,11 @@ void log_message (int level, const char *fmt, ...)
clock_gettime(CLOCK_REALTIME, &nowtime); clock_gettime(CLOCK_REALTIME, &nowtime);
/* Format is month day hour:minute:second (24 time) */ /* Format is month day hour:minute:second (24 time) */
strftime (time_string, TIME_LENGTH, "%b %d %H:%M:%S", strftime (time_string, TIME_LENGTH, "%b %d %H:%M:%S",
localtime (&nowtime.tv_sec)); localtime_r (&nowtime.tv_sec, &tm_buf));
snprintf (str, STRING_LENGTH, "%-9s %s.%03u [%ld]: ", snprintf (str, STRING_LENGTH, "%-9s %s.%03lu [%ld]: ",
syslog_level[level], time_string, syslog_level[level], time_string,
nowtime.tv_nsec/1000000u, (unsigned long) nowtime.tv_nsec/1000000ul,
(long int) getpid ()); (long int) getpid ());
/* /*

View File

@ -254,27 +254,28 @@ change_user (const char *program)
*/ */
int reload_config (int reload_logging) int reload_config (int reload_logging)
{ {
int ret; int ret, ret2;
struct config_s *c_next = get_next_config(); struct config_s *c_next = get_next_config();
log_message (LOG_NOTICE, "Reloading config file"); log_message (LOG_NOTICE, "Reloading config file (%s)", config_file);
if (reload_logging) shutdown_logging (); if (reload_logging) shutdown_logging ();
ret = reload_config_file (config_file, c_next); ret = reload_config_file (config_file, c_next);
if (ret != 0) { if (ret == 0) {
goto done; if(config) free_config (config);
config = c_next;
} }
if(config) free_config (config); ret2 = reload_logging ? setup_logging () : 0;
config = c_next;
if (reload_logging) ret = setup_logging (); if (ret != 0)
log_message (LOG_NOTICE, "Reloading config file finished"); log_message (LOG_WARNING, "Reloading config file failed!");
else
log_message (LOG_NOTICE, "Reloading config file finished");
done: return ret ? ret : ret2;
return ret;
} }
static void setup_sig(int sig, signal_func *sigh, static void setup_sig(int sig, signal_func *sigh,

View File

@ -81,14 +81,19 @@ char* orderedmap_find(struct orderedmap *o, const char *key) {
int orderedmap_remove(struct orderedmap *o, const char *key) { int orderedmap_remove(struct orderedmap *o, const char *key) {
size_t i; size_t i;
char *lk; char *lk;
htab_value *lv, *v = htab_find(o->map, key); char *sk;
char **sv;
htab_value *lv, *v = htab_find2(o->map, key, &sk);
if(!v) return 0; if(!v) return 0;
htab_delete(o->map, key); sv = sblist_get(o->values, v->n);
free(*sv);
sblist_delete(o->values, v->n); sblist_delete(o->values, v->n);
i = 0; i = 0;
while((i = htab_next(o->map, i, &lk, &lv))) { while((i = htab_next(o->map, i, &lk, &lv))) {
if(lv->n > v->n) lv->n--; if(lv->n > v->n) lv->n--;
} }
htab_delete(o->map, key);
free(sk);
return 1; return 1;
} }

View File

@ -268,48 +268,49 @@ establish_http_connection (struct conn_s *connptr, struct request_s *request)
/* host is an IPv6 address literal, so surround it with /* host is an IPv6 address literal, so surround it with
* [] */ * [] */
return write_message (connptr->server_fd, 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" "Host: [%s]%s\r\n"
"Connection: close\r\n", "Connection: close\r\n",
request->method, request->path, request->method, request->path,
connptr->protocol.major != 1 ? 0 :
connptr->protocol.minor,
request->host, portbuff); request->host, portbuff);
} else if (connptr->upstream_proxy && } else if (connptr->upstream_proxy &&
connptr->upstream_proxy->type == PT_HTTP && connptr->upstream_proxy->type == PT_HTTP &&
connptr->upstream_proxy->ua.authstr) { connptr->upstream_proxy->ua.authstr) {
return write_message (connptr->server_fd, 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" "Host: %s%s\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Proxy-Authorization: Basic %s\r\n", "Proxy-Authorization: Basic %s\r\n",
request->method, request->path, request->method, request->path,
connptr->protocol.major != 1 ? 0 :
connptr->protocol.minor,
request->host, portbuff, request->host, portbuff,
connptr->upstream_proxy->ua.authstr); connptr->upstream_proxy->ua.authstr);
} else { } else {
return write_message (connptr->server_fd, 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" "Host: %s%s\r\n"
"Connection: close\r\n", "Connection: close\r\n",
request->method, request->path, request->method, request->path,
connptr->protocol.major != 1 ? 0 :
connptr->protocol.minor,
request->host, portbuff); 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" static int send_connect_method_response (struct conn_s *connptr)
#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)
{ {
return write_message (connptr->client_fd, return write_message (connptr->client_fd,
"%s\r\n" "HTTP/1.%u 200 Connection established\r\n"
"%s\r\n" "Proxy-agent: " PACKAGE "\r\n"
"\r\n", SSL_CONNECTION_RESPONSE, PROXY_AGENT); "\r\n", connptr->protocol.major != 1 ? 0 :
connptr->protocol.minor);
} }
/* /*
@ -321,9 +322,11 @@ static struct request_s *process_request (struct conn_s *connptr,
{ {
char *url; char *url;
struct request_s *request; struct request_s *request;
int ret; int ret, skip_trans;
size_t request_len; size_t request_len;
skip_trans = 0;
/* NULL out all the fields so frees don't cause segfaults. */ /* NULL out all the fields so frees don't cause segfaults. */
request = request =
(struct request_s *) safecalloc (1, sizeof (struct request_s)); (struct request_s *) safecalloc (1, sizeof (struct request_s));
@ -340,8 +343,12 @@ static struct request_s *process_request (struct conn_s *connptr,
goto fail; 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, "%[^ ] %[^ ] %[^ ]", ret = sscanf (connptr->request_line, "%[^ ] %[^ ] %[^ ]",
request->method, url, request->protocol); request->method, url, request->protocol);
if (ret == 2 && !strcasecmp (request->method, "GET")) { if (ret == 2 && !strcasecmp (request->method, "GET")) {
request->protocol[0] = 0; request->protocol[0] = 0;
@ -383,12 +390,20 @@ BAD_REQUEST_ERROR:
* we'll be closing anyway. * we'll be closing anyway.
*/ */
char *reverse_url; 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_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); safefree (url);
url = reverse_url; url = reverse_url;
skip_trans = 1;
} else if (config->reverseonly) { } else if (config->reverseonly) {
log_message (LOG_ERR, log_message (LOG_ERR,
"Bad request, no mapping for '%s' found", "Bad request, no mapping for '%s' found",
@ -438,11 +453,13 @@ BAD_REQUEST_ERROR:
connptr->connect_method = TRUE; connptr->connect_method = TRUE;
} else { } else {
#ifdef TRANSPARENT_PROXY #ifdef TRANSPARENT_PROXY
if (!do_transparent_proxy if (!skip_trans) {
(connptr, hashofheaders, request, config, &url)) { if (!do_transparent_proxy
goto fail; (connptr, hashofheaders, request, config, &url))
} goto fail;
#else } else
#endif
{
indicate_http_error (connptr, 501, "Not Implemented", indicate_http_error (connptr, 501, "Not Implemented",
"detail", "detail",
"Unknown method or unsupported protocol.", "Unknown method or unsupported protocol.",
@ -450,7 +467,7 @@ BAD_REQUEST_ERROR:
log_message (LOG_INFO, "Unknown method (%s) or protocol (%s)", log_message (LOG_INFO, "Unknown method (%s) or protocol (%s)",
request->method, url); request->method, url);
goto fail; goto fail;
#endif }
} }
#ifdef FILTER_ENABLE #ifdef FILTER_ENABLE
@ -458,22 +475,16 @@ BAD_REQUEST_ERROR:
* Filter restricted domains/urls * Filter restricted domains/urls
*/ */
if (config->filter) { if (config->filter) {
if (config->filter_url) int fu = config->filter_opts & FILTER_OPT_URL;
ret = filter_run (url); ret = filter_run (fu ? url : request->host);
else
ret = filter_run (request->host);
if (ret) { if (ret) {
update_stats (STAT_DENIED); update_stats (STAT_DENIED);
if (config->filter_url) log_message (LOG_NOTICE,
log_message (LOG_NOTICE, "Proxying refused on filtered %s \"%s\"",
"Proxying refused on filtered url \"%s\"", fu ? "url" : "domain",
url); fu ? url : request->host);
else
log_message (LOG_NOTICE,
"Proxying refused on filtered domain \"%s\"",
request->host);
indicate_http_error (connptr, 403, "Filtered", indicate_http_error (connptr, 403, "Filtered",
"detail", "detail",
@ -510,7 +521,7 @@ fail:
* server headers can be processed. * server headers can be processed.
* - rjkaes * - rjkaes
*/ */
static int pull_client_data (struct conn_s *connptr, long int length) static int pull_client_data (struct conn_s *connptr, long int length, int iehack)
{ {
char *buffer; char *buffer;
ssize_t len; ssize_t len;
@ -535,39 +546,75 @@ static int pull_client_data (struct conn_s *connptr, long int length)
length -= len; length -= len;
} while (length > 0); } while (length > 0);
/* if (iehack) {
* BUG FIX: Internet Explorer will leave two bytes (carriage /*
* return and line feed) at the end of a POST message. These * BUG FIX: Internet Explorer will leave two bytes (carriage
* need to be eaten for tinyproxy to work correctly. * 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) { ret = socket_nonblocking (connptr->client_fd);
log_message(LOG_ERR, "Failed to set the client socket " if (ret != 0) {
"to non-blocking: %s", strerror(errno)); log_message(LOG_ERR, "Failed to set the client socket "
goto ERROR_EXIT; "to non-blocking: %s", strerror(errno));
} goto ERROR_EXIT;
len = recv (connptr->client_fd, buffer, 2, MSG_PEEK);
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;
}
if (len < 0 && errno != EAGAIN)
goto ERROR_EXIT;
if ((len == 2) && CHECK_CRLF (buffer, len)) {
ssize_t bytes_read;
bytes_read = read (connptr->client_fd, buffer, 2);
if (bytes_read == -1) {
log_message
(LOG_WARNING,
"Could not read two bytes from POST message");
} }
len = recv (connptr->client_fd, buffer, 2, MSG_PEEK);
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;
}
if (len < 0 && errno != EAGAIN)
goto ERROR_EXIT;
if ((len == 2) && CHECK_CRLF (buffer, len)) {
ssize_t bytes_read;
bytes_read = read (connptr->client_fd, buffer, 2);
if (bytes_read == -1) {
log_message
(LOG_WARNING,
"Could not read two bytes from POST message");
}
}
}
safefree (buffer);
return 0;
ERROR_EXIT:
safefree (buffer);
return -1;
}
/* pull chunked client data */
static int pull_client_data_chunked (struct conn_s *connptr) {
char *buffer = 0;
ssize_t len;
long chunklen;
while(1) {
if (buffer) safefree(buffer);
len = readline (connptr->client_fd, &buffer);
if (len <= 0)
goto ERROR_EXIT;
if (!connptr->error_variables) {
if (safe_write (connptr->server_fd, buffer, len) < 0)
goto ERROR_EXIT;
}
chunklen = strtol (buffer, (char**)0, 16);
if (pull_client_data (connptr, chunklen+2, 0) < 0)
goto ERROR_EXIT;
if(!chunklen) break;
} }
safefree (buffer); safefree (buffer);
@ -732,7 +779,7 @@ static int remove_connection_headers (orderedmap hashofheaders)
char *data; char *data;
char *ptr; char *ptr;
ssize_t len; ssize_t len;
int i; int i,j,df;
for (i = 0; i != (sizeof (headers) / sizeof (char *)); ++i) { for (i = 0; i != (sizeof (headers) / sizeof (char *)); ++i) {
/* Look for the connection header. If it's not found, return. */ /* Look for the connection header. If it's not found, return. */
@ -757,7 +804,12 @@ static int remove_connection_headers (orderedmap hashofheaders)
*/ */
ptr = data; ptr = data;
while (ptr < data + len) { while (ptr < data + len) {
orderedmap_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) orderedmap_remove (hashofheaders, ptr);
/* Advance ptr to the next token */ /* Advance ptr to the next token */
ptr += strlen (ptr) + 1; ptr += strlen (ptr) + 1;
@ -774,7 +826,7 @@ static int remove_connection_headers (orderedmap hashofheaders)
/* /*
* If there is a Content-Length header, then return the value; otherwise, return * If there is a Content-Length header, then return the value; otherwise, return
* a negative number. * -1.
*/ */
static long get_content_length (orderedmap hashofheaders) static long get_content_length (orderedmap hashofheaders)
{ {
@ -789,6 +841,13 @@ static long get_content_length (orderedmap hashofheaders)
return content_length; return content_length;
} }
static int is_chunked_transfer (orderedmap hashofheaders)
{
char *data;
data = orderedmap_find (hashofheaders, "transfer-encoding");
return data ? !strcmp (data, "chunked") : 0;
}
/* /*
* Search for Via header in a hash of headers and either write a new Via * 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. * header, or append our information to the end of an existing Via header.
@ -822,15 +881,14 @@ write_via_header (int fd, orderedmap hashofheaders,
data = orderedmap_find (hashofheaders, "via"); data = orderedmap_find (hashofheaders, "via");
if (data) { if (data) {
ret = write_message (fd, ret = write_message (fd,
"Via: %s, %hu.%hu %s (%s/%s)\r\n", "Via: %s, %hu.%hu %s (%s)\r\n",
data, major, minor, hostname, PACKAGE, data, major, minor, hostname, PACKAGE);
VERSION);
orderedmap_remove (hashofheaders, "via"); orderedmap_remove (hashofheaders, "via");
} else { } else {
ret = write_message (fd, ret = write_message (fd,
"Via: %hu.%hu %s (%s/%s)\r\n", "Via: %hu.%hu %s (%s)\r\n",
major, minor, hostname, PACKAGE, VERSION); major, minor, hostname, PACKAGE);
} }
done: done:
@ -883,6 +941,10 @@ process_client_headers (struct conn_s *connptr, orderedmap hashofheaders)
*/ */
connptr->content_length.client = get_content_length (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 * See if there is a "Connection" header. If so, we need to do a bit
* of processing. :) * of processing. :)
@ -947,8 +1009,9 @@ process_client_headers (struct conn_s *connptr, orderedmap hashofheaders)
PULL_CLIENT_DATA: PULL_CLIENT_DATA:
if (connptr->content_length.client > 0) { if (connptr->content_length.client > 0) {
ret = pull_client_data (connptr, ret = pull_client_data (connptr,
connptr->content_length.client); connptr->content_length.client, 1);
} } else if (connptr->content_length.client == -2)
ret = pull_client_data_chunked (connptr);
return ret; return ret;
} }
@ -1223,6 +1286,7 @@ static void relay_connection (struct conn_s *connptr)
return; return;
} }
#ifdef UPSTREAM_SUPPORT
static int static int
connect_to_upstream_proxy(struct conn_s *connptr, struct request_s *request) connect_to_upstream_proxy(struct conn_s *connptr, struct request_s *request)
{ {
@ -1231,7 +1295,6 @@ connect_to_upstream_proxy(struct conn_s *connptr, struct request_s *request)
unsigned short port; unsigned short port;
size_t ulen, passlen; size_t ulen, passlen;
struct hostent *host;
struct upstream *cur_upstream = connptr->upstream_proxy; struct upstream *cur_upstream = connptr->upstream_proxy;
ulen = cur_upstream->ua.user ? strlen(cur_upstream->ua.user) : 0; ulen = cur_upstream->ua.user ? strlen(cur_upstream->ua.user) : 0;
@ -1248,10 +1311,13 @@ connect_to_upstream_proxy(struct conn_s *connptr, struct request_s *request)
buff[1] = 1; /* connect command */ buff[1] = 1; /* connect command */
port = htons(request->port); port = htons(request->port);
memcpy(&buff[2], &port, 2); /* dest port */ memcpy(&buff[2], &port, 2); /* dest port */
host = gethostbyname(request->host); memcpy(&buff[4], "\0\0\0\1" /* socks4a fake ip */
memcpy(&buff[4], host->h_addr_list[0], 4); /* dest ip */ "\0" /* user */, 5);
buff[8] = 0; /* user */ len = strlen(request->host);
if (9 != safe_write(connptr->server_fd, buff, 9)) 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; return -1;
if (8 != safe_read(connptr->server_fd, buff, 8)) if (8 != safe_read(connptr->server_fd, buff, 8))
return -1; return -1;
@ -1337,7 +1403,7 @@ connect_to_upstream_proxy(struct conn_s *connptr, struct request_s *request)
return establish_http_connection(connptr, request); return establish_http_connection(connptr, request);
} }
#endif
/* /*
* Establish a connection to the upstream proxy server. * Establish a connection to the upstream proxy server.
@ -1361,7 +1427,7 @@ connect_to_upstream (struct conn_s *connptr, struct request_s *request)
log_message (LOG_WARNING, log_message (LOG_WARNING,
"No upstream proxy defined for %s.", "No upstream proxy defined for %s.",
request->host); request->host);
indicate_http_error (connptr, 404, indicate_http_error (connptr, 502,
"Unable to connect to upstream proxy."); "Unable to connect to upstream proxy.");
return -1; return -1;
} }
@ -1373,7 +1439,7 @@ connect_to_upstream (struct conn_s *connptr, struct request_s *request)
if (connptr->server_fd < 0) { if (connptr->server_fd < 0) {
log_message (LOG_WARNING, log_message (LOG_WARNING,
"Could not connect to upstream proxy."); "Could not connect to upstream proxy.");
indicate_http_error (connptr, 404, indicate_http_error (connptr, 502,
"Unable to connect to upstream proxy", "Unable to connect to upstream proxy",
"detail", "detail",
"A network error occurred while trying to " "A network error occurred while trying to "
@ -1449,13 +1515,13 @@ get_request_entity(struct conn_s *connptr)
nread = read_buffer (connptr->client_fd, connptr->cbuffer); nread = read_buffer (connptr->client_fd, connptr->cbuffer);
if (nread < 0) { if (nread < 0) {
log_message (LOG_ERR, log_message (LOG_ERR,
"Error reading readable client_fd %d", "Error reading readable client_fd %d (%s)",
connptr->client_fd); connptr->client_fd, strerror(errno));
ret = -1; ret = -1;
} else { } else {
log_message (LOG_INFO, log_message (LOG_INFO,
"Read request entity of %d bytes", "Read request entity of %ld bytes",
nread); (long) nread);
ret = 0; ret = 0;
} }
} else { } else {
@ -1493,6 +1559,19 @@ static void handle_connection_failure(struct conn_s *connptr, int got_headers)
} }
} }
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 * This is the main drive for each connection. As you can tell, for the
@ -1502,6 +1581,9 @@ static void handle_connection_failure(struct conn_s *connptr, int got_headers)
* when we start the relay portion. This makes most of the original * when we start the relay portion. This makes most of the original
* tinyproxy code, which was confusing, redundant. Hail progress. * tinyproxy code, which was confusing, redundant. Hail progress.
* - rjkaes * - rjkaes
* this function is called directly from child_thread() with the newly
* received fd from accept().
*/ */
void handle_connection (struct conn_s *connptr, union sockaddr_union* addr) void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
{ {
@ -1513,7 +1595,6 @@ void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
int got_headers = 0, fd = connptr->client_fd; int got_headers = 0, fd = connptr->client_fd;
size_t i; size_t i;
struct request_s *request = NULL; struct request_s *request = NULL;
struct timeval tv;
orderedmap hashofheaders = NULL; orderedmap hashofheaders = NULL;
char sock_ipaddr[IP_LENGTH]; char sock_ipaddr[IP_LENGTH];
@ -1535,12 +1616,7 @@ void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
return; return;
} }
tv.tv_usec = 0; set_socket_timeout(fd);
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));
if (connection_loops (addr)) { if (connection_loops (addr)) {
log_message (LOG_CONN, log_message (LOG_CONN,
@ -1568,11 +1644,7 @@ void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
if (read_request_line (connptr) < 0) { if (read_request_line (connptr) < 0) {
update_stats (STAT_BADCONN); update_stats (STAT_BADCONN);
indicate_http_error (connptr, 408, "Timeout", goto done;
"detail",
"Server timeout waiting for the HTTP request "
"from the client.", NULL);
HC_FAIL();
} }
/* /*
@ -1618,12 +1690,7 @@ void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
} }
if (!authstring) { if (!authstring) {
if (stathost_connect) goto e401; auth_error(connptr, stathost_connect ? 401 : 407);
update_stats (STAT_DENIED);
indicate_http_error (connptr, 407, "Proxy Authentication Required",
"detail",
"This proxy requires authentication.",
NULL);
HC_FAIL(); HC_FAIL();
} }
if ( /* currently only "basic" auth supported */ if ( /* currently only "basic" auth supported */
@ -1632,13 +1699,7 @@ void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
basicauth_check (config->basicauth_list, authstring + 6) == 1) basicauth_check (config->basicauth_list, authstring + 6) == 1)
failure = 0; failure = 0;
if(failure) { if(failure) {
e401: auth_error(connptr, stathost_connect ? 401 : 407);
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);
HC_FAIL(); HC_FAIL();
} }
orderedmap_remove (hashofheaders, "proxy-authorization"); orderedmap_remove (hashofheaders, "proxy-authorization");
@ -1712,10 +1773,10 @@ e401:
HC_FAIL(); HC_FAIL();
} }
} else { } else {
if (send_ssl_response (connptr) < 0) { if (send_connect_method_response (connptr) < 0) {
log_message (LOG_ERR, log_message (LOG_ERR,
"handle_connection: Could not send SSL greeting " "handle_connection: Could not send CONNECT"
"to client."); " method greeting to client.");
update_stats (STAT_BADCONN); update_stats (STAT_BADCONN);
HC_FAIL(); HC_FAIL();
} }

View File

@ -34,6 +34,7 @@ void reversepath_add (const char *path, const char *url,
struct reversepath **reversepath_list) struct reversepath **reversepath_list)
{ {
struct reversepath *reverse; struct reversepath *reverse;
size_t l;
if (url == NULL) { if (url == NULL) {
log_message (LOG_WARNING, log_message (LOG_WARNING,
@ -65,8 +66,17 @@ void reversepath_add (const char *path, const char *url,
if (!path) if (!path)
reverse->path = safestrdup ("/"); reverse->path = safestrdup ("/");
else else {
reverse->path = safestrdup (path); 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); 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) struct reversepath *reversepath_get (char *url, struct reversepath *reverse)
{ {
size_t l, lu, lp;
while (reverse) { 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; return reverse;
reverse = reverse->next; reverse = reverse->next;
} }
@ -112,23 +128,30 @@ void free_reversepath_list (struct reversepath *reverse)
* Rewrite the URL for reverse proxying. * Rewrite the URL for reverse proxying.
*/ */
char *reverse_rewrite_url (struct conn_s *connptr, orderedmap hashofheaders, char *reverse_rewrite_url (struct conn_s *connptr, orderedmap hashofheaders,
char *url) char *url, int *status)
{ {
char *rewrite_url = NULL; char *rewrite_url = NULL;
char *cookie = NULL; char *cookie = NULL;
char *cookieval; char *cookieval;
struct reversepath *reverse = NULL; struct reversepath *reverse = NULL;
*status = 0;
/* Reverse requests always start with a slash */ /* Reverse requests always start with a slash */
if (*url == '/') { if (*url == '/') {
/* First try locating the reverse mapping by request 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) { if (reverse) {
rewrite_url = (char *) size_t lu = strlen (url);
safemalloc (strlen (url) + strlen (reverse->url) + size_t lrp = strlen (reverse->path);
1); if (lrp > lu) {
strcpy (rewrite_url, reverse->url); rewrite_url = safestrdup (reverse->path);
strcat (rewrite_url, url + strlen (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 } else if (config->reversemagic
&& (cookie = orderedmap_find (hashofheaders, && (cookie = orderedmap_find (hashofheaders,
"cookie"))) { "cookie"))) {

View File

@ -38,6 +38,7 @@ extern struct reversepath *reversepath_get (char *url,
struct reversepath *reverse); struct reversepath *reverse);
void free_reversepath_list (struct reversepath *reverse); void free_reversepath_list (struct reversepath *reverse);
extern char *reverse_rewrite_url (struct conn_s *connptr, extern char *reverse_rewrite_url (struct conn_s *connptr,
orderedmap hashofheaders, char *url); orderedmap hashofheaders, char *url,
int *status);
#endif #endif

View File

@ -34,6 +34,7 @@
#include "text.h" #include "text.h"
#include "conf.h" #include "conf.h"
#include "loop.h" #include "loop.h"
#include "sblist.h"
/* /*
* Return a human readable error for getaddrinfo() and getnameinfo(). * Return a human readable error for getaddrinfo() and getnameinfo().
@ -46,6 +47,16 @@ static const char * get_gai_error (int n)
return gai_strerror (n); 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 * Bind the given socket to the supplied address. The socket is
* returned if the bind succeeded. Otherwise, -1 is returned * returned if the bind succeeded. Otherwise, -1 is returned
@ -68,7 +79,7 @@ bind_socket (int sockfd, const char *addr, int family)
n = getaddrinfo (addr, NULL, &hints, &res); n = getaddrinfo (addr, NULL, &hints, &res);
if (n != 0) { if (n != 0) {
log_message (LOG_INFO, log_message (LOG_INFO,
"bind_socket: getaddrinfo failed for %s: ", addr, get_gai_error (n)); "bind_socket: getaddrinfo failed for %s: %s (af: %s)", addr, get_gai_error (n), family_string(family));
return -1; return -1;
} }
@ -87,6 +98,36 @@ bind_socket (int sockfd, const char *addr, int family)
return sockfd; 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 * Open a connection to a remote host. It's been re-written to use
* the getaddrinfo() library function, which allows for a protocol * the getaddrinfo() library function, which allows for a protocol
@ -134,14 +175,16 @@ int opensock (const char *host, int port, const char *bind_to)
close (sockfd); close (sockfd);
continue; /* can't bind, so try again */ continue; /* can't bind, so try again */
} }
} else if (config->bind_address) { } else if (config->bind_addrs) {
if (bind_socket (sockfd, config->bind_address, if (bind_socket_list (sockfd, config->bind_addrs,
res->ai_family) < 0) { res->ai_family) < 0) {
close (sockfd); close (sockfd);
continue; /* can't bind, so try again */ continue; /* can't bind, so try again */
} }
} }
set_socket_timeout(sockfd);
if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) { if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) {
union sockaddr_union *p = (void*) res->ai_addr, u; union sockaddr_union *p = (void*) res->ai_addr, u;
int af = res->ai_addr->sa_family; int af = res->ai_addr->sa_family;

View File

@ -56,6 +56,8 @@ extern int listen_sock (const char *addr, uint16_t port, sblist* listen_fds);
extern int socket_nonblocking (int sock); extern int socket_nonblocking (int sock);
extern int socket_blocking (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 getsock_ip (int fd, char *ipaddr);
extern void getpeer_information (union sockaddr_union *addr, char *ipaddr, size_t ipaddr_len); extern void getpeer_information (union sockaddr_union *addr, char *ipaddr, size_t ipaddr_len);

View File

@ -87,9 +87,9 @@ err_minus_one:
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" " "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" "
"\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
"<html>\n" "<html>\n"
"<head><title>%s version %s run-time statistics</title></head>\n" "<head><title>%s run-time statistics</title></head>\n"
"<body>\n" "<body>\n"
"<h1>%s version %s run-time statistics</h1>\n" "<h1>%s run-time statistics</h1>\n"
"<p>\n" "<p>\n"
"Number of open connections: %lu<br />\n" "Number of open connections: %lu<br />\n"
"Number of requests: %lu<br />\n" "Number of requests: %lu<br />\n"
@ -98,13 +98,13 @@ err_minus_one:
"Number of refused connections due to high load: %lu\n" "Number of refused connections due to high load: %lu\n"
"</p>\n" "</p>\n"
"<hr />\n" "<hr />\n"
"<p><em>Generated by %s version %s.</em></p>\n" "</body>\n" "<p><em>Generated by %s.</em></p>\n" "</body>\n"
"</html>\n", "</html>\n",
PACKAGE, VERSION, PACKAGE, VERSION, PACKAGE, PACKAGE,
stats->num_open, stats->num_open,
stats->num_reqs, stats->num_reqs,
stats->num_badcons, stats->num_denied, stats->num_badcons, stats->num_denied,
stats->num_refused, PACKAGE, VERSION); stats->num_refused, PACKAGE);
if (send_http_message (connptr, 200, "OK", if (send_http_message (connptr, 200, "OK",
message_buffer) < 0) { message_buffer) < 0) {
@ -122,7 +122,7 @@ err_minus_one:
add_error_variable (connptr, "deniedconns", denied); add_error_variable (connptr, "deniedconns", denied);
add_error_variable (connptr, "refusedconns", refused); add_error_variable (connptr, "refusedconns", refused);
add_standard_vars (connptr); add_standard_vars (connptr);
send_http_headers (connptr, 200, "Statistic requested"); send_http_headers (connptr, 200, "Statistic requested", "");
send_html_file (statfile, connptr); send_html_file (statfile, connptr);
fclose (statfile); fclose (statfile);
pthread_mutex_unlock(&stats_file_lock); pthread_mutex_unlock(&stats_file_lock);

View File

@ -60,11 +60,10 @@ const char* upstream_build_error_string(enum upstream_build_error ube) {
/** /**
* Construct an upstream struct from input data. * Construct an upstream struct from input data.
*/ */
static struct upstream *upstream_build (const char *host, int port, const char *domain, static struct upstream *upstream_build (const char *host, int port, char *domain,
const char *user, const char *pass, const char *user, const char *pass,
proxy_type type, enum upstream_build_error *ube) proxy_type type, enum upstream_build_error *ube)
{ {
char *ptr;
struct upstream *up; struct upstream *up;
*ube = UBE_SUCCESS; *ube = UBE_SUCCESS;
@ -75,8 +74,8 @@ static struct upstream *upstream_build (const char *host, int port, const char *
} }
up->type = type; up->type = type;
up->host = up->domain = up->ua.user = up->pass = NULL; up->target.type = HST_NONE;
up->ip = up->mask = 0; up->host = up->ua.user = up->pass = NULL;
if (user) { if (user) {
if (type == PT_HTTP) { if (type == PT_HTTP) {
char b[BASE64ENC_BYTES((256+2)-1) + 1]; char b[BASE64ENC_BYTES((256+2)-1) + 1];
@ -121,30 +120,10 @@ static struct upstream *upstream_build (const char *host, int port, const char *
up->port = port; up->port = port;
} }
ptr = strchr (domain, '/'); if (hostspec_parse(domain, &up->target)
if (ptr) { || up->target.type == HST_NONE) {
struct in_addr addrstruct; *ube = UBE_NETMASK;
goto fail;
*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);
}
up->ip = up->ip & up->mask;
} else {
*ube = UBE_NETMASK;
goto fail;
}
} else {
up->domain = safestrdup (domain);
} }
if (type == PT_NONE) if (type == PT_NONE)
@ -160,7 +139,8 @@ fail:
safefree (up->ua.user); safefree (up->ua.user);
safefree (up->pass); safefree (up->pass);
safefree (up->host); safefree (up->host);
safefree (up->domain); if(up->target.type == HST_STRING)
safefree (up->target.address.string);
safefree (up); safefree (up);
return NULL; return NULL;
@ -170,7 +150,7 @@ fail:
* Add an entry to the upstream list * Add an entry to the upstream list
*/ */
enum upstream_build_error upstream_add ( enum upstream_build_error upstream_add (
const char *host, int port, const char *domain, const char *host, int port, char *domain,
const char *user, const char *pass, const char *user, const char *pass,
proxy_type type, struct upstream **upstream_list) proxy_type type, struct upstream **upstream_list)
{ {
@ -182,11 +162,11 @@ enum upstream_build_error upstream_add (
return ube; 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; struct upstream *tmp = *upstream_list;
while (tmp) { while (tmp) {
if (!tmp->domain && !tmp->ip) { if (tmp->target.type == HST_NONE) {
log_message (LOG_WARNING, log_message (LOG_WARNING,
"Duplicate default upstream"); "Duplicate default upstream");
goto upstream_cleanup; goto upstream_cleanup;
@ -209,7 +189,8 @@ enum upstream_build_error upstream_add (
upstream_cleanup: upstream_cleanup:
safefree (up->host); safefree (up->host);
safefree (up->domain); if(up->target.type == HST_STRING)
safefree (up->target.address.string);
safefree (up); safefree (up);
return ube; return ube;
@ -220,34 +201,12 @@ upstream_cleanup:
*/ */
struct upstream *upstream_get (char *host, struct upstream *up) struct upstream *upstream_get (char *host, struct upstream *up)
{ {
in_addr_t my_ip = INADDR_NONE;
while (up) { while (up) {
if (up->domain) { if (up->target.type == HST_NONE)
if (strcasecmp (host, up->domain) == 0) break;
break; /* exact match */
if (up->domain[0] == '.') { if (hostspec_match(host, &up->target))
char *dot = strchr (host, '.'); 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 */
}
up = up->next; up = up->next;
} }
@ -269,7 +228,8 @@ void free_upstream_list (struct upstream *up)
while (up) { while (up) {
struct upstream *tmp = up; struct upstream *tmp = up;
up = up->next; up = up->next;
safefree (tmp->domain); if(tmp->target.type == HST_STRING)
safefree (tmp->target.address.string);
safefree (tmp->host); safefree (tmp->host);
safefree (tmp); safefree (tmp);
} }

View File

@ -26,6 +26,7 @@
#define _TINYPROXY_UPSTREAM_H_ #define _TINYPROXY_UPSTREAM_H_
#include "common.h" #include "common.h"
#include "hostspec.h"
enum upstream_build_error { enum upstream_build_error {
UBE_SUCCESS = 0, UBE_SUCCESS = 0,
@ -50,7 +51,6 @@ typedef enum proxy_type {
struct upstream { struct upstream {
struct upstream *next; struct upstream *next;
char *domain; /* optional */
char *host; char *host;
union { union {
char *user; char *user;
@ -58,14 +58,14 @@ struct upstream {
} ua; } ua;
char *pass; char *pass;
int port; int port;
in_addr_t ip, mask; struct hostspec target;
proxy_type type; proxy_type type;
}; };
#ifdef UPSTREAM_SUPPORT #ifdef UPSTREAM_SUPPORT
const char *proxy_type_name(proxy_type type); const char *proxy_type_name(proxy_type type);
extern enum upstream_build_error upstream_add ( extern enum upstream_build_error upstream_add (
const char *host, int port, const char *domain, const char *host, int port, char *domain,
const char *user, const char *pass, const char *user, const char *pass,
proxy_type type, struct upstream **upstream_list); proxy_type type, struct upstream **upstream_list);
extern struct upstream *upstream_get (char *host, struct upstream *up); extern struct upstream *upstream_get (char *host, struct upstream *up);

View File

@ -39,7 +39,7 @@ send_http_message (struct conn_s *connptr, int http_code,
const char *error_title, const char *message) const char *error_title, const char *message)
{ {
static const char *headers[] = { static const char *headers[] = {
"Server: " PACKAGE "/" VERSION, "Server: " PACKAGE,
"Content-type: text/html", "Content-type: text/html",
"Connection: close" "Connection: close"
}; };

View File

@ -18,7 +18,7 @@
# this program; if not, see <http://www.gnu.org/licenses/>. # this program; if not, see <http://www.gnu.org/licenses/>.
SCRIPTS_DIR=$(pwd)/$(dirname $0) SCRIPTS_DIR=$(cd $(dirname $0) && pwd)
BASEDIR=$SCRIPTS_DIR/../.. BASEDIR=$SCRIPTS_DIR/../..
TESTS_DIR=$SCRIPTS_DIR/.. TESTS_DIR=$SCRIPTS_DIR/..
TESTENV_DIR=$TESTS_DIR/env TESTENV_DIR=$TESTS_DIR/env
@ -83,6 +83,7 @@ DefaultErrorFile "$TINYPROXY_DATA_DIR/debug.html"
ErrorFile 400 "$TINYPROXY_DATA_DIR/debug.html" ErrorFile 400 "$TINYPROXY_DATA_DIR/debug.html"
ErrorFile 403 "$TINYPROXY_DATA_DIR/debug.html" ErrorFile 403 "$TINYPROXY_DATA_DIR/debug.html"
ErrorFile 501 "$TINYPROXY_DATA_DIR/debug.html" ErrorFile 501 "$TINYPROXY_DATA_DIR/debug.html"
ErrorFile 502 "$TINYPROXY_DATA_DIR/debug.html"
StatFile "$TINYPROXY_DATA_DIR/stats.html" StatFile "$TINYPROXY_DATA_DIR/stats.html"
Logfile "$TINYPROXY_LOG_FILE" Logfile "$TINYPROXY_LOG_FILE"
PidFile "$TINYPROXY_PID_FILE" PidFile "$TINYPROXY_PID_FILE"
@ -99,6 +100,7 @@ XTinyproxy Yes
AddHeader "X-My-Header1" "Powered by Tinyproxy" AddHeader "X-My-Header1" "Powered by Tinyproxy"
AddHeader "X-My-Header2" "Powered by Tinyproxy" AddHeader "X-My-Header2" "Powered by Tinyproxy"
AddHeader "X-My-Header3" "Powered by Tinyproxy" AddHeader "X-My-Header3" "Powered by Tinyproxy"
Upstream http 255.255.255.255:65535 ".invalid"
EOF EOF
cat << 'EOF' > $TINYPROXY_FILTER_FILE cat << 'EOF' > $TINYPROXY_FILTER_FILE
@ -244,6 +246,10 @@ test "x$?" = "x0" || FAILED=$((FAILED + 1))
echo -n "requesting connect method to denied port..." echo -n "requesting connect method to denied port..."
run_failure_webclient_request 403 --method=CONNECT "$TINYPROXY_IP:$TINYPROXY_PORT" "localhost:12345" run_failure_webclient_request 403 --method=CONNECT "$TINYPROXY_IP:$TINYPROXY_PORT" "localhost:12345"
test "x$?" = "x0" || FAILED=$((FAILED + 1)) test "x$?" = "x0" || FAILED=$((FAILED + 1))
echo -n "testing unavailable backend..."
run_failure_webclient_request 502 "$TINYPROXY_IP:$TINYPROXY_PORT" "http://bogus.invalid"
test "x$?" = "x0" || FAILED=$((FAILED + 1))
} }
basic_test basic_test

View File

@ -26,9 +26,8 @@ use Pod::Usage;
my $EOL = "\015\012"; my $EOL = "\015\012";
my $VERSION = "0.1";
my $NAME = "Tinyproxy-Web-Client"; my $NAME = "Tinyproxy-Web-Client";
my $user_agent = "$NAME/$VERSION"; my $user_agent = "$NAME";
my $user_agent_header = "User-Agent: $user_agent$EOL"; my $user_agent_header = "User-Agent: $user_agent$EOL";
my $http_version = "1.0"; my $http_version = "1.0";
my $method = "GET"; my $method = "GET";

View File

@ -31,9 +31,8 @@ use Getopt::Long;
use Pod::Usage; use Pod::Usage;
use Fcntl ':flock'; # import LOCK_* constants use Fcntl ':flock'; # import LOCK_* constants
my $VERSION = "0.1";
my $NAME = "Tinyproxy-Test-Web-Server"; my $NAME = "Tinyproxy-Test-Web-Server";
my $server_header = "Server: $NAME/$VERSION"; my $server_header = "Server: $NAME";
my $EOL = "\015\012"; my $EOL = "\015\012";