summaryrefslogtreecommitdiff
path: root/tests/ftpserver.pl
diff options
context:
space:
mode:
Diffstat (limited to 'tests/ftpserver.pl')
-rw-r--r--tests/ftpserver.pl382
1 files changed, 273 insertions, 109 deletions
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;