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