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