diff options
author | Daniel Stenberg <daniel@haxx.se> | 2005-04-18 06:57:44 +0000 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2005-04-18 06:57:44 +0000 |
commit | 14424f7058cd6c2a5aa31dc179db3d4b7bc564a0 (patch) | |
tree | 96893b10e2c340f26f7b662a3b9db342191807eb /tests | |
parent | 6063dff8d02adfba758d9f252c5704535d0d2faa (diff) |
Modified the FTP server to use the new 'sockfilt' program to do all the socket
level stuff. The FTP server communicates with sockfilt using perl's open2().
This enables easier IPv6 support and hopefully FTP-SSL support in the future.
Added four test cases for FTP-ipv6.
Diffstat (limited to 'tests')
-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; +} + |