Annotation of embedaddon/curl/tests/ftpserver.pl, revision 1.1.1.1

1.1       misho       1: #!/usr/bin/env perl
                      2: #***************************************************************************
                      3: #                                  _   _ ____  _
                      4: #  Project                     ___| | | |  _ \| |
                      5: #                             / __| | | | |_) | |
                      6: #                            | (__| |_| |  _ <| |___
                      7: #                             \___|\___/|_| \_\_____|
                      8: #
                      9: # Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
                     10: #
                     11: # This software is licensed as described in the file COPYING, which
                     12: # you should have received as part of this distribution. The terms
                     13: # are also available at https://curl.haxx.se/docs/copyright.html.
                     14: #
                     15: # You may opt to use, copy, modify, merge, publish, distribute and/or sell
                     16: # copies of the Software, and permit persons to whom the Software is
                     17: # furnished to do so, under the terms of the COPYING file.
                     18: #
                     19: # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
                     20: # KIND, either express or implied.
                     21: #
                     22: ###########################################################################
                     23: 
                     24: # This is a server designed for the curl test suite.
                     25: #
                     26: # In December 2009 we started remaking the server to support more protocols
                     27: # that are similar in spirit. Like POP3, IMAP and SMTP in addition to the FTP
                     28: # it already supported since a long time. Note that it still only supports one
                     29: # protocol per invoke. You need to start multiple servers to support multiple
                     30: # protocols simultaneously.
                     31: #
                     32: # It is meant to exercise curl, it is not meant to be a fully working
                     33: # or even very standard compliant server.
                     34: #
                     35: # You may optionally specify port on the command line, otherwise it'll
                     36: # default to port 8921.
                     37: #
                     38: # All socket/network/TCP related stuff is done by the 'sockfilt' program.
                     39: #
                     40: 
                     41: BEGIN {
                     42:     push(@INC, $ENV{'srcdir'}) if(defined $ENV{'srcdir'});
                     43:     push(@INC, ".");
                     44:     # sub second timestamping needs Time::HiRes
                     45:     eval {
                     46:         no warnings "all";
                     47:         require Time::HiRes;
                     48:         import  Time::HiRes qw( gettimeofday );
                     49:     }
                     50: }
                     51: 
                     52: use strict;
                     53: use warnings;
                     54: use IPC::Open2;
                     55: use Digest::MD5;
                     56: 
                     57: require "getpart.pm";
                     58: require "ftp.pm";
                     59: require "directories.pm";
                     60: 
                     61: use serverhelp qw(
                     62:     servername_str
                     63:     server_pidfilename
                     64:     server_logfilename
                     65:     mainsockf_pidfilename
                     66:     mainsockf_logfilename
                     67:     datasockf_pidfilename
                     68:     datasockf_logfilename
                     69:     );
                     70: 
                     71: use sshhelp qw(
                     72:     exe_ext
                     73:     );
                     74: 
                     75: #**********************************************************************
                     76: # global vars...
                     77: #
                     78: my $verbose = 0;    # set to 1 for debugging
                     79: my $idstr = "";     # server instance string
                     80: my $idnum = 1;      # server instance number
                     81: my $ipvnum = 4;     # server IPv number (4 or 6)
                     82: my $proto = 'ftp';  # default server protocol
                     83: my $srcdir;         # directory where ftpserver.pl is located
                     84: my $srvrname;       # server name for presentation purposes
                     85: my $cwd_testno;     # test case numbers extracted from CWD command
                     86: my $path   = '.';
                     87: my $logdir = $path .'/log';
                     88: 
                     89: #**********************************************************************
                     90: # global vars used for server address and primary listener port
                     91: #
                     92: my $port = 8921;               # default primary listener port
                     93: my $listenaddr = '127.0.0.1';  # default address for listener port
                     94: 
                     95: #**********************************************************************
                     96: # global vars used for file names
                     97: #
                     98: my $pidfile;            # server pid file name
                     99: my $portfile=".ftpserver.port"; # server port file name
                    100: my $logfile;            # server log file name
                    101: my $mainsockf_pidfile;  # pid file for primary connection sockfilt process
                    102: my $mainsockf_logfile;  # log file for primary connection sockfilt process
                    103: my $datasockf_pidfile;  # pid file for secondary connection sockfilt process
                    104: my $datasockf_logfile;  # log file for secondary connection sockfilt process
                    105: 
                    106: #**********************************************************************
                    107: # global vars used for server logs advisor read lock handling
                    108: #
                    109: my $SERVERLOGS_LOCK = 'log/serverlogs.lock';
                    110: my $serverlogslocked = 0;
                    111: 
                    112: #**********************************************************************
                    113: # global vars used for child processes PID tracking
                    114: #
                    115: my $sfpid;        # PID for primary connection sockfilt process
                    116: my $slavepid;     # PID for secondary connection sockfilt process
                    117: 
                    118: #**********************************************************************
                    119: # global typeglob filehandle vars to read/write from/to sockfilters
                    120: #
                    121: local *SFREAD;    # used to read from primary connection
                    122: local *SFWRITE;   # used to write to primary connection
                    123: local *DREAD;     # used to read from secondary connection
                    124: local *DWRITE;    # used to write to secondary connection
                    125: 
                    126: my $sockfilt_timeout = 5;  # default timeout for sockfilter eXsysreads
                    127: 
                    128: #**********************************************************************
                    129: # global vars which depend on server protocol selection
                    130: #
                    131: my %commandfunc;   # protocol command specific function callbacks
                    132: my %displaytext;   # text returned to client before callback runs
                    133: 
                    134: #**********************************************************************
                    135: # global vars customized for each test from the server commands file
                    136: #
                    137: my $ctrldelay;     # set if server should throttle ctrl stream
                    138: my $datadelay;     # set if server should throttle data stream
                    139: my $retrweirdo;    # set if ftp server should use RETRWEIRDO
                    140: my $retrnosize;    # set if ftp server should use RETRNOSIZE
                    141: my $pasvbadip;     # set if ftp server should use PASVBADIP
                    142: my $nosave;        # set if ftp server should not save uploaded data
                    143: my $nodataconn;    # set if ftp srvr doesn't establish or accepts data channel
                    144: my $nodataconn425; # set if ftp srvr doesn't establish data ch and replies 425
                    145: my $nodataconn421; # set if ftp srvr doesn't establish data ch and replies 421
                    146: my $nodataconn150; # set if ftp srvr doesn't establish data ch and replies 150
                    147: my @capabilities;  # set if server supports capability commands
                    148: my @auth_mechs;    # set if server supports authentication commands
                    149: my %fulltextreply; #
                    150: my %commandreply;  #
                    151: my %customcount;   #
                    152: my %delayreply;    #
                    153: 
                    154: #**********************************************************************
                    155: # global variables for to test ftp wildcardmatching or other test that
                    156: # need flexible LIST responses.. and corresponding files.
                    157: # $ftptargetdir is keeping the fake "name" of LIST directory.
                    158: #
                    159: my $ftplistparserstate;
                    160: my $ftptargetdir="";
                    161: 
                    162: #**********************************************************************
                    163: # global variables used when running a ftp server to keep state info
                    164: # relative to the secondary or data sockfilt process. Values of these
                    165: # variables should only be modified using datasockf_state() sub, given
                    166: # that they are closely related and relationship is a bit awkward.
                    167: #
                    168: my $datasockf_state = 'STOPPED'; # see datasockf_state() sub
                    169: my $datasockf_mode = 'none';     # ['none','active','passive']
                    170: my $datasockf_runs = 'no';       # ['no','yes']
                    171: my $datasockf_conn = 'no';       # ['no','yes']
                    172: 
                    173: #**********************************************************************
                    174: # global vars used for signal handling
                    175: #
                    176: my $got_exit_signal = 0; # set if program should finish execution ASAP
                    177: my $exit_signal;         # first signal handled in exit_signal_handler
                    178: 
                    179: #**********************************************************************
                    180: # Mail related definitions
                    181: #
                    182: my $TEXT_PASSWORD = "secret";
                    183: my $POP3_TIMESTAMP = "<1972.987654321\@curl>";
                    184: 
                    185: #**********************************************************************
                    186: # exit_signal_handler will be triggered to indicate that the program
                    187: # should finish its execution in a controlled way as soon as possible.
                    188: # For now, program will also terminate from within this handler.
                    189: #
                    190: sub exit_signal_handler {
                    191:     my $signame = shift;
                    192:     # For now, simply mimic old behavior.
                    193:     killsockfilters($proto, $ipvnum, $idnum, $verbose);
                    194:     unlink($pidfile);
                    195:     unlink($portfile);
                    196:     if($serverlogslocked) {
                    197:         $serverlogslocked = 0;
                    198:         clear_advisor_read_lock($SERVERLOGS_LOCK);
                    199:     }
                    200:     exit;
                    201: }
                    202: 
                    203: #**********************************************************************
                    204: # logmsg is general message logging subroutine for our test servers.
                    205: #
                    206: sub logmsg {
                    207:     my $now;
                    208:     # sub second timestamping needs Time::HiRes
                    209:     if($Time::HiRes::VERSION) {
                    210:         my ($seconds, $usec) = gettimeofday();
                    211:         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
                    212:             localtime($seconds);
                    213:         $now = sprintf("%02d:%02d:%02d.%06d ", $hour, $min, $sec, $usec);
                    214:     }
                    215:     else {
                    216:         my $seconds = time();
                    217:         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
                    218:             localtime($seconds);
                    219:         $now = sprintf("%02d:%02d:%02d ", $hour, $min, $sec);
                    220:     }
                    221:     if(open(LOGFILEFH, ">>$logfile")) {
                    222:         print LOGFILEFH $now;
                    223:         print LOGFILEFH @_;
                    224:         close(LOGFILEFH);
                    225:     }
                    226: }
                    227: 
                    228: sub ftpmsg {
                    229:   # append to the server.input file
                    230:   open(INPUT, ">>log/server$idstr.input") ||
                    231:     logmsg "failed to open log/server$idstr.input\n";
                    232: 
                    233:   print INPUT @_;
                    234:   close(INPUT);
                    235: 
                    236:   # use this, open->print->close system only to make the file
                    237:   # open as little as possible, to make the test suite run
                    238:   # better on windows/cygwin
                    239: }
                    240: 
                    241: #**********************************************************************
                    242: # eXsysread is a wrapper around perl's sysread() function. This will
                    243: # repeat the call to sysread() until it has actually read the complete
                    244: # number of requested bytes or an unrecoverable condition occurs.
                    245: # On success returns a positive value, the number of bytes requested.
                    246: # On failure or timeout returns zero.
                    247: #
                    248: sub eXsysread {
                    249:     my $FH      = shift;
                    250:     my $scalar  = shift;
                    251:     my $nbytes  = shift;
                    252:     my $timeout = shift; # A zero timeout disables eXsysread() time limit
                    253:     #
                    254:     my $time_limited = 0;
                    255:     my $timeout_rest = 0;
                    256:     my $start_time = 0;
                    257:     my $nread  = 0;
                    258:     my $rc;
                    259: 
                    260:     $$scalar = "";
                    261: 
                    262:     if((not defined $nbytes) || ($nbytes < 1)) {
                    263:         logmsg "Error: eXsysread() failure: " .
                    264:                "length argument must be positive\n";
                    265:         return 0;
                    266:     }
                    267:     if((not defined $timeout) || ($timeout < 0)) {
                    268:         logmsg "Error: eXsysread() failure: " .
                    269:                "timeout argument must be zero or positive\n";
                    270:         return 0;
                    271:     }
                    272:     if($timeout > 0) {
                    273:         # caller sets eXsysread() time limit
                    274:         $time_limited = 1;
                    275:         $timeout_rest = $timeout;
                    276:         $start_time = int(time());
                    277:     }
                    278: 
                    279:     while($nread < $nbytes) {
                    280:         if($time_limited) {
                    281:             eval {
                    282:                 local $SIG{ALRM} = sub { die "alarm\n"; };
                    283:                 alarm $timeout_rest;
                    284:                 $rc = sysread($FH, $$scalar, $nbytes - $nread, $nread);
                    285:                 alarm 0;
                    286:             };
                    287:             $timeout_rest = $timeout - (int(time()) - $start_time);
                    288:             if($timeout_rest < 1) {
                    289:                 logmsg "Error: eXsysread() failure: timed out\n";
                    290:                 return 0;
                    291:             }
                    292:         }
                    293:         else {
                    294:             $rc = sysread($FH, $$scalar, $nbytes - $nread, $nread);
                    295:         }
                    296:         if($got_exit_signal) {
                    297:             logmsg "Error: eXsysread() failure: signalled to die\n";
                    298:             return 0;
                    299:         }
                    300:         if(not defined $rc) {
                    301:             if($!{EINTR}) {
                    302:                 logmsg "Warning: retrying sysread() interrupted system call\n";
                    303:                 next;
                    304:             }
                    305:             if($!{EAGAIN}) {
                    306:                 logmsg "Warning: retrying sysread() due to EAGAIN\n";
                    307:                 next;
                    308:             }
                    309:             if($!{EWOULDBLOCK}) {
                    310:                 logmsg "Warning: retrying sysread() due to EWOULDBLOCK\n";
                    311:                 next;
                    312:             }
                    313:             logmsg "Error: sysread() failure: $!\n";
                    314:             return 0;
                    315:         }
                    316:         if($rc < 0) {
                    317:             logmsg "Error: sysread() failure: returned negative value $rc\n";
                    318:             return 0;
                    319:         }
                    320:         if($rc == 0) {
                    321:             logmsg "Error: sysread() failure: read zero bytes\n";
                    322:             return 0;
                    323:         }
                    324:         $nread += $rc;
                    325:     }
                    326:     return $nread;
                    327: }
                    328: 
                    329: #**********************************************************************
                    330: # read_mainsockf attempts to read the given amount of output from the
                    331: # sockfilter which is in use for the main or primary connection. This
                    332: # reads untranslated sockfilt lingo which may hold data read from the
                    333: # main or primary socket. On success returns 1, otherwise zero.
                    334: #
                    335: sub read_mainsockf {
                    336:     my $scalar  = shift;
                    337:     my $nbytes  = shift;
                    338:     my $timeout = shift; # Optional argument, if zero blocks indefinitely
                    339:     my $FH = \*SFREAD;
                    340: 
                    341:     if(not defined $timeout) {
                    342:         $timeout = $sockfilt_timeout + ($nbytes >> 12);
                    343:     }
                    344:     if(eXsysread($FH, $scalar, $nbytes, $timeout) != $nbytes) {
                    345:         my ($fcaller, $lcaller) = (caller)[1,2];
                    346:         logmsg "Error: read_mainsockf() failure at $fcaller " .
                    347:                "line $lcaller. Due to eXsysread() failure\n";
                    348:         return 0;
                    349:     }
                    350:     return 1;
                    351: }
                    352: 
                    353: #**********************************************************************
                    354: # read_datasockf attempts to read the given amount of output from the
                    355: # sockfilter which is in use for the data or secondary connection. This
                    356: # reads untranslated sockfilt lingo which may hold data read from the
                    357: # data or secondary socket. On success returns 1, otherwise zero.
                    358: #
                    359: sub read_datasockf {
                    360:     my $scalar = shift;
                    361:     my $nbytes = shift;
                    362:     my $timeout = shift; # Optional argument, if zero blocks indefinitely
                    363:     my $FH = \*DREAD;
                    364: 
                    365:     if(not defined $timeout) {
                    366:         $timeout = $sockfilt_timeout + ($nbytes >> 12);
                    367:     }
                    368:     if(eXsysread($FH, $scalar, $nbytes, $timeout) != $nbytes) {
                    369:         my ($fcaller, $lcaller) = (caller)[1,2];
                    370:         logmsg "Error: read_datasockf() failure at $fcaller " .
                    371:                "line $lcaller. Due to eXsysread() failure\n";
                    372:         return 0;
                    373:     }
                    374:     return 1;
                    375: }
                    376: 
                    377: sub sysread_or_die {
                    378:     my $FH     = shift;
                    379:     my $scalar = shift;
                    380:     my $length = shift;
                    381:     my $fcaller;
                    382:     my $lcaller;
                    383:     my $result;
                    384: 
                    385:     $result = sysread($$FH, $$scalar, $length);
                    386: 
                    387:     if(not defined $result) {
                    388:         ($fcaller, $lcaller) = (caller)[1,2];
                    389:         logmsg "Failed to read input\n";
                    390:         logmsg "Error: $srvrname server, sysread error: $!\n";
                    391:         logmsg "Exited from sysread_or_die() at $fcaller " .
                    392:                "line $lcaller. $srvrname server, sysread error: $!\n";
                    393:         killsockfilters($proto, $ipvnum, $idnum, $verbose);
                    394:         unlink($pidfile);
                    395:         unlink($portfile);
                    396:         if($serverlogslocked) {
                    397:             $serverlogslocked = 0;
                    398:             clear_advisor_read_lock($SERVERLOGS_LOCK);
                    399:         }
                    400:         exit;
                    401:     }
                    402:     elsif($result == 0) {
                    403:         ($fcaller, $lcaller) = (caller)[1,2];
                    404:         logmsg "Failed to read input\n";
                    405:         logmsg "Error: $srvrname server, read zero\n";
                    406:         logmsg "Exited from sysread_or_die() at $fcaller " .
                    407:                "line $lcaller. $srvrname server, read zero\n";
                    408:         killsockfilters($proto, $ipvnum, $idnum, $verbose);
                    409:         unlink($pidfile);
                    410:         unlink($portfile);
                    411:         if($serverlogslocked) {
                    412:             $serverlogslocked = 0;
                    413:             clear_advisor_read_lock($SERVERLOGS_LOCK);
                    414:         }
                    415:         exit;
                    416:     }
                    417: 
                    418:     return $result;
                    419: }
                    420: 
                    421: sub startsf {
                    422:     my $mainsockfcmd = "./server/sockfilt".exe_ext('SRV')." " .
                    423:         "--ipv$ipvnum --port $port " .
                    424:         "--pidfile \"$mainsockf_pidfile\" " .
                    425:         "--portfile \"$portfile\" " .
                    426:         "--logfile \"$mainsockf_logfile\"";
                    427:     $sfpid = open2(*SFREAD, *SFWRITE, $mainsockfcmd);
                    428: 
                    429:     print STDERR "$mainsockfcmd\n" if($verbose);
                    430: 
                    431:     print SFWRITE "PING\n";
                    432:     my $pong;
                    433:     sysread_or_die(\*SFREAD, \$pong, 5);
                    434: 
                    435:     if($pong !~ /^PONG/) {
                    436:         logmsg "Failed sockfilt command: $mainsockfcmd\n";
                    437:         killsockfilters($proto, $ipvnum, $idnum, $verbose);
                    438:         unlink($pidfile);
                    439:         unlink($portfile);
                    440:         if($serverlogslocked) {
                    441:             $serverlogslocked = 0;
                    442:             clear_advisor_read_lock($SERVERLOGS_LOCK);
                    443:         }
                    444:         die "Failed to start sockfilt!";
                    445:     }
                    446: }
                    447: 
                    448: #**********************************************************************
                    449: # Returns the given test's reply data
                    450: #
                    451: sub getreplydata {
                    452:     my ($testno) = @_;
                    453:     my $testpart = "";
                    454: 
                    455:     $testno =~ s/^([^0-9]*)//;
                    456:     if($testno > 10000) {
                    457:        $testpart = $testno % 10000;
                    458:        $testno = int($testno / 10000);
                    459:     }
                    460: 
                    461:     loadtest("$srcdir/data/test$testno");
                    462: 
                    463:     my @data = getpart("reply", "data$testpart");
                    464:     if((!@data) && ($testpart ne "")) {
                    465:         @data = getpart("reply", "data");
                    466:     }
                    467: 
                    468:     return @data;
                    469: }
                    470: 
                    471: sub sockfilt {
                    472:     my $l;
                    473:     foreach $l (@_) {
                    474:         printf SFWRITE "DATA\n%04x\n", length($l);
                    475:         print SFWRITE $l;
                    476:     }
                    477: }
                    478: 
                    479: sub sockfiltsecondary {
                    480:     my $l;
                    481:     foreach $l (@_) {
                    482:         printf DWRITE "DATA\n%04x\n", length($l);
                    483:         print DWRITE $l;
                    484:     }
                    485: }
                    486: 
                    487: #**********************************************************************
                    488: # Send data to the client on the control stream, which happens to be plain
                    489: # stdout.
                    490: #
                    491: sub sendcontrol {
                    492:     if(!$ctrldelay) {
                    493:         # spit it all out at once
                    494:         sockfilt @_;
                    495:     }
                    496:     else {
                    497:         my $a = join("", @_);
                    498:         my @a = split("", $a);
                    499: 
                    500:         for(@a) {
                    501:             sockfilt $_;
                    502:             portable_sleep(0.01);
                    503:         }
                    504:     }
                    505:     my $log;
                    506:     foreach $log (@_) {
                    507:         my $l = $log;
                    508:         $l =~ s/\r/[CR]/g;
                    509:         $l =~ s/\n/[LF]/g;
                    510:         logmsg "> \"$l\"\n";
                    511:     }
                    512: }
                    513: 
                    514: #**********************************************************************
                    515: # Send data to the FTP client on the data stream when data connection
                    516: # is actually established. Given that this sub should only be called
                    517: # when a data connection is supposed to be established, calling this
                    518: # without a data connection is an indication of weak logic somewhere.
                    519: #
                    520: sub senddata {
                    521:     my $l;
                    522:     if($datasockf_conn eq 'no') {
                    523:         logmsg "WARNING: Detected data sending attempt without DATA channel\n";
                    524:         foreach $l (@_) {
                    525:             logmsg "WARNING: Data swallowed: $l\n"
                    526:         }
                    527:         return;
                    528:     }
                    529: 
                    530:     foreach $l (@_) {
                    531:         if(!$datadelay) {
                    532:             # spit it all out at once
                    533:             sockfiltsecondary $l;
                    534:         }
                    535:         else {
                    536:             # pause between each byte
                    537:             for (split(//,$l)) {
                    538:                 sockfiltsecondary $_;
                    539:                 portable_sleep(0.01);
                    540:             }
                    541:         }
                    542:     }
                    543: }
                    544: 
                    545: #**********************************************************************
                    546: # protocolsetup initializes the 'displaytext' and 'commandfunc' hashes
                    547: # for the given protocol. References to protocol command callbacks are
                    548: # stored in 'commandfunc' hash, and text which will be returned to the
                    549: # client before the command callback runs is stored in 'displaytext'.
                    550: #
                    551: sub protocolsetup {
                    552:     my $proto = $_[0];
                    553: 
                    554:     if($proto eq 'ftp') {
                    555:         %commandfunc = (
                    556:             'PORT' => \&PORT_ftp,
                    557:             'EPRT' => \&PORT_ftp,
                    558:             'LIST' => \&LIST_ftp,
                    559:             'NLST' => \&NLST_ftp,
                    560:             'PASV' => \&PASV_ftp,
                    561:             'CWD'  => \&CWD_ftp,
                    562:             'PWD'  => \&PWD_ftp,
                    563:             'EPSV' => \&PASV_ftp,
                    564:             'RETR' => \&RETR_ftp,
                    565:             'SIZE' => \&SIZE_ftp,
                    566:             'REST' => \&REST_ftp,
                    567:             'STOR' => \&STOR_ftp,
                    568:             'APPE' => \&STOR_ftp, # append looks like upload
                    569:             'MDTM' => \&MDTM_ftp,
                    570:         );
                    571:         %displaytext = (
                    572:             'USER' => '331 We are happy you popped in!',
                    573:             'PASS' => '230 Welcome you silly person',
                    574:             'PORT' => '200 You said PORT - I say FINE',
                    575:             'TYPE' => '200 I modify TYPE as you wanted',
                    576:             'LIST' => '150 here comes a directory',
                    577:             'NLST' => '150 here comes a directory',
                    578:             'CWD'  => '250 CWD command successful.',
                    579:             'SYST' => '215 UNIX Type: L8', # just fake something
                    580:             'QUIT' => '221 bye bye baby', # just reply something
                    581:             'MKD'  => '257 Created your requested directory',
                    582:             'REST' => '350 Yeah yeah we set it there for you',
                    583:             'DELE' => '200 OK OK OK whatever you say',
                    584:             'RNFR' => '350 Received your order. Please provide more',
                    585:             'RNTO' => '250 Ok, thanks. File renaming completed.',
                    586:             'NOOP' => '200 Yes, I\'m very good at doing nothing.',
                    587:             'PBSZ' => '500 PBSZ not implemented',
                    588:             'PROT' => '500 PROT not implemented',
                    589:             'welcome' => join("",
                    590:             '220-        _   _ ____  _     '."\r\n",
                    591:             '220-    ___| | | |  _ \| |    '."\r\n",
                    592:             '220-   / __| | | | |_) | |    '."\r\n",
                    593:             '220-  | (__| |_| |  _ {| |___ '."\r\n",
                    594:             '220    \___|\___/|_| \_\_____|'."\r\n")
                    595:         );
                    596:     }
                    597:     elsif($proto eq 'pop3') {
                    598:         %commandfunc = (
                    599:             'APOP' => \&APOP_pop3,
                    600:             'AUTH' => \&AUTH_pop3,
                    601:             'CAPA' => \&CAPA_pop3,
                    602:             'DELE' => \&DELE_pop3,
                    603:             'LIST' => \&LIST_pop3,
                    604:             'NOOP' => \&NOOP_pop3,
                    605:             'PASS' => \&PASS_pop3,
                    606:             'QUIT' => \&QUIT_pop3,
                    607:             'RETR' => \&RETR_pop3,
                    608:             'RSET' => \&RSET_pop3,
                    609:             'STAT' => \&STAT_pop3,
                    610:             'TOP'  => \&TOP_pop3,
                    611:             'UIDL' => \&UIDL_pop3,
                    612:             'USER' => \&USER_pop3,
                    613:         );
                    614:         %displaytext = (
                    615:             'welcome' => join("",
                    616:             '        _   _ ____  _     '."\r\n",
                    617:             '    ___| | | |  _ \| |    '."\r\n",
                    618:             '   / __| | | | |_) | |    '."\r\n",
                    619:             '  | (__| |_| |  _ {| |___ '."\r\n",
                    620:             '   \___|\___/|_| \_\_____|'."\r\n",
                    621:             '+OK curl POP3 server ready to serve '."\r\n")
                    622:         );
                    623:     }
                    624:     elsif($proto eq 'imap') {
                    625:         %commandfunc = (
                    626:             'APPEND'     => \&APPEND_imap,
                    627:             'CAPABILITY' => \&CAPABILITY_imap,
                    628:             'CHECK'      => \&CHECK_imap,
                    629:             'CLOSE'      => \&CLOSE_imap,
                    630:             'COPY'       => \&COPY_imap,
                    631:             'CREATE'     => \&CREATE_imap,
                    632:             'DELETE'     => \&DELETE_imap,
                    633:             'EXAMINE'    => \&EXAMINE_imap,
                    634:             'EXPUNGE'    => \&EXPUNGE_imap,
                    635:             'FETCH'      => \&FETCH_imap,
                    636:             'LIST'       => \&LIST_imap,
                    637:             'LSUB'       => \&LSUB_imap,
                    638:             'LOGIN'      => \&LOGIN_imap,
                    639:             'LOGOUT'     => \&LOGOUT_imap,
                    640:             'NOOP'       => \&NOOP_imap,
                    641:             'RENAME'     => \&RENAME_imap,
                    642:             'SEARCH'     => \&SEARCH_imap,
                    643:             'SELECT'     => \&SELECT_imap,
                    644:             'STATUS'     => \&STATUS_imap,
                    645:             'STORE'      => \&STORE_imap,
                    646:             'UID'        => \&UID_imap,
                    647:         );
                    648:         %displaytext = (
                    649:             'welcome' => join("",
                    650:             '        _   _ ____  _     '."\r\n",
                    651:             '    ___| | | |  _ \| |    '."\r\n",
                    652:             '   / __| | | | |_) | |    '."\r\n",
                    653:             '  | (__| |_| |  _ {| |___ '."\r\n",
                    654:             '   \___|\___/|_| \_\_____|'."\r\n",
                    655:             '* OK curl IMAP server ready to serve'."\r\n")
                    656:         );
                    657:     }
                    658:     elsif($proto eq 'smtp') {
                    659:         %commandfunc = (
                    660:             'DATA' => \&DATA_smtp,
                    661:             'EHLO' => \&EHLO_smtp,
                    662:             'EXPN' => \&EXPN_smtp,
                    663:             'HELO' => \&HELO_smtp,
                    664:             'HELP' => \&HELP_smtp,
                    665:             'MAIL' => \&MAIL_smtp,
                    666:             'NOOP' => \&NOOP_smtp,
                    667:             'RSET' => \&RSET_smtp,
                    668:             'RCPT' => \&RCPT_smtp,
                    669:             'VRFY' => \&VRFY_smtp,
                    670:             'QUIT' => \&QUIT_smtp,
                    671:         );
                    672:         %displaytext = (
                    673:             'welcome' => join("",
                    674:             '220-        _   _ ____  _     '."\r\n",
                    675:             '220-    ___| | | |  _ \| |    '."\r\n",
                    676:             '220-   / __| | | | |_) | |    '."\r\n",
                    677:             '220-  | (__| |_| |  _ {| |___ '."\r\n",
                    678:             '220    \___|\___/|_| \_\_____|'."\r\n")
                    679:         );
                    680:     }
                    681: }
                    682: 
                    683: sub close_dataconn {
                    684:     my ($closed)=@_; # non-zero if already disconnected
                    685: 
                    686:     my $datapid = processexists($datasockf_pidfile);
                    687: 
                    688:     logmsg "=====> Closing $datasockf_mode DATA connection...\n";
                    689: 
                    690:     if(!$closed) {
                    691:         if($datapid > 0) {
                    692:             logmsg "Server disconnects $datasockf_mode DATA connection\n";
                    693:             print DWRITE "DISC\n";
                    694:             my $i;
                    695:             sysread DREAD, $i, 5;
                    696:             logmsg "Server disconnected $datasockf_mode DATA connection\n";
                    697:         }
                    698:         else {
                    699:             logmsg "Server finds $datasockf_mode DATA connection already ".
                    700:                    "disconnected\n";
                    701:         }
                    702:     }
                    703:     else {
                    704:         logmsg "Server knows $datasockf_mode DATA connection is already ".
                    705:                "disconnected\n";
                    706:     }
                    707: 
                    708:     if($datapid > 0) {
                    709:         logmsg "DATA sockfilt for $datasockf_mode data channel quits ".
                    710:                "(pid $datapid)\n";
                    711:         print DWRITE "QUIT\n";
                    712:         pidwait($datapid, 0);
                    713:         unlink($datasockf_pidfile) if(-f $datasockf_pidfile);
                    714:         logmsg "DATA sockfilt for $datasockf_mode data channel quit ".
                    715:                "(pid $datapid)\n";
                    716:     }
                    717:     else {
                    718:         logmsg "DATA sockfilt for $datasockf_mode data channel already ".
                    719:                "dead\n";
                    720:     }
                    721: 
                    722:     logmsg "=====> Closed $datasockf_mode DATA connection\n";
                    723: 
                    724:     datasockf_state('STOPPED');
                    725: }
                    726: 
                    727: ################
                    728: ################ SMTP commands
                    729: ################
                    730: 
                    731: # The type of server (SMTP or ESMTP)
                    732: my $smtp_type;
                    733: 
                    734: # The client (which normally contains the test number)
                    735: my $smtp_client;
                    736: 
                    737: sub EHLO_smtp {
                    738:     my ($client) = @_;
                    739:     my @data;
                    740: 
                    741:     # TODO: Get the IP address of the client connection to use in the
                    742:     # EHLO response when the client doesn't specify one but for now use
                    743:     # 127.0.0.1
                    744:     if(!$client) {
                    745:         $client = "[127.0.0.1]";
                    746:     }
                    747: 
                    748:     # Set the server type to ESMTP
                    749:     $smtp_type = "ESMTP";
                    750: 
                    751:     # Calculate the EHLO response
                    752:     push @data, "$smtp_type pingpong test server Hello $client";
                    753: 
                    754:     if((@capabilities) || (@auth_mechs)) {
                    755:         my $mechs;
                    756: 
                    757:         for my $c (@capabilities) {
                    758:             push @data, $c;
                    759:         }
                    760: 
                    761:         for my $am (@auth_mechs) {
                    762:             if(!$mechs) {
                    763:                 $mechs = "$am";
                    764:             }
                    765:             else {
                    766:                 $mechs .= " $am";
                    767:             }
                    768:         }
                    769: 
                    770:         if($mechs) {
                    771:             push @data, "AUTH $mechs";
                    772:         }
                    773:     }
                    774: 
                    775:     # Send the EHLO response
                    776:     for(my $i = 0; $i < @data; $i++) {
                    777:         my $d = $data[$i];
                    778: 
                    779:         if($i < @data - 1) {
                    780:             sendcontrol "250-$d\r\n";
                    781:         }
                    782:         else {
                    783:             sendcontrol "250 $d\r\n";
                    784:         }
                    785:     }
                    786: 
                    787:     # Store the client (as it may contain the test number)
                    788:     $smtp_client = $client;
                    789: 
                    790:     return 0;
                    791: }
                    792: 
                    793: sub HELO_smtp {
                    794:     my ($client) = @_;
                    795: 
                    796:     # TODO: Get the IP address of the client connection to use in the HELO
                    797:     # response when the client doesn't specify one but for now use 127.0.0.1
                    798:     if(!$client) {
                    799:         $client = "[127.0.0.1]";
                    800:     }
                    801: 
                    802:     # Set the server type to SMTP
                    803:     $smtp_type = "SMTP";
                    804: 
                    805:     # Send the HELO response
                    806:     sendcontrol "250 $smtp_type pingpong test server Hello $client\r\n";
                    807: 
                    808:     # Store the client (as it may contain the test number)
                    809:     $smtp_client = $client;
                    810: 
                    811:     return 0;
                    812: }
                    813: 
                    814: sub MAIL_smtp {
                    815:     my ($args) = @_;
                    816: 
                    817:     logmsg "MAIL_smtp got $args\n";
                    818: 
                    819:     if (!$args) {
                    820:         sendcontrol "501 Unrecognized parameter\r\n";
                    821:     }
                    822:     else {
                    823:         my $from;
                    824:         my $size;
                    825:         my $smtputf8 = grep /^SMTPUTF8$/, @capabilities;
                    826:         my @elements = split(/ /, $args);
                    827: 
                    828:         # Get the FROM and SIZE parameters
                    829:         for my $e (@elements) {
                    830:             if($e =~ /^FROM:(.*)$/) {
                    831:                 $from = $1;
                    832:             }
                    833:             elsif($e =~ /^SIZE=(\d+)$/) {
                    834:                 $size = $1;
                    835:             }
                    836:         }
                    837: 
                    838:         # Validate the from address (only <> and a valid email address inside
                    839:         # <> are allowed, such as <user@example.com>)
                    840:         if (($from eq "<>") ||
                    841:             (!$smtputf8 && $from =~
                    842:               /^<([a-zA-Z0-9._%+-]+)\@(([a-zA-Z0-9-]+)\.)+([a-zA-Z]{2,4})>$/) ||
                    843:             ($smtputf8 && $from =~
                    844:               /^<([a-zA-Z0-9\x{80}-\x{ff}._%+-]+)\@(([a-zA-Z0-9\x{80}-\x{ff}-]+)\.)+([a-zA-Z]{2,4})>$/)) {
                    845:             my @found;
                    846:             my $valid = 1;
                    847: 
                    848:             # Check the capabilities for SIZE and if the specified size is
                    849:             # greater than the message size then reject it
                    850:             if (@found = grep /^SIZE (\d+)$/, @capabilities) {
                    851:                 if ($found[0] =~ /^SIZE (\d+)$/) {
                    852:                     if ($size > $1) {
                    853:                         $valid = 0;
                    854:                     }
                    855:                 }
                    856:             }
                    857: 
                    858:             if(!$valid) {
                    859:                 sendcontrol "552 Message size too large\r\n";
                    860:             }
                    861:             else {
                    862:                 sendcontrol "250 Sender OK\r\n";
                    863:             }
                    864:         }
                    865:         else {
                    866:             sendcontrol "501 Invalid address\r\n";
                    867:         }
                    868:     }
                    869: 
                    870:     return 0;
                    871: }
                    872: 
                    873: sub RCPT_smtp {
                    874:     my ($args) = @_;
                    875: 
                    876:     logmsg "RCPT_smtp got $args\n";
                    877: 
                    878:     # Get the TO parameter
                    879:     if($args !~ /^TO:(.*)/) {
                    880:         sendcontrol "501 Unrecognized parameter\r\n";
                    881:     }
                    882:     else {
                    883:         my $smtputf8 = grep /^SMTPUTF8$/, @capabilities;
                    884:         my $to = $1;
                    885: 
                    886:         # Validate the to address (only a valid email address inside <> is
                    887:         # allowed, such as <user@example.com>)
                    888:         if ((!$smtputf8 && $to =~
                    889:               /^<([a-zA-Z0-9._%+-]+)\@(([a-zA-Z0-9-]+)\.)+([a-zA-Z]{2,4})>$/) ||
                    890:             ($smtputf8 && $to =~
                    891:               /^<([a-zA-Z0-9\x{80}-\x{ff}._%+-]+)\@(([a-zA-Z0-9\x{80}-\x{ff}-]+)\.)+([a-zA-Z]{2,4})>$/)) {
                    892:             sendcontrol "250 Recipient OK\r\n";      
                    893:         }
                    894:         else {
                    895:             sendcontrol "501 Invalid address\r\n";
                    896:         }
                    897:     }
                    898: 
                    899:     return 0;
                    900: }
                    901: 
                    902: sub DATA_smtp {
                    903:     my ($args) = @_;
                    904: 
                    905:     if ($args) {
                    906:         sendcontrol "501 Unrecognized parameter\r\n";
                    907:     }
                    908:     elsif ($smtp_client !~ /^(\d*)$/) {
                    909:         sendcontrol "501 Invalid arguments\r\n";
                    910:     }
                    911:     else {
                    912:         sendcontrol "354 Show me the mail\r\n";
                    913: 
                    914:         my $testno = $smtp_client;
                    915:         my $filename = "log/upload.$testno";
                    916: 
                    917:         logmsg "Store test number $testno in $filename\n";
                    918: 
                    919:         open(FILE, ">$filename") ||
                    920:             return 0; # failed to open output
                    921: 
                    922:         my $line;
                    923:         my $ulsize=0;
                    924:         my $disc=0;
                    925:         my $raw;
                    926:         while (5 == (sysread \*SFREAD, $line, 5)) {
                    927:             if($line eq "DATA\n") {
                    928:                 my $i;
                    929:                 my $eob;
                    930:                 sysread \*SFREAD, $i, 5;
                    931: 
                    932:                 my $size = 0;
                    933:                 if($i =~ /^([0-9a-fA-F]{4})\n/) {
                    934:                     $size = hex($1);
                    935:                 }
                    936: 
                    937:                 read_mainsockf(\$line, $size);
                    938: 
                    939:                 $ulsize += $size;
                    940:                 print FILE $line if(!$nosave);
                    941: 
                    942:                 $raw .= $line;
                    943:                 if($raw =~ /(?:^|\x0d\x0a)\x2e\x0d\x0a/) {
                    944:                     # end of data marker!
                    945:                     $eob = 1;
                    946:                 }
                    947: 
                    948:                 logmsg "> Appending $size bytes to file\n";
                    949: 
                    950:                 if($eob) {
                    951:                     logmsg "Found SMTP EOB marker\n";
                    952:                     last;
                    953:                 }
                    954:             }
                    955:             elsif($line eq "DISC\n") {
                    956:                 # disconnect!
                    957:                 $disc=1;
                    958:                 last;
                    959:             }
                    960:             else {
                    961:                 logmsg "No support for: $line";
                    962:                 last;
                    963:             }
                    964:         }
                    965: 
                    966:         if($nosave) {
                    967:             print FILE "$ulsize bytes would've been stored here\n";
                    968:         }
                    969: 
                    970:         close(FILE);
                    971: 
                    972:         logmsg "received $ulsize bytes upload\n";
                    973: 
                    974:         sendcontrol "250 OK, data received!\r\n";
                    975:     }
                    976: 
                    977:     return 0;
                    978: }
                    979: 
                    980: sub NOOP_smtp {
                    981:     my ($args) = @_;
                    982: 
                    983:     if($args) {
                    984:         sendcontrol "501 Unrecognized parameter\r\n";
                    985:     }
                    986:     else {
                    987:         sendcontrol "250 OK\r\n";
                    988:     }
                    989: 
                    990:     return 0;
                    991: }
                    992: 
                    993: sub RSET_smtp {
                    994:     my ($args) = @_;
                    995: 
                    996:     if($args) {
                    997:         sendcontrol "501 Unrecognized parameter\r\n";
                    998:     }
                    999:     else {
                   1000:         sendcontrol "250 Resetting\r\n";
                   1001:     }
                   1002: 
                   1003:     return 0;
                   1004: }
                   1005: 
                   1006: sub HELP_smtp {
                   1007:     my ($args) = @_;
                   1008: 
                   1009:     # One argument is optional
                   1010:     if($args) {
                   1011:         logmsg "HELP_smtp got $args\n";
                   1012:     }
                   1013: 
                   1014:     if($smtp_client eq "verifiedserver") {
                   1015:         # This is the secret command that verifies that this actually is
                   1016:         # the curl test server
                   1017:         sendcontrol "214 WE ROOLZ: $$\r\n";
                   1018: 
                   1019:         if($verbose) {
                   1020:             print STDERR "FTPD: We returned proof we are the test server\n";
                   1021:         }
                   1022: 
                   1023:         logmsg "return proof we are we\n";
                   1024:     }
                   1025:     else {
                   1026:         sendcontrol "214-This server supports the following commands:\r\n";
                   1027: 
                   1028:         if(@auth_mechs) {
                   1029:             sendcontrol "214 HELO EHLO RCPT DATA RSET MAIL VRFY EXPN QUIT HELP AUTH\r\n";
                   1030:         }
                   1031:         else {
                   1032:             sendcontrol "214 HELO EHLO RCPT DATA RSET MAIL VRFY EXPN QUIT HELP\r\n";
                   1033:         }
                   1034:     }
                   1035: 
                   1036:     return 0;
                   1037: }
                   1038: 
                   1039: sub VRFY_smtp {
                   1040:     my ($args) = @_;
                   1041:     my ($username, $address) = split(/ /, $args, 2);
                   1042: 
                   1043:     logmsg "VRFY_smtp got $args\n";
                   1044: 
                   1045:     if($username eq "") {
                   1046:         sendcontrol "501 Unrecognized parameter\r\n";
                   1047:     }
                   1048:     else {
                   1049:         my $smtputf8 = grep /^SMTPUTF8$/, @capabilities;
                   1050: 
                   1051:         # Validate the username (only a valid local or external username is
                   1052:         # allowed, such as user or user@example.com)
                   1053:         if ((!$smtputf8 && $username =~
                   1054:             /^([a-zA-Z0-9._%+-]+)(\@(([a-zA-Z0-9-]+)\.)+([a-zA-Z]{2,4}))?$/) ||
                   1055:             ($smtputf8 && $username =~
                   1056:             /^([a-zA-Z0-9\x{80}-\x{ff}._%+-]+)(\@(([a-zA-Z0-9\x{80}-\x{ff}-]+)\.)+([a-zA-Z]{2,4}))?$/)) {
                   1057: 
                   1058:             my @data = getreplydata($smtp_client);
                   1059: 
                   1060:             if(!@data) {
                   1061:                 if ($username !~
                   1062:                     /^([a-zA-Z0-9._%+-]+)\@(([a-zA-Z0-9-]+)\.)+([a-zA-Z]{2,4})$/) {
                   1063:                   push @data, "250 <$username\@example.com>\r\n"
                   1064:                 }
                   1065:                 else {
                   1066:                   push @data, "250 <$username>\r\n"
                   1067:                 }
                   1068:             }
                   1069: 
                   1070:             for my $d (@data) {
                   1071:                 sendcontrol $d;
                   1072:             }
                   1073:         }
                   1074:         else {
                   1075:             sendcontrol "501 Invalid address\r\n";
                   1076:         }
                   1077:     }
                   1078: 
                   1079:     return 0;
                   1080: }
                   1081: 
                   1082: sub EXPN_smtp {
                   1083:     my ($list_name) = @_;
                   1084: 
                   1085:     logmsg "EXPN_smtp got $list_name\n";
                   1086: 
                   1087:     if(!$list_name) {
                   1088:         sendcontrol "501 Unrecognized parameter\r\n";
                   1089:     }
                   1090:     else {
                   1091:         my @data = getreplydata($smtp_client);
                   1092: 
                   1093:         for my $d (@data) {
                   1094:             sendcontrol $d;
                   1095:         }
                   1096:     }
                   1097: 
                   1098:     return 0;
                   1099: }
                   1100: 
                   1101: sub QUIT_smtp {
                   1102:     sendcontrol "221 curl $smtp_type server signing off\r\n";
                   1103: 
                   1104:     return 0;
                   1105: }
                   1106: 
                   1107: # What was deleted by IMAP STORE / POP3 DELE commands
                   1108: my @deleted;
                   1109: 
                   1110: ################
                   1111: ################ IMAP commands
                   1112: ################
                   1113: 
                   1114: # global to allow the command functions to read it
                   1115: my $cmdid;
                   1116: 
                   1117: # what was picked by SELECT
                   1118: my $selected;
                   1119: 
                   1120: # Any IMAP parameter can come in escaped and in double quotes.
                   1121: # This function is dumb (so far) and just removes the quotes if present.
                   1122: sub fix_imap_params {
                   1123:     foreach (@_) {
                   1124:         $_ = $1 if /^"(.*)"$/;
                   1125:     }
                   1126: }
                   1127: 
                   1128: sub CAPABILITY_imap {
                   1129:     if((!@capabilities) && (!@auth_mechs)) {
                   1130:         sendcontrol "$cmdid BAD Command\r\n";
                   1131:     }
                   1132:     else {
                   1133:         my $data;
                   1134: 
                   1135:         # Calculate the CAPABILITY response
                   1136:         $data = "* CAPABILITY IMAP4";
                   1137: 
                   1138:         for my $c (@capabilities) {
                   1139:             $data .= " $c";
                   1140:         }
                   1141: 
                   1142:         for my $am (@auth_mechs) {
                   1143:             $data .= " AUTH=$am";
                   1144:         }
                   1145: 
                   1146:         $data .= " pingpong test server\r\n";
                   1147: 
                   1148:         # Send the CAPABILITY response
                   1149:         sendcontrol $data;
                   1150:         sendcontrol "$cmdid OK CAPABILITY completed\r\n";
                   1151:     }
                   1152: 
                   1153:     return 0;
                   1154: }
                   1155: 
                   1156: sub LOGIN_imap {
                   1157:     my ($args) = @_;
                   1158:     my ($user, $password) = split(/ /, $args, 2);
                   1159:     fix_imap_params($user, $password);
                   1160: 
                   1161:     logmsg "LOGIN_imap got $args\n";
                   1162: 
                   1163:     if ($user eq "") {
                   1164:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1165:     }
                   1166:     else {
                   1167:         sendcontrol "$cmdid OK LOGIN completed\r\n";
                   1168:     }
                   1169: 
                   1170:     return 0;
                   1171: }
                   1172: 
                   1173: sub SELECT_imap {
                   1174:     my ($mailbox) = @_;
                   1175:     fix_imap_params($mailbox);
                   1176: 
                   1177:     logmsg "SELECT_imap got test $mailbox\n";
                   1178: 
                   1179:     if($mailbox eq "") {
                   1180:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1181:     }
                   1182:     else {
                   1183:         # Example from RFC 3501, 6.3.1. SELECT Command
                   1184:         sendcontrol "* 172 EXISTS\r\n";
                   1185:         sendcontrol "* 1 RECENT\r\n";
                   1186:         sendcontrol "* OK [UNSEEN 12] Message 12 is first unseen\r\n";
                   1187:         sendcontrol "* OK [UIDVALIDITY 3857529045] UIDs valid\r\n";
                   1188:         sendcontrol "* OK [UIDNEXT 4392] Predicted next UID\r\n";
                   1189:         sendcontrol "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n";
                   1190:         sendcontrol "* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited\r\n";
                   1191:         sendcontrol "$cmdid OK [READ-WRITE] SELECT completed\r\n";
                   1192: 
                   1193:         $selected = $mailbox;
                   1194:     }
                   1195: 
                   1196:     return 0;
                   1197: }
                   1198: 
                   1199: sub FETCH_imap {
                   1200:     my ($args) = @_;
                   1201:     my ($uid, $how) = split(/ /, $args, 2);
                   1202:     fix_imap_params($uid, $how);
                   1203: 
                   1204:     logmsg "FETCH_imap got $args\n";
                   1205: 
                   1206:     if ($selected eq "") {
                   1207:         sendcontrol "$cmdid BAD Command received in Invalid state\r\n";
                   1208:     }
                   1209:     else {
                   1210:         my @data;
                   1211:         my $size;
                   1212: 
                   1213:         if($selected eq "verifiedserver") {
                   1214:             # this is the secret command that verifies that this actually is
                   1215:             # the curl test server
                   1216:             my $response = "WE ROOLZ: $$\r\n";
                   1217:             if($verbose) {
                   1218:                 print STDERR "FTPD: We returned proof we are the test server\n";
                   1219:             }
                   1220:             $data[0] = $response;
                   1221:             logmsg "return proof we are we\n";
                   1222:         }
                   1223:         else {
                   1224:             # send mail content
                   1225:             logmsg "retrieve a mail\n";
                   1226: 
                   1227:             @data = getreplydata($selected);
                   1228:         }
                   1229: 
                   1230:         for (@data) {
                   1231:             $size += length($_);
                   1232:         }
                   1233: 
                   1234:         sendcontrol "* $uid FETCH ($how {$size}\r\n";
                   1235: 
                   1236:         for my $d (@data) {
                   1237:             sendcontrol $d;
                   1238:         }
                   1239: 
                   1240:         sendcontrol ")\r\n";
                   1241:         sendcontrol "$cmdid OK FETCH completed\r\n";
                   1242:     }
                   1243: 
                   1244:     return 0;
                   1245: }
                   1246: 
                   1247: sub APPEND_imap {
                   1248:     my ($args) = @_;
                   1249: 
                   1250:     logmsg "APPEND_imap got $args\r\n";
                   1251: 
                   1252:     $args =~ /^([^ ]+) [^{]*\{(\d+)\}$/;
                   1253:     my ($mailbox, $size) = ($1, $2);
                   1254:     fix_imap_params($mailbox);
                   1255: 
                   1256:     if($mailbox eq "") {
                   1257:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1258:     }
                   1259:     else {
                   1260:         sendcontrol "+ Ready for literal data\r\n";
                   1261: 
                   1262:         my $testno = $mailbox;
                   1263:         my $filename = "log/upload.$testno";
                   1264: 
                   1265:         logmsg "Store test number $testno in $filename\n";
                   1266: 
                   1267:         open(FILE, ">$filename") ||
                   1268:             return 0; # failed to open output
                   1269: 
                   1270:         my $received = 0;
                   1271:         my $line;
                   1272:         while(5 == (sysread \*SFREAD, $line, 5)) {
                   1273:             if($line eq "DATA\n") {
                   1274:                 sysread \*SFREAD, $line, 5;
                   1275: 
                   1276:                 my $chunksize = 0;
                   1277:                 if($line =~ /^([0-9a-fA-F]{4})\n/) {
                   1278:                     $chunksize = hex($1);
                   1279:                 }
                   1280: 
                   1281:                 read_mainsockf(\$line, $chunksize);
                   1282: 
                   1283:                 my $left = $size - $received;
                   1284:                 my $datasize = ($left > $chunksize) ? $chunksize : $left;
                   1285: 
                   1286:                 if($datasize > 0) {
                   1287:                     logmsg "> Appending $datasize bytes to file\n";
                   1288:                     print FILE substr($line, 0, $datasize) if(!$nosave);
                   1289:                     $line = substr($line, $datasize);
                   1290: 
                   1291:                     $received += $datasize;
                   1292:                     if($received == $size) {
                   1293:                         logmsg "Received all data, waiting for final CRLF.\n";
                   1294:                     }
                   1295:                 }
                   1296: 
                   1297:                 if($received == $size && $line eq "\r\n") {
                   1298:                     last;
                   1299:                 }
                   1300:             }
                   1301:             elsif($line eq "DISC\n") {
                   1302:                 logmsg "Unexpected disconnect!\n";
                   1303:                 last;
                   1304:             }
                   1305:             else {
                   1306:                 logmsg "No support for: $line";
                   1307:                 last;
                   1308:             }
                   1309:         }
                   1310: 
                   1311:         if($nosave) {
                   1312:             print FILE "$size bytes would've been stored here\n";
                   1313:         }
                   1314: 
                   1315:         close(FILE);
                   1316: 
                   1317:         logmsg "received $size bytes upload\n";
                   1318: 
                   1319:         sendcontrol "$cmdid OK APPEND completed\r\n";
                   1320:     }
                   1321: 
                   1322:     return 0;
                   1323: }
                   1324: 
                   1325: sub STORE_imap {
                   1326:     my ($args) = @_;
                   1327:     my ($uid, $what, $value) = split(/ /, $args, 3);
                   1328:     fix_imap_params($uid);
                   1329: 
                   1330:     logmsg "STORE_imap got $args\n";
                   1331: 
                   1332:     if ($selected eq "") {
                   1333:         sendcontrol "$cmdid BAD Command received in Invalid state\r\n";
                   1334:     }
                   1335:     elsif (($uid eq "") || ($what ne "+Flags") || ($value eq "")) {
                   1336:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1337:     }
                   1338:     else {
                   1339:         if($value eq "\\Deleted") {
                   1340:             push(@deleted, $uid);
                   1341:         }
                   1342: 
                   1343:         sendcontrol "* $uid FETCH (FLAGS (\\Seen $value))\r\n";
                   1344:         sendcontrol "$cmdid OK STORE completed\r\n";
                   1345:     }
                   1346: 
                   1347:     return 0;
                   1348: }
                   1349: 
                   1350: sub LIST_imap {
                   1351:     my ($args) = @_;
                   1352:     my ($reference, $mailbox) = split(/ /, $args, 2);
                   1353:     fix_imap_params($reference, $mailbox);
                   1354: 
                   1355:     logmsg "LIST_imap got $args\n";
                   1356: 
                   1357:     if ($reference eq "") {
                   1358:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1359:     }
                   1360:     elsif ($reference eq "verifiedserver") {
                   1361:         # this is the secret command that verifies that this actually is
                   1362:         # the curl test server
                   1363:         sendcontrol "* LIST () \"/\" \"WE ROOLZ: $$\"\r\n";
                   1364:         sendcontrol "$cmdid OK LIST Completed\r\n";
                   1365: 
                   1366:         if($verbose) {
                   1367:             print STDERR "FTPD: We returned proof we are the test server\n";
                   1368:         }
                   1369: 
                   1370:         logmsg "return proof we are we\n";
                   1371:     }
                   1372:     else {
                   1373:         my @data = getreplydata($reference);
                   1374: 
                   1375:         for my $d (@data) {
                   1376:             sendcontrol $d;
                   1377:         }
                   1378: 
                   1379:         sendcontrol "$cmdid OK LIST Completed\r\n";
                   1380:     }
                   1381: 
                   1382:     return 0;
                   1383: }
                   1384: 
                   1385: sub LSUB_imap {
                   1386:     my ($args) = @_;
                   1387:     my ($reference, $mailbox) = split(/ /, $args, 2);
                   1388:     fix_imap_params($reference, $mailbox);
                   1389: 
                   1390:     logmsg "LSUB_imap got $args\n";
                   1391: 
                   1392:     if ($reference eq "") {
                   1393:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1394:     }
                   1395:     else {
                   1396:         my @data = getreplydata($reference);
                   1397: 
                   1398:         for my $d (@data) {
                   1399:             sendcontrol $d;
                   1400:         }
                   1401: 
                   1402:         sendcontrol "$cmdid OK LSUB Completed\r\n";
                   1403:     }
                   1404: 
                   1405:     return 0;
                   1406: }
                   1407: 
                   1408: sub EXAMINE_imap {
                   1409:     my ($mailbox) = @_;
                   1410:     fix_imap_params($mailbox);
                   1411: 
                   1412:     logmsg "EXAMINE_imap got $mailbox\n";
                   1413: 
                   1414:     if ($mailbox eq "") {
                   1415:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1416:     }
                   1417:     else {
                   1418:         my @data = getreplydata($mailbox);
                   1419: 
                   1420:         for my $d (@data) {
                   1421:             sendcontrol $d;
                   1422:         }
                   1423: 
                   1424:         sendcontrol "$cmdid OK [READ-ONLY] EXAMINE completed\r\n";
                   1425:     }
                   1426: 
                   1427:     return 0;
                   1428: }
                   1429: 
                   1430: sub STATUS_imap {
                   1431:     my ($args) = @_;
                   1432:     my ($mailbox, $what) = split(/ /, $args, 2);
                   1433:     fix_imap_params($mailbox);
                   1434: 
                   1435:     logmsg "STATUS_imap got $args\n";
                   1436: 
                   1437:     if ($mailbox eq "") {
                   1438:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1439:     }
                   1440:     else {
                   1441:         my @data = getreplydata($mailbox);
                   1442: 
                   1443:         for my $d (@data) {
                   1444:             sendcontrol $d;
                   1445:         }
                   1446: 
                   1447:         sendcontrol "$cmdid OK STATUS completed\r\n";
                   1448:     }
                   1449: 
                   1450:     return 0;
                   1451: }
                   1452: 
                   1453: sub SEARCH_imap {
                   1454:     my ($what) = @_;
                   1455:     fix_imap_params($what);
                   1456: 
                   1457:     logmsg "SEARCH_imap got $what\n";
                   1458: 
                   1459:     if ($selected eq "") {
                   1460:         sendcontrol "$cmdid BAD Command received in Invalid state\r\n";
                   1461:     }
                   1462:     elsif ($what eq "") {
                   1463:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1464:     }
                   1465:     else {
                   1466:         my @data = getreplydata($selected);
                   1467: 
                   1468:         for my $d (@data) {
                   1469:             sendcontrol $d;
                   1470:         }
                   1471: 
                   1472:         sendcontrol "$cmdid OK SEARCH completed\r\n";
                   1473:     }
                   1474: 
                   1475:     return 0;
                   1476: }
                   1477: 
                   1478: sub CREATE_imap {
                   1479:     my ($args) = @_;
                   1480:     fix_imap_params($args);
                   1481: 
                   1482:     logmsg "CREATE_imap got $args\n";
                   1483: 
                   1484:     if ($args eq "") {
                   1485:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1486:     }
                   1487:     else {
                   1488:         sendcontrol "$cmdid OK CREATE completed\r\n";
                   1489:     }
                   1490: 
                   1491:     return 0;
                   1492: }
                   1493: 
                   1494: sub DELETE_imap {
                   1495:     my ($args) = @_;
                   1496:     fix_imap_params($args);
                   1497: 
                   1498:     logmsg "DELETE_imap got $args\n";
                   1499: 
                   1500:     if ($args eq "") {
                   1501:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1502:     }
                   1503:     else {
                   1504:         sendcontrol "$cmdid OK DELETE completed\r\n";
                   1505:     }
                   1506: 
                   1507:     return 0;
                   1508: }
                   1509: 
                   1510: sub RENAME_imap {
                   1511:     my ($args) = @_;
                   1512:     my ($from_mailbox, $to_mailbox) = split(/ /, $args, 2);
                   1513:     fix_imap_params($from_mailbox, $to_mailbox);
                   1514: 
                   1515:     logmsg "RENAME_imap got $args\n";
                   1516: 
                   1517:     if (($from_mailbox eq "") || ($to_mailbox eq "")) {
                   1518:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1519:     }
                   1520:     else {
                   1521:         sendcontrol "$cmdid OK RENAME completed\r\n";
                   1522:     }
                   1523: 
                   1524:     return 0;
                   1525: }
                   1526: 
                   1527: sub CHECK_imap {
                   1528:     if ($selected eq "") {
                   1529:         sendcontrol "$cmdid BAD Command received in Invalid state\r\n";
                   1530:     }
                   1531:     else {
                   1532:         sendcontrol "$cmdid OK CHECK completed\r\n";
                   1533:     }
                   1534: 
                   1535:     return 0;
                   1536: }
                   1537: 
                   1538: sub CLOSE_imap {
                   1539:     if ($selected eq "") {
                   1540:         sendcontrol "$cmdid BAD Command received in Invalid state\r\n";
                   1541:     }
                   1542:     elsif (!@deleted) {
                   1543:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1544:     }
                   1545:     else {
                   1546:         sendcontrol "$cmdid OK CLOSE completed\r\n";
                   1547: 
                   1548:         @deleted = ();
                   1549:     }
                   1550: 
                   1551:     return 0;
                   1552: }
                   1553: 
                   1554: sub EXPUNGE_imap {
                   1555:     if ($selected eq "") {
                   1556:         sendcontrol "$cmdid BAD Command received in Invalid state\r\n";
                   1557:     }
                   1558:     else {
                   1559:         if (!@deleted) {
                   1560:             # Report the number of existing messages as per the SELECT
                   1561:             # command
                   1562:             sendcontrol "* 172 EXISTS\r\n";
                   1563:         }
                   1564:         else {
                   1565:             # Report the message UIDs being deleted
                   1566:             for my $d (@deleted) {
                   1567:                 sendcontrol "* $d EXPUNGE\r\n";
                   1568:             }
                   1569: 
                   1570:             @deleted = ();
                   1571:         }
                   1572: 
                   1573:         sendcontrol "$cmdid OK EXPUNGE completed\r\n";
                   1574:     }
                   1575: 
                   1576:     return 0;
                   1577: }
                   1578: 
                   1579: sub COPY_imap {
                   1580:     my ($args) = @_;
                   1581:     my ($uid, $mailbox) = split(/ /, $args, 2);
                   1582:     fix_imap_params($uid, $mailbox);
                   1583: 
                   1584:     logmsg "COPY_imap got $args\n";
                   1585: 
                   1586:     if (($uid eq "") || ($mailbox eq "")) {
                   1587:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1588:     }
                   1589:     else {
                   1590:         sendcontrol "$cmdid OK COPY completed\r\n";
                   1591:     }
                   1592: 
                   1593:     return 0;
                   1594: }
                   1595: 
                   1596: sub UID_imap {
                   1597:     my ($args) = @_;
                   1598:     my ($command) = split(/ /, $args, 1);
                   1599:     fix_imap_params($command);
                   1600: 
                   1601:     logmsg "UID_imap got $args\n";
                   1602: 
                   1603:     if ($selected eq "") {
                   1604:         sendcontrol "$cmdid BAD Command received in Invalid state\r\n";
                   1605:     }
                   1606:     elsif (substr($command, 0, 5) eq "FETCH"){
                   1607:         my $func = $commandfunc{"FETCH"};
                   1608:         if($func) {
                   1609:             &$func($args, $command);
                   1610:         }
                   1611:     }
                   1612:     elsif (($command ne "COPY") &&
                   1613:            ($command ne "STORE") && ($command ne "SEARCH")) {
                   1614:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1615:     }
                   1616:     else {
                   1617:         my @data = getreplydata($selected);
                   1618: 
                   1619:         for my $d (@data) {
                   1620:             sendcontrol $d;
                   1621:         }
                   1622: 
                   1623:         sendcontrol "$cmdid OK $command completed\r\n";
                   1624:     }
                   1625: 
                   1626:     return 0;
                   1627: }
                   1628: 
                   1629: sub NOOP_imap {
                   1630:     my ($args) = @_;
                   1631:     my @data = (
                   1632:         "* 22 EXPUNGE\r\n",
                   1633:         "* 23 EXISTS\r\n",
                   1634:         "* 3 RECENT\r\n",
                   1635:         "* 14 FETCH (FLAGS (\\Seen \\Deleted))\r\n",
                   1636:     );
                   1637: 
                   1638:     if ($args) {
                   1639:         sendcontrol "$cmdid BAD Command Argument\r\n";
                   1640:     }
                   1641:     else {
                   1642:         for my $d (@data) {
                   1643:             sendcontrol $d;
                   1644:         }
                   1645: 
                   1646:         sendcontrol "$cmdid OK NOOP completed\r\n";
                   1647:     }
                   1648: 
                   1649:     return 0;
                   1650: }
                   1651: 
                   1652: sub LOGOUT_imap {
                   1653:     sendcontrol "* BYE curl IMAP server signing off\r\n";
                   1654:     sendcontrol "$cmdid OK LOGOUT completed\r\n";
                   1655: 
                   1656:     return 0;
                   1657: }
                   1658: 
                   1659: ################
                   1660: ################ POP3 commands
                   1661: ################
                   1662: 
                   1663: # Who is attempting to log in
                   1664: my $username;
                   1665: 
                   1666: sub CAPA_pop3 {
                   1667:     my @list = ();
                   1668:     my $mechs;
                   1669: 
                   1670:     # Calculate the capability list based on the specified capabilities
                   1671:     # (except APOP) and any authentication mechanisms
                   1672:     for my $c (@capabilities) {
                   1673:         push @list, "$c\r\n" unless $c eq "APOP";
                   1674:     }
                   1675: 
                   1676:     for my $am (@auth_mechs) {
                   1677:         if(!$mechs) {
                   1678:             $mechs = "$am";
                   1679:         }
                   1680:         else {
                   1681:             $mechs .= " $am";
                   1682:         }
                   1683:     }
                   1684: 
                   1685:     if($mechs) {
                   1686:         push @list, "SASL $mechs\r\n";
                   1687:     }
                   1688: 
                   1689:     if(!@list) {
                   1690:         sendcontrol "-ERR Unrecognized command\r\n";
                   1691:     }
                   1692:     else {
                   1693:         my @data = ();
                   1694: 
                   1695:         # Calculate the CAPA response
                   1696:         push @data, "+OK List of capabilities follows\r\n";
                   1697: 
                   1698:         for my $l (@list) {
                   1699:             push @data, "$l\r\n";
                   1700:         }
                   1701: 
                   1702:         push @data, "IMPLEMENTATION POP3 pingpong test server\r\n";
                   1703: 
                   1704:         # Send the CAPA response
                   1705:         for my $d (@data) {
                   1706:             sendcontrol $d;
                   1707:         }
                   1708: 
                   1709:         # End with the magic 3-byte end of listing marker
                   1710:         sendcontrol ".\r\n";
                   1711:     }
                   1712: 
                   1713:     return 0;
                   1714: }
                   1715: 
                   1716: sub APOP_pop3 {
                   1717:     my ($args) = @_;
                   1718:     my ($user, $secret) = split(/ /, $args, 2);
                   1719: 
                   1720:     if (!grep /^APOP$/, @capabilities) {
                   1721:         sendcontrol "-ERR Unrecognized command\r\n";
                   1722:     }
                   1723:     elsif (($user eq "") || ($secret eq "")) {
                   1724:         sendcontrol "-ERR Protocol error\r\n";
                   1725:     }
                   1726:     else {
                   1727:         my $digest = Digest::MD5::md5_hex($POP3_TIMESTAMP, $TEXT_PASSWORD);
                   1728: 
                   1729:         if ($secret ne $digest) {
                   1730:             sendcontrol "-ERR Login failure\r\n";
                   1731:         }
                   1732:         else {
                   1733:             sendcontrol "+OK Login successful\r\n";
                   1734:         }
                   1735:     }
                   1736: 
                   1737:     return 0;
                   1738: }
                   1739: 
                   1740: sub AUTH_pop3 {
                   1741:     if(!@auth_mechs) {
                   1742:         sendcontrol "-ERR Unrecognized command\r\n";
                   1743:     }
                   1744:     else {
                   1745:         my @data = ();
                   1746: 
                   1747:         # Calculate the AUTH response
                   1748:         push @data, "+OK List of supported mechanisms follows\r\n";
                   1749: 
                   1750:         for my $am (@auth_mechs) {
                   1751:             push @data, "$am\r\n";
                   1752:         }
                   1753: 
                   1754:         # Send the AUTH response
                   1755:         for my $d (@data) {
                   1756:             sendcontrol $d;
                   1757:         }
                   1758: 
                   1759:         # End with the magic 3-byte end of listing marker
                   1760:         sendcontrol ".\r\n";
                   1761:     }
                   1762: 
                   1763:     return 0;
                   1764: }
                   1765: 
                   1766: sub USER_pop3 {
                   1767:     my ($user) = @_;
                   1768: 
                   1769:     logmsg "USER_pop3 got $user\n";
                   1770: 
                   1771:     if (!$user) {
                   1772:         sendcontrol "-ERR Protocol error\r\n";
                   1773:     }
                   1774:     else {
                   1775:         $username = $user;
                   1776: 
                   1777:         sendcontrol "+OK\r\n";
                   1778:     }
                   1779: 
                   1780:     return 0;
                   1781: }
                   1782: 
                   1783: sub PASS_pop3 {
                   1784:     my ($password) = @_;
                   1785: 
                   1786:     logmsg "PASS_pop3 got $password\n";
                   1787: 
                   1788:     sendcontrol "+OK Login successful\r\n";
                   1789: 
                   1790:     return 0;
                   1791: }
                   1792: 
                   1793: sub RETR_pop3 {
                   1794:     my ($msgid) = @_;
                   1795:     my @data;
                   1796: 
                   1797:     if($msgid =~ /^verifiedserver$/) {
                   1798:         # this is the secret command that verifies that this actually is
                   1799:         # the curl test server
                   1800:         my $response = "WE ROOLZ: $$\r\n";
                   1801:         if($verbose) {
                   1802:             print STDERR "FTPD: We returned proof we are the test server\n";
                   1803:         }
                   1804:         $data[0] = $response;
                   1805:         logmsg "return proof we are we\n";
                   1806:     }
                   1807:     else {
                   1808:         # send mail content
                   1809:         logmsg "retrieve a mail\n";
                   1810: 
                   1811:         @data = getreplydata($msgid);
                   1812:     }
                   1813: 
                   1814:     sendcontrol "+OK Mail transfer starts\r\n";
                   1815: 
                   1816:     for my $d (@data) {
                   1817:         sendcontrol $d;
                   1818:     }
                   1819: 
                   1820:     # end with the magic 3-byte end of mail marker, assumes that the
                   1821:     # mail body ends with a CRLF!
                   1822:     sendcontrol ".\r\n";
                   1823: 
                   1824:     return 0;
                   1825: }
                   1826: 
                   1827: sub LIST_pop3 {
                   1828:     # This is a built-in fake-message list
                   1829:     my @data = (
                   1830:         "1 100\r\n",
                   1831:         "2 4294967400\r\n",    # > 4 GB
                   1832:         "3 200\r\n",
                   1833:     );
                   1834: 
                   1835:     logmsg "retrieve a message list\n";
                   1836: 
                   1837:     sendcontrol "+OK Listing starts\r\n";
                   1838: 
                   1839:     for my $d (@data) {
                   1840:         sendcontrol $d;
                   1841:     }
                   1842: 
                   1843:     # End with the magic 3-byte end of listing marker
                   1844:     sendcontrol ".\r\n";
                   1845: 
                   1846:     return 0;
                   1847: }
                   1848: 
                   1849: sub DELE_pop3 {
                   1850:     my ($msgid) = @_;
                   1851: 
                   1852:     logmsg "DELE_pop3 got $msgid\n";
                   1853: 
                   1854:     if (!$msgid) {
                   1855:         sendcontrol "-ERR Protocol error\r\n";
                   1856:     }
                   1857:     else {
                   1858:         push (@deleted, $msgid);
                   1859: 
                   1860:         sendcontrol "+OK\r\n";
                   1861:     }
                   1862: 
                   1863:     return 0;
                   1864: }
                   1865: 
                   1866: sub STAT_pop3 {
                   1867:     my ($args) = @_;
                   1868: 
                   1869:     if ($args) {
                   1870:         sendcontrol "-ERR Protocol error\r\n";
                   1871:     }
                   1872:     else {
                   1873:         # Send statistics for the built-in fake message list as
                   1874:         # detailed in the LIST_pop3 function above
                   1875:         sendcontrol "+OK 3 4294967800\r\n";
                   1876:     }
                   1877: 
                   1878:     return 0;
                   1879: }
                   1880: 
                   1881: sub NOOP_pop3 {
                   1882:     my ($args) = @_;
                   1883: 
                   1884:     if ($args) {
                   1885:         sendcontrol "-ERR Protocol error\r\n";
                   1886:     }
                   1887:     else {
                   1888:         sendcontrol "+OK\r\n";
                   1889:     }
                   1890: 
                   1891:     return 0;
                   1892: }
                   1893: 
                   1894: sub UIDL_pop3 {
                   1895:     # This is a built-in fake-message UID list
                   1896:     my @data = (
                   1897:         "1 1\r\n",
                   1898:         "2 2\r\n",
                   1899:         "3 4\r\n", # Note that UID 3 is a simulated "deleted" message
                   1900:     );
                   1901: 
                   1902:     if (!grep /^UIDL$/, @capabilities) {
                   1903:         sendcontrol "-ERR Unrecognized command\r\n";
                   1904:     }
                   1905:     else {
                   1906:         logmsg "retrieve a message UID list\n";
                   1907: 
                   1908:         sendcontrol "+OK Listing starts\r\n";
                   1909: 
                   1910:         for my $d (@data) {
                   1911:             sendcontrol $d;
                   1912:         }
                   1913: 
                   1914:         # End with the magic 3-byte end of listing marker
                   1915:         sendcontrol ".\r\n";
                   1916:     }
                   1917: 
                   1918:     return 0;
                   1919: }
                   1920: 
                   1921: sub TOP_pop3 {
                   1922:     my ($args) = @_;
                   1923:     my ($msgid, $lines) = split(/ /, $args, 2);
                   1924: 
                   1925:     logmsg "TOP_pop3 got $args\n";
                   1926: 
                   1927:     if (!grep /^TOP$/, @capabilities) {
                   1928:         sendcontrol "-ERR Unrecognized command\r\n";
                   1929:     }
                   1930:     elsif (($msgid eq "") || ($lines eq "")) {
                   1931:         sendcontrol "-ERR Protocol error\r\n";
                   1932:     }
                   1933:     else {
                   1934:         if ($lines == "0") {
                   1935:             logmsg "retrieve header of mail\n";
                   1936:         }
                   1937:         else {
                   1938:             logmsg "retrieve top $lines lines of mail\n";
                   1939:         }
                   1940: 
                   1941:         my @data = getreplydata($msgid);
                   1942: 
                   1943:         sendcontrol "+OK Mail transfer starts\r\n";
                   1944: 
                   1945:         # Send mail content
                   1946:         for my $d (@data) {
                   1947:             sendcontrol $d;
                   1948:         }
                   1949: 
                   1950:         # End with the magic 3-byte end of mail marker, assumes that the
                   1951:         # mail body ends with a CRLF!
                   1952:         sendcontrol ".\r\n";
                   1953:     }
                   1954: 
                   1955:     return 0;
                   1956: }
                   1957: 
                   1958: sub RSET_pop3 {
                   1959:     my ($args) = @_;
                   1960: 
                   1961:     if ($args) {
                   1962:         sendcontrol "-ERR Protocol error\r\n";
                   1963:     }
                   1964:     else {
                   1965:         if (@deleted) {
                   1966:             logmsg "resetting @deleted message(s)\n";
                   1967: 
                   1968:             @deleted = ();
                   1969:         }
                   1970: 
                   1971:         sendcontrol "+OK\r\n";
                   1972:     }
                   1973: 
                   1974:     return 0;
                   1975: }
                   1976: 
                   1977: sub QUIT_pop3 {
                   1978:     if(@deleted) {
                   1979:         logmsg "deleting @deleted message(s)\n";
                   1980: 
                   1981:         @deleted = ();
                   1982:     }
                   1983: 
                   1984:     sendcontrol "+OK curl POP3 server signing off\r\n";
                   1985: 
                   1986:     return 0;
                   1987: }
                   1988: 
                   1989: ################
                   1990: ################ FTP commands
                   1991: ################
                   1992: my $rest=0;
                   1993: sub REST_ftp {
                   1994:     $rest = $_[0];
                   1995:     logmsg "Set REST position to $rest\n"
                   1996: }
                   1997: 
                   1998: sub switch_directory_goto {
                   1999:   my $target_dir = $_;
                   2000: 
                   2001:   if(!$ftptargetdir) {
                   2002:     $ftptargetdir = "/";
                   2003:   }
                   2004: 
                   2005:   if($target_dir eq "") {
                   2006:     $ftptargetdir = "/";
                   2007:   }
                   2008:   elsif($target_dir eq "..") {
                   2009:     if($ftptargetdir eq "/") {
                   2010:       $ftptargetdir = "/";
                   2011:     }
                   2012:     else {
                   2013:       $ftptargetdir =~ s/[[:alnum:]]+\/$//;
                   2014:     }
                   2015:   }
                   2016:   else {
                   2017:     $ftptargetdir .= $target_dir . "/";
                   2018:   }
                   2019: }
                   2020: 
                   2021: sub switch_directory {
                   2022:     my $target_dir = $_[0];
                   2023: 
                   2024:     if($target_dir =~ /^test-(\d+)/) {
                   2025:         $cwd_testno = $1;
                   2026:     }
                   2027:     elsif($target_dir eq "/") {
                   2028:         $ftptargetdir = "/";
                   2029:     }
                   2030:     else {
                   2031:         my @dirs = split("/", $target_dir);
                   2032:         for(@dirs) {
                   2033:           switch_directory_goto($_);
                   2034:         }
                   2035:     }
                   2036: }
                   2037: 
                   2038: sub CWD_ftp {
                   2039:   my ($folder, $fullcommand) = $_[0];
                   2040:   switch_directory($folder);
                   2041:   if($ftptargetdir =~ /^\/fully_simulated/) {
                   2042:     $ftplistparserstate = "enabled";
                   2043:   }
                   2044:   else {
                   2045:     undef $ftplistparserstate;
                   2046:   }
                   2047: }
                   2048: 
                   2049: sub PWD_ftp {
                   2050:     my $mydir;
                   2051:     $mydir = $ftptargetdir ? $ftptargetdir : "/";
                   2052: 
                   2053:     if($mydir ne "/") {
                   2054:         $mydir =~ s/\/$//;
                   2055:     }
                   2056:     sendcontrol "257 \"$mydir\" is current directory\r\n";
                   2057: }
                   2058: 
                   2059: sub LIST_ftp {
                   2060:     #  print "150 ASCII data connection for /bin/ls (193.15.23.1,59196) (0 bytes)\r\n";
                   2061: 
                   2062: # this is a built-in fake-dir ;-)
                   2063: my @ftpdir=("total 20\r\n",
                   2064: "drwxr-xr-x   8 98       98           512 Oct 22 13:06 .\r\n",
                   2065: "drwxr-xr-x   8 98       98           512 Oct 22 13:06 ..\r\n",
                   2066: "drwxr-xr-x   2 98       98           512 May  2  1996 .NeXT\r\n",
                   2067: "-r--r--r--   1 0        1             35 Jul 16  1996 README\r\n",
                   2068: "lrwxrwxrwx   1 0        1              7 Dec  9  1999 bin -> usr/bin\r\n",
                   2069: "dr-xr-xr-x   2 0        1            512 Oct  1  1997 dev\r\n",
                   2070: "drwxrwxrwx   2 98       98           512 May 29 16:04 download.html\r\n",
                   2071: "dr-xr-xr-x   2 0        1            512 Nov 30  1995 etc\r\n",
                   2072: "drwxrwxrwx   2 98       1            512 Oct 30 14:33 pub\r\n",
                   2073: "dr-xr-xr-x   5 0        1            512 Oct  1  1997 usr\r\n");
                   2074: 
                   2075:     if($datasockf_conn eq 'no') {
                   2076:         if($nodataconn425) {
                   2077:             sendcontrol "150 Opening data connection\r\n";
                   2078:             sendcontrol "425 Can't open data connection\r\n";
                   2079:         }
                   2080:         elsif($nodataconn421) {
                   2081:             sendcontrol "150 Opening data connection\r\n";
                   2082:             sendcontrol "421 Connection timed out\r\n";
                   2083:         }
                   2084:         elsif($nodataconn150) {
                   2085:             sendcontrol "150 Opening data connection\r\n";
                   2086:             # client shall timeout
                   2087:         }
                   2088:         else {
                   2089:             # client shall timeout
                   2090:         }
                   2091:         return 0;
                   2092:     }
                   2093: 
                   2094:     if($ftplistparserstate) {
                   2095:       @ftpdir = ftp_contentlist($ftptargetdir);
                   2096:     }
                   2097: 
                   2098:     logmsg "pass LIST data on data connection\n";
                   2099: 
                   2100:     if($cwd_testno) {
                   2101:         loadtest("$srcdir/data/test$cwd_testno");
                   2102: 
                   2103:         my @data = getpart("reply", "data");
                   2104:         for(@data) {
                   2105:             my $send = $_;
                   2106:             # convert all \n to \r\n for ASCII transfer
                   2107:             $send =~ s/\r\n/\n/g;
                   2108:             $send =~ s/\n/\r\n/g;
                   2109:             logmsg "send $send as data\n";
                   2110:             senddata $send;
                   2111:         }
                   2112:         $cwd_testno = 0; # forget it again
                   2113:     }
                   2114:     else {
                   2115:         # old hard-coded style
                   2116:         for(@ftpdir) {
                   2117:             senddata $_;
                   2118:         }
                   2119:     }
                   2120:     close_dataconn(0);
                   2121:     sendcontrol "226 ASCII transfer complete\r\n";
                   2122:     return 0;
                   2123: }
                   2124: 
                   2125: sub NLST_ftp {
                   2126:     my @ftpdir=("file", "with space", "fake", "..", " ..", "funny", "README");
                   2127: 
                   2128:     if($datasockf_conn eq 'no') {
                   2129:         if($nodataconn425) {
                   2130:             sendcontrol "150 Opening data connection\r\n";
                   2131:             sendcontrol "425 Can't open data connection\r\n";
                   2132:         }
                   2133:         elsif($nodataconn421) {
                   2134:             sendcontrol "150 Opening data connection\r\n";
                   2135:             sendcontrol "421 Connection timed out\r\n";
                   2136:         }
                   2137:         elsif($nodataconn150) {
                   2138:             sendcontrol "150 Opening data connection\r\n";
                   2139:             # client shall timeout
                   2140:         }
                   2141:         else {
                   2142:             # client shall timeout
                   2143:         }
                   2144:         return 0;
                   2145:     }
                   2146: 
                   2147:     logmsg "pass NLST data on data connection\n";
                   2148:     for(@ftpdir) {
                   2149:         senddata "$_\r\n";
                   2150:     }
                   2151:     close_dataconn(0);
                   2152:     sendcontrol "226 ASCII transfer complete\r\n";
                   2153:     return 0;
                   2154: }
                   2155: 
                   2156: sub MDTM_ftp {
                   2157:     my $testno = $_[0];
                   2158:     my $testpart = "";
                   2159:     if ($testno > 10000) {
                   2160:         $testpart = $testno % 10000;
                   2161:         $testno = int($testno / 10000);
                   2162:     }
                   2163: 
                   2164:     loadtest("$srcdir/data/test$testno");
                   2165: 
                   2166:     my @data = getpart("reply", "mdtm");
                   2167: 
                   2168:     my $reply = $data[0];
                   2169:     chomp $reply if($reply);
                   2170: 
                   2171:     if($reply && ($reply =~ /^[+-]?\d+$/) && ($reply < 0)) {
                   2172:         sendcontrol "550 $testno: no such file.\r\n";
                   2173:     }
                   2174:     elsif($reply) {
                   2175:         sendcontrol "$reply\r\n";
                   2176:     }
                   2177:     else {
                   2178:         sendcontrol "500 MDTM: no such command.\r\n";
                   2179:     }
                   2180:     return 0;
                   2181: }
                   2182: 
                   2183: sub SIZE_ftp {
                   2184:     my $testno = $_[0];
                   2185:     if($ftplistparserstate) {
                   2186:         my $size = wildcard_filesize($ftptargetdir, $testno);
                   2187:         if($size == -1) {
                   2188:             sendcontrol "550 $testno: No such file or directory.\r\n";
                   2189:         }
                   2190:         else {
                   2191:             sendcontrol "213 $size\r\n";
                   2192:         }
                   2193:         return 0;
                   2194:     }
                   2195: 
                   2196:     if($testno =~ /^verifiedserver$/) {
                   2197:         my $response = "WE ROOLZ: $$\r\n";
                   2198:         my $size = length($response);
                   2199:         sendcontrol "213 $size\r\n";
                   2200:         return 0;
                   2201:     }
                   2202: 
                   2203:     if($testno =~ /(\d+)\/?$/) {
                   2204:         $testno = $1;
                   2205:     }
                   2206:     else {
                   2207:         print STDERR "SIZE_ftp: invalid test number: $testno\n";
                   2208:         return 1;
                   2209:     }
                   2210: 
                   2211:     my $testpart = "";
                   2212:     if($testno > 10000) {
                   2213:         $testpart = $testno % 10000;
                   2214:         $testno = int($testno / 10000);
                   2215:     }
                   2216: 
                   2217:     loadtest("$srcdir/data/test$testno");
                   2218: 
                   2219:     my @data = getpart("reply", "size");
                   2220: 
                   2221:     my $size = $data[0];
                   2222: 
                   2223:     if($size) {
                   2224:         if($size > -1) {
                   2225:             sendcontrol "213 $size\r\n";
                   2226:         }
                   2227:         else {
                   2228:             sendcontrol "550 $testno: No such file or directory.\r\n";
                   2229:         }
                   2230:     }
                   2231:     else {
                   2232:         $size=0;
                   2233:         @data = getpart("reply", "data$testpart");
                   2234:         for(@data) {
                   2235:             $size += length($_);
                   2236:         }
                   2237:         if($size) {
                   2238:             sendcontrol "213 $size\r\n";
                   2239:         }
                   2240:         else {
                   2241:             sendcontrol "550 $testno: No such file or directory.\r\n";
                   2242:         }
                   2243:     }
                   2244:     return 0;
                   2245: }
                   2246: 
                   2247: sub RETR_ftp {
                   2248:     my ($testno) = @_;
                   2249: 
                   2250:     if($datasockf_conn eq 'no') {
                   2251:         if($nodataconn425) {
                   2252:             sendcontrol "150 Opening data connection\r\n";
                   2253:             sendcontrol "425 Can't open data connection\r\n";
                   2254:         }
                   2255:         elsif($nodataconn421) {
                   2256:             sendcontrol "150 Opening data connection\r\n";
                   2257:             sendcontrol "421 Connection timed out\r\n";
                   2258:         }
                   2259:         elsif($nodataconn150) {
                   2260:             sendcontrol "150 Opening data connection\r\n";
                   2261:             # client shall timeout
                   2262:         }
                   2263:         else {
                   2264:             # client shall timeout
                   2265:         }
                   2266:         return 0;
                   2267:     }
                   2268: 
                   2269:     if($ftplistparserstate) {
                   2270:         my @content = wildcard_getfile($ftptargetdir, $testno);
                   2271:         if($content[0] == -1) {
                   2272:             #file not found
                   2273:         }
                   2274:         else {
                   2275:             my $size = length $content[1];
                   2276:             sendcontrol "150 Binary data connection for $testno ($size bytes).\r\n",
                   2277:             senddata $content[1];
                   2278:             close_dataconn(0);
                   2279:             sendcontrol "226 File transfer complete\r\n";
                   2280:         }
                   2281:         return 0;
                   2282:     }
                   2283: 
                   2284:     if($testno =~ /^verifiedserver$/) {
                   2285:         # this is the secret command that verifies that this actually is
                   2286:         # the curl test server
                   2287:         my $response = "WE ROOLZ: $$\r\n";
                   2288:         my $len = length($response);
                   2289:         sendcontrol "150 Binary junk ($len bytes).\r\n";
                   2290:         senddata "WE ROOLZ: $$\r\n";
                   2291:         close_dataconn(0);
                   2292:         sendcontrol "226 File transfer complete\r\n";
                   2293:         if($verbose) {
                   2294:             print STDERR "FTPD: We returned proof we are the test server\n";
                   2295:         }
                   2296:         return 0;
                   2297:     }
                   2298: 
                   2299:     $testno =~ s/^([^0-9]*)//;
                   2300:     my $testpart = "";
                   2301:     if ($testno > 10000) {
                   2302:         $testpart = $testno % 10000;
                   2303:         $testno = int($testno / 10000);
                   2304:     }
                   2305: 
                   2306:     loadtest("$srcdir/data/test$testno");
                   2307: 
                   2308:     my @data = getpart("reply", "data$testpart");
                   2309: 
                   2310:     my $size=0;
                   2311:     for(@data) {
                   2312:         $size += length($_);
                   2313:     }
                   2314: 
                   2315:     my %hash = getpartattr("reply", "data$testpart");
                   2316: 
                   2317:     if($size || $hash{'sendzero'}) {
                   2318: 
                   2319:         if($rest) {
                   2320:             # move read pointer forward
                   2321:             $size -= $rest;
                   2322:             logmsg "REST $rest was removed from size, makes $size left\n";
                   2323:             $rest = 0; # reset REST offset again
                   2324:         }
                   2325:         if($retrweirdo) {
                   2326:             sendcontrol "150 Binary data connection for $testno () ($size bytes).\r\n",
                   2327:             "226 File transfer complete\r\n";
                   2328: 
                   2329:             for(@data) {
                   2330:                 my $send = $_;
                   2331:                 senddata $send;
                   2332:             }
                   2333:             close_dataconn(0);
                   2334:             $retrweirdo=0; # switch off the weirdo again!
                   2335:         }
                   2336:         else {
                   2337:             my $sz = "($size bytes)";
                   2338:             if($retrnosize) {
                   2339:                 $sz = "size?";
                   2340:             }
                   2341: 
                   2342:             sendcontrol "150 Binary data connection for $testno () $sz.\r\n";
                   2343: 
                   2344:             for(@data) {
                   2345:                 my $send = $_;
                   2346:                 senddata $send;
                   2347:             }
                   2348:             close_dataconn(0);
                   2349:             sendcontrol "226 File transfer complete\r\n";
                   2350:         }
                   2351:     }
                   2352:     else {
                   2353:         sendcontrol "550 $testno: No such file or directory.\r\n";
                   2354:     }
                   2355:     return 0;
                   2356: }
                   2357: 
                   2358: sub STOR_ftp {
                   2359:     my $testno=$_[0];
                   2360: 
                   2361:     my $filename = "log/upload.$testno";
                   2362: 
                   2363:     if($datasockf_conn eq 'no') {
                   2364:         if($nodataconn425) {
                   2365:             sendcontrol "150 Opening data connection\r\n";
                   2366:             sendcontrol "425 Can't open data connection\r\n";
                   2367:         }
                   2368:         elsif($nodataconn421) {
                   2369:             sendcontrol "150 Opening data connection\r\n";
                   2370:             sendcontrol "421 Connection timed out\r\n";
                   2371:         }
                   2372:         elsif($nodataconn150) {
                   2373:             sendcontrol "150 Opening data connection\r\n";
                   2374:             # client shall timeout
                   2375:         }
                   2376:         else {
                   2377:             # client shall timeout
                   2378:         }
                   2379:         return 0;
                   2380:     }
                   2381: 
                   2382:     logmsg "STOR test number $testno in $filename\n";
                   2383: 
                   2384:     sendcontrol "125 Gimme gimme gimme!\r\n";
                   2385: 
                   2386:     open(FILE, ">$filename") ||
                   2387:         return 0; # failed to open output
                   2388: 
                   2389:     my $line;
                   2390:     my $ulsize=0;
                   2391:     my $disc=0;
                   2392:     while (5 == (sysread DREAD, $line, 5)) {
                   2393:         if($line eq "DATA\n") {
                   2394:             my $i;
                   2395:             sysread DREAD, $i, 5;
                   2396: 
                   2397:             my $size = 0;
                   2398:             if($i =~ /^([0-9a-fA-F]{4})\n/) {
                   2399:                 $size = hex($1);
                   2400:             }
                   2401: 
                   2402:             read_datasockf(\$line, $size);
                   2403: 
                   2404:             #print STDERR "  GOT: $size bytes\n";
                   2405: 
                   2406:             $ulsize += $size;
                   2407:             print FILE $line if(!$nosave);
                   2408:             logmsg "> Appending $size bytes to file\n";
                   2409:         }
                   2410:         elsif($line eq "DISC\n") {
                   2411:             # disconnect!
                   2412:             $disc=1;
                   2413:             last;
                   2414:         }
                   2415:         else {
                   2416:             logmsg "No support for: $line";
                   2417:             last;
                   2418:         }
                   2419:     }
                   2420:     if($nosave) {
                   2421:         print FILE "$ulsize bytes would've been stored here\n";
                   2422:     }
                   2423:     close(FILE);
                   2424:     close_dataconn($disc);
                   2425:     logmsg "received $ulsize bytes upload\n";
                   2426:     sendcontrol "226 File transfer complete\r\n";
                   2427:     return 0;
                   2428: }
                   2429: 
                   2430: sub PASV_ftp {
                   2431:     my ($arg, $cmd)=@_;
                   2432:     my $pasvport;
                   2433:     my $bindonly = ($nodataconn) ? '--bindonly' : '';
                   2434: 
                   2435:     # kill previous data connection sockfilt when alive
                   2436:     if($datasockf_runs eq 'yes') {
                   2437:         killsockfilters($proto, $ipvnum, $idnum, $verbose, 'data');
                   2438:         logmsg "DATA sockfilt for $datasockf_mode data channel killed\n";
                   2439:     }
                   2440:     datasockf_state('STOPPED');
                   2441: 
                   2442:     logmsg "====> Passive DATA channel requested by client\n";
                   2443: 
                   2444:     logmsg "DATA sockfilt for passive data channel starting...\n";
                   2445: 
                   2446:     # We fire up a new sockfilt to do the data transfer for us.
                   2447:     my $datasockfcmd = "./server/sockfilt".exe_ext('SRV')." " .
                   2448:         "--ipv$ipvnum $bindonly --port 0 " .
                   2449:         "--pidfile \"$datasockf_pidfile\" " .
                   2450:         "--logfile \"$datasockf_logfile\"";
                   2451:     $slavepid = open2(\*DREAD, \*DWRITE, $datasockfcmd);
                   2452: 
                   2453:     if($nodataconn) {
                   2454:         datasockf_state('PASSIVE_NODATACONN');
                   2455:     }
                   2456:     else {
                   2457:         datasockf_state('PASSIVE');
                   2458:     }
                   2459: 
                   2460:     print STDERR "$datasockfcmd\n" if($verbose);
                   2461: 
                   2462:     print DWRITE "PING\n";
                   2463:     my $pong;
                   2464:     sysread_or_die(\*DREAD, \$pong, 5);
                   2465: 
                   2466:     if($pong =~ /^FAIL/) {
                   2467:         logmsg "DATA sockfilt said: FAIL\n";
                   2468:         logmsg "DATA sockfilt for passive data channel failed\n";
                   2469:         logmsg "DATA sockfilt not running\n";
                   2470:         datasockf_state('STOPPED');
                   2471:         sendcontrol "500 no free ports!\r\n";
                   2472:         return;
                   2473:     }
                   2474:     elsif($pong !~ /^PONG/) {
                   2475:         logmsg "DATA sockfilt unexpected response: $pong\n";
                   2476:         logmsg "DATA sockfilt for passive data channel failed\n";
                   2477:         logmsg "DATA sockfilt killed now\n";
                   2478:         killsockfilters($proto, $ipvnum, $idnum, $verbose, 'data');
                   2479:         logmsg "DATA sockfilt not running\n";
                   2480:         datasockf_state('STOPPED');
                   2481:         sendcontrol "500 no free ports!\r\n";
                   2482:         return;
                   2483:     }
                   2484: 
                   2485:     logmsg "DATA sockfilt for passive data channel started (pid $slavepid)\n";
                   2486: 
                   2487:     # Find out on what port we listen on or have bound
                   2488:     my $i;
                   2489:     print DWRITE "PORT\n";
                   2490: 
                   2491:     # READ the response code
                   2492:     sysread_or_die(\*DREAD, \$i, 5);
                   2493: 
                   2494:     # READ the response size
                   2495:     sysread_or_die(\*DREAD, \$i, 5);
                   2496: 
                   2497:     my $size = 0;
                   2498:     if($i =~ /^([0-9a-fA-F]{4})\n/) {
                   2499:         $size = hex($1);
                   2500:     }
                   2501: 
                   2502:     # READ the response data
                   2503:     read_datasockf(\$i, $size);
                   2504: 
                   2505:     # The data is in the format
                   2506:     # IPvX/NNN
                   2507: 
                   2508:     if($i =~ /IPv(\d)\/(\d+)/) {
                   2509:         # FIX: deal with IP protocol version
                   2510:         $pasvport = $2;
                   2511:     }
                   2512: 
                   2513:     if(!$pasvport) {
                   2514:         logmsg "DATA sockfilt unknown listener port\n";
                   2515:         logmsg "DATA sockfilt for passive data channel failed\n";
                   2516:         logmsg "DATA sockfilt killed now\n";
                   2517:         killsockfilters($proto, $ipvnum, $idnum, $verbose, 'data');
                   2518:         logmsg "DATA sockfilt not running\n";
                   2519:         datasockf_state('STOPPED');
                   2520:         sendcontrol "500 no free ports!\r\n";
                   2521:         return;
                   2522:     }
                   2523: 
                   2524:     if($nodataconn) {
                   2525:         my $str = nodataconn_str();
                   2526:         logmsg "DATA sockfilt for passive data channel ($str) bound on port ".
                   2527:                "$pasvport\n";
                   2528:     }
                   2529:     else {
                   2530:         logmsg "DATA sockfilt for passive data channel listens on port ".
                   2531:                "$pasvport\n";
                   2532:     }
                   2533: 
                   2534:     if($cmd ne "EPSV") {
                   2535:         # PASV reply
                   2536:         my $p=$listenaddr;
                   2537:         $p =~ s/\./,/g;
                   2538:         if($pasvbadip) {
                   2539:             $p="1,2,3,4";
                   2540:         }
                   2541:         sendcontrol sprintf("227 Entering Passive Mode ($p,%d,%d)\n",
                   2542:                             int($pasvport/256), int($pasvport%256));
                   2543:     }
                   2544:     else {
                   2545:         # EPSV reply
                   2546:         sendcontrol sprintf("229 Entering Passive Mode (|||%d|)\n", $pasvport);
                   2547:     }
                   2548: 
                   2549:     logmsg "Client has been notified that DATA conn ".
                   2550:            "will be accepted on port $pasvport\n";
                   2551: 
                   2552:     if($nodataconn) {
                   2553:         my $str = nodataconn_str();
                   2554:         logmsg "====> Client fooled ($str)\n";
                   2555:         return;
                   2556:     }
                   2557: 
                   2558:     eval {
                   2559:         local $SIG{ALRM} = sub { die "alarm\n" };
                   2560: 
                   2561:         # assume swift operations unless explicitly slow
                   2562:         alarm ($datadelay?20:10);
                   2563: 
                   2564:         # Wait for 'CNCT'
                   2565:         my $input;
                   2566: 
                   2567:         # FIX: Monitor ctrl conn for disconnect
                   2568: 
                   2569:         while(sysread(DREAD, $input, 5)) {
                   2570: 
                   2571:             if($input !~ /^CNCT/) {
                   2572:                 # we wait for a connected client
                   2573:                 logmsg "Odd, we got $input from client\n";
                   2574:                 next;
                   2575:             }
                   2576:             logmsg "Client connects to port $pasvport\n";
                   2577:             last;
                   2578:         }
                   2579:         alarm 0;
                   2580:     };
                   2581:     if ($@) {
                   2582:         # timed out
                   2583:         logmsg "$srvrname server timed out awaiting data connection ".
                   2584:             "on port $pasvport\n";
                   2585:         logmsg "accept failed or connection not even attempted\n";
                   2586:         logmsg "DATA sockfilt killed now\n";
                   2587:         killsockfilters($proto, $ipvnum, $idnum, $verbose, 'data');
                   2588:         logmsg "DATA sockfilt not running\n";
                   2589:         datasockf_state('STOPPED');
                   2590:         return;
                   2591:     }
                   2592:     else {
                   2593:         logmsg "====> Client established passive DATA connection ".
                   2594:                "on port $pasvport\n";
                   2595:     }
                   2596: 
                   2597:     return;
                   2598: }
                   2599: 
                   2600: #
                   2601: # Support both PORT and EPRT here.
                   2602: #
                   2603: 
                   2604: sub PORT_ftp {
                   2605:     my ($arg, $cmd) = @_;
                   2606:     my $port;
                   2607:     my $addr;
                   2608: 
                   2609:     # kill previous data connection sockfilt when alive
                   2610:     if($datasockf_runs eq 'yes') {
                   2611:         killsockfilters($proto, $ipvnum, $idnum, $verbose, 'data');
                   2612:         logmsg "DATA sockfilt for $datasockf_mode data channel killed\n";
                   2613:     }
                   2614:     datasockf_state('STOPPED');
                   2615: 
                   2616:     logmsg "====> Active DATA channel requested by client\n";
                   2617: 
                   2618:     # We always ignore the given IP and use localhost.
                   2619: 
                   2620:     if($cmd eq "PORT") {
                   2621:         if($arg !~ /(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/) {
                   2622:             logmsg "DATA sockfilt for active data channel not started ".
                   2623:                    "(bad PORT-line: $arg)\n";
                   2624:             sendcontrol "500 silly you, go away\r\n";
                   2625:             return;
                   2626:         }
                   2627:         $port = ($5<<8)+$6;
                   2628:         $addr = "$1.$2.$3.$4";
                   2629:     }
                   2630:     # EPRT |2|::1|49706|
                   2631:     elsif($cmd eq "EPRT") {
                   2632:         if($arg !~ /(\d+)\|([^\|]+)\|(\d+)/) {
                   2633:             logmsg "DATA sockfilt for active data channel not started ".
                   2634:                    "(bad EPRT-line: $arg)\n";
                   2635:             sendcontrol "500 silly you, go away\r\n";
                   2636:             return;
                   2637:         }
                   2638:         sendcontrol "200 Thanks for dropping by. We contact you later\r\n";
                   2639:         $port = $3;
                   2640:         $addr = $2;
                   2641:     }
                   2642:     else {
                   2643:         logmsg "DATA sockfilt for active data channel not started ".
                   2644:                "(invalid command: $cmd)\n";
                   2645:         sendcontrol "500 we don't like $cmd now\r\n";
                   2646:         return;
                   2647:     }
                   2648: 
                   2649:     if(!$port || $port > 65535) {
                   2650:         logmsg "DATA sockfilt for active data channel not started ".
                   2651:                "(illegal PORT number: $port)\n";
                   2652:         return;
                   2653:     }
                   2654: 
                   2655:     if($nodataconn) {
                   2656:         my $str = nodataconn_str();
                   2657:         logmsg "DATA sockfilt for active data channel not started ($str)\n";
                   2658:         datasockf_state('ACTIVE_NODATACONN');
                   2659:         logmsg "====> Active DATA channel not established\n";
                   2660:         return;
                   2661:     }
                   2662: 
                   2663:     logmsg "DATA sockfilt for active data channel starting...\n";
                   2664: 
                   2665:     # We fire up a new sockfilt to do the data transfer for us.
                   2666:     my $datasockfcmd = "./server/sockfilt".exe_ext('SRV')." " .
                   2667:         "--ipv$ipvnum --connect $port --addr \"$addr\" " .
                   2668:         "--pidfile \"$datasockf_pidfile\" " .
                   2669:         "--logfile \"$datasockf_logfile\"";
                   2670:     $slavepid = open2(\*DREAD, \*DWRITE, $datasockfcmd);
                   2671: 
                   2672:     datasockf_state('ACTIVE');
                   2673: 
                   2674:     print STDERR "$datasockfcmd\n" if($verbose);
                   2675: 
                   2676:     print DWRITE "PING\n";
                   2677:     my $pong;
                   2678:     sysread_or_die(\*DREAD, \$pong, 5);
                   2679: 
                   2680:     if($pong =~ /^FAIL/) {
                   2681:         logmsg "DATA sockfilt said: FAIL\n";
                   2682:         logmsg "DATA sockfilt for active data channel failed\n";
                   2683:         logmsg "DATA sockfilt not running\n";
                   2684:         datasockf_state('STOPPED');
                   2685:         # client shall timeout awaiting connection from server
                   2686:         return;
                   2687:     }
                   2688:     elsif($pong !~ /^PONG/) {
                   2689:         logmsg "DATA sockfilt unexpected response: $pong\n";
                   2690:         logmsg "DATA sockfilt for active data channel failed\n";
                   2691:         logmsg "DATA sockfilt killed now\n";
                   2692:         killsockfilters($proto, $ipvnum, $idnum, $verbose, 'data');
                   2693:         logmsg "DATA sockfilt not running\n";
                   2694:         datasockf_state('STOPPED');
                   2695:         # client shall timeout awaiting connection from server
                   2696:         return;
                   2697:     }
                   2698: 
                   2699:     logmsg "DATA sockfilt for active data channel started (pid $slavepid)\n";
                   2700: 
                   2701:     logmsg "====> Active DATA channel connected to client port $port\n";
                   2702: 
                   2703:     return;
                   2704: }
                   2705: 
                   2706: #**********************************************************************
                   2707: # datasockf_state is used to change variables that keep state info
                   2708: # relative to the FTP secondary or data sockfilt process as soon as
                   2709: # one of the five possible stable states is reached. Variables that
                   2710: # are modified by this sub may be checked independently but should
                   2711: # not be changed except by calling this sub.
                   2712: #
                   2713: sub datasockf_state {
                   2714:     my $state = $_[0];
                   2715: 
                   2716:   if($state eq 'STOPPED') {
                   2717:     # Data sockfilter initial state, not running,
                   2718:     # not connected and not used.
                   2719:     $datasockf_state = $state;
                   2720:     $datasockf_mode = 'none';
                   2721:     $datasockf_runs = 'no';
                   2722:     $datasockf_conn = 'no';
                   2723:   }
                   2724:   elsif($state eq 'PASSIVE') {
                   2725:     # Data sockfilter accepted connection from client.
                   2726:     $datasockf_state = $state;
                   2727:     $datasockf_mode = 'passive';
                   2728:     $datasockf_runs = 'yes';
                   2729:     $datasockf_conn = 'yes';
                   2730:   }
                   2731:   elsif($state eq 'ACTIVE') {
                   2732:     # Data sockfilter has connected to client.
                   2733:     $datasockf_state = $state;
                   2734:     $datasockf_mode = 'active';
                   2735:     $datasockf_runs = 'yes';
                   2736:     $datasockf_conn = 'yes';
                   2737:   }
                   2738:   elsif($state eq 'PASSIVE_NODATACONN') {
                   2739:     # Data sockfilter bound port without listening,
                   2740:     # client won't be able to establish data connection.
                   2741:     $datasockf_state = $state;
                   2742:     $datasockf_mode = 'passive';
                   2743:     $datasockf_runs = 'yes';
                   2744:     $datasockf_conn = 'no';
                   2745:   }
                   2746:   elsif($state eq 'ACTIVE_NODATACONN') {
                   2747:     # Data sockfilter does not even run,
                   2748:     # client awaits data connection from server in vain.
                   2749:     $datasockf_state = $state;
                   2750:     $datasockf_mode = 'active';
                   2751:     $datasockf_runs = 'no';
                   2752:     $datasockf_conn = 'no';
                   2753:   }
                   2754:   else {
                   2755:       die "Internal error. Unknown datasockf state: $state!";
                   2756:   }
                   2757: }
                   2758: 
                   2759: #**********************************************************************
                   2760: # nodataconn_str returns string of effective nodataconn command. Notice
                   2761: # that $nodataconn may be set alone or in addition to a $nodataconnXXX.
                   2762: #
                   2763: sub nodataconn_str {
                   2764:     my $str;
                   2765:     # order matters
                   2766:     $str = 'NODATACONN' if($nodataconn);
                   2767:     $str = 'NODATACONN425' if($nodataconn425);
                   2768:     $str = 'NODATACONN421' if($nodataconn421);
                   2769:     $str = 'NODATACONN150' if($nodataconn150);
                   2770:     return "$str";
                   2771: }
                   2772: 
                   2773: #**********************************************************************
                   2774: # customize configures test server operation for each curl test, reading
                   2775: # configuration commands/parameters from server commands file each time
                   2776: # a new client control connection is established with the test server.
                   2777: # On success returns 1, otherwise zero.
                   2778: #
                   2779: sub customize {
                   2780:     $ctrldelay = 0;     # default is no throttling of the ctrl stream
                   2781:     $datadelay = 0;     # default is no throttling of the data stream
                   2782:     $retrweirdo = 0;    # default is no use of RETRWEIRDO
                   2783:     $retrnosize = 0;    # default is no use of RETRNOSIZE
                   2784:     $pasvbadip = 0;     # default is no use of PASVBADIP
                   2785:     $nosave = 0;        # default is to actually save uploaded data to file
                   2786:     $nodataconn = 0;    # default is to establish or accept data channel
                   2787:     $nodataconn425 = 0; # default is to not send 425 without data channel
                   2788:     $nodataconn421 = 0; # default is to not send 421 without data channel
                   2789:     $nodataconn150 = 0; # default is to not send 150 without data channel
                   2790:     @capabilities = (); # default is to not support capability commands
                   2791:     @auth_mechs = ();   # default is to not support authentication commands
                   2792:     %fulltextreply = ();#
                   2793:     %commandreply = (); #
                   2794:     %customcount = ();  #
                   2795:     %delayreply = ();   #
                   2796: 
                   2797:     open(CUSTOM, "<log/ftpserver.cmd") ||
                   2798:         return 1;
                   2799: 
                   2800:     logmsg "FTPD: Getting commands from log/ftpserver.cmd\n";
                   2801: 
                   2802:     while(<CUSTOM>) {
                   2803:         if($_ =~ /REPLY \"([A-Z]+ [A-Za-z0-9+-\/=\*. ]+)\" (.*)/) {
                   2804:             $fulltextreply{$1}=eval "qq{$2}";
                   2805:             logmsg "FTPD: set custom reply for $1\n";
                   2806:         }
                   2807:         elsif($_ =~ /REPLY(LF|) ([A-Za-z0-9+\/=\*]*) (.*)/) {
                   2808:             $commandreply{$2}=eval "qq{$3}";
                   2809:             if($1 ne "LF") {
                   2810:                 $commandreply{$2}.="\r\n";
                   2811:             }
                   2812:             else {
                   2813:                 $commandreply{$2}.="\n";
                   2814:             }
                   2815:             if($2 eq "") {
                   2816:                 logmsg "FTPD: set custom reply for empty command\n";
                   2817:             }
                   2818:             else {
                   2819:                 logmsg "FTPD: set custom reply for $2 command\n";
                   2820:             }
                   2821:         }
                   2822:         elsif($_ =~ /COUNT ([A-Z]+) (.*)/) {
                   2823:             # we blank the custom reply for this command when having
                   2824:             # been used this number of times
                   2825:             $customcount{$1}=$2;
                   2826:             logmsg "FTPD: blank custom reply for $1 command after $2 uses\n";
                   2827:         }
                   2828:         elsif($_ =~ /DELAY ([A-Z]+) (\d*)/) {
                   2829:             $delayreply{$1}=$2;
                   2830:             logmsg "FTPD: delay reply for $1 with $2 seconds\n";
                   2831:         }
                   2832:         elsif($_ =~ /SLOWDOWN/) {
                   2833:             $ctrldelay=1;
                   2834:             $datadelay=1;
                   2835:             logmsg "FTPD: send response with 0.01 sec delay between each byte\n";
                   2836:         }
                   2837:         elsif($_ =~ /RETRWEIRDO/) {
                   2838:             logmsg "FTPD: instructed to use RETRWEIRDO\n";
                   2839:             $retrweirdo=1;
                   2840:         }
                   2841:         elsif($_ =~ /RETRNOSIZE/) {
                   2842:             logmsg "FTPD: instructed to use RETRNOSIZE\n";
                   2843:             $retrnosize=1;
                   2844:         }
                   2845:         elsif($_ =~ /PASVBADIP/) {
                   2846:             logmsg "FTPD: instructed to use PASVBADIP\n";
                   2847:             $pasvbadip=1;
                   2848:         }
                   2849:         elsif($_ =~ /NODATACONN425/) {
                   2850:             # applies to both active and passive FTP modes
                   2851:             logmsg "FTPD: instructed to use NODATACONN425\n";
                   2852:             $nodataconn425=1;
                   2853:             $nodataconn=1;
                   2854:         }
                   2855:         elsif($_ =~ /NODATACONN421/) {
                   2856:             # applies to both active and passive FTP modes
                   2857:             logmsg "FTPD: instructed to use NODATACONN421\n";
                   2858:             $nodataconn421=1;
                   2859:             $nodataconn=1;
                   2860:         }
                   2861:         elsif($_ =~ /NODATACONN150/) {
                   2862:             # applies to both active and passive FTP modes
                   2863:             logmsg "FTPD: instructed to use NODATACONN150\n";
                   2864:             $nodataconn150=1;
                   2865:             $nodataconn=1;
                   2866:         }
                   2867:         elsif($_ =~ /NODATACONN/) {
                   2868:             # applies to both active and passive FTP modes
                   2869:             logmsg "FTPD: instructed to use NODATACONN\n";
                   2870:             $nodataconn=1;
                   2871:         }
                   2872:         elsif($_ =~ /CAPA (.*)/) {
                   2873:             logmsg "FTPD: instructed to support CAPABILITY command\n";
                   2874:             @capabilities = split(/ (?!(?:[^" ]|[^"] [^"])+")/, $1);
                   2875:             foreach (@capabilities) {
                   2876:                 $_ = $1 if /^"(.*)"$/;
                   2877:             }
                   2878:         }
                   2879:         elsif($_ =~ /AUTH (.*)/) {
                   2880:             logmsg "FTPD: instructed to support AUTHENTICATION command\n";
                   2881:             @auth_mechs = split(/ /, $1);
                   2882:         }
                   2883:         elsif($_ =~ /NOSAVE/) {
                   2884:             # don't actually store the file we upload - to be used when
                   2885:             # uploading insanely huge amounts
                   2886:             $nosave = 1;
                   2887:             logmsg "FTPD: NOSAVE prevents saving of uploaded data\n";
                   2888:         }
                   2889:     }
                   2890:     close(CUSTOM);
                   2891: }
                   2892: 
                   2893: #----------------------------------------------------------------------
                   2894: #----------------------------------------------------------------------
                   2895: #---------------------------  END OF SUBS  ----------------------------
                   2896: #----------------------------------------------------------------------
                   2897: #----------------------------------------------------------------------
                   2898: 
                   2899: #**********************************************************************
                   2900: # Parse command line options
                   2901: #
                   2902: # Options:
                   2903: #
                   2904: # --verbose   # verbose
                   2905: # --srcdir    # source directory
                   2906: # --id        # server instance number
                   2907: # --proto     # server protocol
                   2908: # --pidfile   # server pid file
                   2909: # --portfile  # server port file
                   2910: # --logfile   # server log file
                   2911: # --ipv4      # server IP version 4
                   2912: # --ipv6      # server IP version 6
                   2913: # --port      # server listener port
                   2914: # --addr      # server address for listener port binding
                   2915: #
                   2916: while(@ARGV) {
                   2917:     if($ARGV[0] eq '--verbose') {
                   2918:         $verbose = 1;
                   2919:     }
                   2920:     elsif($ARGV[0] eq '--srcdir') {
                   2921:         if($ARGV[1]) {
                   2922:             $srcdir = $ARGV[1];
                   2923:             shift @ARGV;
                   2924:         }
                   2925:     }
                   2926:     elsif($ARGV[0] eq '--id') {
                   2927:         if($ARGV[1] && ($ARGV[1] =~ /^(\d+)$/)) {
                   2928:             $idnum = $1 if($1 > 0);
                   2929:             shift @ARGV;
                   2930:         }
                   2931:     }
                   2932:     elsif($ARGV[0] eq '--proto') {
                   2933:         if($ARGV[1] && ($ARGV[1] =~ /^(ftp|imap|pop3|smtp)$/)) {
                   2934:             $proto = $1;
                   2935:             shift @ARGV;
                   2936:         }
                   2937:         else {
                   2938:             die "unsupported protocol $ARGV[1]";
                   2939:         }
                   2940:     }
                   2941:     elsif($ARGV[0] eq '--pidfile') {
                   2942:         if($ARGV[1]) {
                   2943:             $pidfile = $ARGV[1];
                   2944:             shift @ARGV;
                   2945:         }
                   2946:     }
                   2947:     elsif($ARGV[0] eq '--portfile') {
                   2948:         if($ARGV[1]) {
                   2949:             $portfile = $ARGV[1];
                   2950:             shift @ARGV;
                   2951:         }
                   2952:     }
                   2953:     elsif($ARGV[0] eq '--logfile') {
                   2954:         if($ARGV[1]) {
                   2955:             $logfile = $ARGV[1];
                   2956:             shift @ARGV;
                   2957:         }
                   2958:     }
                   2959:     elsif($ARGV[0] eq '--ipv4') {
                   2960:         $ipvnum = 4;
                   2961:         $listenaddr = '127.0.0.1' if($listenaddr eq '::1');
                   2962:     }
                   2963:     elsif($ARGV[0] eq '--ipv6') {
                   2964:         $ipvnum = 6;
                   2965:         $listenaddr = '::1' if($listenaddr eq '127.0.0.1');
                   2966:     }
                   2967:     elsif($ARGV[0] eq '--port') {
                   2968:         if($ARGV[1] =~ /^(\d+)$/) {
                   2969:             $port = $1;
                   2970:             shift @ARGV;
                   2971:         }
                   2972:     }
                   2973:     elsif($ARGV[0] eq '--addr') {
                   2974:         if($ARGV[1]) {
                   2975:             my $tmpstr = $ARGV[1];
                   2976:             if($tmpstr =~ /^(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)$/) {
                   2977:                 $listenaddr = "$1.$2.$3.$4" if($ipvnum == 4);
                   2978:             }
                   2979:             elsif($ipvnum == 6) {
                   2980:                 $listenaddr = $tmpstr;
                   2981:                 $listenaddr =~ s/^\[(.*)\]$/$1/;
                   2982:             }
                   2983:             shift @ARGV;
                   2984:         }
                   2985:     }
                   2986:     else {
                   2987:         print STDERR "\nWarning: ftpserver.pl unknown parameter: $ARGV[0]\n";
                   2988:     }
                   2989:     shift @ARGV;
                   2990: }
                   2991: 
                   2992: #***************************************************************************
                   2993: # Initialize command line option dependent variables
                   2994: #
                   2995: 
                   2996: if(!$srcdir) {
                   2997:     $srcdir = $ENV{'srcdir'} || '.';
                   2998: }
                   2999: if(!$pidfile) {
                   3000:     $pidfile = "$path/". server_pidfilename($proto, $ipvnum, $idnum);
                   3001: }
                   3002: if(!$logfile) {
                   3003:     $logfile = server_logfilename($logdir, $proto, $ipvnum, $idnum);
                   3004: }
                   3005: 
                   3006: $mainsockf_pidfile = "$path/".
                   3007:     mainsockf_pidfilename($proto, $ipvnum, $idnum);
                   3008: $mainsockf_logfile =
                   3009:     mainsockf_logfilename($logdir, $proto, $ipvnum, $idnum);
                   3010: 
                   3011: if($proto eq 'ftp') {
                   3012:     $datasockf_pidfile = "$path/".
                   3013:         datasockf_pidfilename($proto, $ipvnum, $idnum);
                   3014:     $datasockf_logfile =
                   3015:         datasockf_logfilename($logdir, $proto, $ipvnum, $idnum);
                   3016: }
                   3017: 
                   3018: $srvrname = servername_str($proto, $ipvnum, $idnum);
                   3019: 
                   3020: $idstr = "$idnum" if($idnum > 1);
                   3021: 
                   3022: protocolsetup($proto);
                   3023: 
                   3024: $SIG{INT} = \&exit_signal_handler;
                   3025: $SIG{TERM} = \&exit_signal_handler;
                   3026: 
                   3027: startsf();
                   3028: 
                   3029: # actual port
                   3030: if($portfile && !$port) {
                   3031:     my $aport;
                   3032:     open(P, "<$portfile");
                   3033:     $aport = <P>;
                   3034:     close(P);
                   3035:     $port = 0 + $aport;
                   3036: }
                   3037: 
                   3038: logmsg sprintf("%s server listens on port IPv${ipvnum}/${port}\n", uc($proto));
                   3039: 
                   3040: open(PID, ">$pidfile");
                   3041: print PID $$."\n";
                   3042: close(PID);
                   3043: 
                   3044: logmsg("logged pid $$ in $pidfile\n");
                   3045: 
                   3046: while(1) {
                   3047: 
                   3048:     # kill previous data connection sockfilt when alive
                   3049:     if($datasockf_runs eq 'yes') {
                   3050:         killsockfilters($proto, $ipvnum, $idnum, $verbose, 'data');
                   3051:         logmsg "DATA sockfilt for $datasockf_mode data channel killed now\n";
                   3052:     }
                   3053:     datasockf_state('STOPPED');
                   3054: 
                   3055:     #
                   3056:     # We read 'sockfilt' commands.
                   3057:     #
                   3058:     my $input;
                   3059: 
                   3060:     logmsg "Awaiting input\n";
                   3061:     sysread_or_die(\*SFREAD, \$input, 5);
                   3062: 
                   3063:     if($input !~ /^CNCT/) {
                   3064:         # we wait for a connected client
                   3065:         logmsg "MAIN sockfilt said: $input";
                   3066:         next;
                   3067:     }
                   3068:     logmsg "====> Client connect\n";
                   3069: 
                   3070:     set_advisor_read_lock($SERVERLOGS_LOCK);
                   3071:     $serverlogslocked = 1;
                   3072: 
                   3073:     # flush data:
                   3074:     $| = 1;
                   3075: 
                   3076:     &customize(); # read test control instructions
                   3077: 
                   3078:     my $welcome = $commandreply{"welcome"};
                   3079:     if(!$welcome) {
                   3080:         $welcome = $displaytext{"welcome"};
                   3081:     }
                   3082:     else {
                   3083:         # clear it after use
                   3084:         $commandreply{"welcome"}="";
                   3085:         if($welcome !~ /\r\n\z/) {
                   3086:             $welcome .= "\r\n";
                   3087:         }
                   3088:     }
                   3089:     sendcontrol $welcome;
                   3090: 
                   3091:     #remove global variables from last connection
                   3092:     if($ftplistparserstate) {
                   3093:       undef $ftplistparserstate;
                   3094:     }
                   3095:     if($ftptargetdir) {
                   3096:       $ftptargetdir = "";
                   3097:     }
                   3098: 
                   3099:     if($verbose) {
                   3100:         print STDERR "OUT: $welcome";
                   3101:     }
                   3102: 
                   3103:     my $full = "";
                   3104: 
                   3105:     while(1) {
                   3106:         my $i;
                   3107: 
                   3108:         # Now we expect to read DATA\n[hex size]\n[prot], where the [prot]
                   3109:         # part only is FTP lingo.
                   3110: 
                   3111:         # COMMAND
                   3112:         sysread_or_die(\*SFREAD, \$i, 5);
                   3113: 
                   3114:         if($i !~ /^DATA/) {
                   3115:             logmsg "MAIN sockfilt said $i";
                   3116:             if($i =~ /^DISC/) {
                   3117:                 # disconnect
                   3118:                 last;
                   3119:             }
                   3120:             next;
                   3121:         }
                   3122: 
                   3123:         # SIZE of data
                   3124:         sysread_or_die(\*SFREAD, \$i, 5);
                   3125: 
                   3126:         my $size = 0;
                   3127:         if($i =~ /^([0-9a-fA-F]{4})\n/) {
                   3128:             $size = hex($1);
                   3129:         }
                   3130: 
                   3131:         # data
                   3132:         read_mainsockf(\$input, $size);
                   3133: 
                   3134:         ftpmsg $input;
                   3135: 
                   3136:         $full .= $input;
                   3137: 
                   3138:         # Loop until command completion
                   3139:         next unless($full =~ /\r\n$/);
                   3140: 
                   3141:         # Remove trailing CRLF.
                   3142:         $full =~ s/[\n\r]+$//;
                   3143: 
                   3144:         my $FTPCMD;
                   3145:         my $FTPARG;
                   3146:         if($proto eq "imap") {
                   3147:             # IMAP is different with its identifier first on the command line
                   3148:             if(($full =~ /^([^ ]+) ([^ ]+) (.*)/) ||
                   3149:                ($full =~ /^([^ ]+) ([^ ]+)/)) {
                   3150:                 $cmdid=$1; # set the global variable
                   3151:                 $FTPCMD=$2;
                   3152:                 $FTPARG=$3;
                   3153:             }
                   3154:             # IMAP authentication cancellation
                   3155:             elsif($full =~ /^\*$/) {
                   3156:                 # Command id has already been set
                   3157:                 $FTPCMD="*";
                   3158:                 $FTPARG="";
                   3159:             }
                   3160:             # IMAP long "commands" are base64 authentication data
                   3161:             elsif($full =~ /^[A-Z0-9+\/]*={0,2}$/i) {
                   3162:                 # Command id has already been set
                   3163:                 $FTPCMD=$full;
                   3164:                 $FTPARG="";
                   3165:             }
                   3166:             else {
                   3167:                 sendcontrol "$full BAD Command\r\n";
                   3168:                 last;
                   3169:             }
                   3170:         }
                   3171:         elsif($full =~ /^([A-Z]{3,4})(\s(.*))?$/i) {
                   3172:             $FTPCMD=$1;
                   3173:             $FTPARG=$3;
                   3174:         }
                   3175:         elsif($proto eq "pop3") {
                   3176:             # POP3 authentication cancellation
                   3177:             if($full =~ /^\*$/) {
                   3178:                 $FTPCMD="*";
                   3179:                 $FTPARG="";
                   3180:             }
                   3181:             # POP3 long "commands" are base64 authentication data
                   3182:             elsif($full =~ /^[A-Z0-9+\/]*={0,2}$/i) {
                   3183:                 $FTPCMD=$full;
                   3184:                 $FTPARG="";
                   3185:             }
                   3186:             else {
                   3187:                 sendcontrol "-ERR Unrecognized command\r\n";
                   3188:                 last;
                   3189:             }
                   3190:         }
                   3191:         elsif($proto eq "smtp") {
                   3192:             # SMTP authentication cancellation
                   3193:             if($full =~ /^\*$/) {
                   3194:                 $FTPCMD="*";
                   3195:                 $FTPARG="";
                   3196:             }
                   3197:             # SMTP long "commands" are base64 authentication data
                   3198:             elsif($full =~ /^[A-Z0-9+\/]{0,512}={0,2}$/i) {
                   3199:                 $FTPCMD=$full;
                   3200:                 $FTPARG="";
                   3201:             }
                   3202:             else {
                   3203:                 sendcontrol "500 Unrecognized command\r\n";
                   3204:                 last;
                   3205:             }
                   3206:         }
                   3207:         else {
                   3208:             sendcontrol "500 Unrecognized command\r\n";
                   3209:             last;
                   3210:         }
                   3211: 
                   3212:         logmsg "< \"$full\"\n";
                   3213: 
                   3214:         if($verbose) {
                   3215:             print STDERR "IN: $full\n";
                   3216:         }
                   3217: 
                   3218:         $full = "";
                   3219: 
                   3220:         my $delay = $delayreply{$FTPCMD};
                   3221:         if($delay) {
                   3222:             # just go sleep this many seconds!
                   3223:             logmsg("Sleep for $delay seconds\n");
                   3224:             my $twentieths = $delay * 20;
                   3225:             while($twentieths--) {
                   3226:                 portable_sleep(0.05) unless($got_exit_signal);
                   3227:             }
                   3228:         }
                   3229: 
                   3230:         my $check = 1; # no response yet
                   3231: 
                   3232:         # See if there is a custom reply for the full text
                   3233:         my $fulltext = $FTPARG ? $FTPCMD . " " . $FTPARG : $FTPCMD;
                   3234:         my $text = $fulltextreply{$fulltext};
                   3235:         if($text && ($text ne "")) {
                   3236:             sendcontrol "$text\r\n";
                   3237:             $check = 0;
                   3238:         }
                   3239:         else {
                   3240:             # See if there is a custom reply for the command
                   3241:             $text = $commandreply{$FTPCMD};
                   3242:             if($text && ($text ne "")) {
                   3243:                 if($customcount{$FTPCMD} && (!--$customcount{$FTPCMD})) {
                   3244:                     # used enough times so blank the custom command reply
                   3245:                     $commandreply{$FTPCMD}="";
                   3246:                 }
                   3247: 
                   3248:                 sendcontrol $text;
                   3249:                 $check = 0;
                   3250:             }
                   3251:             else {
                   3252:                 # See if there is any display text for the command
                   3253:                 $text = $displaytext{$FTPCMD};
                   3254:                 if($text && ($text ne "")) {
                   3255:                     if($proto eq 'imap') {
                   3256:                         sendcontrol "$cmdid $text\r\n";
                   3257:                     }
                   3258:                     else {
                   3259:                         sendcontrol "$text\r\n";
                   3260:                     }
                   3261: 
                   3262:                     $check = 0;
                   3263:                 }
                   3264: 
                   3265:                 # only perform this if we're not faking a reply
                   3266:                 my $func = $commandfunc{uc($FTPCMD)};
                   3267:                 if($func) {
                   3268:                     &$func($FTPARG, $FTPCMD);
                   3269:                     $check = 0;
                   3270:                 }
                   3271:             }
                   3272:         }
                   3273: 
                   3274:         if($check) {
                   3275:             logmsg "$FTPCMD wasn't handled!\n";
                   3276:             if($proto eq 'pop3') {
                   3277:                 sendcontrol "-ERR $FTPCMD is not dealt with!\r\n";
                   3278:             }
                   3279:             elsif($proto eq 'imap') {
                   3280:                 sendcontrol "$cmdid BAD $FTPCMD is not dealt with!\r\n";
                   3281:             }
                   3282:             else {
                   3283:                 sendcontrol "500 $FTPCMD is not dealt with!\r\n";
                   3284:             }
                   3285:         }
                   3286: 
                   3287:     } # while(1)
                   3288:     logmsg "====> Client disconnected\n";
                   3289: 
                   3290:     if($serverlogslocked) {
                   3291:         $serverlogslocked = 0;
                   3292:         clear_advisor_read_lock($SERVERLOGS_LOCK);
                   3293:     }
                   3294: }
                   3295: 
                   3296: killsockfilters($proto, $ipvnum, $idnum, $verbose);
                   3297: unlink($pidfile);
                   3298: if($serverlogslocked) {
                   3299:     $serverlogslocked = 0;
                   3300:     clear_advisor_read_lock($SERVERLOGS_LOCK);
                   3301: }
                   3302: 
                   3303: exit;

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>