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