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