From 82180643f4886d47816cf654f2ee46114e9c296f Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 17 Dec 2011 23:47:22 +0100 Subject: 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 section in 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. --- tests/FILEFORMAT | 24 +++- tests/data/Makefile.am | 2 +- tests/data/test1078 | 7 +- tests/data/test1316 | 62 +++++++++ tests/data/test275 | 15 ++- tests/data/test503 | 13 +- tests/data/test80 | 9 +- tests/data/test83 | 7 +- tests/data/test95 | 7 +- tests/httpserver.pl | 14 +- tests/runtests.pl | 108 +++++++++++++-- tests/server/sws.c | 357 +++++++++++++++++++++++++++++++++++++++++++++---- tests/server/util.c | 29 +++- tests/server/util.h | 3 +- 14 files changed, 597 insertions(+), 60 deletions(-) create mode 100644 tests/data/test1316 (limited to 'tests') diff --git a/tests/FILEFORMAT b/tests/FILEFORMAT index ec8506e25..5432b4378 100644 --- a/tests/FILEFORMAT +++ b/tests/FILEFORMAT @@ -160,6 +160,7 @@ pop3 smtp httptls+srp httptls+srp-ipv6 +http-proxy Give only one per line. This subsection is mandatory. @@ -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/" + -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 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 section. The + and rules are applied before comparisons are made. + + + + +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 section. The and + rules are applied before comparisons are made. + + + This verifies that this data was passed to stdout. Variables are substituted as in the section. diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 53d94841f..6b661a216 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -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 diff --git a/tests/data/test1078 b/tests/data/test1078 index e2355e3ae..cacdf4dd4 100644 --- a/tests/data/test1078 +++ b/tests/data/test1078 @@ -31,12 +31,13 @@ contents http +http-proxy HTTP 1.0 CONNECT with proxytunnel and downgrade GET to HTTP/1.0 ---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 @@ -46,11 +47,13 @@ HTTP 1.0 CONNECT with proxytunnel and downgrade GET to HTTP/1.0 ^User-Agent:.* - + CONNECT %HOSTIP:%HTTPPORT HTTP/1.0 Host: %HOSTIP:%HTTPPORT Proxy-Connection: Keep-Alive + + GET /we/want/that/page/1078 HTTP/1.1 Host: %HOSTIP:%HTTPPORT Accept: */* diff --git a/tests/data/test1316 b/tests/data/test1316 new file mode 100644 index 000000000..d485c0553 --- /dev/null +++ b/tests/data/test1316 @@ -0,0 +1,62 @@ + + + +FTP +PASV +LIST +HTTP CONNECT + + +# +# Server-side + +# When doing LIST, we get the default list output hard-coded in the test +# FTP server + +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 + + + +# +# Client-side + + +ftp +http-proxy + + +FTP LIST tunneled through HTTP proxy + + +ftp://%HOSTIP:%FTPPORT/ -p -x %HOSTIP:%PROXYPORT + + + +# +# Verify data after the test has been "shot" + + +USER anonymous +PASS ftp@example.com +PWD +EPSV +TYPE A +LIST +QUIT + + + diff --git a/tests/data/test275 b/tests/data/test275 index 145c163b3..713990544 100644 --- a/tests/data/test275 +++ b/tests/data/test275 @@ -41,12 +41,13 @@ contents http +http-proxy HTTP CONNECT with proxytunnel getting two URLs from the same host -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 @@ -56,21 +57,23 @@ http://remotesite.com/we/want/that/page/275 -p -x %HOSTIP:%HTTPPORT --user iam:m ^User-Agent:.* - -CONNECT remotesite.com:80 HTTP/1.1 -Host: remotesite.com:80 + +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 + + 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: */* diff --git a/tests/data/test503 b/tests/data/test503 index 3f29d8f6e..e7543593f 100644 --- a/tests/data/test503 +++ b/tests/data/test503 @@ -37,6 +37,7 @@ ETag: "21025-dc7-39462498" http +http-proxy # tool is what to use instead of 'curl' @@ -47,7 +48,7 @@ lib503 simple multi http:// through proxytunnel with authentication info -http://%HOSTIP:%HTTPSPORT/503 %HOSTIP:%HTTPPORT +http://%HOSTIP:%HTTPPORT/503 %HOSTIP:%PROXYPORT foo @@ -60,15 +61,17 @@ moo # Verify data after the test has been "shot" - -CONNECT %HOSTIP:%HTTPSPORT HTTP/1.1 -Host: %HOSTIP:%HTTPSPORT + +CONNECT %HOSTIP:%HTTPPORT HTTP/1.1 +Host: %HOSTIP:%HTTPPORT Proxy-Authorization: Basic dGVzdDppbmc= Proxy-Connection: Keep-Alive + + GET /503 HTTP/1.1 Authorization: Basic dGVzdDppbmc= -Host: %HOSTIP:%HTTPSPORT +Host: %HOSTIP:%HTTPPORT Accept: */* diff --git a/tests/data/test80 b/tests/data/test80 index a405af7a1..2fa196910 100644 --- a/tests/data/test80 +++ b/tests/data/test80 @@ -43,12 +43,13 @@ contents http +http-proxy -HTTP 1.0 CONNECT with proxytunnel and host Basic authentication +HTTP 1.0 CONNECT with proxytunnel and proxy+host Basic authentication -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 @@ -58,13 +59,15 @@ http://%HOSTIP:%HTTPPORT/we/want/that/page/80 -p --proxy1.0 %HOSTIP:%HTTPPORT -- ^User-Agent:.* - + 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 + + 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 diff --git a/tests/data/test83 b/tests/data/test83 index 0ada5d1af..3015c9ca7 100644 --- a/tests/data/test83 +++ b/tests/data/test83 @@ -40,12 +40,13 @@ contents http +http-proxy HTTP over proxy-tunnel with site authentication -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 @@ -55,12 +56,14 @@ http://%HOSTIP:%HTTPPORT/we/want/that/page/83 -p -x %HOSTIP:%HTTPPORT --user iam ^User-Agent:.* - + 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 + + 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 diff --git a/tests/data/test95 b/tests/data/test95 index 9fea1bec1..55b0e6583 100644 --- a/tests/data/test95 +++ b/tests/data/test95 @@ -40,12 +40,13 @@ contents http +http-proxy HTTP over proxytunnel using POST -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" @@ -55,12 +56,14 @@ http://%HOSTIP:%HTTPPORT/we/want/that/page/95 -p -x %HOSTIP:%HTTPPORT -d "datato ^User-Agent:.* - + 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 + + 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 diff --git a/tests/httpserver.pl b/tests/httpserver.pl index 37161f061..693c67f8f 100755 --- a/tests/httpserver.pl +++ b/tests/httpserver.pl @@ -6,7 +6,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. +# Copyright (C) 1998 - 2012, Daniel Stenberg, , 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"); diff --git a/tests/runtests.pl b/tests/runtests.pl index 76816887c..7ee7d60a3 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -6,7 +6,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. +# Copyright (C) 1998 - 2012, Daniel Stenberg, , 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 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 + 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: diff --git a/tests/server/sws.c b/tests/server/sws.c index b2d6df7a6..fd56c8e99 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2012, Daniel Stenberg, , 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; diff --git a/tests/server/util.c b/tests/server/util.c index 52802a82c..602f11e14 100644 --- a/tests/server/util.c +++ b/tests/server/util.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2012, Daniel Stenberg, , 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; diff --git a/tests/server/util.h b/tests/server/util.h index 76cd88d16..e1c8f9ac3 100644 --- a/tests/server/util.h +++ b/tests/server/util.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2008, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2012, Daniel Stenberg, , 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" -- cgit v1.2.3