Newer
Older
#!/usr/bin/env perl
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
Yang Tse
committed
# Copyright (C) 1998 - 2009, 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$
###########################################################################
# This is a server designed for the curl test suite.
#
# In December 2009 we started remaking the server to support more protocols
# that are similar in spirit. Like POP3, IMAP and SMTP in addition to the
# FTP it already supported since a long time.
#
Daniel Stenberg
committed
# It is meant to exercise curl, it is not meant to be a fully working
# or even very standard compliant server.
#
# You may optionally specify port on the command line, otherwise it'll
# default to port 8921.
#
Daniel Stenberg
committed
# All socket/network/TCP related stuff is done by the 'sockfilt' program.
#
Daniel Stenberg
committed
use strict;
Daniel Stenberg
committed
use IPC::Open2;
#use Time::HiRes qw( gettimeofday ); # not available in perl 5.6
Daniel Stenberg
committed
Daniel Stenberg
committed
require "ftp.pm";
Daniel Stenberg
committed
my $ftpdnum="";
# open and close each time to allow removal at any time
# if later than perl 5.6 is used
# my ($seconds, $microseconds) = gettimeofday;
my $seconds = time();
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime($seconds);
Daniel Stenberg
committed
open(FTPLOG, ">>log/ftpd$ftpdnum.log");
printf FTPLOG ("%02d:%02d:%02d ", $hour, $min, $sec);
close(FTPLOG);
Daniel Stenberg
committed
sub ftpmsg {
# append to the server.input file
Daniel Stenberg
committed
open(INPUT, ">>log/server$ftpdnum.input") ||
logmsg "failed to open log/server$ftpdnum.input\n";
print INPUT @_;
close(INPUT);
# use this, open->print->close system only to make the file
# open as little as possible, to make the test suite run
# better on windows/cygwin
}
Daniel Stenberg
committed
my $verbose=0; # set to 1 for debugging
Daniel Stenberg
committed
my $retrweirdo=0;
my $retrnosize=0;
my $srcdir=".";
Daniel Stenberg
committed
my $controldelay=0; # set to 1 to delay the control connect data sending to
# test that curl deals with that nicely
Daniel Stenberg
committed
my $slavepid; # for the DATA connection sockfilt slave process
my $ipv6;
my $ext; # append to log/pid file names
my $grok_eprt;
Daniel Stenberg
committed
my $port = 8921; # just a default
my $listenaddr = "127.0.0.1"; # just a default
Daniel Stenberg
committed
my $pidfile = ".ftpd.pid"; # a default, use --pidfile
my $SERVERLOGS_LOCK="log/serverlogs.lock"; # server logs advisor read lock
Yang Tse
committed
my $serverlogslocked=0;
my $proto="ftp";
Daniel Stenberg
committed
do {
if($ARGV[0] eq "-v") {
$verbose=1;
}
elsif($ARGV[0] eq "-s") {
$srcdir=$ARGV[1];
shift @ARGV;
}
Daniel Stenberg
committed
elsif($ARGV[0] eq "--id") {
$ftpdnum=$ARGV[1];
shift @ARGV;
}
elsif($ARGV[0] eq "--proto") {
# ftp pop3 imap smtp
$proto=$ARGV[1];
shift @ARGV;
}
Daniel Stenberg
committed
elsif($ARGV[0] eq "--pidfile") {
$pidfile=$ARGV[1];
shift @ARGV;
}
Daniel Stenberg
committed
elsif($ARGV[0] eq "--ipv6") {
$ipv6="--ipv6";
$ext="ipv6";
$grok_eprt = 1;
}
elsif($ARGV[0] eq "--port") {
$port = $ARGV[1];
shift @ARGV;
Daniel Stenberg
committed
}
elsif($ARGV[0] eq "--addr") {
$listenaddr = $ARGV[1];
$listenaddr =~ s/^\[(.*)\]$/$1/;
shift @ARGV;
}
Daniel Stenberg
committed
} while(shift @ARGV);
# a dedicated protocol has been selected, check that it's a fine one
if($proto !~ /^(ftp|imap|pop3|smtp)\z/) {
die "unsupported protocol selected";
}
sub catch_zap {
my $signame = shift;
ftpkillslaves(1);
Yang Tse
committed
if($serverlogslocked) {
$serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK);
}
}
$SIG{INT} = \&catch_zap;
Daniel Stenberg
committed
my $sfpid;
Daniel Stenberg
committed
local(*SFREAD, *SFWRITE);
sub sysread_or_die {
my $FH = shift;
my $scalar = shift;
my $length = shift;
my $fcaller;
my $lcaller;
my $result;
$result = sysread($$FH, $$scalar, $length);
if(not defined $result) {
($fcaller, $lcaller) = (caller)[1,2];
logmsg "Failed to read input\n";
logmsg "Error: ftp$ftpdnum$ext sysread error: $!\n";
kill(9, $sfpid);
logmsg "Exited from sysread_or_die() at $fcaller " .
"line $lcaller. ftp$ftpdnum$ext sysread error: $!\n";
Yang Tse
committed
if($serverlogslocked) {
$serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK);
}
exit;
}
elsif($result == 0) {
($fcaller, $lcaller) = (caller)[1,2];
logmsg "Failed to read input\n";
logmsg "Error: ftp$ftpdnum$ext read zero\n";
kill(9, $sfpid);
logmsg "Exited from sysread_or_die() at $fcaller " .
"line $lcaller. ftp$ftpdnum$ext read zero\n";
Yang Tse
committed
if($serverlogslocked) {
$serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK);
}
exit;
}
return $result;
}
Daniel Stenberg
committed
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);
Daniel Stenberg
committed
Daniel Stenberg
committed
print STDERR "$cmd\n" if($verbose);
Daniel Stenberg
committed
Daniel Stenberg
committed
print SFWRITE "PING\n";
my $pong;
sysread SFREAD, $pong, 5;
Daniel Stenberg
committed
if($pong !~ /^PONG/) {
logmsg "Failed sockfilt command: $cmd\n";
kill(9, $sfpid);
Yang Tse
committed
if($serverlogslocked) {
$serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK);
}
Daniel Stenberg
committed
die "Failed to start sockfilt!";
}
}
Daniel Stenberg
committed
startsf();
Daniel Stenberg
committed
logmsg sprintf("%s server listens on port IPv%d/$port\n", uc($proto),
$ipv6?6:4);
Daniel Stenberg
committed
open(PID, ">$pidfile");
Daniel Stenberg
committed
close(PID);
logmsg("logged pid $$ in $pidfile\n");
Daniel Stenberg
committed
sub sockfilt {
my $l;
foreach $l (@_) {
printf SFWRITE "DATA\n%04x\n", length($l);
print SFWRITE $l;
Daniel Stenberg
committed
}
}
sub sockfiltsecondary {
my $l;
foreach $l (@_) {
printf DWRITE "DATA\n%04x\n", length($l);
print DWRITE $l;
}
}
Daniel Stenberg
committed
# 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
Daniel Stenberg
committed
sockfilt @_;
Daniel Stenberg
committed
}
else {
my $a = join("", @_);
my @a = split("", $a);
for(@a) {
Daniel Stenberg
committed
sockfilt $_;
select(undef, undef, undef, 0.01);
Daniel Stenberg
committed
}
}
Daniel Stenberg
committed
my $log;
foreach $log (@_) {
my $l = $log;
$l =~ s/[\r\n]//g;
logmsg "> \"$l\"\n";
Daniel Stenberg
committed
}
Daniel Stenberg
committed
}
# Send data to the client on the data stream
sub senddata {
Daniel Stenberg
committed
my $l;
foreach $l (@_) {
if(!$controldelay) {
# spit it all out at once
sockfiltsecondary $l;
}
else {
# pause between each byte
for (split(//,$l)) {
sockfiltsecondary $_;
select(undef, undef, undef, 0.01);
}
}
Daniel Stenberg
committed
}
Daniel Stenberg
committed
}
my %displaytext;
my %commandfunc;
Daniel Stenberg
committed
Daniel Stenberg
committed
# callback functions for certain commands
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# and text shown before the function specified below is run
if($proto eq "ftp") {
%displaytext = ('USER' => '331 We are happy you popped in!',
'PASS' => '230 Welcome you silly person',
'PORT' => '200 You said PORT - I say FINE',
'TYPE' => '200 I modify TYPE as you wanted',
'LIST' => '150 here comes a directory',
'NLST' => '150 here comes a directory',
'CWD' => '250 CWD command successful.',
'SYST' => '215 UNIX Type: L8', # just fake something
'QUIT' => '221 bye bye baby', # just reply something
'PWD' => '257 "/nowhere/anywhere" is current directory',
'MKD' => '257 Created your requested directory',
'REST' => '350 Yeah yeah we set it there for you',
'DELE' => '200 OK OK OK whatever you say',
'RNFR' => '350 Received your order. Please provide more',
'RNTO' => '250 Ok, thanks. File renaming completed.',
'NOOP' => '200 Yes, I\'m very good at doing nothing.',
'PBSZ' => '500 PBSZ not implemented',
'PROT' => '500 PROT not implemented',
);
%commandfunc = ( 'PORT' => \&PORT_command,
'EPRT' => \&PORT_command,
'LIST' => \&LIST_command,
'NLST' => \&NLST_command,
'PASV' => \&PASV_command,
'EPSV' => \&PASV_command,
'RETR' => \&RETR_command,
'SIZE' => \&SIZE_command,
'REST' => \&REST_command,
'STOR' => \&STOR_command,
'APPE' => \&STOR_command, # append looks like upload
'MDTM' => \&MDTM_command,
);
}
elsif($proto eq "pop3") {
%commandfunc = ('RETR' => \&RETR_pop3,
);
%displaytext = ('USER' => '+OK We are happy you popped in!',
'PASS' => '+OK Access granted',
'QUIT' => '+OK byebye',
);
}
elsif($proto eq "imap") {
%commandfunc = ('FETCH' => \&FETCH_imap,
);
%displaytext = ('LOGIN' => ' OK We are happy you popped in!',
'SELECT' => ' OK selection done',
);
}
Daniel Stenberg
committed
Daniel Stenberg
committed
my ($closed)=@_; # non-zero if already disconnected
if(!$closed) {
Daniel Stenberg
committed
logmsg "* disconnect data connection\n";
Daniel Stenberg
committed
print DWRITE "DISC\n";
my $i;
sysread DREAD, $i, 5;
}
else {
logmsg "data connection already disconnected\n";
}
Daniel Stenberg
committed
logmsg "=====> Closed data connection\n";
Daniel Stenberg
committed
Daniel Stenberg
committed
logmsg "* quit sockfilt for data (pid $slavepid)\n";
Daniel Stenberg
committed
print DWRITE "QUIT\n";
waitpid $slavepid, 0;
$slavepid=0;
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
################
################ IMAP commands
################
sub FETCH_imap {
my ($testno) = @_;
my @data;
if($testno =~ /^verifiedserver$/) {
# this is the secret command that verifies that this actually is
# the curl test server
my $response = "WE ROOLZ: $$\r\n";
if($verbose) {
print STDERR "FTPD: We returned proof we are the test server\n";
}
$data[0] = $response;
logmsg "return proof we are we\n";
}
else {
logmsg "retrieve a mail\n";
$testno =~ s/^([^0-9]*)//;
my $testpart = "";
if ($testno > 10000) {
$testpart = $testno % 10000;
$testno = int($testno / 10000);
}
# send mail content
loadtest("$srcdir/data/test$testno");
@data = getpart("reply", "data$testpart");
}
sendcontrol "- OK Mail transfer starts\r\n";
for my $d (@data) {
sendcontrol $d;
}
return 0;
}
################
################ POP3 commands
################
sub RETR_pop3 {
my ($testno) = @_;
my @data;
if($testno =~ /^verifiedserver$/) {
# this is the secret command that verifies that this actually is
# the curl test server
my $response = "WE ROOLZ: $$\r\n";
if($verbose) {
print STDERR "FTPD: We returned proof we are the test server\n";
}
$data[0] = $response;
logmsg "return proof we are we\n";
}
else {
logmsg "retrieve a mail\n";
$testno =~ s/^([^0-9]*)//;
my $testpart = "";
if ($testno > 10000) {
$testpart = $testno % 10000;
$testno = int($testno / 10000);
}
# send mail content
loadtest("$srcdir/data/test$testno");
@data = getpart("reply", "data$testpart");
}
sendcontrol "+OK Mail transfer starts\r\n";
for my $d (@data) {
sendcontrol $d;
}
# end with the magic 5-byte end of mail marker
sendcontrol "\r\n.\r\n";
return 0;
}
################
################ FTP commands
################
Daniel Stenberg
committed
my $rest=0;
sub REST_command {
$rest = $_[0];
logmsg "Set REST position to $rest\n"
Daniel Stenberg
committed
}
sub LIST_command {
# print "150 ASCII data connection for /bin/ls (193.15.23.1,59196) (0 bytes)\r\n";
Daniel Stenberg
committed
my @ftpdir=("total 20\r\n",
"drwxr-xr-x 8 98 98 512 Oct 22 13:06 .\r\n",
"drwxr-xr-x 8 98 98 512 Oct 22 13:06 ..\r\n",
"drwxr-xr-x 2 98 98 512 May 2 1996 .NeXT\r\n",
"-r--r--r-- 1 0 1 35 Jul 16 1996 README\r\n",
"lrwxrwxrwx 1 0 1 7 Dec 9 1999 bin -> usr/bin\r\n",
"dr-xr-xr-x 2 0 1 512 Oct 1 1997 dev\r\n",
"drwxrwxrwx 2 98 98 512 May 29 16:04 download.html\r\n",
"dr-xr-xr-x 2 0 1 512 Nov 30 1995 etc\r\n",
"drwxrwxrwx 2 98 1 512 Oct 30 14:33 pub\r\n",
"dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr\r\n");
logmsg "pass LIST data on data connection\n";
Daniel Stenberg
committed
for(@ftpdir) {
Daniel Stenberg
committed
senddata $_;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
close_dataconn(0);
Daniel Stenberg
committed
sendcontrol "226 ASCII transfer complete\r\n";
Daniel Stenberg
committed
return 0;
}
sub NLST_command {
my @ftpdir=("file", "with space", "fake", "..", " ..", "funny", "README");
logmsg "pass NLST data on data connection\n";
Daniel Stenberg
committed
senddata "$_\r\n";
Daniel Stenberg
committed
close_dataconn(0);
Daniel Stenberg
committed
sendcontrol "226 ASCII transfer complete\r\n";
my $testpart = "";
if ($testno > 10000) {
$testpart = $testno % 10000;
$testno = int($testno / 10000);
loadtest("$srcdir/data/test$testno");
my @data = getpart("reply", "mdtm");
my $reply = $data[0];
chomp $reply;
if($reply <0) {
Daniel Stenberg
committed
sendcontrol "550 $testno: no such file.\r\n";
Daniel Stenberg
committed
sendcontrol "$reply\r\n";
Daniel Stenberg
committed
sendcontrol "500 MDTM: no such command.\r\n";
my $testpart = "";
if ($testno > 10000) {
$testpart = $testno % 10000;
$testno = int($testno / 10000);
loadtest("$srcdir/data/test$testno");
Daniel Stenberg
committed
if($testno eq "verifiedserver") {
my $response = "WE ROOLZ: $$\r\n";
my $size = length($response);
sendcontrol "213 $size\r\n";
return 0;
}
Daniel Stenberg
committed
if($size) {
if($size > -1) {
Daniel Stenberg
committed
sendcontrol "213 $size\r\n";
}
else {
Daniel Stenberg
committed
sendcontrol "550 $testno: No such file or directory.\r\n";
@data = getpart("reply", "data$testpart");
for(@data) {
$size += length($_);
}
if($size) {
Daniel Stenberg
committed
sendcontrol "213 $size\r\n";
Daniel Stenberg
committed
sendcontrol "550 $testno: No such file or directory.\r\n";
my ($testno) = @_;
if($testno =~ /^verifiedserver$/) {
# this is the secret command that verifies that this actually is
# the curl test server
my $response = "WE ROOLZ: $$\r\n";
my $len = length($response);
Daniel Stenberg
committed
sendcontrol "150 Binary junk ($len bytes).\r\n";
senddata "WE ROOLZ: $$\r\n";
Daniel Stenberg
committed
close_dataconn(0);
Daniel Stenberg
committed
sendcontrol "226 File transfer complete\r\n";
if($verbose) {
print STDERR "FTPD: We returned proof we are the test server\n";
}
return 0;
}
$testno =~ s/^([^0-9]*)//;
my $testpart = "";
if ($testno > 10000) {
$testpart = $testno % 10000;
$testno = int($testno / 10000);
loadtest("$srcdir/data/test$testno");
my @data = getpart("reply", "data$testpart");
my $size=0;
for(@data) {
my %hash = getpartattr("reply", "data$testpart");
Daniel Stenberg
committed
if($size || $hash{'sendzero'}) {
if($rest) {
# move read pointer forward
$size -= $rest;
logmsg "REST $rest was removed from size, makes $size left\n";
Daniel Stenberg
committed
if($retrweirdo) {
Daniel Stenberg
committed
sendcontrol "150 Binary data connection for $testno () ($size bytes).\r\n",
Daniel Stenberg
committed
"226 File transfer complete\r\n";
for(@data) {
my $send = $_;
Daniel Stenberg
committed
senddata $send;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
close_dataconn(0);
Daniel Stenberg
committed
$retrweirdo=0; # switch off the weirdo again!
Daniel Stenberg
committed
else {
my $sz = "($size bytes)";
if($retrnosize) {
$sz = "size?";
}
Daniel Stenberg
committed
sendcontrol "150 Binary data connection for $testno () $sz.\r\n";
Daniel Stenberg
committed
for(@data) {
my $send = $_;
Daniel Stenberg
committed
senddata $send;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
close_dataconn(0);
Daniel Stenberg
committed
sendcontrol "226 File transfer complete\r\n";
Daniel Stenberg
committed
}
Daniel Stenberg
committed
sendcontrol "550 $testno: No such file or directory.\r\n";
logmsg "STOR test number $testno in $filename\n";
Daniel Stenberg
committed
sendcontrol "125 Gimme gimme gimme!\r\n";
open(FILE, ">$filename") ||
return 0; # failed to open output
my $line;
Daniel Stenberg
committed
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
my $disc=0;
while (5 == (sysread DREAD, $line, 5)) {
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!
$disc=1;
last;
}
else {
logmsg "No support for: $line";
last;
}
}
if($nosave) {
print FILE "$ulsize bytes would've been stored here\n";
Daniel Stenberg
committed
close_dataconn($disc);
Daniel Stenberg
committed
sendcontrol "226 File transfer complete\r\n";
Daniel Stenberg
committed
sub PASV_command {
Daniel Stenberg
committed
my $pasvport;
Daniel Stenberg
committed
my $pidf=".sockdata$ftpdnum$ext.pid";
Daniel Stenberg
committed
if($prev > 0) {
print "kill existing server: $prev\n" if($verbose);
kill(9, $prev);
waitpid($prev, 0);
Daniel Stenberg
committed
}
# We fire up a new sockfilt to do the data transfer for us.
Daniel Stenberg
committed
$slavepid = open2(\*DREAD, \*DWRITE,
"./server/sockfilt --port 0 --logfile log/sockdata$ftpdnum$ext.log --pidfile $pidf $ipv6");
Daniel Stenberg
committed
Daniel Stenberg
committed
print DWRITE "PING\n";
my $pong;
sysread_or_die(\*DREAD, \$pong, 5);
Daniel Stenberg
committed
Daniel Stenberg
committed
if($pong !~ /^PONG/) {
waitpid($slavepid, 0);
Daniel Stenberg
committed
sendcontrol "500 no free ports!\r\n";
logmsg "failed to run sockfilt for data connection\n";
return 0;
}
Daniel Stenberg
committed
Daniel Stenberg
committed
logmsg "Run sockfilt for data on pid $slavepid\n";
Daniel Stenberg
committed
# Find out what port we listen on
my $i;
print DWRITE "PORT\n";
# READ the response code
sysread_or_die(\*DREAD, \$i, 5);
Daniel Stenberg
committed
# READ the response size
sysread_or_die(\*DREAD, \$i, 5);
Daniel Stenberg
committed
my $size = hex($i);
# READ the response data
sysread_or_die(\*DREAD, \$i, $size);
Daniel Stenberg
committed
# The data is in the format
# IPvX/NNN
if($i =~ /IPv(\d)\/(\d+)/) {
# FIX: deal with IP protocol version
$pasvport = $2;
}
Daniel Stenberg
committed
my $p=$listenaddr;
$p =~ s/\./,/g;
if($pasvbadip) {
$p="1,2,3,4";
}
sendcontrol sprintf("227 Entering Passive Mode ($p,%d,%d)\n",
Daniel Stenberg
committed
($pasvport/256), ($pasvport%256));
Daniel Stenberg
committed
sendcontrol sprintf("229 Entering Passive Mode (|||%d|)\n", $pasvport);
Daniel Stenberg
committed
Daniel Stenberg
committed
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
Daniel Stenberg
committed
# assume swift operations unless explicitly slow
alarm ($controldelay?20:10);
Daniel Stenberg
committed
# Wait for 'CNCT'
Daniel Stenberg
committed
while(sysread(DREAD, $input, 5)) {
if($input !~ /^CNCT/) {
# we wait for a connected client
logmsg "Odd, we got $input from client\n";
next;
}
logmsg "====> Client DATA connect\n";
last;
}
Daniel Stenberg
committed
alarm 0;
};
if ($@) {
# timed out
Daniel Stenberg
committed
print DWRITE "QUIT\n";
waitpid $slavepid, 0;
Daniel Stenberg
committed
logmsg "accept failed\n";
Daniel Stenberg
committed
$slavepid=0;
Daniel Stenberg
committed
return;
}
else {
Daniel Stenberg
committed
logmsg "data connection setup on port $pasvport\n";
Daniel Stenberg
committed
}
Daniel Stenberg
committed
return;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
# Support both PORT and EPRT here. Consider LPRT too.
Daniel Stenberg
committed
sub PORT_command {
Daniel Stenberg
committed
my ($arg, $cmd) = @_;
my $port;
my $addr;
Daniel Stenberg
committed
# We always ignore the given IP and use localhost.
Daniel Stenberg
committed
Daniel Stenberg
committed
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;
$addr = "$1.$2.$3.$4";
Daniel Stenberg
committed
}
# EPRT |2|::1|49706|
elsif(($cmd eq "EPRT") && ($grok_eprt)) {
if($arg !~ /(\d+)\|([^\|]+)\|(\d+)/) {
sendcontrol "500 silly you, go away\r\n";
return 0;
}
sendcontrol "200 Thanks for dropping by. We contact you later\r\n";
$port = $3;
$addr = $2;
Daniel Stenberg
committed
}
else {
sendcontrol "500 we don't like $cmd now\r\n";
return 0;
Daniel Stenberg
committed
}
if(!$port || $port > 65535) {
print STDERR "very illegal PORT number: $port\n";
return 1;
}
# We fire up a new sockfilt to do the data transfer for us.
Daniel Stenberg
committed
# FIX: make it use IPv6 if need be
my $filtcmd="./server/sockfilt --connect $port --addr $addr --logfile log/sockdata$ftpdnum$ext.log --pidfile .sockdata$ftpdnum$ext.pid $ipv6";
$slavepid = open2(\*DREAD, \*DWRITE, $filtcmd);
print STDERR "$filtcmd\n" if($verbose);
Daniel Stenberg
committed
Daniel Stenberg
committed
print DWRITE "PING\n";
my $pong;
sysread DREAD, $pong, 5;
Daniel Stenberg
committed
Daniel Stenberg
committed
if($pong !~ /^PONG/) {
logmsg "Failed sockfilt for data connection\n";
kill(9, $slavepid);
waitpid($slavepid, 0);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
Daniel Stenberg
committed
logmsg "====> Client DATA connect to port $port\n";
Daniel Stenberg
committed
Daniel Stenberg
committed
return;
}
Daniel Stenberg
committed
my %customreply;
my %customcount;
sub customize {
$nosave = 0; # default is to save as normal
$controldelay = 0; # default is no delaying the responses
$retrweirdo = 0;
$retrnosize = 0;
$pasvbadip = 0;
$nosave = 0;
%customreply = ();
%customcount = ();
%delayreply = ();
open(CUSTOM, "<log/ftpserver.cmd") ||
return 1;
logmsg "FTPD: Getting commands from log/ftpserver.cmd\n";
while(<CUSTOM>) {
if($_ =~ /REPLY ([A-Z]+) (.*)/) {
$customreply{$1}=eval "qq{$2}";
logmsg "FTPD: set custom reply for $1\n";
if($_ =~ /COUNT ([A-Z]+) (.*)/) {
# we blank the customreply for this command when having
# been used this number of times
$customcount{$1}=$2;
logmsg "FTPD: blank custom reply for $1 after $2 uses\n";
}
elsif($_ =~ /DELAY ([A-Z]+) (\d*)/) {
$delayreply{$1}=$2;
logmsg "FTPD: delay reply for $1 with $2 seconds\n";
elsif($_ =~ /SLOWDOWN/) {
$controldelay=1;
logmsg "FTPD: send response with 0.1 sec delay between each byte\n";
}
Daniel Stenberg
committed
elsif($_ =~ /RETRWEIRDO/) {
logmsg "FTPD: instructed to use RETRWEIRDO\n";
Daniel Stenberg
committed
$retrweirdo=1;
}
elsif($_ =~ /RETRNOSIZE/) {
logmsg "FTPD: instructed to use RETRNOSIZE\n";
$retrnosize=1;
}
elsif($_ =~ /PASVBADIP/) {
logmsg "FTPD: instructed to use PASVBADIP\n";
$pasvbadip=1;
}
elsif($_ =~ /NOSAVE/) {
# don't actually store the file we upload - to be used when
# uploading insanely huge amounts
$nosave = 1;
logmsg "FTPD: NOSAVE prevents saving of uploaded data\n";
}
}
close(CUSTOM);
}
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
my @welcome;
if($proto eq "ftp") {
@welcome=(
'220- _ _ ____ _ '."\r\n",
'220- ___| | | | _ \| | '."\r\n",
'220- / __| | | | |_) | | '."\r\n",
'220- | (__| |_| | _ <| |___ '."\r\n",
'220 \___|\___/|_| \_\_____|'."\r\n");
}
elsif($proto eq "pop3") {
@welcome=(
' _ _ ____ _ '."\r\n",
' ___| | | | _ \| | '."\r\n",
' / __| | | | |_) | | '."\r\n",
' | (__| |_| | _ <| |___ '."\r\n",
' \___|\___/|_| \_\_____|'."\r\n",
'+OK cURL POP3 server ready to serve'."\r\n");
}
elsif($proto eq "imap") {
@welcome=(
' _ _ ____ _ '."\r\n",
' ___| | | | _ \| | '."\r\n",
' / __| | | | |_) | | '."\r\n",
' | (__| |_| | _ <| |___ '."\r\n",
' \___|\___/|_| \_\_____|'."\r\n",
'* OK cURL IMAP server ready to serve'."\r\n");
}
Daniel Stenberg
committed
while(1) {
#
# We read 'sockfilt' commands.
#
my $input;
logmsg "Awaiting input\n";
sysread_or_die(\*SFREAD, \$input, 5);
Daniel Stenberg
committed
if($input !~ /^CNCT/) {
# we wait for a connected client
logmsg "sockfilt said: $input";
Daniel Stenberg
committed
next;
}
logmsg "====> Client connect\n";
Daniel Stenberg
committed
set_advisor_read_lock($SERVERLOGS_LOCK);
Yang Tse
committed
$serverlogslocked = 1;
Daniel Stenberg
committed
kill(9, $slavepid) if($slavepid);
waitpid($slavepid, 0) if($slavepid);
Daniel Stenberg
committed
$slavepid=0;
&customize(); # read test control instructions
Daniel Stenberg
committed
sendcontrol @welcome;
for(@welcome) {
print STDERR "OUT: $_";
}