ftpserver.pl 89 KB

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