test proxy supports CONNECT

There's a new 'http-proxy' server for tests that runs on a separate port
and lets clients do HTTP CONNECT to other ports on the same host to
allow us to test HTTP "tunneling" properly.

Test cases now have a <proxy> section in <verify> to check that the
proxy protocol part matches correctly.

Test case 80, 83, 95, 275, 503 and 1078 have been converted. Test 1316
was added.
This commit is contained in:
Daniel Stenberg 2011-12-17 23:47:22 +01:00
parent 585b89a6c3
commit 82180643f4
14 changed files with 597 additions and 60 deletions

View File

@ -160,6 +160,7 @@ pop3
smtp
httptls+srp
httptls+srp-ipv6
http-proxy
Give only one per line. This subsection is mandatory.
</server>
@ -275,6 +276,7 @@ Available substitute variables include:
%HOST6IP - IPv6 address of the host running this test
%HTTP6PORT - IPv6 port number of the HTTP server
%HTTPSPORT - Port number of the HTTPS server
%PROXYPORT - Port number of the HTTP proxy
%FTPPORT - Port number of the FTP server
%FTP6PORT - IPv6 port number of the FTP server
%FTPSPORT - Port number of the FTPS server
@ -321,12 +323,26 @@ changing protocol data such as port numbers or user-agent strings.
One perl op per line that operates on the protocol dump. This is pretty
advanced. Example: "s/^EPRT .*/EPRT stripped/"
</strippart>
<protocol [nonewline="yes"]>
the protocol dump curl should transmit, if 'nonewline' is set, we will cut
off the trailing newline of this given data before comparing with the one
actually sent by the client
Variables are substituted as in the <command> section.
the protocol dump curl should transmit, if 'nonewline' is set, we will cut off
the trailing newline of this given data before comparing with the one actually
sent by the client Variables are substituted as in the <command> section. The
<strip> and <strippart> rules are applied before comparisons are made.
</protocol>
<proxy [nonewline="yes"]>
The protocol dump curl should transmit to a HTTP proxy (when the http-proxy
server is used), if 'nonewline' is set, we will cut off the trailing newline
of this given data before comparing with the one actually sent by the client
Variables are substituted as in the <command> section. The <strip> and
<strippart> rules are applied before comparisons are made.
</proxy>
<stdout [mode="text"] [nonewline="yes"]>
This verifies that this data was passed to stdout. Variables are
substituted as in the <command> section.

View File

@ -81,7 +81,7 @@ test1208 test1209 test1210 test1211 \
test1220 \
test1300 test1301 test1302 test1303 test1304 test1305 \
test1306 test1307 test1308 test1309 test1310 test1311 test1312 test1313 \
test1314 test1315 test1317 test1318 \
test1314 test1315 test1316 test1317 test1318 \
test2000 test2001 test2002 test2003 test2004
EXTRA_DIST = $(TESTCASES) DISABLED

View File

@ -31,12 +31,13 @@ contents
<client>
<server>
http
http-proxy
</server>
<name>
HTTP 1.0 CONNECT with proxytunnel and downgrade GET to HTTP/1.0
</name>
<command>
--proxy1.0 %HOSTIP:%HTTPPORT -p http://%HOSTIP:%HTTPPORT/we/want/that/page/1078 http://%HOSTIP:%HTTPPORT/we/want/that/page/1078
--proxy1.0 %HOSTIP:%PROXYPORT -p http://%HOSTIP:%HTTPPORT/we/want/that/page/1078 http://%HOSTIP:%HTTPPORT/we/want/that/page/1078
</command>
</client>
@ -46,11 +47,13 @@ HTTP 1.0 CONNECT with proxytunnel and downgrade GET to HTTP/1.0
<strip>
^User-Agent:.*
</strip>
<protocol>
<proxy>
CONNECT %HOSTIP:%HTTPPORT HTTP/1.0
Host: %HOSTIP:%HTTPPORT
Proxy-Connection: Keep-Alive
</proxy>
<protocol>
GET /we/want/that/page/1078 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*

62
tests/data/test1316 Normal file
View File

@ -0,0 +1,62 @@
<testcase>
<info>
<keywords>
FTP
PASV
LIST
HTTP CONNECT
</keywords>
</info>
#
# Server-side
<reply>
# When doing LIST, we get the default list output hard-coded in the test
# FTP server
<datacheck>
HTTP/1.1 200 Mighty fine indeed
HTTP/1.1 200 Mighty fine indeed
total 20
drwxr-xr-x 8 98 98 512 Oct 22 13:06 .
drwxr-xr-x 8 98 98 512 Oct 22 13:06 ..
drwxr-xr-x 2 98 98 512 May 2 1996 .NeXT
-r--r--r-- 1 0 1 35 Jul 16 1996 README
lrwxrwxrwx 1 0 1 7 Dec 9 1999 bin -> usr/bin
dr-xr-xr-x 2 0 1 512 Oct 1 1997 dev
drwxrwxrwx 2 98 98 512 May 29 16:04 download.html
dr-xr-xr-x 2 0 1 512 Nov 30 1995 etc
drwxrwxrwx 2 98 1 512 Oct 30 14:33 pub
dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr
</datacheck>
</reply>
#
# Client-side
<client>
<server>
ftp
http-proxy
</server>
<name>
FTP LIST tunneled through HTTP proxy
</name>
<command>
ftp://%HOSTIP:%FTPPORT/ -p -x %HOSTIP:%PROXYPORT
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
USER anonymous
PASS ftp@example.com
PWD
EPSV
TYPE A
LIST
QUIT
</protocol>
</verify>
</testcase>

