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