Annotation of embedaddon/curl/tests/ftpserver.pl, revision 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>