View File

@ -41,12 +41,13 @@ contents
<client>
<server>
http
http-proxy
</server>
<name>
HTTP CONNECT with proxytunnel getting two URLs from the same host
</name>
<command>
http://remotesite.com/we/want/that/page/275 -p -x %HOSTIP:%HTTPPORT --user iam:myself --proxy-user youare:yourself http://remotesite.com/we/want/that/page/275
http://remotesite.com:%HTTPPORT/we/want/that/page/275 -p -x %HOSTIP:%PROXYPORT --user iam:myself --proxy-user youare:yourself http://remotesite.com:%HTTPPORT/we/want/that/page/275
</command>
</client>
@ -56,21 +57,23 @@ http://remotesite.com/we/want/that/page/275 -p -x %HOSTIP:%HTTPPORT --user iam:m
<strip>
^User-Agent:.*
</strip>
<protocol>
CONNECT remotesite.com:80 HTTP/1.1
Host: remotesite.com:80
<proxy>
CONNECT remotesite.com:%HTTPPORT HTTP/1.1
Host: remotesite.com:%HTTPPORT
Proxy-Authorization: Basic eW91YXJlOnlvdXJzZWxm
User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3
Proxy-Connection: Keep-Alive
</proxy>
<protocol>
GET /we/want/that/page/275 HTTP/1.1
Authorization: Basic aWFtOm15c2VsZg==
Host: remotesite.com
Host: remotesite.com:%HTTPPORT
Accept: */*
GET /we/want/that/page/275 HTTP/1.1
Authorization: Basic aWFtOm15c2VsZg==
Host: remotesite.com
Host: remotesite.com:%HTTPPORT
Accept: */*
</protocol>

View File

@ -37,6 +37,7 @@ ETag: "21025-dc7-39462498"
<client>
<server>
http
http-proxy
</server>
# tool is what to use instead of 'curl'
<tool>
@ -47,7 +48,7 @@ lib503
simple multi http:// through proxytunnel with authentication info
</name>
<command>
http://%HOSTIP:%HTTPSPORT/503 %HOSTIP:%HTTPPORT
http://%HOSTIP:%HTTPPORT/503 %HOSTIP:%PROXYPORT
</command>
<file name="log/test503.txt">
foo
@ -60,15 +61,17 @@ moo
# Verify data after the test has been "shot"
<verify>
<protocol>
CONNECT %HOSTIP:%HTTPSPORT HTTP/1.1
Host: %HOSTIP:%HTTPSPORT
<proxy>
CONNECT %HOSTIP:%HTTPPORT HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Proxy-Authorization: Basic dGVzdDppbmc=
Proxy-Connection: Keep-Alive
</proxy>
<protocol>
GET /503 HTTP/1.1
Authorization: Basic dGVzdDppbmc=
Host: %HOSTIP:%HTTPSPORT
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>

View File

@ -43,12 +43,13 @@ contents
<client>
<server>
http
http-proxy
</server>
<name>
HTTP 1.0 CONNECT with proxytunnel and host Basic authentication
HTTP 1.0 CONNECT with proxytunnel and proxy+host Basic authentication
</name>
<command>
http://%HOSTIP:%HTTPPORT/we/want/that/page/80 -p --proxy1.0 %HOSTIP:%HTTPPORT --user iam:myself --proxy-user youare:yourself
http://%HOSTIP:%HTTPPORT/we/want/that/page/80 -p --proxy1.0 %HOSTIP:%PROXYPORT --user iam:myself --proxy-user youare:yourself
</command>
</client>
@ -58,13 +59,15 @@ http://%HOSTIP:%HTTPPORT/we/want/that/page/80 -p --proxy1.0 %HOSTIP:%HTTPPORT --
<strip>
^User-Agent:.*
</strip>
<protocol>
<proxy>
CONNECT %HOSTIP:%HTTPPORT HTTP/1.0
Host: %HOSTIP:%HTTPPORT
Proxy-Authorization: Basic eW91YXJlOnlvdXJzZWxm
User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3
Proxy-Connection: Keep-Alive
</proxy>
<protocol>
GET /we/want/that/page/80 HTTP/1.1
Authorization: Basic aWFtOm15c2VsZg==
User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3

View File

