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