diff options
-rw-r--r-- | tests/Makefile.am | 3 | ||||
-rw-r--r-- | tests/data/Makefile.am | 2 | ||||
-rw-r--r-- | tests/data/test103 | 6 | ||||
-rw-r--r-- | tests/data/test252 | 57 | ||||
-rw-r--r-- | tests/data/test253 | 57 | ||||
-rw-r--r-- | tests/data/test254 | 58 | ||||
-rw-r--r-- | tests/data/test255 | 58 | ||||
-rw-r--r-- | tests/ftp.pm | 17 | ||||
-rw-r--r-- | tests/ftpserver.pl | 382 | ||||
-rwxr-xr-x | tests/runtests.pl | 74 | ||||
-rw-r--r-- | tests/server/Makefile.am | 16 | ||||
-rw-r--r-- | tests/server/getpart.c | 16 | ||||
-rw-r--r-- | tests/server/sockfilt.c | 668 | ||||
-rw-r--r-- | tests/server/testpart.c | 52 |
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; +} + |