@ -40,12 +40,13 @@ contents
<client>
<server>
http
http-proxy
</server>
<name>
HTTP over proxy-tunnel with site authentication
</name>
<command>
http://%HOSTIP:%HTTPPORT/we/want/that/page/83 -p -x %HOSTIP:%HTTPPORT --user iam:myself
http://%HOSTIP:%HTTPPORT/we/want/that/page/83 -p -x %HOSTIP:%PROXYPORT --user iam:myself
</command>
</client>
@ -55,12 +56,14 @@ http://%HOSTIP:%HTTPPORT/we/want/that/page/83 -p -x %HOSTIP:%HTTPPORT --user iam
<strip>
^User-Agent:.*
</strip>
<protocol>
<proxy>
CONNECT %HOSTIP:%HTTPPORT HTTP/1.1
User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3
Host: %HOSTIP:%HTTPPORT
Proxy-Connection: Keep-Alive
</proxy>
<protocol>
GET /we/want/that/page/83 HTTP/1.1
Authorization: Basic aWFtOm15c2VsZg==
User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3

View File

@ -40,12 +40,13 @@ contents
<client>
<server>
http
http-proxy
</server>
<name>
HTTP over proxytunnel using POST
</name>
<command>
http://%HOSTIP:%HTTPPORT/we/want/that/page/95 -p -x %HOSTIP:%HTTPPORT -d "datatopost=ohthatsfunyesyes"
http://%HOSTIP:%HTTPPORT/we/want/that/page/95 -p -x %HOSTIP:%PROXYPORT -d "datatopost=ohthatsfunyesyes"
</command>
</client>
@ -55,12 +56,14 @@ http://%HOSTIP:%HTTPPORT/we/want/that/page/95 -p -x %HOSTIP:%HTTPPORT -d "datato
<strip>
^User-Agent:.*
</strip>
<protocol nonewline="yes">
<proxy>
CONNECT %HOSTIP:%HTTPPORT HTTP/1.1
User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3
Host: %HOSTIP:%HTTPPORT
Proxy-Connection: Keep-Alive
</proxy>
<protocol nonewline="yes">
POST /we/want/that/page/95 HTTP/1.1
User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3
Host: %HOSTIP:%HTTPPORT

View File

