aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/Makefile.am3
-rw-r--r--tests/data/Makefile.am2
-rw-r--r--tests/data/test1036
-rw-r--r--tests/data/test25257
-rw-r--r--tests/data/test25357
-rw-r--r--tests/data/test25458
-rw-r--r--tests/data/test25558
-rw-r--r--tests/ftp.pm17
-rw-r--r--tests/ftpserver.pl382
-rwxr-xr-xtests/runtests.pl74
-rw-r--r--tests/server/Makefile.am16
-rw-r--r--tests/server/getpart.c16
-rw-r--r--tests/server/sockfilt.c668
-rw-r--r--tests/server/testpart.c52
14 files changed, 1315 insertions, 151 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am
index c1cfb73ec..9f02c1483 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -26,7 +26,8 @@ PDFPAGES = testcurl.pdf runtests.pdf
EXTRA_DIST = ftpserver.pl httpserver.pl httpsserver.pl runtests.pl \
ftpsserver.pl getpart.pm FILEFORMAT README stunnel.pem memanalyze.pl \
- testcurl.pl valgrind.pm testcurl.1 runtests.1 $(HTMLPAGES) $(PDFPAGES)
+ testcurl.pl valgrind.pm testcurl.1 runtests.1 $(HTMLPAGES) $(PDFPAGES) \
+ ftp.pm
SUBDIRS = data server libtest
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 66c5f52ef..a1e455efa 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -34,7 +34,7 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46 \
test199 test225 test226 test227 test230 test231 test232 test228 \
test229 test233 test234 test235 test236 test520 test237 test238 \
test239 test243 test245 test246 test247 test248 test249 test250 \
- test251
+ test251 test252 test253 test254 test255
# The following tests have been removed from the dist since they no longer
# work. We need to fix the test suite's FTPS server first, then bring them
diff --git a/tests/data/test103 b/tests/data/test103
index 87a640d20..b7ae60baf 100644
--- a/tests/data/test103
+++ b/tests/data/test103
@@ -26,8 +26,8 @@ ftp://%HOSTIP:%FTPPORT/a/path/103 -P -
# Verify data after the test has been "shot"
<verify>
<strippart>
-s/^LPRT.*[\n]//
-s/^EPRT.*[\n]//
+s/^LPRT.*/LPRT/
+s/^EPRT.*/EPRT/
s/^(PORT 127,0,0,1,)([0-9,]+)/$1/
</strippart>
<protocol>
@@ -36,6 +36,8 @@ PASS curl_by_daniel@haxx.se
PWD
CWD a
CWD path
+EPRT
+LPRT
PORT 127,0,0,1,
TYPE I
SIZE 103
diff --git a/tests/data/test252 b/tests/data/test252
new file mode 100644
index 000000000..3e5ffa6d3
--- /dev/null
+++ b/tests/data/test252
@@ -0,0 +1,57 @@
+<info>
+<keywords>
+FTP-ipv6
+EPSV
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+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
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+ftp-ipv6
+</server>
+ <name>
+FTP IPv6 dir list PASV
+ </name>
+ <command>
+-g "ftp://%HOST6IP:%FTP6PORT/"
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+filter off really nothing
+</strip>
+<protocol>
+USER anonymous
+PASS curl_by_daniel@haxx.se
+PWD
+EPSV
+TYPE A
+LIST
+QUIT
+</protocol>
+</verify>
diff --git a/tests/data/test253 b/tests/data/test253
new file mode 100644
index 000000000..7f66ae604
--- /dev/null
+++ b/tests/data/test253
@@ -0,0 +1,57 @@
+<info>
+<keywords>
+FTP-ipv6
+EPRT
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+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
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+ftp-ipv6
+</server>
+ <name>
+FTP IPv6 dir list with EPRT
+ </name>
+ <command>
+-g "ftp://%HOST6IP:%FTP6PORT/" -P -
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strippart>
+s/^(EPRT \|2\|::1\|)(.*)/$1/
+</strippart>
+<protocol>
+USER anonymous
+PASS curl_by_daniel@haxx.se
+PWD
+EPRT |2|::1|
+TYPE A
+LIST
+QUIT
+</protocol>
+</verify>
diff --git a/tests/data/test254 b/tests/data/test254
new file mode 100644
index 000000000..8466ce200
--- /dev/null
+++ b/tests/data/test254
@@ -0,0 +1,58 @@
+<info>
+<keywords>
+FTP-ipv6
+EPSV
+--disable-epsv
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+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
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+ftp-ipv6
+</server>
+ <name>
+FTP IPv6 dir list PASV and --disable-epsv
+ </name>
+ <command>
+-g "ftp://%HOST6IP:%FTP6PORT/" --disable-epsv
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+filter off really nothing
+</strip>
+<protocol>
+USER anonymous
+PASS curl_by_daniel@haxx.se
+PWD
+EPSV
+TYPE A
+LIST
+QUIT
+</protocol>
+</verify>
diff --git a/tests/data/test255 b/tests/data/test255
new file mode 100644
index 000000000..212847356
--- /dev/null
+++ b/tests/data/test255
@@ -0,0 +1,58 @@
+<info>
+<keywords>
+FTP-ipv6
+EPRT
+--disable-eprt
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+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
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+ipv6
+</features>
+<server>
+ftp-ipv6
+</server>
+ <name>
+FTP IPv6 dir list with EPRT and --disable-eprt
+ </name>
+ <command>
+-g "ftp://%HOST6IP:%FTP6PORT/" -P - --disable-eprt
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strippart>
+s/^(EPRT \|2\|::1\|)(.*)/$1/
+</strippart>
+<protocol>
+USER anonymous
+PASS curl_by_daniel@haxx.se
+PWD
+EPRT |2|::1|
+TYPE A
+LIST
+QUIT
+</protocol>
+</verify>
diff --git a/tests/ftp.pm b/tests/ftp.pm
new file mode 100644
index 000000000..390c21459
--- /dev/null
+++ b/tests/ftp.pm
@@ -0,0 +1,17 @@
+# make sure no leftovers are still running
+sub ftpkillslaves {
+ for $ext (("", "ipv6")) {
+ for $id (("", "2")) {
+ for $base (('filt', 'data')) {
+ my $f = ".sock$base$id$ext.pid";
+ my $pid = checkserver($f);
+ if($pid > 0) {
+ kill (9, $pid); # die!
+ }
+ unlink($f);
+ }
+ }
+ }
+}
+
+1;
diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl
index e51bfeee6..a47a54a25 100644
--- a/tests/ftpserver.pl
+++ b/tests/ftpserver.pl
@@ -30,13 +30,14 @@
# You may optionally specify port on the command line, otherwise it'll
# default to port 8921.
#
-
-use Socket;
-use FileHandle;
+# All socket/network/TCP related stuff is done by the 'sockfilt' program.
+#
use strict;
+use IPC::Open2;
require "getpart.pm";
+require "ftp.pm";
my $ftpdnum="";
@@ -70,7 +71,10 @@ my $srcdir=".";
my $nosave=0;
my $controldelay=0; # set to 1 to delay the control connect data sending to
# test that curl deals with that nicely
-
+my $slavepid; # for the DATA connection sockfilt slave process
+my $ipv6;
+my $ext; # append to log/pid file names
+my $grok_eprt;
my $port = 8921; # just a default
do {
if($ARGV[0] eq "-v") {
@@ -84,41 +88,68 @@ do {
$ftpdnum=$ARGV[1];
shift @ARGV;
}
- elsif($ARGV[0] =~ /^(\d+)$/) {
- $port = $1;
+ elsif($ARGV[0] eq "--ipv6") {
+ $ipv6="--ipv6";
+ $ext="ipv6";
+ $grok_eprt = 1;
+ }
+ elsif($ARGV[0] eq "--port") {
+ $port = $ARGV[1];
+ shift @ARGV;
}
} while(shift @ARGV);
-my $proto = getprotobyname('tcp') || 6;
+my $sfpid;
-socket(Server, PF_INET, SOCK_STREAM, $proto)|| die "socket: $!";
-setsockopt(Server, SOL_SOCKET, SO_REUSEADDR,
- pack("l", 1)) || die "setsockopt: $!";
-bind(Server, sockaddr_in($port, INADDR_ANY))|| die "bind: $!";
-listen(Server,SOMAXCONN) || die "listen: $!";
+sub startsf {
+ my $cmd="./server/sockfilt --port $port --logfile log/sockctrl$ftpdnum$ext.log --pidfile .sockfilt$ftpdnum$ext.pid $ipv6";
+ $sfpid = open2(\*SFREAD, \*SFWRITE, $cmd);
+ print STDERR "$cmd\n" if($verbose);
-logmsg "FTP server started on port $port\n";
+ print SFWRITE "PING\n";
+ my $pong = <SFREAD>;
+
+ if($pong !~ /^PONG/) {
+ die "Failed to start sockfilt!";
+ }
+ open(STDIN, "<&SFREAD") || die "can't dup client to stdin";
+ open(STDOUT, ">&SFWRITE") || die "can't dup client to stdout";
+}
+
+startsf();
+
+logmsg sprintf("FTP server started on port IPv%d/$port\n",
+ $ipv6?6:4);
open(PID, ">.ftp$ftpdnum.pid");
print PID $$;
close(PID);
+sub sockfilt {
+ my $l;
+ foreach $l (@_) {
+ printf "DATA\n%04x\n", length($l);
+ print $l;
+ }
+}
+
+
# Send data to the client on the control stream, which happens to be plain
# stdout.
sub sendcontrol {
if(!$controldelay) {
# spit it all out at once
- print @_;
+ sockfilt @_;
}
else {
my $a = join("", @_);
my @a = split("", $a);
for(@a) {
- print $_;
- select(undef, undef, undef, 0.02);
+ sockfilt $_;
+ select(undef, undef, undef, 0.01);
}
}
@@ -127,16 +158,11 @@ sub sendcontrol {
# Send data to the client on the data stream
sub senddata {
- print SOCK @_;
-}
-
-my $waitedpid = 0;
-my $paddr;
-
-sub REAPER {
- $waitedpid = wait;
- $SIG{CHLD} = \&REAPER; # loathe sysV
- logmsg "reaped $waitedpid" . ($? ? " with exit $?\n" : "\n");
+ my $l;
+ foreach $l (@_) {
+ printf DWRITE "DATA\n%04x\n", length($l);
+ print DWRITE $l;
+ }
}
# USER is ok in fresh state
@@ -146,6 +172,7 @@ my %commandok = (
'PASV' => 'loggedin|twosock',
'EPSV' => 'loggedin|twosock',
'PORT' => 'loggedin|twosock',
+ 'EPRT' => 'loggedin|twosock',
'TYPE' => 'loggedin|twosock',
'LIST' => 'twosock',
'NLST' => 'twosock',
@@ -171,6 +198,7 @@ my %commandok = (
my %statechange = ( 'USER' => 'passwd', # USER goes to passwd state
'PASS' => 'loggedin', # PASS goes to loggedin state
'PORT' => 'twosock', # PORT goes to twosock
+ 'EPRT' => 'twosock', # EPRT goes to twosock
'PASV' => 'twosock', # PASV goes to twosock
'EPSV' => 'twosock', # EPSV goes to twosock
);
@@ -196,6 +224,7 @@ my %displaytext = ('USER' => '331 We are happy you popped in!',
# callback functions for certain commands
my %commandfunc = ( 'PORT' => \&PORT_command,
+ 'EPRT' => \&PORT_command,
'LIST' => \&LIST_command,
'NLST' => \&NLST_command,
'PASV' => \&PASV_command,
@@ -210,8 +239,25 @@ my %commandfunc = ( 'PORT' => \&PORT_command,
sub close_dataconn {
- close(SOCK);
- logmsg "Closed data connection\n";
+ my ($closed)=@_; # non-zero if already disconnected
+
+ if(!$closed) {
+ logmsg "time to kill the data connection\n";
+ print DWRITE "DISC\n";
+ my $i;
+ sysread DREAD, $i, 5;
+ }
+ else {
+ logmsg "data connection already disconnected\n";
+ }
+
+ logmsg "time to quit sockfilt for data\n";
+ print DWRITE "QUIT\n";
+ logmsg "told data slave to die (pid $slavepid)\n";
+ waitpid $slavepid, 0;
+ $slavepid=0;
+ logmsg "=====> Closed data connection\n";
+
}
my $rest=0;
@@ -240,9 +286,8 @@ my @ftpdir=("total 20\r\n",
for(@ftpdir) {
senddata $_;
}
- close_dataconn();
+ close_dataconn(0);
logmsg "done passing data\n";
-
sendcontrol "226 ASCII transfer complete\r\n";
return 0;
}
@@ -253,7 +298,7 @@ sub NLST_command {
for(@ftpdir) {
senddata "$_\r\n";
}
- close_dataconn();
+ close_dataconn(0);
sendcontrol "226 ASCII transfer complete\r\n";
return 0;
}
@@ -292,6 +337,14 @@ sub SIZE_command {
logmsg "SIZE file \"$testno\"\n";
+ if($testno eq "verifiedserver") {
+ my $response = "WE ROOLZ: $$\r\n";
+ my $size = length($response);
+ sendcontrol "213 $size\r\n";
+ logmsg "SIZE $testno returned $size\n";
+ return 0;
+ }
+
my @data = getpart("reply", "size");
my $size = $data[0];
@@ -337,7 +390,8 @@ sub RETR_command {
sendcontrol "150 Binary junk ($len bytes).\r\n";
logmsg "pass our pid on the data connection\n";
senddata "WE ROOLZ: $$\r\n";
- close_dataconn();
+ close_dataconn(0);
+ logmsg "Data sent, sending a 226-reponse now\n";
sendcontrol "226 File transfer complete\r\n";
if($verbose) {
print STDERR "FTPD: We returned proof we are the test server\n";
@@ -377,7 +431,7 @@ sub RETR_command {
my $send = $_;
senddata $send;
}
- close_dataconn();
+ close_dataconn(0);
$retrweirdo=0; # switch off the weirdo again!
}
else {
@@ -394,7 +448,7 @@ sub RETR_command {
my $send = $_;
senddata $send;
}
- close_dataconn();
+ close_dataconn(0);
sendcontrol "226 File transfer complete\r\n";
}
}
@@ -421,121 +475,184 @@ sub STOR_command {
my $line;
my $ulsize=0;
- while (defined($line = <SOCK>)) {
- $ulsize += length($line);
- print FILE $line if(!$nosave);
+ my $disc=0;
+ while (5 == (sysread DREAD, $line, 5)) {
+ logmsg "command from sockfilt: $line";
+ if($line eq "DATA\n") {
+ my $i;
+ sysread DREAD, $i, 5;
+
+ #print STDERR " GOT: $i";
+
+ my $size = hex($i);
+ sysread DREAD, $line, $size;
+
+ #print STDERR " GOT: $size bytes\n";
+
+ $ulsize += $size;
+ print FILE $line if(!$nosave);
+ logmsg "> Appending $size bytes to file\n";
+ }
+ elsif($line eq "DISC\n") {
+ # disconnect!
+ logmsg "DISC means disconnect!\n";
+ $disc=1;
+ last;
+ }
+ else {
+ logmsg "No support for: $line";
+ last;
+ }
}
if($nosave) {
print FILE "$ulsize bytes would've been stored here\n";
}
close(FILE);
- close_dataconn();
-
+ close_dataconn($disc);
logmsg "received $ulsize bytes upload\n";
-
sendcontrol "226 File transfer complete\r\n";
return 0;
}
-my $pasvport=9000;
sub PASV_command {
my ($arg, $cmd)=@_;
+ my $pasvport;
- socket(Server2, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
- setsockopt(Server2, SOL_SOCKET, SO_REUSEADDR,
- pack("l", 1)) || die "setsockopt: $!";
+ # We fire up a new sockfilt to do the data tranfer for us.
+ $slavepid = open2(\*DREAD, \*DWRITE,
+ "./server/sockfilt --port 0 --logfile log/sockdata$ftpdnum$ext.log --pidfile .sockdata$ftpdnum$ext.pid $ipv6");
- my $ok=0;
+ print DWRITE "PING\n";
+ my $pong = <DREAD>;
- $pasvport++; # don't reuse the previous
- for(1 .. 10) {
- if($pasvport > 65535) {
- $pasvport = 1025;
- }
- if(bind(Server2, sockaddr_in($pasvport, INADDR_ANY))) {
- $ok=1;
- last;
- }
- $pasvport+= 3; # try another port please
- }
- if(!$ok) {
+ if($pong !~ /^PONG/) {
sendcontrol "500 no free ports!\r\n";
logmsg "couldn't find free port\n";
return 0;
}
- listen(Server2,SOMAXCONN) || die "listen: $!";
+
+ logmsg "sockfilt for data on pid $slavepid\n";
+
+ # Find out what port we listen on
+ my $i;
+ print DWRITE "PORT\n";
+
+ # READ the response code
+ sysread(DREAD, $i, 5) || die;
+
+ # READ the response size
+ sysread(DREAD, $i, 5) || die;
+
+ my $size = hex($i);
+
+ # READ the response data
+ sysread(DREAD, $i, $size) || die;
+
+ # The data is in the format
+ # IPvX/NNN
+
+ if($i =~ /IPv(\d)\/(\d+)/) {
+ # FIX: deal with IP protocol version
+ $pasvport = $2;
+ }
if($cmd ne "EPSV") {
# PASV reply
- logmsg "replying to a $cmd command\n";
- printf("227 Entering Passive Mode (127,0,0,1,%d,%d)\n",
- ($pasvport/256), ($pasvport%256));
+ logmsg "replying to a $cmd command, waiting on port $pasvport\n";
+ sendcontrol sprintf("227 Entering Passive Mode (127,0,0,1,%d,%d)\n",
+ ($pasvport/256), ($pasvport%256));
}
else {
# EPSV reply
- logmsg "replying to a $cmd command\n";
- printf("229 Entering Passive Mode (|||%d|)\n", $pasvport);
+ logmsg "replying to a $cmd command, waiting on port $pasvport\n";
+ sendcontrol sprintf("229 Entering Passive Mode (|||%d|)\n", $pasvport);
}
- my $paddr;
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
- alarm 2; # assume swift operations!
- $paddr = accept(SOCK, Server2);
+
+ alarm 2; # assume swift operations
+
+ # Wait for 'CNCT'
+ my $input = <DREAD>;
+
+ if($input !~ /^CNCT/) {
+ # we wait for a connected client
+ next;
+ }
+ logmsg "====> Client DATA connect\n";
+
alarm 0;
};
if ($@) {
# timed out
-
- close(Server2);
+
+ print DWRITE "QUIT\n";
+ waitpid $slavepid, 0;
logmsg "accept failed\n";
+ $slavepid=0;
return;
}
else {
- logmsg "accept worked\n";
-
- my($iport,$iaddr) = sockaddr_in($paddr);
- my $name = gethostbyaddr($iaddr,AF_INET);
-
- close(Server2); # close the listener when its served its purpose!
-
- logmsg "data connection from $name [", inet_ntoa($iaddr),
- "] at port $iport\n";
+ logmsg "data connection setup on port $pasvport\n";
}
return;
}
+# Support both PORT and EPRT here. Consider LPRT too.
sub PORT_command {
- my $arg = $_[0];
+ my ($arg, $cmd) = @_;
+ my $port;
+
+ # We always ignore the given IP and use localhost.
- if($arg !~ /(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/) {
- logmsg "bad PORT-line: $arg\n";
- sendcontrol "500 silly you, go away\r\n";
+ if($cmd eq "PORT") {
+ if($arg !~ /(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/) {
+ logmsg "bad PORT-line: $arg\n";
+ sendcontrol "500 silly you, go away\r\n";
+ return 0;
+ }
+ $port = ($5<<8)+$6;
+ }
+ # EPRT |2|::1|49706|
+ elsif(($cmd eq "EPRT") && ($grok_eprt)) {
+ if($arg !~ /(\d+)\|([^\|]+)\|(\d+)/) {
+ logmsg "bad EPRT-line: $arg\n";
+ sendcontrol "500 silly you, go away\r\n";
+ return 0;
+ }
+ sendcontrol "200 Thanks for dropping by. We contact you later\r\n";
+ $port = $3;
+ }
+ else {
+ logmsg "got a $cmd line we don't like\n";
+ sendcontrol "500 we don't like $cmd now\r\n";
return 0;
}
- #my $iaddr = inet_aton("$1.$2.$3.$4");
- my $iaddr = inet_aton("127.0.0.1"); # always use localhost
-
- my $port = ($5<<8)+$6;
if(!$port || $port > 65535) {
print STDERR "very illegal PORT number: $port\n";
return 1;
}
- my $paddr = sockaddr_in($port, $iaddr);
- my $proto = getprotobyname('tcp') || 6;
+ # We fire up a new sockfilt to do the data tranfer for us.
+ # FIX: make it use IPv6 if need be
+ $slavepid = open2(\*DREAD, \*DWRITE,
+ "./server/sockfilt --connect $port --logfile log/sockdata$ftpdnum$ext.log --pidfile .sockdata$ftpdnum$ext.pid $ipv6");
- socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "major failure";
- connect(SOCK, $paddr) || return 1;
+ print DWRITE "PING\n";
+ my $pong = <DREAD>;
- return \&SOCK;
-}
+ if($pong !~ /^PONG/) {
+ logmsg "sockfilt failed!\n";
+ }
+ logmsg "====> Client DATA connect to port $port\n";
-$SIG{CHLD} = \&REAPER;
+ return;
+}
my %customreply;
my %customcount;
@@ -595,22 +712,45 @@ my @welcome=(
'220- | (__| |_| | _ <| |___ '."\r\n",
'220 \___|\___/|_| \_\_____|'."\r\n");
-for ( $waitedpid = 0;
- ($paddr = accept(Client,Server)) || $waitedpid;
- $waitedpid = 0, close Client)
-{
- next if $waitedpid and not $paddr;
- my($port,$iaddr) = sockaddr_in($paddr);
- my $name = gethostbyaddr($iaddr,AF_INET);
+
+while(1) {
+ #
+ # We read 'sockfilt' commands.
+ #
+ my $input;
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n" };
+ alarm 5; # just in case things go bad
+ $input = <STDIN>;
+ alarm 0;
+ };
+ if ($@) {
+ # timed out
+ logmsg "reading stdin timed out\n";
+ }
+
+ if($input !~ /^CNCT/) {
+ # we wait for a connected client
+ if(!length($input)) {
+ # it probably died, restart it
+ kill(9, $sfpid);
+ waitpid $sfpid, 0;
+ startsf();
+ logmsg "restarted sockfilt\n";
+ }
+ else {
+ logmsg "sockfilt said: $input";
+ }
+ next;
+ }
+ logmsg "====> Client connect\n";
# flush data:
$| = 1;
+
+ kill(9, $slavepid) if($slavepid);
+ $slavepid=0;
- logmsg "connection from $name [", inet_ntoa($iaddr), "] at port $port\n";
-
- open(STDIN, "<&Client") || die "can't dup client to stdin";
- open(STDOUT, ">&Client") || die "can't dup client to stdout";
-
&customize(); # read test control instructions
sendcontrol @welcome;
@@ -622,8 +762,29 @@ for ( $waitedpid = 0;
my $state="fresh";
while(1) {
+ my $i;
+
+ # Now we expect to read DATA\n[hex size]\n[prot], where the [prot]
+ # part only is FTP lingo.
+
+ # COMMAND
+ sysread(STDIN, $i, 5) || die;
- last unless defined ($_ = <STDIN>);
+ if($i !~ /^DATA/) {
+ logmsg "sockfilt said $i";
+ if($i =~ /^DISC/) {
+ # disconnect
+ last;
+ }
+ next;
+ }
+
+ # SIZE of data
+ sysread(STDIN, $i, 5) || die;
+ my $size = hex($i);
+
+ # data
+ sysread STDIN, $_, $size;
ftpmsg $_;
@@ -632,7 +793,7 @@ for ( $waitedpid = 0;
unless (m/^([A-Z]{3,4})\s?(.*)/i) {
sendcontrol "500 '$_': command not understood.\r\n";
- logmsg "unknown crap received, bailing out hard\n";
+ logmsg "unknown crap received: $_, bailing out hard\n";
last;
}
my $FTPCMD=$1;
@@ -692,11 +853,14 @@ for ( $waitedpid = 0;
my $func = $commandfunc{$FTPCMD};
if($func) {
# it is!
- \&$func($FTPARG, $FTPCMD);
+ &$func($FTPARG, $FTPCMD);
}
}
} # while(1)
- logmsg "client disconnected\n";
- close(Client);
+ logmsg "====> Client disconnected\n";
}
+
+print SFWRITE "QUIT\n";
+waitpid $sfpid, 0;
+exit;
diff --git a/tests/runtests.pl b/tests/runtests.pl
index 221013410..04ef999ad 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -30,6 +30,7 @@ use strict;
require "getpart.pm"; # array functions
require "valgrind.pm"; # valgrind report parser
+require "ftp.pm";
my $srcdir = $ENV{'srcdir'} || '.';
my $HOSTIP="127.0.0.1";
@@ -43,6 +44,7 @@ my $HTTPSPORT; # HTTPS server port
my $FTPPORT; # FTP server port
my $FTP2PORT; # FTP server 2 port
my $FTPSPORT; # FTPS server port
+my $FTP6PORT; # FTP IPv6 server port
my $CURL="../src/curl"; # what curl executable to run on the tests
my $DBGCURL=$CURL; #"../src/.libs/curl"; # alternative for debugging
@@ -69,6 +71,7 @@ my $HTTPPIDFILE=".http.pid";
my $HTTP6PIDFILE=".http6.pid";
my $HTTPSPIDFILE=".https.pid";
my $FTPPIDFILE=".ftp.pid";
+my $FTP6PIDFILE=".ftp6.pid";
my $FTP2PIDFILE=".ftp2.pid";
my $FTPSPIDFILE=".ftps.pid";
@@ -112,6 +115,7 @@ my $ssl_version; # set if libcurl is built with SSL support
my $large_file; # set if libcurl is built with large file support
my $has_idn; # set if libcurl is built with IDN support
my $http_ipv6; # set if HTTP server has IPv6 support
+my $ftp_ipv6; # set if FTP server has IPv6 support
my $has_ipv6; # set if libcurl is built with IPv6 support
my $has_libz; # set if libcurl is built with libz support
my $has_getrlimit; # set if system has getrlimit()
@@ -365,7 +369,7 @@ sub runhttpserver {
$pid = checkserver($pidfile);
# verify if our/any server is running on this port
- my $cmd = "$CURL -o log/verifiedserver -g \"http://$ip:$port/verifiedserver\" 2>log/verifystderr";
+ my $cmd = "$CURL -o log/verifiedserver -g \"http://$ip:$port/verifiedserver\" 2>log/verifyhttp";
print "CMD; $cmd\n" if ($verbose);
my $res = system($cmd);
@@ -425,7 +429,7 @@ sub runhttpserver {
my $verified;
for(1 .. 30) {
# verify that our server is up and running:
- my $data=`$CURL --silent -g \"$ip:$port/verifiedserver\" 2>/dev/null`;
+ my $data=`$CURL --silent -g \"$ip:$port/verifiedserver\" 2>>log/verifyhttp`;
if ( $data =~ /WE ROOLZ: (\d+)/ ) {
$pid = 0+$1;
@@ -503,24 +507,39 @@ sub runhttpsserver {
return $pid;
}
+
#######################################################################
# start the ftp server if needed
#
sub runftpserver {
- my ($id, $verbose) = @_;
+ my ($id, $verbose, $ipv6) = @_;
my $STATUS;
my $RUNNING;
my $port = $id?$FTP2PORT:$FTPPORT;
# check for pidfile
- my $pid = checkserver ($id?$FTP2PIDFILE:$FTPPIDFILE);
+ my $pidfile = $id?$FTP2PIDFILE:$FTPPIDFILE;
+ my $ip=$HOSTIP;
+ my $nameext;
+
+ ftpkillslaves();
+
+ if($ipv6) {
+ # if IPv6, use a different setup
+ $pidfile = $FTP6PIDFILE;
+ $port = $FTP6PORT;
+ $ip = $HOST6IP;
+ $nameext="-ipv6";
+ }
+
+ my $pid = checkserver ();
if ($pid <= 0) {
- print "RUN: Check port $port for the FTP$id server\n"
+ print "RUN: Check port $port for the FTP$id$nameext server\n"
if ($verbose);
my $time=time();
# check if this is our server running on this port:
- my @data=`$CURL -m4 --silent ftp://$HOSTIP:$port/verifiedserver 2>/dev/null`;
+ my @data=`$CURL -m4 --silent -vg \"ftp://$ip:$port/verifiedserver\" 2>log/verifyftp`;
my $line;
# if this took more than 2 secs, we assume it "hung" on a weird server
@@ -534,7 +553,7 @@ sub runftpserver {
}
if(!$pid && $data[0]) {
# this is not a known server
- print "RUN: Unknown server on our favourite FTP port: $port\n";
+ print "RUN: Unknown server on our favourite FTP$nameext port: $port\n";
return -1;
}
}
@@ -543,7 +562,7 @@ sub runftpserver {
print "RUN: Killing a previous server using pid $pid\n" if($verbose);
my $res = kill (9, $pid); # die!
if(!$res) {
- print "RUN: Failed to kill FTP$id test server, do it manually and",
+ print "RUN: Failed to kill FTP$id$nameext test server, do it manually and",
" restart the tests.\n";
return -1;
}
@@ -556,7 +575,10 @@ sub runftpserver {
if($id) {
$flag .="--id $id ";
}
- my $cmd="$perl $srcdir/ftpserver.pl $flag $port &";
+ if($ipv6) {
+ $flag .="--ipv6 ";
+ }
+ my $cmd="$perl $srcdir/ftpserver.pl $flag --port $port &";
if($verbose) {
print "CMD: $cmd\n";
}
@@ -567,7 +589,7 @@ sub runftpserver {
for(1 .. 30) {
# verify that our server is up and running:
my $line;
- my $cmd="$CURL --silent ftp://$HOSTIP:$port/verifiedserver 2>/dev/null";
+ my $cmd="$CURL --silent -g \"ftp://$ip:$port/verifiedserver\" 2>>log/verifyftp";
print "$cmd\n" if($verbose);
my @data = `$cmd`;
foreach $line (@data) {
@@ -579,7 +601,7 @@ sub runftpserver {
}
if(!$pid) {
if($verbose) {
- print STDERR "RUN: Retrying FTP$id server existence in a sec\n";
+ print STDERR "RUN: Retrying FTP$id$nameext server existence in a sec\n";
}
sleep(1);
next;
@@ -594,7 +616,7 @@ sub runftpserver {
}
if($verbose) {
- print "RUN: FTP$id server is now verified to be our server\n";
+ print "RUN: FTP$id$nameext server is now verified to be our server\n";
}
return $pid;
@@ -850,12 +872,21 @@ sub checkcurl {
}
if($has_ipv6) {
- # client has ipv6 support, check that the HTTP server has it!
+ # client has ipv6 support
+
+ # check if the HTTP server has it!
my @sws = `server/sws --version`;
if($sws[0] =~ /IPv6/) {
# HTTP server has ipv6 support!
$http_ipv6 = 1;
}
+
+ # check if the FTP server has it!
+ my @sws = `server/sockfilt --version`;
+ if($sws[0] =~ /IPv6/) {
+ # FTP server has ipv6 support!
+ $ftp_ipv6 = 1;
+ }
}
if(!$curl_debug && $torture) {
@@ -877,6 +908,7 @@ sub checkcurl {
printf("* libcurl debug: %s\n", $curl_debug?"ON":"OFF");
printf("* valgrind: %s\n", $valgrind?"ON":"OFF");
printf("* HTTP IPv6 %s\n", $http_ipv6?"ON":"OFF");
+ printf("* FTP IPv6 %s\n", $ftp_ipv6?"ON":"OFF");
printf("* HTTP port: %d\n", $HTTPPORT);
printf("* FTP port: %d\n", $FTPPORT);
@@ -888,6 +920,9 @@ sub checkcurl {
if($http_ipv6) {
printf("* HTTP IPv6 port: %d\n", $HTTP6PORT);
}
+ if($ftp_ipv6) {
+ printf("* FTP IPv6 port: %d\n", $FTP6PORT);
+ }
print "***************************************** \n";
}
@@ -903,6 +938,7 @@ sub subVariables {
$$thing =~ s/%HTTP6PORT/$HTTP6PORT/g;
$$thing =~ s/%HTTPSPORT/$HTTPSPORT/g;
$$thing =~ s/%FTPPORT/$FTPPORT/g;
+ $$thing =~ s/%FTP6PORT/$FTP6PORT/g;
$$thing =~ s/%FTP2PORT/$FTP2PORT/g;
$$thing =~ s/%FTPSPORT/$FTPSPORT/g;
$$thing =~ s/%SRCDIR/$srcdir/g;
@@ -1533,6 +1569,7 @@ sub stopservers {
printf ("* kill pid for %-5s => %-5d\n", $_, $run{$_}) if($verbose);
stopserver($run{$_}); # the pid file is in the hash table
}
+ ftpkillslaves();
}
#######################################################################
@@ -1566,6 +1603,16 @@ sub startservers {
$run{'ftp2'}=$pid;
}
}
+ elsif($what eq "ftp-ipv6") {
+ if(!$run{'ftp-ipv6'}) {
+ $pid = runftpserver("", $verbose, "ipv6");
+ if($pid <= 0) {
+ return "failed starting FTP-ipv6 server";
+ }
+ printf ("* pid ftp-ipv6 => %-5d\n", $pid) if($verbose);
+ $run{'ftp-ipv6'}=$pid;
+ }
+ }
elsif($what eq "http") {
if(!$run{'http'}) {
$pid = runhttpserver($verbose);
@@ -1803,6 +1850,7 @@ $FTPSPORT = $base + 3; # FTPS server port
$HTTP6PORT = $base + 4; # HTTP IPv6 server port (different IP protocol
# but we follow the same port scheme anyway)
$FTP2PORT = $base + 5; # FTP server 2 port
+$FTP6PORT = $base + 6; # FTP IPv6 port
#######################################################################
# Output curl version and host info being tested
diff --git a/tests/server/Makefile.am b/tests/server/Makefile.am
index 565441caa..07727101a 100644
--- a/tests/server/Makefile.am
+++ b/tests/server/Makefile.am
@@ -25,16 +25,14 @@ AUTOMAKE_OPTIONS = foreign
INCLUDES = -I$(top_srcdir)/lib -I$(top_srcdir)/include
-noinst_PROGRAMS = sws getpart
+noinst_PROGRAMS = sws getpart sockfilt
-sws_SOURCES= sws.c getpart.c getpart.h $(top_srcdir)/lib/strequal.c \
- $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c \
+useful = getpart.c getpart.h $(top_srcdir)/lib/strequal.c \
+ $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c \
$(top_srcdir)/lib/memdebug.c
-extra_DIST = base64.pl
-
-getpart_CPPFLAGS = -DGETPART_TEST
+sws_SOURCES= sws.c $(useful)
+sockfilt_SOURCES = sockfilt.c $(useful) $(top_srcdir)/lib/inet_pton.c
+getpart_SOURCES= testpart.c $(useful)
-getpart_SOURCES= getpart.c getpart.h $(top_srcdir)/lib/strequal.c \
- $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c \
- $(top_srcdir)/lib/memdebug.c
+extra_DIST = base64.pl
diff --git a/tests/server/getpart.c b/tests/server/getpart.c
index c493f252e..85bf3ed7b 100644
--- a/tests/server/getpart.c
+++ b/tests/server/getpart.c
@@ -218,19 +218,3 @@ const char *spitout(FILE *stream,
return string;
}
-#ifdef GETPART_TEST
-int main(int argc, char **argv)
-{
- if(argc< 3) {
- printf("./moo main sub\n");
- }
- else {
- size_t size;
- unsigned int i;
- const char *buffer = spitout(stdin, argv[1], argv[2], &size);
- for(i=0; i< size; i++)
- printf("%c", buffer[i]);
- }
- return 0;
-}
-#endif
diff --git a/tests/server/sockfilt.c b/tests/server/sockfilt.c
new file mode 100644
index 000000000..07c81b11d
--- /dev/null
+++ b/tests/server/sockfilt.c
@@ -0,0 +1,668 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2005, 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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+/* Purpose
+ *
+ * 1. Accept a TCP connection on a custom port (ipv4 or ipv6).
+ *
+ * 2. Get commands on STDIN. Pass data on to the TCP stream.
+ * Get data from TCP stream and pass on to STDOUT.
+ *
+ * This program is made to perform all the socket/stream/connection stuff for
+ * the test suite's (perl) FTP server. Previously the perl code did all of
+ * this by its own, but I decided to let this program do the socket layer
+ * because of several things:
+ *
+ * o We want the perl code to work with rather old perl installations, thus
+ * we cannot use recent perl modules or features.
+ *
+ * o We want IPv6 support for systems that provide it, and doing optional IPv6
+ * support in perl seems if not impossible so at least awkward.
+ *
+ * o We want FTP-SSL support, which means that a connection that starts with
+ * plain sockets needs to be able to "go SSL" in the midst. This would also
+ * require some nasty perl stuff I'd rather avoid.
+ *
+ * (Source originally based on sws.c)
+ */
+#include "setup.h" /* portability help from the lib directory */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <time.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef _XOPEN_SOURCE_EXTENDED
+/* This define is "almost" required to build on HPUX 11 */
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#include "curlx.h" /* from the private lib dir */
+#include "getpart.h"
+#include "inet_pton.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+#include <windows.h>
+#include <winsock2.h>
+#include <process.h>
+
+#define sleep(sec) Sleep ((sec)*1000)
+
+#define EINPROGRESS WSAEINPROGRESS
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define EISCONN WSAEISCONN
+#define ENOTSOCK WSAENOTSOCK
+#define ECONNREFUSED WSAECONNREFUSED
+
+static void win32_cleanup(void);
+
+#if defined(ENABLE_IPV6) && defined(__MINGW32__)
+const struct in6_addr in6addr_any = {{ IN6ADDR_ANY_INIT }};
+#endif
+#endif
+
+/* include memdebug.h last */
+#include "memdebug.h"
+
+#define DEFAULT_PORT 8999
+
+#ifndef DEFAULT_LOGFILE
+#define DEFAULT_LOGFILE "log/sockfilt.log"
+#endif
+
+#ifdef SIGPIPE
+static volatile int sigpipe; /* Why? It's not used */
+#endif
+
+char *socklogfile = (char *)DEFAULT_LOGFILE;
+
+/*
+ * ourerrno() returns the errno (or equivalent) on this platform to
+ * hide platform specific for the function that calls this.
+ */
+static int ourerrno(void)
+{
+#ifdef WIN32
+ return (int)GetLastError();
+#else
+ return errno;
+#endif
+}
+
+static void logmsg(const char *msg, ...)
+{
+ time_t t = time(NULL);
+ va_list ap;
+ struct tm *curr_time = localtime(&t);
+ char buffer[256]; /* possible overflow if you pass in a huge string */
+ FILE *logfp;
+
+ va_start(ap, msg);
+ vsprintf(buffer, msg, ap);
+ va_end(ap);
+
+ logfp = fopen(socklogfile, "a");
+
+ fprintf(logfp?logfp:stderr, /* write to stderr if the logfile doesn't open */
+ "%02d:%02d:%02d %s\n",
+ curr_time->tm_hour,
+ curr_time->tm_min,
+ curr_time->tm_sec, buffer);
+ if(logfp)
+ fclose(logfp);
+}
+
+static void lograw(unsigned char *buffer, int len)
+{
+ char data[120];
+ int i;
+ unsigned char *ptr = buffer;
+ char *optr = data;
+ int width=0;
+
+ for(i=0; i<len; i++) {
+ sprintf(optr, "%c",
+ (isgraph(ptr[i]) || ptr[i]==0x20) ?ptr[i]:'.');
+ optr += 1;
+ width += 1;
+
+ if(width>60) {
+ logmsg("RAW: '%s'", data);
+ width = 0;
+ optr = data;
+ }
+ }
+ if(width)
+ logmsg("RAW: '%s'", data);
+}
+
+#ifdef SIGPIPE
+static void sigpipe_handler(int sig)
+{
+ (void)sig; /* prevent warning */
+ sigpipe = 1;
+}
+#endif
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+#undef perror
+#define perror(m) win32_perror(m)
+
+static void win32_perror(const char *msg)
+{
+ char buf[256];
+ DWORD err = WSAGetLastError();
+
+ if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
+ LANG_NEUTRAL, buf, sizeof(buf), NULL))
+ snprintf(buf, sizeof(buf), "Unknown error %lu (%#lx)", err, err);
+ if (msg)
+ fprintf(stderr, "%s: ", msg);
+ fprintf(stderr, "%s\n", buf);
+}
+#endif
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+static void win32_init(void)
+{
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ int err;
+ wVersionRequested = MAKEWORD(2, 0);
+
+ err = WSAStartup(wVersionRequested, &wsaData);
+
+ if (err != 0) {
+ perror("Winsock init failed");
+ logmsg("Error initialising winsock -- aborting\n");
+ exit(1);
+ }
+
+ if ( LOBYTE( wsaData.wVersion ) != 2 ||
+ HIBYTE( wsaData.wVersion ) != 0 ) {
+
+ WSACleanup();
+ perror("Winsock init failed");
+ logmsg("No suitable winsock.dll found -- aborting\n");
+ exit(1);
+ }
+}
+static void win32_cleanup(void)
+{
+ WSACleanup();
+}
+#endif
+
+char use_ipv6=FALSE;
+unsigned short port = DEFAULT_PORT;
+unsigned short connectport = 0; /* if non-zero, we activate this mode */
+
+enum sockmode {
+ PASSIVE_LISTEN, /* as a server waiting for connections */
+ PASSIVE_CONNECT, /* as a server, connected to a client */
+ ACTIVE /* as a client, connected to a server */
+};
+
+/*
+ sockfdp is a pointer to an established stream or CURL_SOCKET_BAD
+
+ if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must
+ accept()
+*/
+static int juggle(curl_socket_t *sockfdp,
+ curl_socket_t listenfd,
+ enum sockmode *mode)
+{
+ struct timeval timeout;
+ fd_set fds_read;
+ fd_set fds_write;
+ fd_set fds_err;
+ curl_socket_t maxfd;
+ int r;
+ unsigned char buffer[256]; /* FIX: bigger buffer */
+ char data[256];
+ int sockfd;
+
+ timeout.tv_sec = 120;
+ timeout.tv_usec = 0;
+
+ FD_ZERO(&fds_read);
+ FD_ZERO(&fds_write);
+ FD_ZERO(&fds_err);
+
+ FD_SET(fileno(stdin), &fds_read);
+
+ switch(*mode) {
+ case PASSIVE_LISTEN:
+ /* server mode */
+ sockfd = listenfd;
+ logmsg("waiting for a client to connect on socket %d", (int)sockfd);
+ /* there's always a socket to wait for */
+ FD_SET(sockfd, &fds_read);
+ maxfd = sockfd;
+ break;
+
+ case PASSIVE_CONNECT:
+ sockfd = *sockfdp;
+ logmsg("waiting for data from client on socket %d", (int)sockfd);
+ /* there's always a socket to wait for */
+ FD_SET(sockfd, &fds_read);
+ maxfd = sockfd;
+ break;
+
+ case ACTIVE:
+ sockfd = *sockfdp;
+
+ /* sockfd turns CURL_SOCKET_BAD when our connection has been closed */
+ if(sockfd != CURL_SOCKET_BAD) {
+ FD_SET(sockfd, &fds_read);
+ maxfd = sockfd;
+ logmsg("waiting for data from client on socket %d", (int)sockfd);
+ }
+ else {
+ logmsg("No socket to read on");
+ maxfd = 0;
+ }
+ break;
+ }
+
+ do {
+ r = select(maxfd + 1, &fds_read, &fds_write, &fds_err, &timeout);
+ } while((r == -1) && (ourerrno() == EINTR));
+
+ logmsg("select() returned %d", r);
+
+ switch(r) {
+ case -1:
+ return FALSE;
+
+ case 0: /* timeout! */
+ return TRUE;
+ }
+
+
+ if(FD_ISSET(fileno(stdin), &fds_read)) {
+ size_t nread;
+ logmsg("data on stdin");
+ /* read from stdin, commands/data to be dealt with and possibly passed on
+ to the socket
+
+ protocol:
+
+ 4 letter command + LF [mandatory]
+
+ 4-digit hexadecimal data length + LF [if the command takes data]
+ data [the data being as long as set above]
+
+ Commands:
+
+ DATA - plain pass-thru data
+ */
+ nread = read(fileno(stdin), buffer, 5);
+ if(5 == nread) {
+
+ logmsg("Received command %c%c%c%c",
+ buffer[0], buffer[1], buffer[2], buffer[3] );
+
+ if(!memcmp("PING", buffer, 4)) {
+ /* send reply on stdout, just proving we are alive */
+ write(fileno(stdout), "PONG\n", 5);
+ }
+
+ else if(!memcmp("PORT", buffer, 4)) {
+ /* question asking us what PORT number we are listening to.
+ Replies with PORT with "IPv[num]/[port]" */
+ sprintf((char *)buffer, "IPv%d/%d\n", use_ipv6?6:4, port);
+ r = strlen((char *)buffer);
+ sprintf(data, "PORT\n%04x\n", r);
+ write(fileno(stdout), data, 10);
+ write(fileno(stdout), buffer, r);
+ }
+ else if(!memcmp("QUIT", buffer, 4)) {
+ /* just die */
+ logmsg("quits");
+ exit(0);
+ }
+ else if(!memcmp("DATA", buffer, 4)) {
+ /* data IN => data OUT */
+ long len;
+
+ if(5 != read(fileno(stdin), buffer, 5))
+ return FALSE;
+
+ len = strtol((char *)buffer, NULL, 16);
+ if(len != read(fileno(stdin), buffer, len))
+ return FALSE;
+
+ logmsg("> %d bytes data, server => client", len);
+ lograw(buffer, len);
+
+ if(*mode == PASSIVE_LISTEN) {
+ logmsg(".., but we are disconnected!");
+ write(fileno(stdout), "DISC\n", 5);
+ }
+ else
+ /* send away on the socket */
+ swrite(sockfd, buffer, len);
+ }
+ else if(!memcmp("DISC", buffer, 4)) {
+ /* disconnect! */
+ write(fileno(stdout), "DISC\n", 5);
+ if(sockfd != CURL_SOCKET_BAD) {
+ logmsg("====> Client forcibly disconnected");
+ sclose(sockfd);
+ *sockfdp = CURL_SOCKET_BAD;
+ }
+ else
+ logmsg("attempt to close already dead connection");
+ return TRUE;
+ }
+ }
+ else {
+ logmsg("read %d from stdin, exiting", (int)nread);
+ exit(0);
+ }
+ }
+
+ if((sockfd != CURL_SOCKET_BAD) && (FD_ISSET(sockfd, &fds_read)) ) {
+ logmsg("data on socket");
+
+ if(*mode == PASSIVE_LISTEN) {
+ /* there's no stream set up yet, this is an indication that there's a
+ client connecting. */
+ sockfd = accept(sockfd, NULL, NULL);
+ if(-1 == sockfd)
+ logmsg("accept() failed\n");
+ else {
+ logmsg("====> Client connect");
+ write(fileno(stdout), "CNCT\n", 5);
+ *sockfdp = sockfd; /* store the new socket */
+ *mode = PASSIVE_CONNECT; /* we have connected */
+ }
+ return TRUE;
+ }
+
+ /* read from socket, pass on data to stdout */
+ r = sread(sockfd, buffer, sizeof(buffer));
+
+ if(r <= 0) {
+ logmsg("====> Client disconnect");
+ write(fileno(stdout), "DISC\n", 5);
+ sclose(sockfd);
+ *sockfdp = CURL_SOCKET_BAD;
+ if(*mode == PASSIVE_CONNECT)
+ *mode = PASSIVE_LISTEN;
+ return TRUE;
+ }
+
+ sprintf(data, "DATA\n%04x\n", r);
+ write(fileno(stdout), data, 10);
+ write(fileno(stdout), buffer, r);
+
+ logmsg("< %d bytes data, client => server", r);
+ lograw(buffer, r);
+ }
+
+ return TRUE;
+}
+
+int main(int argc, char *argv[])
+{
+ struct sockaddr_in me;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 me6;
+#endif /* ENABLE_IPV6 */
+ int sock;
+ int msgsock = CURL_SOCKET_BAD; /* no stream socket yet */
+ int flag;
+ FILE *pidfile;
+ char *pidname= (char *)".sockfilt.pid";
+ int rc;
+ int arg=1;
+ bool ok = FALSE;
+ enum sockmode mode = PASSIVE_LISTEN; /* default */
+
+ while(argc>arg) {
+ if(!strcmp("--version", argv[arg])) {
+ printf("sockfilt IPv4%s\n",
+#ifdef ENABLE_IPV6
+ "/IPv6"
+#else
+ ""
+#endif
+ );
+ return 0;
+ }
+ else if(!strcmp("--pidfile", argv[arg])) {
+ arg++;
+ if(argc>arg)
+ pidname = argv[arg++];
+ }
+ else if(!strcmp("--logfile", argv[arg])) {
+ arg++;
+ if(argc>arg)
+ socklogfile = argv[arg++];
+ }
+ else if(!strcmp("--ipv6", argv[arg])) {
+#ifdef ENABLE_IPV6
+ use_ipv6=TRUE;
+#endif
+ arg++;
+ }
+ else if(!strcmp("--ipv4", argv[arg])) {
+ /* for completeness, we support this option as well */
+ use_ipv6=FALSE;
+ arg++;
+ }
+ else if(!strcmp("--port", argv[arg])) {
+ arg++;
+ if(argc>arg) {
+ port = (unsigned short)atoi(argv[arg]);
+ arg++;
+ }
+ }
+ else if(!strcmp("--connect", argv[arg])) {
+ /* Asked to actively connect to the specified local port instead of
+ doing a passive server-style listening. */
+ arg++;
+ if(argc>arg) {
+ connectport = (unsigned short)atoi(argv[arg]);
+ arg++;
+ }
+ }
+ else {
+ puts("Usage: sockfilt [option]\n"
+ " --version\n"
+ " --logfile [file]\n"
+ " --pidfile [file]\n"
+ " --ipv4\n"
+ " --ipv6\n"
+ " --port [port]");
+ exit(0);
+ }
+ }
+
+#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
+ win32_init();
+ atexit(win32_cleanup);
+#else
+
+#ifdef SIGPIPE
+#ifdef HAVE_SIGNAL
+ signal(SIGPIPE, sigpipe_handler);
+#endif
+#ifdef HAVE_SIGINTERRUPT
+ siginterrupt(SIGPIPE, 1);
+#endif
+#endif
+#endif
+
+#ifdef ENABLE_IPV6
+ if(!use_ipv6)
+#endif
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+#ifdef ENABLE_IPV6
+ else
+ sock = socket(AF_INET6, SOCK_STREAM, 0);
+#endif
+
+ if (sock < 0) {
+ perror("opening stream socket");
+ logmsg("Error opening socket");
+ exit(1);
+ }
+
+ if(connectport) {
+ /* Active mode, we should connect to the given port number */
+ mode = ACTIVE;
+#ifdef ENABLE_IPV6
+ if(!use_ipv6) {
+#endif
+ memset(&me, 0, sizeof(me));
+ me.sin_family = AF_INET;
+ me.sin_port = htons(connectport);
+ me.sin_addr.s_addr = INADDR_ANY;
+ Curl_inet_pton(AF_INET, "127.0.0.1", &me.sin_addr);
+
+ rc = connect(sock, (struct sockaddr *) &me, sizeof(me));
+#ifdef ENABLE_IPV6
+ }
+ else {
+ memset(&me6, 0, sizeof(me6));
+ me6.sin6_family = AF_INET6;
+ me6.sin6_port = htons(connectport);
+ Curl_inet_pton(AF_INET, "::1", &me6.sin6_addr);
+
+ rc = connect(sock, (struct sockaddr *) &me6, sizeof(me6));
+ }
+#endif /* ENABLE_IPV6 */
+ if(rc) {
+ perror("connecting stream socket");
+ logmsg("Error connecting to port %d", port);
+ exit(1);
+ }
+ logmsg("====> Client connect");
+ msgsock = sock; /* use this as stream */
+ }
+ else {
+ /* passive daemon style */
+
+ flag = 1;
+ if (setsockopt
+ (sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &flag,
+ sizeof(int)) < 0) {
+ perror("setsockopt(SO_REUSEADDR)");
+ }
+
+#ifdef ENABLE_IPV6
+ if(!use_ipv6) {
+#endif
+ me.sin_family = AF_INET;
+ me.sin_addr.s_addr = INADDR_ANY;
+ me.sin_port = htons(port);
+ rc = bind(sock, (struct sockaddr *) &me, sizeof(me));
+#ifdef ENABLE_IPV6
+ }
+ else {
+ memset(&me6, 0, sizeof(struct sockaddr_in6));
+ me6.sin6_family = AF_INET6;
+ me6.sin6_addr = in6addr_any;
+ me6.sin6_port = htons(port);
+ rc = bind(sock, (struct sockaddr *) &me6, sizeof(me6));
+ }
+#endif /* ENABLE_IPV6 */
+ if(rc < 0) {
+ perror("binding stream socket");
+ logmsg("Error binding socket");
+ exit(1);
+ }
+
+ if(!port) {
+ /* The system picked a port number, now figure out which port we actually
+ got */
+ /* we succeeded to bind */
+ struct sockaddr_in add;
+ socklen_t socksize = sizeof(add);
+
+ if(getsockname(sock, (struct sockaddr *) &add,
+ &socksize)<0) {
+ fprintf(stderr, "getsockname() failed");
+ return 1;
+ }
+ port = ntohs(add.sin_port);
+ }
+
+ /* start accepting connections */
+ listen(sock, 0);
+
+ }
+
+ pidfile = fopen(pidname, "w");
+ if(pidfile) {
+ fprintf(pidfile, "%d\n", (int)getpid());
+ fclose(pidfile);
+ }
+ else
+ fprintf(stderr, "Couldn't write pid file\n");
+
+ logmsg("Running IPv%d version",
+ (use_ipv6?6:4));
+
+ if(connectport)
+ logmsg("Connected to port %d", connectport);
+ else
+ logmsg("Listening on port %d", port);
+
+ do {
+ ok = juggle(&msgsock, sock, &mode);
+ } while(ok);
+
+ sclose(sock);
+
+ return 0;
+}
+
diff --git a/tests/server/testpart.c b/tests/server/testpart.c
new file mode 100644
index 000000000..c1ebdd44f
--- /dev/null
+++ b/tests/server/testpart.c
@@ -0,0 +1,52 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2005, 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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "setup.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include "getpart.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+/* include memdebug.h last */
+#include "memdebug.h"
+
+int main(int argc, char **argv)
+{
+ if(argc< 3) {
+ printf("./testpart main sub\n");
+ }
+ else {
+ size_t size;
+ unsigned int i;
+ const char *buffer = spitout(stdin, argv[1], argv[2], &size);
+ for(i=0; i< size; i++)
+ printf("%c", buffer[i]);
+ }
+ return 0;
+}
+