]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
test proxy supports CONNECT
authorDaniel Stenberg <daniel@haxx.se>
Sat, 17 Dec 2011 22:47:22 +0000 (23:47 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 3 Jan 2012 14:01:22 +0000 (15:01 +0100)
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.

14 files changed:
tests/FILEFORMAT
tests/data/Makefile.am
tests/data/test1078
tests/data/test1316 [new file with mode: 0644]
tests/data/test275
tests/data/test503
tests/data/test80
tests/data/test83
tests/data/test95
tests/httpserver.pl
tests/runtests.pl
tests/server/sws.c
tests/server/util.c
tests/server/util.h

index ec8506e25c20bb0a7763c3c04702968d8c1e56a3..5432b43789137a22a10f8aea2d81e45cc516dc77 100644 (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.
index 53d94841f5464e160b0d70cf5aa5699463886cd3..6b661a216db2c5efb4adb68ebb0020f2bc61932b 100644 (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
index e2355e3aefdc480e9580b7cf8f23973b6340c5d4..cacdf4dd490a3d2b25fe23c6c1f970fc3b8207bf 100644 (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\r
 Host: %HOSTIP:%HTTPPORT\r
 Proxy-Connection: Keep-Alive\r
 \r
+</proxy>
+<protocol>
 GET /we/want/that/page/1078 HTTP/1.1\r
 Host: %HOSTIP:%HTTPPORT\r
 Accept: */*\r
diff --git a/tests/data/test1316 b/tests/data/test1316
new file mode 100644 (file)
index 0000000..d485c05
--- /dev/null
@@ -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\r
+\r
+HTTP/1.1 200 Mighty fine indeed\r
+\r
+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\r
+PASS ftp@example.com\r
+PWD\r
+EPSV\r
+TYPE A\r
+LIST\r
+QUIT\r
+</protocol>
+</verify>
+</testcase>
index 145c163b394314c34a2cab62a1405fb16c12c015..713990544c660957deb792c764eb177367bda73c 100644 (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\r
-Host: remotesite.com:80\r
+<proxy>
+CONNECT remotesite.com:%HTTPPORT HTTP/1.1\r
+Host: remotesite.com:%HTTPPORT\r
 Proxy-Authorization: Basic eW91YXJlOnlvdXJzZWxm\r
 User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3\r
 Proxy-Connection: Keep-Alive\r
 \r
+</proxy>
+<protocol>
 GET /we/want/that/page/275 HTTP/1.1\r
 Authorization: Basic aWFtOm15c2VsZg==\r
-Host: remotesite.com\r
+Host: remotesite.com:%HTTPPORT\r
 Accept: */*\r
 \r
 GET /we/want/that/page/275 HTTP/1.1\r
 Authorization: Basic aWFtOm15c2VsZg==\r
-Host: remotesite.com\r
+Host: remotesite.com:%HTTPPORT\r
 Accept: */*\r
 \r
 </protocol>
index 3f29d8f6e203e5ee513428a4ef9b614fced364b3..e7543593fb239641dd3fdac95ec72591706e2e35 100644 (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\r
-Host: %HOSTIP:%HTTPSPORT\r
+<proxy>
+CONNECT %HOSTIP:%HTTPPORT HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
 Proxy-Authorization: Basic dGVzdDppbmc=\r
 Proxy-Connection: Keep-Alive\r
 \r
+</proxy>
+<protocol>
 GET /503 HTTP/1.1\r
 Authorization: Basic dGVzdDppbmc=\r
-Host: %HOSTIP:%HTTPSPORT\r
+Host: %HOSTIP:%HTTPPORT\r
 Accept: */*\r
 \r
 </protocol>
index a405af7a13170d6395ebeb12aeea7c22cba1c18b..2fa196910cc15eaf6d446c374525363958adeeb1 100644 (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\r
 Host: %HOSTIP:%HTTPPORT\r
 Proxy-Authorization: Basic eW91YXJlOnlvdXJzZWxm\r
 User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3\r
 Proxy-Connection: Keep-Alive\r
 \r
+</proxy>
+<protocol>
 GET /we/want/that/page/80 HTTP/1.1\r
 Authorization: Basic aWFtOm15c2VsZg==\r
 User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3\r
index 0ada5d1af6fd89deb73a8ef46ae1683e8f2eeaa6..3015c9ca78005635b321751dc69d882db5bbebfd 100644 (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\r
 User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3\r
 Host: %HOSTIP:%HTTPPORT\r
 Proxy-Connection: Keep-Alive\r
 \r
+</proxy>
+<protocol>
 GET /we/want/that/page/83 HTTP/1.1\r
 Authorization: Basic aWFtOm15c2VsZg==\r
 User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3\r
index 9fea1bec110794be849639a027d62bc8d63f3717..55b0e65833a795c709d354da7064d24531b507ab 100644 (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\r
 User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3\r
 Host: %HOSTIP:%HTTPPORT\r
 Proxy-Connection: Keep-Alive\r
 \r
+</proxy>
+<protocol nonewline="yes">
 POST /we/want/that/page/95 HTTP/1.1\r
 User-Agent: curl/7.10.7-pre2 (i686-pc-linux-gnu) libcurl/7.10.7-pre2 OpenSSL/0.9.7a zlib/1.1.3\r
 Host: %HOSTIP:%HTTPPORT\r
index 37161f061fa8916a2a979fcc4b96158d7175ea00..693c67f8fb1f48f0758f98c95307da7f8522f862 100755 (executable)
@@ -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");
index 76816887c3a5ad605b586b8450b078e20677fdc7..7ee7d60a3b864070a1535287736015be8155d137 100755 (executable)
@@ -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:
index b2d6df7a60572e2976e743ea36219a6da872147f..fd56c8e994547cddfe311e7bf61eab4b10e4c650 100644 (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;
index 52802a82ca14c03daff1f911e117fbaa6864195b..602f11e14158cedd93f98cd7ea0fa7a2afe68741 100644 (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
 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;
index 76cd88d163c61f66e84e43b7a0e01f8469fbdfa2..e1c8f9ac3e0d5e2f497112f48369a6cd51be2660 100644 (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"