@ -6,7 +6,7 @@
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
# Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
@ -40,6 +40,7 @@ my $idnum = 1; # dafault http server instance number
my $proto = 'http'; # protocol the http server speaks
my $pidfile; # http server pid file
my $logfile; # http server log file
my $connect; # IP to connect to on CONNECT
my $srcdir;
my $fork;
my $gopher = 0;
@ -82,6 +83,12 @@ while(@ARGV) {
shift @ARGV;
}
}
elsif($ARGV[0] eq '--connect') {
if($ARGV[1]) {
$connect = $ARGV[1];
shift @ARGV;
}
}
elsif($ARGV[0] eq '--id') {
if($ARGV[1] =~ /^(\d+)$/) {
$idnum = $1 if($1 > 0);
@ -112,7 +119,12 @@ if(!$logfile) {
$flags .= "--gopher " if($gopher);
$flags .= "--fork " if(defined($fork));
$flags .= "--connect $connect " if($connect);
$flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" ";
$flags .= "--ipv$ipvnum --port $port --srcdir \"$srcdir\"";
if($verbose) {
print STDERR "RUN: server/sws $flags\n";
}
exec("server/sws $flags");

View File

@ -6,7 +6,7 @@
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
# Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
@ -138,6 +138,7 @@ my $GOPHERPORT; # Gopher
my $GOPHER6PORT; # Gopher IPv6 server port
my $HTTPTLSPORT; # HTTP TLS (non-stunnel) server port
my $HTTPTLS6PORT; # HTTP TLS (non-stunnel) IPv6 server port
my $HTTPPROXYPORT; # HTTP proxy port, when using CONNECT
my $srcdir = $ENV{'srcdir'} || '.';
my $CURL="../src/curl".exe_ext(); # what curl executable to run on the tests
@ -151,6 +152,8 @@ my $LIBDIR="./libtest";
my $UNITDIR="./unit";
# TODO: change this to use server_inputfilename()
my $SERVERIN="$LOGDIR/server.input"; # what curl sent the server
my $SERVER2IN="$LOGDIR/server2.input"; # what curl sent the second server
my $PROXYIN="$LOGDIR/proxy.input"; # what curl sent the proxy
my $CURLLOG="$LOGDIR/curl.log"; # all command lines run
my $FTPDCMD="$LOGDIR/ftpserver.cmd"; # copy ftp server instructions here
my $SERVERLOGS_LOCK="$LOGDIR/serverlogs.lock"; # server logs advisor read lock
@ -1154,7 +1157,7 @@ sub responsiveserver {
# start the http server
#
sub runhttpserver {
my ($proto, $verbose, $ipv6, $port) = @_;
my ($proto, $verbose, $alt, $port) = @_;
my $ip = $HOSTIP;
my $ipvnum = 4;
my $idnum = 1;
@ -1164,11 +1167,15 @@ sub runhttpserver {
my $logfile;
my $flags = "";
if($ipv6) {
if($alt eq "ipv6") {
# if IPv6, use a different setup
$ipvnum = 6;
$ip = $HOST6IP;
}
elsif($alt eq "proxy") {
# basically the same, but another ID
$idnum = 2;
}
$server = servername_id($proto, $ipvnum, $idnum);
@ -1191,6 +1198,7 @@ sub runhttpserver {
$flags .= "--fork " if($forkserver);
$flags .= "--gopher " if($proto eq "gopher");
$flags .= "--connect $HOSTIP " if($alt eq "proxy");
$flags .= "--verbose " if($debugprotocol);
$flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" ";
$flags .= "--id $idnum " if($idnum > 1);
@ -1974,16 +1982,19 @@ sub runsocksserver {
# be used to verify that a server present in %run hash is still functional
#
sub responsive_http_server {
my ($proto, $verbose, $ipv6, $port) = @_;
my ($proto, $verbose, $alt, $port) = @_;
my $ip = $HOSTIP;
my $ipvnum = 4;
my $idnum = 1;
if($ipv6) {
if($alt eq "ipv6") {
# if IPv6, use a different setup
$ipvnum = 6;
$ip = $HOST6IP;
}
elsif($alt eq "proxy") {
$idnum = 2;
}
return &responsiveserver($proto, $ipvnum, $idnum, $ip, $port);
}
@ -2280,6 +2291,9 @@ sub checksystem {
# compiled in because the <features> test will fail.
push @protocols, map($_ . '-ipv6', @protocols);
# 'http-proxy' is used in test cases to do CONNECT through
push @protocols, 'http-proxy';
# 'none' is used in test cases to mean no server
push @protocols, 'none';
}
@ -2505,6 +2519,7 @@ sub subVariables {
$$thing =~ s/%HTTP6PORT/$HTTP6PORT/g;
$$thing =~ s/%HTTPSPORT/$HTTPSPORT/g;
$$thing =~ s/%HTTPPORT/$HTTPPORT/g;
$$thing =~ s/%PROXYPORT/$HTTPPROXYPORT/g;
$$thing =~ s/%IMAP6PORT/$IMAP6PORT/g;
$$thing =~ s/%IMAPPORT/$IMAPPORT/g;
@ -2896,6 +2911,9 @@ sub singletest {
# this is the valid protocol blurb curl should generate
my @protocol= fixarray ( getpart("verify", "protocol") );
# this is the valid protocol blurb curl should generate to a proxy
my @proxyprot = fixarray ( getpart("verify", "proxy") );
# redirected stdout/stderr to these files
$STDOUT="$LOGDIR/stdout$testnum";
$STDERR="$LOGDIR/stderr$testnum";
@ -2935,6 +2953,8 @@ sub singletest {
# remove server output logfile
unlink($SERVERIN);
unlink($SERVER2IN);
unlink($PROXYIN);
if(@ftpservercmd) {
# write the instructions to file
@ -3428,6 +3448,56 @@ sub singletest {
$ok .= "-"; # protocol not checked
}
if(@proxyprot) {
# Verify the sent proxy request
my @out = loadarray($PROXYIN);
# what to cut off from the live protocol sent by curl, we use the
# same rules as for <protocol>
my @strip = getpart("verify", "strip");
my @protstrip=@proxyprot;
# check if there's any attributes on the verify/protocol section
my %hash = getpartattr("verify", "proxy");
if($hash{'nonewline'}) {
# Yes, we must cut off the final newline from the final line
# of the protocol data
chomp($protstrip[$#protstrip]);
}
for(@strip) {
# strip off all lines that match the patterns from both arrays
chomp $_;
@out = striparray( $_, \@out);
@protstrip= striparray( $_, \@protstrip);
}
# what parts to cut off from the protocol
my @strippart = getpart("verify", "strippart");
my $strip;
for $strip (@strippart) {
chomp $strip;
for(@out) {
eval $strip;
}
}
$res = compare("proxy", \@out, \@protstrip);
if($res) {
# timestamp test result verification end
$timevrfyend{$testnum} = Time::HiRes::time() if($timestats);
return 1;
}
$ok .= "P";
}
else {
$ok .= "-"; # protocol not checked
}
my @outfile=getpart("verify", "file");
if(@outfile) {
# we're supposed to verify a dynamically generated file!
@ -3718,7 +3788,8 @@ sub startservers {
if($pid <= 0) {
return "failed starting GOPHER server";
}
printf ("* pid gopher => %d %d\n", $pid, $pid2) if($verbose);
logmsg sprintf ("* pid gopher => %d %d\n", $pid, $pid2)
if($verbose);
$run{'gopher'}="$pid $pid2";
}
}
@ -3750,17 +3821,35 @@ sub startservers {
if($pid <= 0) {
return "failed starting HTTP server";
}
printf ("* pid http => %d %d\n", $pid, $pid2) if($verbose);
logmsg sprintf ("* pid http => %d %d\n", $pid, $pid2)
if($verbose);
$run{'http'}="$pid $pid2";
}
}
elsif($what eq "http-proxy") {
if($torture && $run{'http-proxy'} &&
!responsive_http_server("http", $verbose, "proxy",
$HTTPPROXYPORT)) {
stopserver('http-proxy');
}
if(!$run{'http-proxy'}) {
($pid, $pid2) = runhttpserver("http", $verbose, "proxy",
$HTTPPROXYPORT);
if($pid <= 0) {
return "failed starting HTTP-proxy server";
}
logmsg sprintf ("* pid http-proxy => %d %d\n", $pid, $pid2)
if($verbose);
$run{'http-proxy'}="$pid $pid2";
}
}
elsif($what eq "http-ipv6") {
if($torture && $run{'http-ipv6'} &&
!responsive_http_server("http", $verbose, "IPv6", $HTTP6PORT)) {
stopserver('http-ipv6');
}
if(!$run{'http-ipv6'}) {
($pid, $pid2) = runhttpserver("http", $verbose, "IPv6",
($pid, $pid2) = runhttpserver("http", $verbose, "ipv6",
$HTTP6PORT);
if($pid <= 0) {
return "failed starting HTTP-IPv6 server";
@ -4021,7 +4110,7 @@ sub serverfortest {
return "curl lacks $tlsext support";
}
else {
return "curl lacks $server support";
return "curl lacks $server server support";
}
}
}
@ -4411,6 +4500,7 @@ $GOPHERPORT = $base++; # Gopher IPv4 server port
$GOPHER6PORT = $base++; # Gopher IPv6 server port
$HTTPTLSPORT = $base++; # HTTP TLS (non-stunnel) server port
$HTTPTLS6PORT = $base++; # HTTP TLS (non-stunnel) IPv6 server port
$HTTPPROXYPORT = $base++; # HTTP proxy port, when using CONNECT
#######################################################################
# clear and create logging directory:

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -79,6 +79,7 @@ static bool use_ipv6 = FALSE;
static bool use_gopher = FALSE;
static const char *ipv_inuse = "IPv4";
static int serverlogslocked = 0;
static bool is_proxy = FALSE;
#define REQBUFSIZ 150000
#define REQBUFSIZ_TXT "149999"
@ -118,6 +119,7 @@ struct httprequest {
int prot_version; /* HTTP version * 10 */
bool pipelining; /* true if request is pipelined */
int callcount; /* times ProcessRequest() gets called */
int connect_port; /* the port number CONNECT used */
};
static int ProcessRequest(struct httprequest *req);
@ -136,6 +138,11 @@ const char *serverlogfile = DEFAULT_LOGFILE;
#define REQUEST_DUMP "log/server.input"
#define RESPONSE_DUMP "log/server.response"
/* when told to run as proxy, we store the logs in different files so that
they can co-exist with the same program running as a "server" */
#define REQUEST_PROXY_DUMP "log/proxy.input"
#define RESPONSE_PROXY_DUMP "log/proxy.response"
/* very-big-path support */
#define MAXDOCNAMELEN 140000
#define MAXDOCNAMELEN_TXT "139999"
@ -476,25 +483,28 @@ static int ProcessRequest(struct httprequest *req)
else {
if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
doc, &prot_major, &prot_minor) == 3) {
char *portp;
sprintf(logbuf, "Received a CONNECT %s HTTP/%d.%d request",
doc, prot_major, prot_minor);
logmsg("%s", logbuf);
portp = strchr(doc, ':');
if(portp && (*(portp+1) != '\0') && ISDIGIT(*(portp+1)))
req->connect_port = strtol(portp+1, NULL, 10);
else
req->connect_port = 0;
if(req->prot_version == 10)
req->open = FALSE; /* HTTP 1.0 closes connection by default */
if(!strncmp(doc, "bad", 3))
/* if the host name starts with bad, we fake an error here */
req->testno = DOCNUMBER_BADCONNECT;
else if(!strncmp(doc, "test", 4)) {
else if(!strncmp(doc, "test", 4))
/* if the host name starts with test, the port number used in the
CONNECT line will be used as test number! */
char *portp = strchr(doc, ':');
if(portp && (*(portp+1) != '\0') && ISDIGIT(*(portp+1)))
req->testno = strtol(portp+1, NULL, 10);
else
req->testno = DOCNUMBER_CONNECT;
}
req->testno = req->connect_port?req->connect_port:DOCNUMBER_CONNECT;
else
req->testno = DOCNUMBER_CONNECT;
}
@ -707,6 +717,7 @@ static void storerequest(char *reqbuf, size_t totalsize)
size_t written;
size_t writeleft;
FILE *dump;
const char *dumpfile=is_proxy?REQUEST_PROXY_DUMP:REQUEST_DUMP;
if (reqbuf == NULL)
return;
@ -714,12 +725,12 @@ static void storerequest(char *reqbuf, size_t totalsize)
return;
do {
dump = fopen(REQUEST_DUMP, "ab");
dump = fopen(dumpfile, "ab");
} while ((dump == NULL) && ((error = ERRNO) == EINTR));
if (dump == NULL) {
logmsg("Error opening file %s error: %d %s",
REQUEST_DUMP, error, strerror(error));
logmsg("Failed to write request input to " REQUEST_DUMP);
dumpfile, error, strerror(error));
logmsg("Failed to write request input ");
return;
}
@ -734,12 +745,12 @@ static void storerequest(char *reqbuf, size_t totalsize)
} while ((writeleft > 0) && ((error = ERRNO) == EINTR));
if(writeleft == 0)
logmsg("Wrote request (%zu bytes) input to " REQUEST_DUMP, totalsize);
logmsg("Wrote request (%zu bytes) input to %s", totalsize, dumpfile);
else if(writeleft > 0) {
logmsg("Error writing file %s error: %d %s",
REQUEST_DUMP, error, strerror(error));
dumpfile, error, strerror(error));
logmsg("Wrote only (%zu bytes) of (%zu bytes) request input to %s",
totalsize-writeleft, totalsize, REQUEST_DUMP);
totalsize-writeleft, totalsize, dumpfile);
}
storerequest_cleanup:
@ -749,7 +760,7 @@ storerequest_cleanup:
} while(res && ((error = ERRNO) == EINTR));
if(res)
logmsg("Error closing file %s error: %d %s",
REQUEST_DUMP, error, strerror(error));
dumpfile, error, strerror(error));
}
/* return 0 on success, non-zero on failure */
@ -788,6 +799,7 @@ static int get_request(curl_socket_t sock, struct httprequest *req)
req->prot_version = 0;
req->pipelining = FALSE;
req->callcount = 0;
req->connect_port = 0;
/*** end of httprequest init ***/
@ -878,7 +890,7 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
size_t responsesize;
int error = 0;
int res;
const char *responsedump = is_proxy?RESPONSE_PROXY_DUMP:RESPONSE_DUMP;
static char weare[256];
char partbuf[80]="data";
@ -1026,12 +1038,11 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
else
prevbounce = FALSE;
dump = fopen(RESPONSE_DUMP, "ab");
dump = fopen(responsedump, "ab");
if(!dump) {
error = ERRNO;
logmsg("fopen() failed with error: %d %s", error, strerror(error));
logmsg("Error opening file: %s", RESPONSE_DUMP);
logmsg("couldn't create logfile: " RESPONSE_DUMP);
logmsg("Error opening file: %s", responsedump);
if(ptr)
free(ptr);
if(cmd)
@ -1073,7 +1084,7 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
} while(res && ((error = ERRNO) == EINTR));
if(res)
logmsg("Error closing file %s error: %d %s",
RESPONSE_DUMP, error, strerror(error));
responsedump, error, strerror(error));
if(got_exit_signal) {
if(ptr)
@ -1093,8 +1104,8 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
return -1;
}
logmsg("Response sent (%zu bytes) and written to " RESPONSE_DUMP,
responsesize);
logmsg("Response sent (%zu bytes) and written to %s",
responsesize, responsedump);
if(ptr)
free(ptr);
@ -1146,6 +1157,287 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
return 0;
}
static curl_socket_t connect_to(const char *ipaddr, int port)
{
int flag;
struct sockaddr_in sin;
curl_socket_t serverfd;
unsigned long hostaddr;
hostaddr = inet_addr(ipaddr);
if(hostaddr == ( in_addr_t)-1)
return -1;
logmsg("about to connect to %s:%d", ipaddr, port);
serverfd = socket(AF_INET, SOCK_STREAM, 0);
#ifdef TCP_NODELAY
/*
* Disable the Nagle algorithm
*/
flag = 1;
if (setsockopt(serverfd, IPPROTO_TCP, TCP_NODELAY,
(void *)&flag, sizeof(flag)) == -1) {
logmsg("====> TCP_NODELAY for server conection failed");
}
#endif
sin.sin_family = AF_INET;
sin.sin_port = htons((short)port);
sin.sin_addr.s_addr = hostaddr;
if (connect(serverfd, (struct sockaddr*)(&sin),
sizeof(struct sockaddr_in)) != 0) {
fprintf(stderr, "failed to connect!\n");
return -1;
}
logmsg("connected fine to %s:%d, now tunnel!", ipaddr, port);
return serverfd;
}
/*
* A CONNECT has been received, a CONNECT response has been sent.
*
* This function needs to connect to the server, and then pass data between
* the client and the server back and forth until the connection is closed by
* either end.
*
* When doing FTP through a CONNECT proxy, we expect that the data connection
* will be setup while the first connect is still being kept up. Therefor we
* must accept a new connection and deal with it appropriately.
*/
#define data_or_ctrl(x) ((x)?"DATA":"CTRL")
static int http_connect(curl_socket_t infd,
curl_socket_t rootfd,
struct httprequest *req,
const char *ipaddr)
{
curl_socket_t serverfd[2];
curl_socket_t clientfd[2];
curl_socket_t datafd = CURL_SOCKET_BAD;
int toc[2] = {0, 0}; /* number of bytes to client */
int tos[2] = {0, 0}; /* number of bytes to server */
char readclient[2][256];
char readserver[2][256];
bool poll_client[2] = { TRUE, TRUE };
bool poll_server[2] = { TRUE, TRUE };
int control=0;
int i;
sleep(1); /* sleep here to make sure the client gets the CONNECT response
first and separate from the data that might follow here */
clientfd[0] = infd;
clientfd[1] = CURL_SOCKET_BAD;
serverfd[0] = connect_to(ipaddr, req->connect_port);
if(CURL_SOCKET_BAD == serverfd[0])
return 1; /* failure */
serverfd[1] = CURL_SOCKET_BAD; /* nothing there (yet) */
/* connected, now tunnel */
while(1) {
fd_set input;
fd_set output;
struct timeval timeout={1,0};
ssize_t rc;
int maxfd=0;
int used;
FD_ZERO(&input);
FD_ZERO(&output);
if(CURL_SOCKET_BAD != rootfd) {
FD_SET(rootfd, &input); /* monitor this for new connections */
maxfd=rootfd;
}
/* set sockets to wait for */
for(i=0; i<=control; i++) {
int mostfd = clientfd[i] > serverfd[i]? clientfd[i]: serverfd[i];
used = 0;
if(mostfd > maxfd)
maxfd = mostfd;
if(poll_client[i]) {
FD_SET(clientfd[i], &input);
used |= 1 << (i*4);
}
if(poll_server[i]) {
FD_SET(serverfd[i], &input);
used |= 2 << (i*4);
}
if(toc[i]) { /* if there is data to client, wait until we can write */
FD_SET(clientfd[i], &output);
used |= 4 << (i*4);
}
if(tos[i]) { /* if there is data to server, wait until we can write */
FD_SET(serverfd[i], &output);
used |= 8 << (i*4);
}
}
rc = select(maxfd+1, &input, &output, NULL, &timeout);
if(rc > 0) {
/* socket action */
size_t len;
int precontrol;
if((CURL_SOCKET_BAD != rootfd) &&
FD_ISSET(rootfd, &input)) {
/* a new connection! */
struct httprequest req2;
datafd = accept(rootfd, NULL, NULL);
if(CURL_SOCKET_BAD == datafd)
return 4; /* error! */
logmsg("====> Client connect DATA");
if(get_request(datafd, &req2))
/* non-zero means error, break out of loop */
break;
send_doc(datafd, &req2);
if(DOCNUMBER_CONNECT != req2.testno) {
/* eeek, not a CONNECT */
close(datafd);
break;
}
/* deal with the new connection */
rootfd = CURL_SOCKET_BAD; /* prevent new connections */
clientfd[1] = datafd;
/* connect to the server */
serverfd[1] = connect_to(ipaddr, req2.connect_port);
if(serverfd[1] == CURL_SOCKET_BAD) {
/* BADNESS, bail out */
break;
}
control = 1; /* now we have two connections to work with */
}
/* store the value before the loop starts */
precontrol = control;
for(i=0; i<=control; i++) {
len = sizeof(readclient[i])-tos[i];
if(len && FD_ISSET(clientfd[i], &input)) {
/* read from client */
rc = recv(clientfd[i], &readclient[i][tos[i]], len, 0);
if(rc <= 0) {
logmsg("[%s] got %d at %s:%d, STOP READING client", data_or_ctrl(i),
rc, __FILE__, __LINE__);
poll_client[i] = FALSE;
}
else {
logmsg("[%s] READ %d bytes from client", data_or_ctrl(i), rc);
logmsg("[%s] READ \"%s\"", data_or_ctrl(i),
data_to_hex(&readclient[i][tos[i]], rc));
tos[i] += rc;
}
}
len = sizeof(readserver[i])-toc[i];
if(len && FD_ISSET(serverfd[i], &input)) {
/* read from server */
rc = recv(serverfd[i], &readserver[i][toc[i]], len, 0);
if(rc <= 0) {
logmsg("[%s] got %d at %s:%d, STOP READING server", data_or_ctrl(i),
rc, __FILE__, __LINE__);
poll_server[i] = FALSE;
}
else {
logmsg("[%s] READ %d bytes from server", data_or_ctrl(i), rc);
logmsg("[%s] READ \"%s\"", data_or_ctrl(i),
data_to_hex(&readserver[i][toc[i]], rc));
toc[i] += rc;
}
}
if(toc[i] && FD_ISSET(clientfd[i], &output)) {
/* write to client */
rc = send(clientfd[i], readserver[i], toc[i], 0);
if(rc <= 0) {
logmsg("[%s] got %d at %s:%d", data_or_ctrl(i),
rc, __FILE__, __LINE__);
control--;
break;
}
logmsg("[%s] SENT %d bytes to client", data_or_ctrl(i), rc);
logmsg("[%s] SENT \"%s\"", data_or_ctrl(i),
data_to_hex(readserver[i], rc));
if(toc[i] - rc)
memmove(&readserver[i][0], &readserver[i][rc], toc[i]-rc);
toc[i] -= rc;
}
if(tos[i] && FD_ISSET(serverfd[i], &output)) {
/* write to server */
rc = send(serverfd[i], readclient[i], tos[i], 0);
if(rc <= 0) {
logmsg("[%s] got %d at %s:%d", data_or_ctrl(i),
rc, __FILE__, __LINE__);
control--;
break;
}
logmsg("[%s] SENT %d bytes to server", data_or_ctrl(i), rc);
logmsg("[%s] SENT \"%s\"", data_or_ctrl(i),
data_to_hex(readclient[i], rc));
if(tos - rc)
memmove(&readclient[i][0], &readclient[i][rc], tos[i]-rc);
tos[i] -= rc;
}
if(!toc[i] && !poll_server[i]) {
/* nothing to send to the client is left, and server polling is
switched off, bail out */
logmsg("[%s] ENDING1", data_or_ctrl(i));
control--;
}
if(!tos[i] && !poll_client[i]) {
/* nothing to send to the server is left, and client polling is
switched off, bail out */
logmsg("[%s] ENDING2", data_or_ctrl(i));
control--;
}
}
if(precontrol > control) {
/* if the value was decremented we close the "lost" sockets */
if(serverfd[precontrol] != CURL_SOCKET_BAD)
shutdown(serverfd[precontrol], SHUT_RDWR);
if(clientfd[precontrol] != CURL_SOCKET_BAD)
shutdown(clientfd[precontrol], SHUT_RDWR);
sleep(1);
if(serverfd[precontrol] != CURL_SOCKET_BAD)
close(serverfd[precontrol]);
if(clientfd[precontrol] != CURL_SOCKET_BAD)
close(clientfd[precontrol]);
}
if(control < 0)
break;
}
}
#if 0
/* close all sockets we created */
for(i=0; i<2; i++) {
if(serverfd[i] != CURL_SOCKET_BAD)
close(serverfd[i]);
if(clientfd[i] != CURL_SOCKET_BAD)
close(clientfd[i]);
}
#endif
return 0;
}
int main(int argc, char *argv[])
{
@ -1161,6 +1453,7 @@ int main(int argc, char *argv[])
int error;
int arg=1;
long pid;
const char *hostport = "127.0.0.1";
#ifdef CURL_SWS_FORK_ENABLED
bool use_fork = FALSE;
#endif
@ -1238,6 +1531,17 @@ int main(int argc, char *argv[])
arg++;
}
}
else if(!strcmp("--connect", argv[arg])) {
/* store the connect host, but also use this as a hint that we
run as a proxy and do a few different internal choices */
arg++;
if(argc>arg) {
hostport = argv[arg];
arg++;
is_proxy = TRUE;
logmsg("Run as proxy, CONNECT to %s", hostport);
}
}
else {
puts("Usage: sws [option]\n"
" --version\n"
@ -1247,6 +1551,7 @@ int main(int argc, char *argv[])
" --ipv6\n"
" --port [port]\n"
" --srcdir [path]\n"
" --connect [ip4-addr]\n"
" --gopher\n"
" --fork");
return 0;
@ -1316,7 +1621,7 @@ int main(int argc, char *argv[])
use_gopher?"GOPHER":"HTTP", ipv_inuse, (int)port);
/* start accepting connections */
rc = listen(sock, 5);
rc = listen(sock, 2);
if(0 != rc) {
error = SOCKERRNO;
logmsg("listen() failed with error: (%d) %s",
@ -1417,6 +1722,12 @@ int main(int argc, char *argv[])
if(got_exit_signal)
break;
if(DOCNUMBER_CONNECT == req.testno) {
/* a CONNECT request, setup and talk the tunnel */
http_connect(msgsock, sock, &req, hostport);
break;
}
if((req.testno < 0) && (req.testno != DOCNUMBER_CONNECT)) {
logmsg("special request received, no persistency");
break;

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -61,6 +61,33 @@
const struct in6_addr in6addr_any = {{ IN6ADDR_ANY_INIT }};
#endif
/* This function returns a pointer to STATIC memory. It converts the given
* binary lump to a hex formatted string usable for output in logs or
* whatever.
*/
char *data_to_hex(char *data, size_t len)
{
static char buf[256*3];
size_t i;
char *optr = buf;
char *iptr = data;
if(len > 255)
len = 255;
for(i=0; i < len; i++) {
if((data[i] >= 0x20) && (data[i] < 0x7f))
*optr++ = *iptr++;
else {
sprintf(optr, "%%%02x", *iptr++);
optr+=3;
}
}
*optr=0; /* in case no sprintf() was used */
return buf;
}
void logmsg(const char *msg, ...)
{
va_list ap;

View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -22,6 +22,7 @@
*
***************************************************************************/
char *data_to_hex(char *data, size_t len);
void logmsg(const char *msg, ...);
#define TEST_DATA_PATH "%s/data/test%ld"