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