IdFTPServer.pas 273 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.146 3/23/2005 5:16:56 AM JPMugaas
  18. Should compile.
  19. Rev 1.145 3/14/05 11:28:50 AM RLebeau
  20. Bug fix for CommandSIZE() not checking the FTPFileSystem property.
  21. Updated to reflect changes in TIdReply.NumericCode handling.
  22. Rev 1.144 3/5/2005 3:33:58 PM JPMugaas
  23. Fix for some compiler warnings having to do with TStream.Read being platform
  24. specific. This was fixed by changing the Compressor API to use TIdStreamVCL
  25. instead of TStream. I also made appropriate adjustments to other units for
  26. this.
  27. Rev 1.143 11/22/2004 8:29:20 PM JPMugaas
  28. Fix for a compiler warning.
  29. Rev 1.142 11/22/2004 7:49:36 PM JPMugaas
  30. You now can access help before you are logged in. This is done to conform
  31. to RFC 959.
  32. Rev 1.141 2004.10.27 9:17:48 AM czhower
  33. For TIdStrings
  34. Rev 1.140 10/26/2004 9:40:42 PM JPMugaas
  35. Updated ref.
  36. Rev 1.139 9/15/2004 5:01:00 PM DSiders
  37. Added localization comments.
  38. Rev 1.138 2004.08.13 11:03:22 czhower
  39. Removed unused var.
  40. Rev 1.137 7/29/2004 1:33:10 AM JPMugaas
  41. Reordered AUTH command values for a new property under development. This
  42. should make things more logical.
  43. Rev 1.136 7/18/2004 3:00:42 PM DSiders
  44. Added localization comments.
  45. Rev 1.135 7/15/2004 1:33:00 AM JPMugaas
  46. Bug fix for error 105. I fixed this by changing data channel command
  47. processing. If the command is not ABOR or STAT, the command is put into a
  48. FIFO queue. After the data channel operation is completed, the commands from
  49. the FIFO queue are processed. I have tested FlashFXP 3.0 RC4 and it does
  50. worki as expected. The behavior is also the same as what NcFTPD does with a
  51. NOOP being sent during a data transfer.
  52. This may also help with FTP command pipelining as proposed by:
  53. http://cr.yp.to/ftp/pipelining.html
  54. Note that we can not use the regular command handler framework for data
  55. channel commands because STAT and ABOR need to be handled IMMEDIATELY.
  56. Rev 1.134 7/13/04 9:08:10 PM RLebeau
  57. Renamed OnPASV event to OnPASVBeforeBind and added new OnPASVReply event
  58. Rev 1.133 7/13/04 8:13:56 PM RLebeau
  59. Various changed for DefaultDataPort handling
  60. Rev 1.132 7/13/2004 3:34:12 AM JPMugaas
  61. CCC command and a few other minor modifications to comply with
  62. http://www.ietf.org/internet-drafts/draft-murray-auth-ftp-ssl-14.txt .
  63. I also fixed a few minor bugs in the help and a problem with some error
  64. replies sending an extra 200 after a 5xxx code messing up some clients.
  65. I also expanded the Security options to selectively disable CCC per user.
  66. Some administrators may want to do this for security reasons.
  67. Rev 1.131 7/12/2004 11:46:44 PM JPMugaas
  68. Improvement in OPTS MODE Z handling. It will give an error if there's only
  69. one param. Params must be in pairs. If no valid parameters are present, we
  70. give an error.
  71. Rev 1.130 07/07/2004 17:34:38 ANeillans
  72. Corrected compile bug.
  73. Line 6026,
  74. if PosInStrArray(IntToStr(LNoVal),STATES,False)>-1 then
  75. Function expected a string, not an integer.
  76. Rev 1.129 7/6/2004 4:52:16 PM DSiders
  77. Corrected spelling of Challenge in properties, methods, types.
  78. Rev 1.128 6/29/2004 4:09:04 PM JPMugaas
  79. OPTS MODE Z now supported as per draft-preston-ftpext-deflate-02.txt. This
  80. should keep FTP Voyager 11 happy.
  81. Rev 1.127 6/28/2004 7:23:20 PM JPMugaas
  82. Bugfix. An invalid site command would cause no reply to be given. Now a
  83. syntax is given in such cases.
  84. Rev 1.126 6/27/2004 1:45:30 AM JPMugaas
  85. Can now optionally support LastAccessTime like Smartftp's FTP Server could.
  86. I also made the MLST listing object and parser support this as well.
  87. Rev 1.125 6/17/2004 3:56:28 PM JPMugaas
  88. Fix for AV that happens after data channel operation.
  89. Rev 1.124 6/16/2004 2:29:32 PM JPMugaas
  90. Removed direct access to a FConnection. We now use the Connection property
  91. in the TIdContext.
  92. Rev 1.123 6/12/2004 9:05:52 AM JPMugaas
  93. Telnet control sequences should now work during a data transfer.
  94. Removed HandleTelnetSequences. It was part of a crude workaround which had
  95. never works and the matter was fixed in another way.
  96. OnCustomDir should now work if the DirStyle is custom.
  97. Rev 1.122 6/11/2004 9:35:12 AM DSiders
  98. Added "Do not Localize" comments.
  99. Rev 1.121 2004.05.20 11:37:26 AM czhower
  100. IdStreamVCL
  101. Rev 1.120 5/16/04 5:30:26 PM RLebeau
  102. Added setter methods to the ReplyUnknownSITECommand and SITECommands
  103. properties
  104. Added GetRepliesClass() overrides
  105. Rev 1.119 5/1/2004 1:52:20 PM JPMugaas
  106. Updated for PeekBytes API change.
  107. Rev 1.118 4/8/2004 12:19:08 PM JPMugaas
  108. Should work with new code.
  109. Rev 1.117 3/3/2004 6:34:46 PM JPMugaas
  110. Improved help system.
  111. Some manditory (RFC 1123 were rutning syntax errors instead of not
  112. implemented.
  113. Add some mention of some other RFC 2228 commands for completness. Not that
  114. there are not supported or implemented.
  115. Rev 1.116 3/3/2004 6:02:14 AM JPMugaas
  116. Command descriptions.
  117. Rev 1.115 3/2/2004 8:13:28 AM JPMugaas
  118. Fixup for minor API change.
  119. Rev 1.113 3/1/2004 12:41:40 PM JPMugaas
  120. Should compile with new code.
  121. Rev 1.112 2/29/2004 6:02:38 PM JPMugaas
  122. Improved bug fix for problem with Telnet sequences not being handled properly
  123. in the FTP server. Litteral CR and LF are now handled properly (according to
  124. the Telnet Specification).
  125. Rev 1.111 2/25/2004 3:27:04 PM JPMugaas
  126. STAT -l now works like a LIST command except that it returns output on the
  127. control channel. This is for consistancy with microsoft FTP Service,
  128. RaidenFTPD, and a few other servers. FlashFXP can take advantage of this
  129. feature as well to gain some efficiency. Note that I do not do not advocate
  130. doing this on the FTP client because some servers will act differently than
  131. you would assume. I may see about possible options for using STAT -l but I
  132. can NOT promise anything.
  133. Rev 1.110 2/17/2004 6:37:28 PM JPMugaas
  134. OnPASV event added for people needing to change the IP address or port value
  135. in commands such as PASV. This should only be done if you have a compelling
  136. reason to do it.
  137. Note that the IP address parameter can NOT work with EPSV and SPSV because
  138. only the port number is returned. The IP address is presumed to be the same
  139. one that the host is connecting to.
  140. Rev 1.109 2/17/2004 12:26:06 PM JPMugaas
  141. The client now supports MODE Z (deflate) uploads and downloads as specified
  142. by http://www.ietf.org/internet-drafts/draft-preston-ftpext-deflate-00.txt
  143. Rev 1.108 2/15/2004 12:11:04 AM JPMugaas
  144. SPSV support. SPSV is an old propoal to help FTP support IPv6. This was
  145. mentioned at: http://cr.yp.to/ftp/retr.html and is supported by PureFTPD.
  146. Rev 1.107 2/14/2004 10:00:40 PM JPMugaas
  147. Both upload and download should now work in MODE Z. Dir already worked
  148. properly.
  149. Rev 1.106 2/12/2004 11:34:38 PM JPMugaas
  150. FTP Deflate preliminary support. Work still needs to be done for upload and
  151. downloading.
  152. Rev 1.105 2004.02.08 3:08:10 PM czhower
  153. .Net fix.
  154. Rev 1.104 2004.02.07 5:03:10 PM czhower
  155. .net fixes.
  156. Rev 1.103 2004.02.03 5:45:54 PM czhower
  157. Name changes
  158. Rev 1.102 1/29/2004 3:15:52 PM JPMugaas
  159. Fix for P@SW in InitCommandHandlers used "PASV" isntead of "P@SW". Fixed.
  160. Rev 1.101 1/22/2004 8:29:06 AM JPMugaas
  161. Removed Ansi*.
  162. Rev 1.100 1/21/2004 2:34:38 PM JPMugaas
  163. Fixed SITE ZONE reply.
  164. InitComponent
  165. Rev 1.99 1/19/2004 4:37:02 AM JPMugaas
  166. MinutesFromGMT was moved to IdFTPCommon because the client now uses it.
  167. Rev 1.98 1/18/2004 9:19:08 AM JPMugaas
  168. P@SW now supported.
  169. This is necessary as some routers that replace a PASV with a P@SW
  170. as part of a misguided attempt to add a feature.
  171. A router would do a replacement so a client would think that
  172. PASV wasn't supported and then the client would do a PORT command
  173. instead. That doesn't happen so this just caused the client not to work.
  174. See: http://www.gbnetwork.co.uk/smcftpd/
  175. Rev 1.97 1/17/2004 7:40:08 PM JPMugaas
  176. MLSD added to FEAT list for consistancy with other FTP servers.
  177. Fixed bug that would cause FXP transfers to fail when receiving a PASV.
  178. Rev 1.96 1/16/2004 12:25:06 AM JPMugaas
  179. Fixes for MTDM set modified time.
  180. Rev 1.94 1/15/2004 2:36:50 AM JPMugaas
  181. XMD5 command support.
  182. SITE ZONE command added for FTP Voyager.
  183. Minor adjustment in AUTH line in the FEAT response to indicate that we
  184. support the AUTH TLS, AUTH TLS-C, AUTH SSL, and AUTH TLS-P explicit TLS
  185. commands.
  186. Rev 1.93 1/14/2004 4:11:30 PM JPMugaas
  187. CPSV support added. This is like PASV but indicates that we use ssl_connect
  188. instead of ssl_accept. CPSV is used in FlashFXP for secure site-to-site file
  189. transfers.
  190. Rev 1.92 1/14/2004 12:24:06 PM JPMugaas
  191. SSCN Support for secure Site to Site Transfers using SSL.
  192. SSCN is defined at:
  193. http://www.raidenftpd.com/kb/kb000000037.htm
  194. Rev 1.91 1/13/2004 6:30:38 AM JPMugaas
  195. Numerous bug fixes.
  196. Now supports XCWD (a predicessor to CWD).
  197. Command Reply for unknown command works again.
  198. Started putting some formatting into common routines.
  199. CuteFTP goes bonkers with a "215 " reply to SYST command. Now indicate that
  200. SYST isn't implemented instead of giving that "215 ". Note that a
  201. "CustomSystID" should be provided when DirFormat is ftpdfCustom.
  202. If DirFormat is ftpdfCustom and OnListDirectory is provided; MLST, MLSD, and
  203. OPTS MLSD will be DISABLED. OnListDirectory is used in the custom format for
  204. structed standardized output with the MLSD and MLST commands.
  205. A not implemented is now given for some commands.
  206. Rev 1.90 1/5/2004 11:53:00 PM JPMugaas
  207. Some messages moved to resource strings. Minor tweeks. EIdException no
  208. longer raised.
  209. Rev 1.88 1/4/2004 3:51:32 PM JPMugaas
  210. Fixed a CWD bug. The parameter was being ignored.
  211. Rev 1.87 1/3/2004 8:05:18 PM JPMugaas
  212. Bug fix: Sometimes, replies will appear twice due to the way functionality
  213. was enherited.
  214. Rev 1.86 1/3/2004 5:37:56 PM JPMugaas
  215. Changes from Bas:
  216. added function GetReplyClass, this function returns the class of reply this
  217. server class uses, this is because in dotnet there can be no code before the
  218. inherited in the constructor ( that is used mow to determine the reply class )
  219.  
  220. changed System.Delete to IdDelete (in coreglobal) because System.Delete is
  221. not in dotnet
  222.  
  223. SplitLines is not enabled in dotnet yet, so i made it a todo, make sure to
  224. enable it and remove the todo if you check it in 
  225.  
  226. Rev 1.85 1/2/2004 1:02:08 AM JPMugaas
  227. Made comment about why the SYST descriptor is determined the way it is.
  228. Rev 1.84 1/2/2004 12:55:32 AM JPMugaas
  229. Now compiles. Removed the EmulateSystem property. Replaced one part with
  230. the DirFormat property.
  231. Rev 1.83 1/1/2004 10:55:10 PM JPMugaas
  232. Remy Lebeau found a bug with path processing in the FTP server. I was
  233. passing an emptry Result string instead of APath in FTPNormalize.
  234. Rev 1.77 10/11/2003 10:17:28 AM JPMugaas
  235. Checked in a more recent version which should be worked on instead.
  236. Rev 1.75 9/19/2003 12:50:18 PM JPMugaas
  237. Started attempt to get the server to compile.
  238. Rev 1.74 9/18/2003 10:20:06 AM JPMugaas
  239. Updated for new API.
  240. Rev 1.73 8/24/2003 06:50:02 PM JPMugaas
  241. API Change in the FileSystem component so that a thread is passed instead of
  242. some data from the thread. This should also make the API's easier to manage
  243. than before and provide more flexibility for developers writing their own
  244. file system components.
  245. Rev 1.72 7/13/2003 7:56:00 PM SPerry
  246. fixed problem with commandhandlers
  247. Rev 1.69 6/17/2003 09:30:20 PM JPMugaas
  248. Fixed an AV with the ALLO command if no parameters were passed. Stated in
  249. HELP command that we don't support some old FTP E-Mail commands from RFC 765
  250. which have not been in use for many years. We now give a reply saying those
  251. aren't implemented to be consistant with some Unix FTP deamons.
  252. Rev 1.68 6/17/2003 03:16:36 PM JPMugaas
  253. I redid the help and site help implementations so that they list commands.
  254. It did mean loosing the FHelpText TIdStrings property but this should be more
  255. consistant with common practices.
  256. Rev 1.67 6/17/2003 09:07:40 AM JPMugaas
  257. Improved SITE HELP handling.
  258. Rev 1.65 5/26/2003 12:22:50 PM JPMugaas
  259. Rev 1.64 5/25/2003 03:54:28 AM JPMugaas
  260. Rev 1.63 5/21/2003 3:59:32 PM BGooijen
  261. removed with in InitializeCommandHandlers, and changed exception replies
  262. Rev 1.62 5/21/2003 09:29:40 AM JPMugaas
  263. Rev 1.61 5/19/2003 08:11:44 PM JPMugaas
  264. Now should compile properly with new code in Core.
  265. Rev 1.60 4/10/2003 02:54:14 PM JPMugaas
  266. Improvement for FTP STOU command. Unique filename now uses
  267. IdGlobal.GetUniqueFileName instead of Rand. I also fixed GetUniqueFileName
  268. so that it can accept an empty path specification.
  269. Rev 1.59 3/30/2003 12:18:38 AM BGooijen
  270. bug fix + ssl one data channel fixed
  271. Rev 1.58 3/24/2003 11:08:42 PM BGooijen
  272. 'transfer'-commands now block, until the transfer is done/aborted.
  273. this made it possible to send the reply after the transfer in the
  274. control-thread
  275. Rev 1.57 3/16/2003 06:11:18 PM JPMugaas
  276. Server now derrives from a TLS framework.
  277. Rev 1.56 3/14/2003 11:33:46 PM JPMugaas
  278. Rev 1.55 3/14/2003 10:44:38 PM BGooijen
  279. Removed warnings, changed StartSSL to PassThrough:=false;
  280. Rev 1.54 3/14/2003 10:00:24 PM BGooijen
  281. Removed TIdServerIOHandlerSSLBase.PeerPassthrough, the ssl is now enabled in
  282. the server-protocol-files
  283. Rev 1.53 3/13/2003 05:21:18 PM JPMugaas
  284. Bas's bug fix. There was a wrong typecast.
  285. Rev 1.52 3/13/2003 8:57:30 PM BGooijen
  286. changed TIdSSLIOHandlerSocketBase to TIdIOHandlerSocket in
  287. TIdDataChannelContext.SetupDataChannel
  288. Rev 1.51 3/13/2003 09:49:06 AM JPMugaas
  289. Now uses an abstract SSL base class instead of OpenSSL so 3rd-party vendors
  290. can plug-in their products.
  291. Rev 1.50 3/13/2003 06:11:54 AM JPMugaas
  292. Updated with Bas's change.
  293. Rev 1.49 3/10/2003 09:12:46 PM JPMugaas
  294. Most command handlers now use Do methods for consistancy with other Indy code.
  295. Rev 1.48 3/10/2003 05:09:22 PM JPMugaas
  296. MLST now works as expected with the file system. Note that the MLST means
  297. simply to give information about an item instead of its contents.
  298. GetRealFileName in IdFTPFileSystem now can accept the wildcard *.
  299. When doing dirs in EPLF, only information about a directory is retruned if it
  300. is specified.
  301. Rev 1.47 3/9/2003 02:11:34 PM JPMugaas
  302. Removed server support for MODE B and MODE C. It turns out that we do not
  303. support those modes properly. We only implemented Stream mode. We now
  304. simply return a 504 for modes we don't support instead of a 200 okay. This
  305. was throwing off Opera 7.02.
  306. Rev 1.46 3/6/2003 11:00:12 AM JPMugaas
  307. Now handles the MFMT command and the MFCT (Modified Date fact) command.
  308. Rev 1.45 3/6/2003 08:26:28 AM JPMugaas
  309. Bug fixes.
  310. FTP COMB command can now work in the FTPFileSystem component.
  311. Rev 1.44 3/5/2003 03:28:16 PM JPMugaas
  312. MD5, MMD5, and XCRC are now supported in the Virtual File System.
  313. Rev 1.43 3/5/2003 11:46:38 AM JPMugaas
  314. Rename now works in Virtual FileSystem.
  315. Rev 1.42 3/2/2003 04:54:34 PM JPMugaas
  316. Now does recursive dir lists with the Virtual File System layer as well as
  317. honors other switches.
  318. Rev 1.41 3/2/2003 02:18:32 PM JPMugaas
  319. Bug fix for where a reply was not returned when using a file system component.
  320. Rev 1.40 3/2/2003 02:23:38 AM JPMugaas
  321. fix for problem with pathes in the virtual file system.
  322. Rev 1.39 2/24/2003 08:50:44 PM JPMugaas
  323. Rev 1.38 2/24/2003 07:56:22 PM JPMugaas
  324. Now uses /bin/ls strings.
  325. Rev 1.37 2/24/2003 07:21:10 AM JPMugaas
  326. FTP Server now strips out any -R switches when emulating EPLF servers.
  327. Recursive lists aren't supported with EPLF.
  328. Rev 1.36 2/21/2003 06:54:10 PM JPMugaas
  329. The FTP list processing has been restructured so that Directory output is not
  330. done by IdFTPList. This now also uses the IdFTPListParserBase for parsing so
  331. that the code is more scalable.
  332. Rev 1.35 2/15/2003 10:29:42 AM JPMugaas
  333. Added support for some Unix specific facts with MLSD and MLST.
  334. Rev 1.34 2/14/2003 05:42:08 PM JPMugaas
  335. Moved everything from IdFTPUtils to IdFTPCommon at Kudzu's suggestion.
  336. Rev 1.33 2/14/2003 11:57:48 AM JPMugaas
  337. Updated for new API. Made sure that there were no calls to a function we
  338. removed.
  339. Rev 1.32 2/14/2003 10:45:18 AM JPMugaas
  340. Updated for minor API change.
  341. Rev 1.30 2/13/2003 01:28:08 AM JPMugaas
  342. MLSD and MLST should now work better.
  343. Rev 1.29 2/12/2003 12:30:56 PM JPMugaas
  344. Now honors parameters with the NLIST command.
  345. Rev 1.28 2/5/2003 10:30:04 PM BGooijen
  346. Re-enabled ssl-support
  347. Rev 1.27 2/4/2003 05:31:40 PM JPMugaas
  348. Added ASwitches parameter to the ListEvent so we can pass parameters such as
  349. "-R" in addition to the standard path.
  350. Rev 1.26 2/3/2003 11:01:50 AM JPMugaas
  351. Moved list export to IdFTPList.
  352. Rev 1.25 1/31/2003 01:59:18 PM JPMugaas
  353. Security options are now reenabled.
  354. Rev 1.24 1/31/2003 01:19:00 PM JPMugaas
  355. Now passes the ControlConnection context instead of the ControlConnection
  356. object itself.
  357. Rev 1.23 1/31/2003 06:34:52 AM JPMugaas
  358. Now SYST command works as expected.
  359. Rev 1.22 1/31/2003 04:23:24 AM JPMugaas
  360. FTP Server security options can be set for individual users and the server
  361. will now use the Context's security options. THis should permit more
  362. flexibility in security.
  363. Rev 1.21 1/30/2003 03:31:06 AM JPMugaas
  364. Now should also properly handle exceptions in the MLSx commands.
  365. Rev 1.20 1/30/2003 02:55:26 AM JPMugaas
  366. Now properly handles exceptions in the ListEvent for the STAT and LIST
  367. commands.
  368. Rev 1.19 1/29/2003 01:17:18 AM JPMugaas
  369. Exception handling should mostly work as it should. There's still a problem
  370. with the list.
  371. Rev 1.18 1/28/2003 02:27:26 AM JPMugaas
  372. Improved exception handling in several events to try to be more consistant.
  373. Now can optionally hide the exception message when giving an error reply to
  374. the user. This should prevent some inadvertant information about a computer
  375. going to a troublemaker.
  376. Rev 1.17 1/27/2003 05:03:16 AM JPMugaas
  377. Now a developer can provide status information to a user with the STAT
  378. command if they want. We format the reply in a standard manner for them.
  379. They just provide the information.
  380. Rev 1.16 1/27/2003 02:13:30 AM JPMugaas
  381. Added more security options as suggested by:
  382. http://www.sans.org/rr/infowar/fingerprint.php to help slow down an attack.
  383. You can optionally disable both SYST and the STAT commands. Trouble makers
  384. can use those to help determine server type and then use known flaws to
  385. compromise it. Note that these do not completely prevent attacks and should
  386. not lull administrators into a false sense of security.
  387. Rev 1.15 1/27/2003 12:32:08 AM JPMugaas
  388. Now can optionally return the identifier for the real operating system. By
  389. default, this property is false for security reasons.
  390. Rev 1.14 1/26/2003 11:59:16 PM JPMugaas
  391. SystemDescriptor behavior change as well as SYST command change.
  392. SystemDescriptor no longer needs an OS type as the first word. That is now
  393. handled by the SYST commandhandler to better comply with RFC 959.
  394. Rev 1.13 1/25/2003 02:00:58 AM JPMugaas
  395. MMD5 (for multiple MD5 checksums) is now supported.
  396. Refined MD5 command support slgihtly.
  397. This is based on:
  398. http://www.ietf.org/internet-drafts/draft-twine-ftpmd5-00.txt
  399. Rev 1.12 1/24/2003 6:07:24 PM BGooijen
  400. Changed TIdDataChannelThread to TIdDataChannelContext
  401. Rev 1.11 1/23/2003 9:06:26 PM BGooijen
  402. changed the CommandAbor
  403. Rev 1.10 1/23/2003 10:39:38 AM BGooijen
  404. TIdDataChannelContext.FServer was never assigned
  405. Rev 1.9 1/20/2003 1:15:40 PM BGooijen
  406. Changed to TIdTCPServer / TIdCmdTCPServer classes
  407. Rev 1.8 1/17/2003 06:21:02 PM JPMugaas
  408. Now works with new design.
  409. Rev 1.7 1/17/2003 05:28:42 PM JPMugaas
  410. Rev 1.6 1-9-2003 14:45:30 BGooijen
  411. Added ABOR command with telnet escape characters
  412. Fixed hanging of ABOR command
  413. STOR and STOU now use REST-position
  414. ABOR now returns 226 instead of 200
  415. Rev 1.5 1-9-2003 14:35:52 BGooijen
  416. changed TIdFTPServerContext(ASender.Context.Thread) to
  417. TIdFTPServerContext(ASender.Context) on some places
  418. Rev 1.4 1/9/2003 06:08:10 AM JPMugaas
  419. Updated to be based on IdContext.
  420. Rev 1.3 1-1-2003 20:13:06 BGooijen
  421. Changed to support the new TIdContext class
  422. Rev 1.2 12-15-2002 21:15:46 BGooijen
  423. IFDEF-ed all SSL code, the IFDEF-s are removed as soon as the SSL works again.
  424. Rev 1.1 11/14/2002 02:55:58 PM JPMugaas
  425. FEAT and MLST now completely use the RFC Reply objects instead of
  426. Connection.WriteLn. The Connection.WriteLn was a workaround for a deficit in
  427. the original RFC Reply object. The workaround is no longer needed.
  428. }
  429. unit IdFTPServer;
  430. {
  431. Original Author: Sergio Perry
  432. Date: 04/21/2001
  433. Fixes and modifications: Doychin Bondzhev
  434. Date: 08/10/2001
  435. Further Extensive changes by Chad Z. Hower (Kudzu)
  436. EPSV/EPRT support for IPv6 by Johannes Berg
  437. TODO:
  438. both EPSV and EPRT only allow data connections that have the same
  439. protocol as the control connection, because the ftp server could be
  440. used in a network only supporting one of them
  441. TODO:
  442. Change events to use DoXXXX
  443. }
  444. interface
  445. {$i IdCompilerDefines.inc}
  446. uses
  447. Classes,
  448. IdAssignedNumbers, IdCommandHandlers, IdGlobal, IdContext, IdException,
  449. IdExplicitTLSClientServerBase, IdFTPBaseFileSystem, IdFTPCommon,
  450. IdBaseComponent, IdFTPList, IdFTPListOutput, IdFTPServerContextBase,
  451. IdReply, IdReplyFTP, IdReplyRFC, IdServerIOHandler,
  452. IdTCPConnection, IdCmdTCPServer, IdThread, IdUserAccounts,
  453. IdYarn, IdZLibCompressorBase, SysUtils;
  454. type
  455. TIdFTPDirFormat = (ftpdfDOS, ftpdfUnix, ftpdfEPLF, ftpdfCustom, ftpdfOSDependent);
  456. TIdFTPPathProcessing = (ftppDOS, ftppUnix, ftpOSDependent, ftppCustom);
  457. TIdFTPOperation = (ftpRetr, ftpStor);
  458. TIdMLSDAttr = (mlsdUniqueID,
  459. mlsdPerms,
  460. mlsdUnixModes,
  461. mlsdUnixOwner,
  462. mlsdUnixGroup,
  463. mlsdFileCreationTime,
  464. mlsdFileLastAccessTime,
  465. mlsdWin32Attributes,
  466. mlsdWin32DriveType,
  467. mlstWin32DriveLabel);
  468. TIdMLSDAttrs = set of TIdMLSDAttr;
  469. const
  470. DEF_DIRFORMAT = ftpdfUnix; //ftpdfOSDependent;
  471. Id_DEF_AllowAnon = False;
  472. Id_DEF_PassStrictCheck = True;
  473. DEF_FTP_IMPLICIT_FTP = False;
  474. DEF_FTP_HIDE_INVALID_USER = True;
  475. DEF_FTP_PASSWORDATTEMPTS = 3;
  476. DEF_FTP_INVALIDPASS_DELAY = 3000; //3 seconds
  477. DEF_FTP_PASV_SAME_IP = True;
  478. DEF_FTP_PORT_SAME_IP = True;
  479. DEF_FTP_NO_RESERVED_PORTS = True;
  480. DEF_FTP_BLOCK_ALL_PORTS = False;
  481. DEF_FTP_DISABLE_SYST = False;
  482. DEF_FTP_DISABLE_STAT = False;
  483. DEF_FTP_PERMIT_CCC = False;
  484. DEF_FTP_REPORT_EX_MSG = False;
  485. DEF_PASV_BOUND_MIN = 0;
  486. DEF_PASV_BOUND_MAX = 0;
  487. DEF_PATHPROCESSING = ftpOSDependent;
  488. {Do not change these as it could break some clients}
  489. SYST_ID_UNIX = 'UNIX Type: L8'; {Do not translate}
  490. SYST_ID_NT = 'Windows_NT'; {Do not translate}
  491. const AAlwaysValidOpts : array [0..2] of string =
  492. ('SIZE', 'TYPE', 'MODIFY'); {Do not translate}
  493. type
  494. TIdFTPServerContext = class;
  495. //The final parameter could've been one item but I decided against that
  496. //because occaisionally, you might have a situation where you need to specify
  497. //the "type" fact to be several different things.
  498. //
  499. //http://www.ietf.org/internet-drafts/draft-ietf-ftpext-mlst-16.txt
  500. TIdOnMLST = procedure(ASender : TIdFTPServerContext; const APath: TIdFTPFileName;
  501. ADirectoryListing: TIdFTPListOutput) of object;
  502. //data port binding events
  503. TOnDataPortBind = procedure(ASender : TIdFTPServerContext) of object;
  504. //note that the CHMOD value is now a VAR because we also want to support a "MFF UNIX.mode="
  505. //to do the same thing as a chmod. MFF is to "Modify a file fact".
  506. TOnSetATTRIB = procedure(ASender: TIdFTPServerContext; var VAttr : UInt32; const AFileName : TIdFTPFileName; var VAUth : Boolean) of object;
  507. //Note that VAuth : Boolean is used because you may want to deny permission for
  508. //users to change their Unix permissions or UMASK - which is done in anonymous FTP
  509. TOnSiteUMASK = procedure(ASender: TIdFTPServerContext; var VUMASK : Integer; var VAUth : Boolean) of object;
  510. //note that the CHMOD value is now a VAR because we also want to support a "MFF UNIX.mode="
  511. //to do the same thing as a chmod. MFF is to "Modify a file fact".
  512. TOnSiteCHMOD = procedure(ASender: TIdFTPServerContext; var APermissions : Integer; const AFileName : TIdFTPFileName; var VAUth : Boolean) of object;
  513. //chown as an option can specify group
  514. TOnSiteCHOWN = procedure(ASender: TIdFTPServerContext; var AOwner, AGroup : String; const AFileName : TIdFTPFileName; var VAUth : Boolean) of object;
  515. TOnSiteCHGRP = procedure(ASender: TIdFTPServerContext; var AGroup : String; const AFileName : TIdFTPFileName; var VAUth : Boolean) of object;
  516. TOnCustomPathProcess = procedure(ASender: TIdFTPServerContext; var VPath : TIdFTPFileName) of object;
  517. //
  518. TOnFTPUserLoginEvent = procedure(ASender: TIdFTPServerContext; const AUsername, APassword: string;
  519. var AAuthenticated: Boolean) of object;
  520. TOnFTPUserAccountEvent = procedure(ASender : TIdFTPServerContext; const AUsername, APassword,AAcount: string; var AAuthenticated: Boolean) of object;
  521. TOnAfterUserLoginEvent = procedure(ASender: TIdFTPServerContext) of object;
  522. TOnDirectoryEvent = procedure(ASender: TIdFTPServerContext; var VDirectory: TIdFTPFileName) of object;
  523. TOnGetFileSizeEvent = procedure(ASender: TIdFTPServerContext; const AFilename: TIdFTPFileName;
  524. var VFileSize: Int64) of object;
  525. TOnGetFileDateEvent = procedure(ASender: TIdFTPServerContext; const AFilename: TIdFTPFileName;
  526. var VFileDate: TDateTime) of object;
  527. //note we have to use a switches parameter because LIST in practice can have both a path and some
  528. //some switches such as -R for recursive.
  529. TOnListDirectoryEvent = procedure(ASender: TIdFTPServerContext; const APath: TIdFTPFileName;
  530. ADirectoryListing: TIdFTPListOutput; const ACmd : String; const ASwitches : String) of object;
  531. TOnCustomListDirectoryEvent = procedure(ASender: TIdFTPServerContext; const APath: TIdFTPFileName;
  532. ADirectoryListing: TStrings; const ACmd : String; const ASwitches : String) of object;
  533. TOnFileEvent = procedure(ASender: TIdFTPServerContext; const APathName: TIdFTPFileName) of object;
  534. TOnCheckFileEvent = procedure(ASender: TIdFTPServerContext; const APathName: TIdFTPFileName; var VExist : Boolean) of object;
  535. TOnRenameFileEvent = procedure(ASender: TIdFTPServerContext; const ARenameFromFile, ARenameToFile: TIdFTPFileName) of object;
  536. TOnRetrieveFileEvent = procedure(ASender: TIdFTPServerContext; const AFileName: TIdFTPFileName;
  537. var VStream: TStream) of object;
  538. TOnStoreFileEvent = procedure(ASender: TIdFTPServerContext; const AFileName: TIdFTPFileName;
  539. AAppend: Boolean; var VStream: TStream) of object;
  540. TOnCombineFiles = procedure(ASender: TIdFTPServerContext; const ATargetFileName: TIdFTPFileName;
  541. AParts : TStrings) of object;
  542. TOnCheckSumFile = procedure(ASender: TIdFTPServerContext; const AFileName : TIdFTPFileName; var VStream : TStream) of object;
  543. TOnCacheChecksum = procedure(ASender: TIdFTPServerContext; const AFileName : TIdFTPFileName; var VCheckSum : String) of object;
  544. TOnVerifyChecksum = procedure(ASender: TIdFTPServerContext; const AFileName : TIdFTPFileName; const ACheckSum : String) of object;
  545. TOnSetFileDateEvent = procedure(ASender: TIdFTPServerContext; const AFileName : TIdFTPFileName; var AFileTime : TDateTime) of object;
  546. TOnHostCheck = procedure(ASender:TIdFTPServerContext; const AHost : String; var VAccepted : Boolean) of object;
  547. //This is just to be efficient with the SITE UTIME command and for setting the windows.lastaccesstime fact
  548. TOnSiteUTIME = procedure(ASender: TIdFTPServerContext; const AFileName : TIdFTPFileName;
  549. var VLastAccessTime, VLastModTime, VCreateDate : TDateTime;
  550. var VAUth : Boolean) of object;
  551. EIdFTPServerException = class(EIdException);
  552. EIdFTPServerNoOnListDirectory = class(EIdFTPServerException);
  553. EIdFTPImplicitTLSRequiresSSL = class(EIdFTPServerException);
  554. EIdFTPBoundPortMaxGreater = class(EIdFTPServerException);
  555. EIdFTPBoundPortMinLess = class(EIdFTPServerException);
  556. EIdFTPCannotBeNegative = class(EIdFTPServerException);
  557. //we don't parse CLNT parameters as they might be freeform for all we know
  558. TIdOnClientID = procedure(ASender: TIdFTPServerContext; const AID : String) of object;
  559. TIdOnClientIDEx = procedure(ASender: TIdFTPServerContext; AClientInfo : TIdFTPClientIdentifier) of object;
  560. TIdOnFTPStatEvent = procedure(ASender: TIdFTPServerContext; AStatusInfo : TStrings) of object;
  561. TIdOnBanner = procedure(ASender: TIdFTPServerContext; AGreeting : TIdReply) of object;
  562. //This is for EPSV and PASV support - do not change the values unless you
  563. //have an extremely compelling reason to do so. This even is ONLY for those compelling case.
  564. TIdOnPASV = procedure(ASender: TIdFTPServerContext; var VIP : String;
  565. var VPort : TIdPort; const AIPVer : TIdIPVersion) of object;
  566. TIdOnPASVRange = procedure(ASender: TIdFTPServerContext; var VIP : String;
  567. var VPortMin, VPortMax : TIdPort; const AIPVer : TIdIPVersion) of object;
  568. TIdOnDirSizeInfo = procedure(ASender : TIdFTPServerContext;
  569. const APathName : TIdFTPFileName;
  570. var VIsAFile : Boolean; var VSpace : Int64) of object;
  571. TIdFTPServer = class;
  572. TIdFTPSecurityOptions = class(TPersistent)
  573. protected
  574. // RFC 2577 Recommends these
  575. // Note that the current code already hides user ID's by
  576. // only authenticating after the password
  577. FPasswordAttempts : UInt32;
  578. FInvalidPassDelay : UInt32;
  579. // http://cr.yp.to/ftp/security.html Recommends these
  580. FRequirePASVFromSameIP : Boolean;
  581. FRequirePORTFromSameIP : Boolean;
  582. FNoReservedRangePORT : Boolean;
  583. FBlockAllPORTTransfers : Boolean;
  584. FDisableSYSTCommand : Boolean;
  585. FDisableSTATCommand : Boolean;
  586. FPermitCCC : Boolean;
  587. public
  588. constructor Create; virtual;
  589. procedure Assign(Source: TPersistent); override;
  590. published
  591. //limit login attempts - some hackers will try guessing passwords from a dictionary
  592. property PasswordAttempts : UInt32 read FPasswordAttempts write FPasswordAttempts
  593. default DEF_FTP_PASSWORDATTEMPTS;
  594. //should slow-down a password guessing attack - note those dictionaries
  595. property InvalidPassDelay : UInt32 read FInvalidPassDelay write FInvalidPassDelay
  596. default DEF_FTP_INVALIDPASS_DELAY;
  597. //client IP Address is the only one that we will accept a PASV
  598. //transfer from
  599. //http://cr.yp.to/ftp/security.html
  600. property RequirePASVFromSameIP : Boolean read FRequirePASVFromSameIP write FRequirePASVFromSameIP
  601. default DEF_FTP_PASV_SAME_IP;
  602. //Accept port transfers from the same IP address as the client -
  603. //should prevent bounce attacks
  604. property RequirePORTFromSameIP : Boolean read FRequirePORTFromSameIP write FRequirePORTFromSameIP
  605. default DEF_FTP_PORT_SAME_IP;
  606. //Do not accept port requests to ports in the reserved range. That is dangerous on some systems
  607. property NoReservedRangePORT : Boolean read FNoReservedRangePORT write FNoReservedRangePORT
  608. default DEF_FTP_NO_RESERVED_PORTS;
  609. //Do not accept any PORT transfers at all. This is a little extreme but reduces troubles further.
  610. //This will break the the Win32 console clients and a number of other programs.
  611. property BlockAllPORTTransfers : Boolean read FBlockAllPORTTransfers write FBlockAllPORTTransfers
  612. default DEF_FTP_BLOCK_ALL_PORTS;
  613. //Disable SYST command. SYST usually gives the system description.
  614. //Disabling it may make it harder for a trouble maker to know about your computer
  615. //but will not be a complete security solution. See http://www.sans.org/rr/infowar/fingerprint.php for details
  616. //On the other hand, disabling it will break RFC 959 complience and may break some FTP programs.
  617. property DisableSYSTCommand : Boolean read FDisableSYSTCommand write FDisableSYSTCommand
  618. default DEF_FTP_DISABLE_SYST;
  619. //Disable STAT command. STAT gives freeform information about the connection status.
  620. // http://www.sans.org/rr/infowar/fingerprint.php advises administrators to disable this
  621. //because servers tend to give distinct patterns of information and some trouble makers
  622. //can figure out what type of server you are running simply with this.
  623. property DisableSTATCommand : Boolean read FDisableSTATCommand write FDisableSTATCommand
  624. default DEF_FTP_DISABLE_STAT;
  625. //Permit CCC (Clear Command Connection) in TLS FTP
  626. property PermitCCC : Boolean read FPermitCCC write FPermitCCC default DEF_FTP_PERMIT_CCC;
  627. end;
  628. TIdDataChannel = class(TObject)
  629. protected
  630. FNegotiateTLS : Boolean;
  631. FControlContext: TIdFTPServerContext;
  632. FDataChannel: TIdTCPConnection;
  633. FErrorReply: TIdReplyRFC;
  634. FFtpOperation: TIdFTPOperation;
  635. FOKReply: TIdReplyRFC;
  636. FReply: TIdReplyRFC;
  637. FServer : TIdFTPServer;
  638. FRequirePASVFromSameIP : Boolean;
  639. FStopped : Boolean;
  640. FData : TObject;
  641. procedure SetErrorReply(const AValue: TIdReplyRFC);
  642. procedure SetOKReply(const AValue: TIdReplyRFC);
  643. function GetPeerIP: String;
  644. function GetPeerPort: TIdPort;
  645. function GetLocalIP: String;
  646. function GetLocalPort: TIdPort;
  647. public
  648. constructor Create(APASV: Boolean; AControlContext: TIdFTPServerContext; const ARequirePASVFromSameIP : Boolean; AServer : TIdFTPServer); reintroduce;
  649. destructor Destroy; override;
  650. procedure InitOperation(const AConnectMode : Boolean = False);
  651. property PeerIP : String read GetPeerIP;
  652. property PeerPort : TIdPort read GetPeerPort;
  653. property LocalIP : String read GetLocalIP;
  654. property LocalPort : TIdPort read GetLocalPort;
  655. property Stopped : Boolean read FStopped write FStopped;
  656. property Data : TObject read FData write FData;
  657. property Server : TIdFTPServer read FServer;
  658. property OKReply: TIdReplyRFC read FOKReply write SetOKReply;
  659. property ErrorReply: TIdReplyRFC read FErrorReply write SetErrorReply;
  660. end;
  661. TIdFTPServerContext = class(TIdFTPServerContextBase)
  662. protected
  663. FXAUTKey : UInt32;
  664. FRESTPos: Integer;
  665. FDataChannel : TIdDataChannel;
  666. FAuthMechanism : String;
  667. FCCC : Boolean; //flag for CCC issuance
  668. FDataType: TIdFTPTransferType;
  669. FDataMode : TIdFTPTransferMode;
  670. FDataPort: TIdPort;
  671. FDataProtBufSize : UInt32;
  672. FDataStruct: TIdFTPDataStructure;
  673. FPasswordAttempts : UInt32;
  674. FPASV: Boolean;
  675. FEPSVAll: Boolean;
  676. FDataPortDenied : Boolean;
  677. FDataProtection : TIdFTPDataPortSecurity;
  678. FDataPBSZCalled : Boolean;
  679. FMLSOpts : TIdFTPFactOutputs;
  680. FSSCNOn : Boolean;
  681. FServer : TIdFTPServer;
  682. FUserSecurity : TIdFTPSecurityOptions;
  683. FUMask : Integer; //for SITE UMASK command
  684. //only used for Windows NT imitation
  685. FMSDOSMode : Boolean; //False - off imitate Unix, //True - On imitate DOS
  686. //This is a queued request to quite.
  687. //if it's issued during a data transfer, we treat it as quit
  688. //only after the request is completed.
  689. FQuitReply : String;
  690. //ZLib settings
  691. FZLibCompressionLevel : Integer; //7
  692. FZLibWindowBits : Integer; //-15
  693. FZLibMemLevel : Integer; //8
  694. FZLibStratagy : Integer; //0 - default
  695. //
  696. procedure ResetZLibSettings;
  697. procedure PortOnAfterBind(ASender : TObject);
  698. procedure PortOnBeforeBind(ASender : TObject);
  699. procedure SetUserSecurity(const Value: TIdFTPSecurityOptions);
  700. procedure CreateDataChannel(APASV: Boolean = False);
  701. function IsAuthenticated(ASender: TIdCommand): Boolean;
  702. procedure ReInitialize; override;
  703. public
  704. constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
  705. destructor Destroy; override;
  706. procedure KillDataChannel;
  707. property DataChannel : TIdDataChannel read FDataChannel;
  708. property Server : TIdFTPServer read FServer write FServer;
  709. property UserSecurity : TIdFTPSecurityOptions read FUserSecurity write SetUserSecurity;
  710. //
  711. //This is for tracking what AUTH mechanism was specified and that
  712. //we support. This may not matter as much now, but it could later on
  713. //RFC 2228
  714. property AuthMechanism : String read FAuthMechanism write FAuthMechanism;
  715. property DataType: TIdFTPTransferType read FDataType write FDataType;
  716. property DataMode : TIdFTPTransferMode read FDataMode write FDataMode;
  717. property DataPort: TIdPort read FDataPort;
  718. //We do not use this much for now but if more AUTH mechanisms are added,
  719. //we may need this property
  720. property DataProtBufSize : UInt32 read FDataProtBufSize write FDataProtBufSize;
  721. property DataPBSZCalled : Boolean read FDataPBSZCalled write FDataPBSZCalled;
  722. property DataStruct: TIdFTPDataStructure read FDataStruct write FDataStruct;
  723. //currently, only <C>lear and <P>rivate are used. This could change
  724. //later on
  725. property DataProtection : TIdFTPDataPortSecurity read FDataProtection write FDataProtection;
  726. property PasswordAttempts : UInt32 read FPasswordAttempts write FPasswordAttempts;
  727. property PASV: Boolean read FPASV write FPASV;
  728. property RESTPos: Integer read FRESTPos write FRESTPos;
  729. property MLSOpts : TIdFTPFactOutputs read FMLSOpts write FMLSOpts;
  730. //SSCN secure FTPX - http://www.raidenftpd.com/kb/kb000000037.htm
  731. property SSCNOn : Boolean read FSSCNOn write FSSCNOn;
  732. //SITE DIRSTYLE flag - true for MSDOS, false for Unix
  733. property MSDOSMode : Boolean read FMSDOSMode write FMSDOSMode;
  734. //SITE UMASK settings
  735. property UMask : Integer read FUMask write FUMask;
  736. //ZLib settings
  737. property ZLibCompressionLevel : Integer read FZLibCompressionLevel write FZLibCompressionLevel; //7
  738. property ZLibWindowBits : Integer read FZLibWindowBits write FZLibWindowBits; //-15
  739. property ZLibMemLevel : Integer read FZLibMemLevel write FZLibMemLevel; //8
  740. property ZLibStratagy : Integer read FZLibStratagy write FZLibStratagy; //0 - default
  741. end;
  742. TIdOnGetCustomListFormat = procedure(ASender: TIdFTPServer; AItem: TIdFTPListItem;
  743. var VText: string) of object;
  744. TIdOnQuerySSLPort = procedure(APort: TIdPort; var VUseSSL: Boolean) of object;
  745. { FTP Server }
  746. TIdFTPServer = class(TIdExplicitTLSServer)
  747. protected
  748. FSupportXAUTH: Boolean;
  749. FDirFormat : TIdFTPDirFormat;
  750. FPathProcessing : TIdFTPPathProcessing;
  751. FOnClientID : TIdOnClientID;
  752. FOnClientIDEx : TIdOnClientIDEx;
  753. FDataChannelCommands: TIdCommandHandlers;
  754. FSITECommands: TIdCommandHandlers;
  755. FOPTSCommands: TIdCommandHandlers;
  756. FMLSDFacts : TIdMLSDAttrs;
  757. FAnonymousAccounts: TStrings;
  758. FAllowAnonymousLogin: Boolean;
  759. FAnonymousPassStrictCheck: Boolean;
  760. // FEmulateSystem: TIdFTPSystems;
  761. FPASVBoundPortMin : TIdPort;
  762. FPASVBoundPortMax : TIdPort;
  763. FSystemType: string;
  764. FDefaultDataPort : TIdPort;
  765. {$IF DEFINED(HAS_UNSAFE_OBJECT_REF)}[Unsafe]
  766. {$ELSEIF DEFINED(HAS_WEAK_OBJECT_REF)}[Weak]
  767. {$IFEND} FUserAccounts: TIdCustomUserManager;
  768. FOnUserAccount : TOnFTPUserAccountEvent;
  769. FOnAfterUserLogin: TOnAfterUserLoginEvent;
  770. FOnUserLogin: TOnFTPUserLoginEvent;
  771. FOnChangeDirectory: TOnDirectoryEvent;
  772. FOnGetFileSize: TOnGetFileSizeEvent;
  773. FOnGetFileDate:TOnGetFileDateEvent;
  774. FOnListDirectory: TOnListDirectoryEvent;
  775. FOnCustomListDirectory : TOnCustomListDirectoryEvent;
  776. FOnRenameFile: TOnRenameFileEvent;
  777. FOnDeleteFile: TOnFileEvent;
  778. FOnRetrieveFile: TOnRetrieveFileEvent;
  779. FOnStoreFile: TOnStoreFileEvent;
  780. FOnMakeDirectory: TOnDirectoryEvent;
  781. FOnRemoveDirectory: TOnDirectoryEvent;
  782. FOnStat : TIdOnFTPStatEvent;
  783. FFTPSecurityOptions : TIdFTPSecurityOptions;
  784. FServerInfo : TIdFTPServerIdentifier;
  785. FOnCRCFile : TOnCheckSumFile;
  786. FOnCombineFiles : TOnCombineFiles;
  787. FOnSetModifiedTime : TOnSetFileDateEvent;
  788. FOnFileExistCheck : TOnCheckFileEvent; //for MDTM variation to set the file time
  789. FOnSetCreationTime : TOnSetFileDateEvent;
  790. FOnMD5Cache : TOnCacheChecksum;
  791. FOnMD5Verify : TOnVerifyChecksum;
  792. FOnGreeting : TIdOnBanner;
  793. FOnLoginSuccessBanner : TIdOnBanner;
  794. FOnLoginFailureBanner : TIdOnBanner;
  795. FOnQuitBanner : TIdOnBanner;
  796. FOnSetATTRIB : TOnSetATTRIB;
  797. FOnSiteUMASK : TOnSiteUMASK;
  798. FOnSiteCHMOD : TOnSiteCHMOD;
  799. FOnSiteCHOWN : TOnSiteCHOWN;
  800. FOnSiteCHGRP : TOnSiteCHGRP;
  801. FOnAvailDiskSpace : TIdOnDirSizeInfo;
  802. FOnCompleteDirSize : TIdOnDirSizeInfo;
  803. FOnRemoveDirectoryAll: TOnDirectoryEvent;
  804. FOnCustomPathProcess : TOnCustomPathProcess;
  805. FOnDataPortBeforeBind : TOnDataPortBind;
  806. FOnDataPortAfterBind : TOnDataPortBind;
  807. FOnPASVBeforeBind : TIdOnPASVRange;
  808. FOnPASVReply : TIdOnPASV;
  809. {$IF DEFINED(HAS_UNSAFE_OBJECT_REF)}[Unsafe]
  810. {$ELSEIF DEFINED(HAS_WEAK_OBJECT_REF)}[Weak]
  811. {$IFEND} FFTPFileSystem: TIdFTPBaseFileSystem;
  812. FEndOfHelpLine : String;
  813. FCustomSystID : String;
  814. FReplyUnknownSITECommand : TIdReply;
  815. FCompressor : TIdZLibCompressorBase;
  816. FOnMLST : TIdOnMLST;
  817. FOnSiteUTIME : TOnSiteUTIME;
  818. FOnHostCheck : TOnHostCheck;
  819. FOnQuerySSLPort: TIdOnQuerySSLPort;
  820. procedure SetOnUserAccount(AValue : TOnFTPUserAccountEvent);
  821. procedure AuthenticateUser(ASender: TIdCommand);
  822. function SupportTaDirSwitches(AContext : TIdFTPServerContext) : Boolean;
  823. function IgnoreLastPathDelim(const APath : String) : String;
  824. procedure DoOnPASVBeforeBind(ASender : TIdFTPServerContext; var VIP : String;
  825. var VPortMin, VPortMax : TIdPort; const AIPVersion : TIdIPVersion);
  826. procedure DoOnPASVReply(ASender : TIdFTPServerContext; var VIP : String;
  827. var VPort : TIdPort; const AIPVersion : TIdIPVersion);
  828. function InternalPASV(ASender: TIdCommand; var VIP : String;
  829. var VPort: TIdPort; var VIPVersion : TIdIPVersion): Boolean;
  830. function DoSysType(ASender : TIdFTPServerContext) : String;
  831. function DoProcessPath(ASender : TIdFTPServerContext; const APath: TIdFTPFileName): TIdFTPFileName;
  832. function FTPNormalizePath(const APath: String) : String;
  833. function FTPPathSeparator : Char;
  834. function FTPIsCaseSensitive : Boolean;
  835. function MLSFEATLine(const AFactMask : TIdMLSDAttrs; const AFacts : TIdFTPFactOutputs) : String;
  836. function HelpText(Cmds : TStrings) : String;
  837. function IsValidPermNumbers(const APermNos : String) : Boolean;
  838. procedure SetRFCReplyFormat(AReply : TIdReply);
  839. function CDUPDir(AContext : TIdFTPServerContext) : String;
  840. procedure DisconUser(ASender: TIdCommand);
  841. //command reply common code
  842. procedure CmdNotImplemented(ASender : TIdCommand);
  843. procedure CmdFileActionAborted(ASender : TIdCommand);
  844. procedure CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply : TIdReply = nil); overload;
  845. procedure CmdSyntaxError(ASender : TIdCommand); overload;
  846. procedure CmdInvalidParams(ASender: TIdCommand);
  847. procedure CmdInvalidParamNum(ASender:TIdCommand);
  848. //The http://www.potaroo.net/ietf/idref/draft-twine-ftpmd5/
  849. //draft didn't specify 550 as an error. It said use 504.
  850. procedure CmdTwineFileActionAborted(ASender : TIdCommand);
  851. //success reply codes can vary amoung commands
  852. procedure CmdCommandSuccessful(ASender: TIdCommand; const AReplyCode : Integer = 250);
  853. //Command replies
  854. procedure CommandQUIT(ASender:TIdCommand);
  855. procedure CommandUSER(ASender: TIdCommand);
  856. procedure CommandPASS(ASender: TIdCommand);
  857. procedure CommandACCT(ASender: TIdCommand);
  858. procedure CommandXAUT(ASender : TIdCommand);
  859. procedure CommandCWD(ASender: TIdCommand);
  860. procedure CommandCDUP(ASender: TIdCommand);
  861. procedure CommandREIN(ASender: TIdCommand);
  862. procedure CommandPORT(ASender: TIdCommand);
  863. procedure CommandPASV(ASender: TIdCommand);
  864. procedure CommandTYPE(ASender: TIdCommand);
  865. procedure CommandSTRU(ASender: TIdCommand);
  866. procedure CommandMODE(ASender: TIdCommand);
  867. procedure CommandRETR(ASender: TIdCommand);
  868. procedure CommandSSAP(ASender: TIdCommand);
  869. procedure CommandALLO(ASender: TIdCommand);
  870. procedure CommandREST(ASender: TIdCommand);
  871. procedure CommandRNFR(ASender: TIdCommand);
  872. procedure CommandRNTO(ASender: TIdCommand);
  873. procedure CommandABOR(ASender: TIdCommand);
  874. //AVBL from Streamlined FTP Command Extensions
  875. // draft-peterson-streamlined-ftp-command-extensions-01.txt
  876. procedure CommandAVBL(ASender: TIdCommand);
  877. procedure CommandDELE(ASender: TIdCommand);
  878. //DSIZ from Streamlined FTP Command Extensions
  879. // draft-peterson-streamlined-ftp-command-extensions-01.txt
  880. procedure CommandDSIZ(ASender : TIdCommand);
  881. procedure CommandRMDA(ASender : TIdCommand);
  882. procedure CommandRMD(ASender: TIdCommand);
  883. procedure CommandMKD(ASender: TIdCommand);
  884. procedure CommandPWD(ASender: TIdCommand);
  885. procedure CommandLIST(ASender: TIdCommand);
  886. procedure CommandSYST(ASender: TIdCommand);
  887. procedure CommandSTAT(ASender: TIdCommand);
  888. procedure CommandSIZE(ASender: TIdCommand);
  889. procedure CommandFEAT(ASender: TIdCommand);
  890. procedure CommandOPTS(ASender: TIdCommand);
  891. procedure CommandAUTH(ASender: TIdCommand);
  892. procedure CommandCCC(ASender: TIdCommand);
  893. // rfc 2428:
  894. procedure CommandEPSV(ASender: TIdCommand);
  895. procedure CommandEPRT(ASender: TIdCommand);
  896. //
  897. procedure CommandMDTM(ASender: TIdCommand);
  898. procedure CommandMFF(ASender: TIdCommand);
  899. //
  900. procedure CommandMD5(ASender: TIdCommand);
  901. procedure CommandMMD5(ASender: TIdCommand);
  902. //
  903. procedure CommandPROT(ASender: TIdCommand);
  904. procedure CommandPBSZ(ASender: TIdCommand);
  905. procedure CommandMFMT(ASender: TIdCommand);
  906. procedure CommandMFCT(ASender: TIdCommand);
  907. procedure CommandMLSD(ASender: TIdCommand);
  908. procedure CommandMLST(ASender: TIdCommand);
  909. procedure CommandCheckSum(ASender: TIdCommand);
  910. procedure CommandCOMB(ASender: TIdCommand);
  911. procedure CommandCLNT(ASender: TIdCommand);
  912. procedure CommandCSID(ASender: TIdCommand);
  913. //SSCN Secure FTPX - http://www.raidenftpd.com/kb/kb000000037.htm
  914. procedure CommandSSCN(ASender: TIdCommand);
  915. //Informal - like PASV accept SSL is in client mode - used by FlashXP
  916. procedure CommandCPSV(ASender: TIdCommand);
  917. //Informal - like PASV except that only the port is communicated.
  918. //
  919. procedure CommandSPSV(ASender: TIdCommand);
  920. procedure CommandHOST(ASender : TIdCommand);
  921. procedure CommandSecRFC(ASender : TIdCommand); //stub for some commands in 2228
  922. procedure CommandSITE(ASender: TIdCommand);
  923. procedure CommandSiteHELP(ASender : TIdCommand);
  924. //site commands - Unix
  925. procedure CommandSiteUMASK(ASender : TIdCommand);
  926. procedure CommandSiteCHMOD(ASender : TIdCommand);
  927. //SITE CHOWN - supported by some Unix servers
  928. procedure CommandSiteCHOWN(ASender : TIdCommand);
  929. //SITE CHGRP - supported by some Unix servers
  930. procedure CommandSiteCHGRP(ASender : TIdCommand);
  931. //site commans - MS IIS
  932. procedure CommandSiteDIRSTYLE(ASender : TIdCommand);
  933. //used by FTP Voyager
  934. procedure CommandSiteZONE(ASender : TIdCommand);
  935. //supported by RaidenFTP - http://www.raidenftpd.com/kb/kb000000049.htm
  936. procedure CommandSiteATTRIB(ASender : TIdCommand);
  937. //McFTP client uses this to set the time stamps for a file.
  938. procedure CommandSiteUTIME(ASender : TIdCommand);
  939. // end site commands
  940. procedure CommandOptsMLST(ASender : TIdCommand);
  941. procedure CommandOptsMODEZ(ASender : TIdCommand);
  942. procedure CommandOptsUTF8(ASender: TIdCommand);
  943. procedure CommandHELP(ASender: TIdCommand);
  944. //
  945. procedure DoOnRenameFile(ASender: TIdFTPServerContext; const ARenameFromFile, ARenameToFile: string);
  946. procedure DoOnDeleteFile(ASender: TIdFTPServerContext; const APathName: string);
  947. procedure DoOnChangeDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  948. procedure DoOnMakeDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  949. procedure DoOnRemoveDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  950. procedure DoOnGetFileSize(ASender: TIdFTPServerContext; const AFilename: string; var VFileSize: Int64);
  951. procedure DoOnGetFileDate(ASender: TIdFTPServerContext; const AFilename: string; var VFileDate: TDateTime);
  952. procedure DoOnSetModifiedTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime); overload;
  953. procedure DoOnSetModifiedTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTimeStr : String); overload;
  954. procedure DoOnFileExistCheck(AContext: TIdFTPServerContext; const AFileName : String; var VExist : Boolean);
  955. procedure DoOnSetModifiedTimeGMT(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime);
  956. procedure DoOnSetCreationTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime); overload;
  957. procedure DoOnSetCreationTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTimeStr : String); overload;
  958. procedure DoOnSetCreationTimeGMT(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime);
  959. procedure DoOnCRCFile(ASender: TIdFTPServerContext; const AFileName : String; var VStream : TStream);
  960. procedure DoOnMD5Verify(ASender: TIdFTPServerContext; const AFileName : String; const ACheckSum : String);
  961. procedure DoOnMD5Cache(ASender: TIdFTPServerContext; const AFileName : String; var VCheckSum : String);
  962. procedure DoOnCombineFiles(ASender: TIdFTPServerContext; const ATargetFileName: string; AParts : TStrings);
  963. procedure DoOnSetATTRIB(ASender: TIdFTPServerContext; var VAttr : UInt32; const AFileName : String; var VAUth : Boolean);
  964. procedure DoOnSiteUMASK(ASender: TIdFTPServerContext; var VUMASK : Integer; var VAUth : Boolean);
  965. procedure DoOnSiteCHMOD(ASender: TIdFTPServerContext; var APermissions : Integer; const AFileName : String; var VAUth : Boolean);
  966. procedure DoOnSiteCHOWN(ASender: TIdFTPServerContext; var AOwner, AGroup : String; const AFileName : String; var VAUth : Boolean);
  967. procedure DoOnSiteCHGRP(ASender: TIdFTPServerContext; var AGroup : String; const AFileName : String; var VAUth : Boolean);
  968. procedure SetUseTLS(AValue: TIdUseTLS); override;
  969. procedure SetSupportXAUTH(AValue : Boolean);
  970. procedure InitializeCommandHandlers; override;
  971. procedure ListDirectory(ASender: TIdFTPServerContext; ADirectory: string;
  972. ADirContents: TStrings; ADetails: Boolean; const ACmd : String = 'LIST';
  973. const ASwitches : String = ''); {do not localize}
  974. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  975. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  976. {$ENDIF}
  977. procedure SetAnonymousAccounts(const AValue: TStrings);
  978. procedure SetUserAccounts(const AValue: TIdCustomUserManager);
  979. procedure SetFTPSecurityOptions(const AValue: TIdFTPSecurityOptions);
  980. procedure SetServerInfo(const AValue: TIdFTPServerIdentifier);
  981. procedure SetPASVBoundPortMax(const AValue: TIdPort);
  982. procedure SetPASVBoundPortMin(const AValue: TIdPort);
  983. procedure SetReplyUnknownSITECommand(AValue: TIdReply);
  984. procedure SetSITECommands(AValue: TIdCommandHandlers);
  985. procedure ThreadException(AThread: TIdThread; AException: Exception);
  986. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  987. procedure SetFTPFileSystem(const AValue: TIdFTPBaseFileSystem);
  988. {$ENDIF}
  989. function GetMD5Checksum(ASender : TIdFTPServerContext; const AFileName : String) : String;
  990. //overrides from TIdTCPServer
  991. procedure DoConnect(AContext:TIdContext); override;
  992. procedure DoDisconnect(AContext:TIdContext); override;
  993. procedure ContextCreated(AContext:TIdContext); override;
  994. procedure DoOnDataPortBeforeBind(ASender : TIdFTPServerContext); virtual;
  995. procedure DoDataChannelOperation(ASender: TIdCommand; const AConnectMode : Boolean = False);virtual;
  996. procedure DoOnDataPortAfterBind(ASender : TIdFTPServerContext); virtual;
  997. procedure DoOnCustomListDirectory(ASender: TIdFTPServerContext; const APath: string;
  998. ADirectoryListing: TStrings; const ACmd : String; const ASwitches : String);
  999. function DoQuerySSLPort(APort: TIdPort): Boolean; virtual;
  1000. function GetReplyClass: TIdReplyClass; override;
  1001. function GetRepliesClass: TIdRepliesClass; override;
  1002. procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); override;
  1003. // overriden so we can close active transfers during a shutdown
  1004. procedure DoTerminateContext(AContext: TIdContext); override;
  1005. //overriden so we can handle telnet sequences
  1006. function ReadCommandLine(AContext: TIdContext): string; override;
  1007. function GetCaseSensitive: Boolean;
  1008. procedure SetCaseSensitive(const AValue : Boolean);
  1009. function GetDirSeparator : Char;
  1010. procedure SetDirSeparator(const AValue : Char);
  1011. public
  1012. constructor Create(AOwner: TComponent); override;
  1013. destructor Destroy; override;
  1014. property SupportXAUTH : Boolean read FSupportXAUTH write SetSupportXAUTH;
  1015. published
  1016. {This is an object that can compress and decompress HTTP Deflate encoding}
  1017. property Compressor : TIdZLibCompressorBase read FCompressor write FCompressor;
  1018. property CustomSystID : String read FCustomSystID write FCustomSystID;
  1019. property DirFormat : TIdFTPDirFormat read FDirFormat write FDirFormat default DEF_DIRFORMAT;
  1020. property PathProcessing : TIdFTPPathProcessing read FPathProcessing write FPathProcessing default DEF_PATHPROCESSING;
  1021. {Only used if PathProcessing is ftppCustom }
  1022. property CaseSensitive : Boolean read GetCaseSensitive write SetCaseSensitive default DEF_CASE_SENSITIVE;
  1023. property DirSeparator : Char read GetDirSeparator write SetDirSeparator;
  1024. property UseTLS;
  1025. property DefaultPort default IDPORT_FTP;
  1026. property AllowAnonymousLogin: Boolean read FAllowAnonymousLogin write FAllowAnonymousLogin default Id_DEF_AllowAnon;
  1027. property AnonymousAccounts: TStrings read FAnonymousAccounts write SetAnonymousAccounts;
  1028. property AnonymousPassStrictCheck: Boolean read FAnonymousPassStrictCheck
  1029. write FAnonymousPassStrictCheck default Id_DEF_PassStrictCheck;
  1030. property DefaultDataPort : TIdPort read FDefaultDataPort write FDefaultDataPort default IdPORT_FTP_DATA;
  1031. property FTPFileSystem:TIdFTPBaseFileSystem read FFTPFileSystem write {$IFDEF USE_OBJECT_REF_FREENOTIF}SetFTPFileSystem{$ELSE}FFTPFileSystem{$ENDIF};
  1032. property FTPSecurityOptions : TIdFTPSecurityOptions read FFTPSecurityOptions write SetFTPSecurityOptions;
  1033. property EndOfHelpLine : String read FEndOfHelpLine write FEndOfHelpLine;
  1034. property PASVBoundPortMin : TIdPort read FPASVBoundPortMin write SetPASVBoundPortMin default DEF_PASV_BOUND_MIN;
  1035. property PASVBoundPortMax : TIdPort read FPASVBoundPortMax write SetPASVBoundPortMax default DEF_PASV_BOUND_MAX;
  1036. property UserAccounts: TIdCustomUserManager read FUserAccounts write SetUserAccounts;
  1037. property ServerInfo : TIdFTPServerIdentifier read FServerInfo write SetServerInfo;
  1038. property SystemType: string read FSystemType write FSystemType;
  1039. property OnGreeting : TIdOnBanner read FOnGreeting write FOnGreeting;
  1040. property OnLoginSuccessBanner : TIdOnBanner read FOnLoginSuccessBanner write FOnLoginSuccessBanner;
  1041. property OnLoginFailureBanner : TIdOnBanner read FOnLoginFailureBanner write FOnLoginFailureBanner;
  1042. //for retreiving MD5 Checksums from a cache
  1043. property OnMD5Cache : TOnCacheChecksum read FOnMD5Cache write FOnMD5Cache;
  1044. property OnMD5Verify : TOnVerifyChecksum read FOnMD5Verify write FOnMD5Verify;
  1045. property OnQuitBanner : TIdOnBanner read FOnQuitBanner write FOnQuitBanner;
  1046. property OnCustomListDirectory : TOnCustomListDirectoryEvent read FOnCustomListDirectory write FOnCustomListDirectory;
  1047. property OnCustomPathProcess : TOnCustomPathProcess read FOnCustomPathProcess write FOnCustomPathProcess;
  1048. property OnAfterUserLogin: TOnAfterUserLoginEvent read FOnAfterUserLogin write FOnAfterUserLogin;
  1049. property OnChangeDirectory: TOnDirectoryEvent read FOnChangeDirectory write FOnChangeDirectory;
  1050. property OnGetFileSize: TOnGetFileSizeEvent read FOnGetFileSize write FOnGetFileSize;
  1051. property OnGetFileDate: TOnGetFileDateEvent read FOnGetFileDate write FOnGetFileDate;
  1052. property OnUserLogin: TOnFTPUserLoginEvent read FOnUserLogin write FOnUserLogin;
  1053. property OnUserAccount : TOnFTPUserAccountEvent read FOnUserAccount write SetOnUserAccount;
  1054. property OnListDirectory: TOnListDirectoryEvent read FOnListDirectory write FOnListDirectory;
  1055. property OnDataPortBeforeBind : TOnDataPortBind read FOnDataPortBeforeBind write FOnDataPortBeforeBind;
  1056. property OnDataPortAfterBind : TOnDataPortBind read FOnDataPortAfterBind write FOnDataPortAfterBind;
  1057. property OnRenameFile: TOnRenameFileEvent read FOnRenameFile write FOnRenameFile;
  1058. property OnDeleteFile: TOnFileEvent read FOnDeleteFile write FOnDeleteFile;
  1059. property OnRetrieveFile: TOnRetrieveFileEvent read FOnRetrieveFile write FOnRetrieveFile;
  1060. property OnStoreFile: TOnStoreFileEvent read FOnStoreFile write FOnStoreFile;
  1061. property OnMakeDirectory: TOnDirectoryEvent read FOnMakeDirectory write FOnMakeDirectory;
  1062. property OnRemoveDirectory: TOnDirectoryEvent read FOnRemoveDirectory write FOnRemoveDirectory;
  1063. property OnStat : TIdOnFTPStatEvent read FOnStat write FOnStat;
  1064. property OnCombineFiles : TOnCombineFiles read FOnCombineFiles write FOnCombineFiles;
  1065. property OnCRCFile : TOnCheckSumFile read FOnCRCFile write FOnCRCFile;
  1066. property OnSetCreationTime : TOnSetFileDateEvent read FOnSetCreationTime write FOnSetCreationTime;
  1067. property OnSetModifiedTime : TOnSetFileDateEvent read FOnSetModifiedTime write FOnSetModifiedTime;
  1068. property OnFileExistCheck : TOnCheckFileEvent read FOnFileExistCheck write FOnFileExistCheck;
  1069. property OnHostCheck : TOnHostCheck read FOnHostCheck write FOnHostCheck;
  1070. property OnSetATTRIB : TOnSetATTRIB read FOnSetATTRIB write FOnSetATTRIB;
  1071. property OnSiteUMASK : TOnSiteUMASK read FOnSiteUMASK write FOnSiteUMASK;
  1072. property OnSiteCHMOD : TOnSiteCHMOD read FOnSiteCHMOD write FOnSiteCHMOD;
  1073. property OnSiteCHOWN : TOnSiteCHOWN read FOnSiteCHOWN write FOnSiteCHOWN;
  1074. property OnSiteCHGRP : TOnSiteCHGRP read FOnSiteCHGRP write FOnSiteCHGRP;
  1075. {
  1076. READ THIS!!!
  1077. Do not change values in the OnPASV event unless you have a compelling reason to do so.
  1078. In SPSV, the PORT is the only thing that can work because that's all which is
  1079. given as a reply. The server IP is the same one that the client connects to.
  1080. In EPSV, the PORT is the only thing that can work because that's all which is
  1081. given as a reply. The server IP is the same one that the client connects to.
  1082. }
  1083. property OnPASVBeforeBind : TIdOnPASVRange read FOnPASVBeforeBind write FOnPASVBeforeBind;
  1084. property OnPASVReply : TIdOnPASV read FOnPASVReply write FOnPASVReply;
  1085. property OnMLST : TIdOnMLST read FOnMLST write FOnMLST;
  1086. property OnSiteUTIME : TOnSiteUTIME read FOnSiteUTIME write FOnSiteUTIME;
  1087. property OnAvailDiskSpace : TIdOnDirSizeInfo read FOnAvailDiskSpace write FOnAvailDiskSpace;
  1088. property OnCompleteDirSize : TIdOnDirSizeInfo read FOnCompleteDirSize write FOnCompleteDirSize;
  1089. property SITECommands: TIdCommandHandlers read FSITECommands write SetSITECommands;
  1090. property MLSDFacts : TIdMLSDAttrs read FMLSDFacts write FMLSDFacts;
  1091. property OnClientID : TIdOnClientID read FOnClientID write FOnClientID;
  1092. property OnClientIDEx : TIdOnClientIDEx read FOnClientIDEx write FOnClientIDEx;
  1093. property ReplyUnknownSITCommand: TIdReply read FReplyUnknownSITECommand write SetReplyUnknownSITECommand;
  1094. property OnQuerySSLPort: TIdOnQuerySSLPort read FOnQuerySSLPort write FOnQuerySSLPort;
  1095. end;
  1096. {This is used internally for some Telnet sequence parsing}
  1097. type
  1098. TIdFTPTelnetState = (tsData, tsCheckCR, tsIAC, tsWill, tsDo, tsWont, tsDont,
  1099. tsNegotiate, tsNegotiateData, tsNegotiateIAC, tsInterrupt, tsInterruptIAC);
  1100. implementation
  1101. uses
  1102. {$IFDEF USE_VCL_POSIX}
  1103. Posix.SysSelect,
  1104. Posix.SysTime,
  1105. {$ENDIF}
  1106. IdFIPS,
  1107. IdHash, IdHashCRC, IdHashMessageDigest, IdHashSHA, IdIOHandlerSocket,
  1108. IdResourceStringsProtocols, IdGlobalProtocols, IdSimpleServer, IdSSL,
  1109. IdIOHandlerStack, IdSocketHandle, IdTCPClient, IdEMailAddress,
  1110. IdStack, IdFTPListTypes;
  1111. const
  1112. //THese commands need some special treatment in the Indy 10 FTP Server help system
  1113. //as they will not always work
  1114. HELP_SPEC_CMDS : array [0..25] of string =
  1115. ('SIZE','MDTM', {do not localize}
  1116. 'AUTH','PBSZ','PROT','CCC','MIC','CONF','ENC', 'SSCN','CPSV', {do not localize}
  1117. 'MFMT','MFF',
  1118. 'MD5','MMD5','XCRC','XMD5','XSHA1','XSHA256','XSHA512', {do not localize}
  1119. 'COMB','AVBL','DSIZ','RMDA','HOST','XAUT'); {do not localize}
  1120. //These commands must always be present even if not implemented
  1121. //alt help topics and superscripts should be used sometimes.
  1122. //These are mandated by RFC 1123
  1123. HELP_ALT_MD_CMD : array [0..17] of string =
  1124. ('RETR', {do not localize}
  1125. 'STOR','STOU', {do not localize}
  1126. 'APPE', {do not localize}
  1127. 'RNFR', 'RNTO', {do not localize}
  1128. 'DELE', {do not localize}
  1129. 'LIST','NLST', {do not localize}
  1130. 'CWD','XCWD', {do not localize}
  1131. 'CDUP','XCUP', {do not localize}
  1132. 'RMD','XRMD', {do not localize}
  1133. 'MKD', 'XMKD', {do not localize}
  1134. 'SYST'); {do not localize}
  1135. HELP_ALT_MD_TP : array [0..17] of string =
  1136. ('RETR (retrieve); unimplemented.', {do not localize}
  1137. 'STOR (store); unimplemented.', {do not localize}
  1138. 'STOU (store unique); unimplemented.', {do not localize}
  1139. 'APPE (append); unimplemented.', {do not localize}
  1140. 'RNFR (rename from); unimplemented.', {do not localize}
  1141. 'RNTO (rename to); unimplemented.', {do not localize}
  1142. 'DELE (delete); unimplemented.', {do not localize}
  1143. 'LIST (list); unimplemented.', {do not localize}
  1144. 'NLIST (name-list); unimplemented.', {do not localize}
  1145. 'CWD (change working directory); unimplemented.', {do not localize}
  1146. 'XCWD (change working directory); unimplemented.', {do not localize}
  1147. 'CDUP (change to parent directory); unimplemented.', {do not localize}
  1148. 'XCDUP (change to parent directory); unimplemented.', {do not localize}
  1149. 'RMD (remove Directory); unimplemented.', {do not localize}
  1150. 'XRMD (remove Directory); unimplemented.', {do not localize}
  1151. 'MKD (make Directory); unimplemented.', {do not localize}
  1152. 'XMKD (make Directory); unimplemented.', {do not localize}
  1153. 'SYST (system); unimplemented.' {do not localize}
  1154. );
  1155. //SSCN, OPTS MODE Z EXTRA, and OPTS UTF8 states
  1156. OnOffStates : array [0..1] of string =
  1157. ('ON', {do not localize}
  1158. 'OFF' {do not localize}
  1159. );
  1160. const
  1161. //%s = host
  1162. //%n = xauth key
  1163. XAUTHBANNER = '%s X2 WS_FTP Server Compatible(%d)';
  1164. ACCT_HELP_DISABLED = 'ACCT (specify account); unimplemented.'; {do not localize}
  1165. ACCT_HELP_ENABLED = 'Syntax: ACCT <SP> <account-information> <CRLF>';
  1166. const
  1167. NLSTEncType: array[Boolean] of IdTextEncodingType = (encASCII, encUTF8);
  1168. function CalculateCheckSum(AHashClass: TIdHashClass; AStrm: TStream; ABeginPos, AEndPos: Int64): String;
  1169. {$IFDEF USE_INLINE} inline; {$ENDIF}
  1170. var
  1171. LHash: TIdHash;
  1172. begin
  1173. LHash := AHashClass.Create;
  1174. try
  1175. Result := LHash.HashStreamAsHex(AStrm, ABeginPos, AEndPos-ABeginPos);
  1176. finally
  1177. LHash.Free;
  1178. end;
  1179. end;
  1180. procedure XAutGreeting(AContext: TIdContext; AGreeting : TIdReply; const AHostName : String);
  1181. {$IFDEF USE_INLINE} inline; {$ENDIF}
  1182. var
  1183. s : String;
  1184. begin
  1185. //for XAUT to work with WS-FTP Pro, you need a banner mentioning "WS_FTP Server"
  1186. //and that banner can only be one line in length.
  1187. s := IndyFormat(XAUTHBANNER,
  1188. [ GStack.HostName, (AContext as TIdFTPServerContext).FXAUTKey]) + ' '+AGreeting.Text.Text;
  1189. s := Fetch(s,CR);
  1190. s := Fetch(s,LF);
  1191. AGreeting.Text.Text := s;
  1192. end;
  1193. { TIdFTPServer }
  1194. constructor TIdFTPServerContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn;
  1195. AList: TIdContextThreadList = nil);
  1196. begin
  1197. inherited Create(AConnection, AYarn, AList);
  1198. FUserSecurity := TIdFTPSecurityOptions.Create;
  1199. //we don't initialize FCCC flag here because that shouldn't be cleared with implicit SSL
  1200. FCCC := False;
  1201. FDataMode := dmStream;
  1202. FMLSOpts := [ItemType, Modify, Size];
  1203. //no write permissions for group and others
  1204. FUMask := 22;
  1205. ResetZLibSettings;
  1206. ReInitialize;
  1207. end;
  1208. procedure TIdFTPServerContext.SetUserSecurity(const Value: TIdFTPSecurityOptions);
  1209. begin
  1210. FUserSecurity.Assign( Value);
  1211. end;
  1212. destructor TIdFTPServerContext.Destroy;
  1213. begin
  1214. KillDataChannel;
  1215. FUserSecurity.Free;
  1216. inherited Destroy;
  1217. end;
  1218. procedure TIdFTPServerContext.CreateDataChannel(APASV: Boolean = False);
  1219. begin
  1220. KillDataChannel; //let the old one terminate
  1221. FDataChannel := TIdDataChannel.Create(APASV, Self, UserSecurity.RequirePASVFromSameIP, Server);
  1222. end;
  1223. procedure TIdFTPServerContext.KillDataChannel;
  1224. begin
  1225. if Assigned(FDataChannel) then begin
  1226. if not FDataChannel.Stopped then begin
  1227. FDataChannel.Stopped := True;
  1228. FDataChannel.FDataChannel.Disconnect(False);
  1229. // TODO: use FDataChannel.FDataChannel.Binding.CloseSocket() instead?
  1230. end;
  1231. FreeAndNil(FDataChannel);
  1232. end;
  1233. end;
  1234. procedure TIdFTPServerContext.ReInitialize;
  1235. begin
  1236. inherited;
  1237. FDataType := ftASCII;
  1238. // FDataMode := dmStream;
  1239. FDataPort := 0;
  1240. FDataStruct := dsFile;
  1241. FPASV := False;
  1242. FEPSVAll := False;
  1243. FDataProtection := ftpdpsClear;
  1244. DataPBSZCalled := False;
  1245. FDataProtBufSize := 0;
  1246. end;
  1247. function TIdFTPServerContext.IsAuthenticated(ASender: TIdCommand): Boolean;
  1248. begin
  1249. Result := FAuthenticated;
  1250. if not Result then begin
  1251. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  1252. end;
  1253. end;
  1254. { TIdFTPServer }
  1255. constructor TIdFTPServer.Create(AOwner: TComponent);
  1256. begin
  1257. inherited Create(AOwner);
  1258. HelpReply.Code := ''; //we will handle the help ourselves
  1259. FDataChannelCommands := TIdCommandHandlers.Create(Self, FReplyClass, ReplyTexts, ExceptionReply);
  1260. FSITECommands := TIdCommandHandlers.Create(Self, FReplyClass, ReplyTexts, ExceptionReply);
  1261. FOPTSCommands := TIdCommandHandlers.Create(Self, FReplyClass, ReplyTexts, ExceptionReply);
  1262. //inherited from TLS classes
  1263. FRegularProtPort := IdPORT_FTP;
  1264. FImplicitTLSProtPort := IdPORT_ftps;
  1265. FExplicitTLSProtPort := IdPORT_FTP;
  1266. //
  1267. FAnonymousAccounts := TStringList.Create;
  1268. // By default these user names will be treated as anonymous.
  1269. FAnonymousAccounts.Add('anonymous'); { do not localize }
  1270. FAnonymousAccounts.Add('ftp'); { do not localize }
  1271. FAnonymousAccounts.Add('guest'); { do not localize }
  1272. FAllowAnonymousLogin := Id_DEF_AllowAnon;
  1273. FAnonymousPassStrictCheck := Id_DEF_PassStrictCheck;
  1274. DefaultPort := IDPORT_FTP;
  1275. DefaultDataPort := IdPORT_FTP_DATA;
  1276. // FEmulateSystem := Id_DEF_SystemType;
  1277. Greeting.SetReply(220, RSFTPDefaultGreeting);
  1278. FContextClass := TIdFTPServerContext;
  1279. ReplyUnknownCommand.SetReply(500, 'Unknown Command'); {do not localize}
  1280. FReplyUnknownSITECommand := FReplyClass.Create(nil);
  1281. FReplyUnknownSITECommand.SetReply(500, 'Invalid SITE command.'); {do not localize}
  1282. FFTPSecurityOptions := TIdFTPSecurityOptions.Create;
  1283. FServerInfo := TIdFTPServerIdentifier.Create;
  1284. FPASVBoundPortMin := DEF_PASV_BOUND_MIN;
  1285. FPASVBoundPortMax := DEF_PASV_BOUND_MAX;
  1286. FPathProcessing := DEF_PATHPROCESSING;
  1287. FServerInfo.CaseSensitive := DEF_CASE_SENSITIVE;
  1288. FServerInfo.DirSeparator := DEF_DIRSEPARATOR;
  1289. FDirFormat := DEF_DIRFORMAT;
  1290. end;
  1291. destructor TIdFTPServer.Destroy;
  1292. begin
  1293. FAnonymousAccounts.Free;
  1294. FFTPSecurityOptions.Free;
  1295. FServerInfo.Free;
  1296. FOPTSCommands.Free;
  1297. FDataChannelCommands.Free;
  1298. FSITECommands.Free;
  1299. FReplyUnknownSITECommand.Free;
  1300. inherited Destroy;
  1301. end;
  1302. function TIdFTPServer.GetReplyClass: TIdReplyClass;
  1303. begin
  1304. Result := TIdReplyFTP;
  1305. end;
  1306. function TIdFTPServer.GetRepliesClass: TIdRepliesClass;
  1307. begin
  1308. Result := TIdRepliesFTP;
  1309. end;
  1310. procedure TIdFTPServer.CommandHELP(ASender: TIdCommand);
  1311. var
  1312. s : String;
  1313. LCmds : TStringList;
  1314. i : Integer;
  1315. LExp : String;
  1316. function ShouldShowCommand(const ACommand : String) : Boolean;
  1317. begin
  1318. Result := False;
  1319. case PosInStrArray(ACommand, HELP_SPEC_CMDS, False) of
  1320. -1 :
  1321. Result := True;
  1322. 0 : //'SIZE'
  1323. if Assigned(FOnGetFileSize) then begin
  1324. Result := True;
  1325. end;
  1326. 1 :// 'MDTM',
  1327. if Assigned(FOnGetFileDate) or Assigned(FTPFileSystem) then begin
  1328. Result := True;
  1329. end;
  1330. 2 : // 'AUTH'
  1331. if (FUseTLS in ExplicitTLSVals) then begin
  1332. Result := True;
  1333. end;
  1334. 3,4,5,6,7,8,9,10 : //'PBSZ','PROT', 'CCC','MIC','CONF','ENC','SSCN','CPSV',
  1335. if (FUseTLS <> utNoTLSSupport) then begin
  1336. Result := True;
  1337. end;
  1338. 11,12 : // 'MFMT','MFF',
  1339. if Assigned(FOnSetModifiedTime) or Assigned(FTPFileSystem) then begin
  1340. Result := True;
  1341. end;
  1342. 13,14, 15,16 : //'MD5','MMD5','XCRC','XMD5',
  1343. begin
  1344. Result := False;
  1345. if not GetFIPSMode then begin
  1346. if Assigned(FOnCRCFile) or Assigned(FTPFileSystem) then begin
  1347. Result := True;
  1348. end;
  1349. end;
  1350. end;
  1351. 17 : // 'XSHA1',
  1352. if Assigned(FOnCRCFile) or Assigned(FTPFileSystem) then begin
  1353. Result := True;
  1354. end;
  1355. 18 : //'XSHA256'
  1356. if (Assigned(FOnCRCFile) or Assigned(FTPFileSystem))
  1357. and TIdHashSHA256.IsAvailable then begin
  1358. Result := True;
  1359. end;
  1360. 19 : //'XSHA512'
  1361. if (Assigned(FOnCRCFile) or Assigned(FTPFileSystem)) and
  1362. TIdHashSHA512.IsAvailable then begin
  1363. Result := True;
  1364. end;
  1365. 20 : // 'COMB');
  1366. if Assigned(OnCombineFiles) or Assigned(FTPFileSystem) then begin
  1367. Result := True;
  1368. end;
  1369. 21 : // AVBL
  1370. if Assigned(FOnAvailDiskSpace) then begin
  1371. Result := True;
  1372. end;
  1373. 22 : // DSIZ
  1374. if Assigned(FOnCompleteDirSize) then begin
  1375. Result := True;
  1376. end;
  1377. 23 : // RMDA
  1378. if Assigned(FOnRemoveDirectoryAll) then begin
  1379. Result := True;
  1380. end;
  1381. 24 : // HOST
  1382. if Assigned( FOnHostCheck ) then begin
  1383. Result := True;
  1384. end;
  1385. 25 : // XAUT
  1386. if (not GetFIPSMode) and Self.FSupportXAUTH then begin
  1387. Result := True;
  1388. end;
  1389. end;
  1390. end;
  1391. function IsNotImplemented(const ACommand : String; var VHelp : String) : Boolean;
  1392. var
  1393. idx : Integer;
  1394. begin
  1395. Result := False; //presume that the command is implemented
  1396. idx := PosInStrArray(ACommand, HELP_ALT_MD_CMD, False);
  1397. if idx = -1 then begin
  1398. Exit;
  1399. end;
  1400. case idx of
  1401. 0 : // 'RETR'
  1402. begin
  1403. if (not Assigned(FOnRetrieveFile)) and (not Assigned(FFTPFileSystem)) then begin
  1404. Result := True;
  1405. end;
  1406. end;
  1407. 1,2,3 : //'STOR','STOU', 'APPE',
  1408. begin
  1409. if (not Assigned(FOnStoreFile)) and (not Assigned(FFTPFileSystem)) then begin
  1410. Result := True;
  1411. end;
  1412. end;
  1413. 4,5 : // 'RNFR', 'RNTO',
  1414. begin
  1415. if (not Assigned(FOnRenameFile)) and (not Assigned(FFTPFileSystem)) then begin
  1416. Result := True;
  1417. end;
  1418. end;
  1419. 6 : // 'DELE',
  1420. begin
  1421. if (not Assigned(FOnDeleteFile)) and (not Assigned(FFTPFileSystem)) then begin
  1422. Result := True;
  1423. end;
  1424. end;
  1425. 7,8 :// 'LIST','NLST',
  1426. begin
  1427. if (not Assigned(FOnListDirectory)) or
  1428. ((FDirFormat = ftpdfCustom) and (not Assigned(OnCustomListDirectory))) then begin
  1429. Result := True;
  1430. end;
  1431. end;
  1432. 9, 10, //'CWD','XCWD',
  1433. 11, 12 : // 'CDUP','XCUP',
  1434. begin
  1435. if (not Assigned(FOnChangeDirectory)) and (not Assigned(FFTPFileSystem)) then begin
  1436. Result := True;
  1437. end;
  1438. end;
  1439. 13, 14 : //'RMD','XRMD',
  1440. begin
  1441. if (not Assigned(FOnRemoveDirectory)) and (not Assigned(FFTPFileSystem)) then begin
  1442. Result := True;
  1443. end;
  1444. end;
  1445. 15,16 : //'MKD', 'XMKD',
  1446. begin
  1447. if (not Assigned(FOnMakeDirectory)) and (not Assigned(FFTPFileSystem)) then begin
  1448. Result := True;
  1449. end;
  1450. end;
  1451. 17 :// 'SYST',
  1452. begin
  1453. if (not Assigned(FOnMakeDirectory)) and (not Assigned(FFTPFileSystem)) then begin
  1454. Result := True;
  1455. end;
  1456. end;
  1457. end;
  1458. if Result then begin
  1459. LExp := HELP_ALT_MD_TP[idx];
  1460. end;
  1461. end;
  1462. begin
  1463. if ASender.Params.Count > 0 then begin
  1464. for i := 0 to CommandHandlers.Count-1 do begin
  1465. if TextIsSame(ASender.Params[0], CommandHandlers.Items[i].Command) then begin
  1466. if CommandHandlers.Items[i].HelpVisible and ShouldShowCommand(ASender.Params[0]) then begin
  1467. if IsNotImplemented(CommandHandlers.Items[i].Command, LExp) then begin
  1468. ASender.Reply.SetReply(214, LExp);
  1469. end else begin
  1470. ASender.Reply.SetReply(214, CommandHandlers.Items[i].Description.Text);
  1471. end;
  1472. end else begin
  1473. ASender.Reply.SetReply(502, IndyFormat(RSFTPCmdHelpNotKnown, [UpperCase(ASender.Params[0])]));
  1474. end;
  1475. Exit;
  1476. end;
  1477. end;
  1478. ASender.Reply.SetReply(502, IndyFormat(RSFTPCmdHelpNotKnown, [UpperCase(ASender.Params[0])]));
  1479. end else begin
  1480. s := RSFTPHelpBegining + EOL;
  1481. LCmds := TStringList.Create;
  1482. try
  1483. //
  1484. for i := 0 to CommandHandlers.Count -1 do begin
  1485. if CommandHandlers.Items[i].HelpVisible and ShouldShowCommand(CommandHandlers.Items[i].Command) then begin
  1486. if IsNotImplemented(CommandHandlers.Items[i].Command, LExp) then begin
  1487. LCmds.Add(CommandHandlers.Items[i].Command + '*'); {do not localize}
  1488. end else begin
  1489. LCmds.Add(CommandHandlers.Items[i].Command + CommandHandlers.Items[i].HelpSuperScript);
  1490. end;
  1491. end;
  1492. end;
  1493. LCmds.Sort;
  1494. s := s + HelpText(LCmds) + FEndOfHelpLine;
  1495. if FEndOfHelpLine = '' then begin
  1496. s := s + EOL; //prevent ugliness if last row out of alignment with the rest
  1497. end;
  1498. ASender.Reply.SetReply(214, s);
  1499. finally
  1500. LCmds.Free;
  1501. end;
  1502. end;
  1503. end;
  1504. procedure TIdFTPServer.CommandHOST(ASender: TIdCommand);
  1505. var LTmp : String;
  1506. LValid : Boolean;
  1507. LContext : TIdFTPServerContext;
  1508. begin
  1509. LContext := TIdFTPServerContext(ASender.Context);
  1510. if Assigned(OnHostCheck) then begin
  1511. if LContext.Username <> '' then begin
  1512. ASender.Reply.SetReply(530, RSFTPNotAfterAuthentication );
  1513. Exit;
  1514. end;
  1515. if (ASender.Params.Count > 0) then begin
  1516. LTmp := ASender.Params[0];
  1517. if Copy(LTmp,1,1)='[' then begin
  1518. Delete(LTmp,1,1);
  1519. end;
  1520. LTmp := Fetch(LTmp,']');
  1521. LValid := False;
  1522. FOnHostCheck(LContext,LTmp,LValid);
  1523. if LValid then begin
  1524. LContext.Host := LTmp;
  1525. if Assigned(OnGreeting) then begin
  1526. OnGreeting(LContext,ASender.Reply);
  1527. end;
  1528. if ASender.Reply.NumericCode = 421 then begin
  1529. ASender.Disconnect := True;
  1530. end else begin
  1531. if not GetFIPSMode then begin
  1532. //setting the reply code number directly causes the text to be cleared
  1533. if FSupportXAUTH and (ASender.Reply.NumericCode = 220) then begin
  1534. XAutGreeting(LContext,ASender.Reply, LTmp);
  1535. end;
  1536. end;
  1537. ASender.Reply.SetReply(220,ASender.Reply.Text.Text);
  1538. end;
  1539. ASender.SendReply;
  1540. end else begin
  1541. ASender.Reply.SetReply(530,RSFTPHostNotFound);
  1542. end;
  1543. end;
  1544. end else begin
  1545. CmdSyntaxError(ASender);
  1546. end;
  1547. end;
  1548. procedure TIdFTPServer.InitializeCommandHandlers;
  1549. var
  1550. LCmd : TIdCommandHandler;
  1551. begin
  1552. inherited InitializeCommandHandlers;
  1553. //ACCESS CONTROL COMMANDS
  1554. //USER <SP> <username> <CRLF>
  1555. LCmd := CommandHandlers.Add;
  1556. LCmd.Command := 'USER'; {Do not Localize}
  1557. LCmd.OnCommand := CommandUSER;
  1558. LCmd.Description.Text := 'Syntax: USER <sp> username'; {do not localize}
  1559. //PASS <SP> <password> <CRLF>
  1560. LCmd := CommandHandlers.Add;
  1561. LCmd.Command := 'PASS'; {Do not Localize}
  1562. LCmd.OnCommand := CommandPASS;
  1563. LCmd.Description.Text := 'Syntax: PASS <sp> password'; {do not localize}
  1564. //ACCT <SP> <account-information> <CRLF>
  1565. LCmd := CommandHandlers.Add;
  1566. LCmd.Command := 'ACCT'; {Do not Localize}
  1567. // LCMd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['ACCT'])); {do not localize}
  1568. LCmd.OnCommand := CommandACCT;
  1569. if Assigned(Self.FOnUserAccount) then begin
  1570. LCmd.HelpSuperScript := ''; //not supported
  1571. LCmd.Description.Text := ACCT_HELP_ENABLED;
  1572. end else begin
  1573. LCmd.HelpSuperScript := '*'; //not supported
  1574. LCmd.Description.Text := ACCT_HELP_DISABLED;
  1575. end;
  1576. // 'ACCT (specify account); unimplemented.'; {do not localize}
  1577. {
  1578. LCmd.NormalReply.SetReply(502, Format(RSFTPCmdNotImplemented, ['ACCT'])); {Do not Localize}
  1579. //CWD <SP> <pathname> <CRLF>
  1580. LCmd := CommandHandlers.Add;
  1581. LCmd.Command := 'CWD'; {Do not Localize}
  1582. LCmd.OnCommand := CommandCWD;
  1583. LCmd.ExceptionReply.NumericCode := 550;
  1584. LCmd.Description.Text := 'Syntax: CWD [ <sp> directory-name ]'; {do not localize}
  1585. //CDUP <CRLF>
  1586. LCmd := CommandHandlers.Add;
  1587. LCmd.Command := 'CDUP'; {Do not Localize}
  1588. LCmd.OnCommand := CommandCDUP;
  1589. LCmd.ExceptionReply.NumericCode := 550;
  1590. LCmd.Description.Text := 'Syntax: CDUP (change to parent directory)'; {do not localize}
  1591. //SMNT <SP> <pathname> <CRLF>
  1592. LCmd := CommandHandlers.Add;
  1593. LCmd.Command := 'SMNT'; {Do not Localize}
  1594. LCmd.NormalReply.SetReply(502, RSFTPFileActionCompleted);//250 for success
  1595. LCmd.HelpSuperScript := '*';
  1596. LCmd.Description.Text := 'SMNT (structure mount); unimplemented.'; {do not localize}
  1597. //QUIT <CRLF>
  1598. LCmd := CommandHandlers.Add;
  1599. LCmd.Command := 'QUIT'; {Do not Localize}
  1600. LCmd.OnCommand := CommandQUIT;
  1601. LCmd.Disconnect := True;
  1602. LCmd.NormalReply.SetReply(221, RSFTPQuitGoodby); {Do not Localize}
  1603. LCmd.Description.Text := 'Syntax: QUIT (terminate service)'; {do not localize}
  1604. //REIN <CRLF>
  1605. LCmd := CommandHandlers.Add;
  1606. LCmd.Command := 'REIN'; {Do not Localize}
  1607. LCmd.OnCommand := CommandREIN;
  1608. LCmd.Description.Text := 'Syntax: REIN (reinitialize server state)'; {do not localize}
  1609. //PORT <SP> <host-port> <CRLF>
  1610. LCmd := CommandHandlers.Add;
  1611. LCmd.Command := 'PORT'; {Do not Localize}
  1612. LCmd.OnCommand := CommandPORT;
  1613. LCmd.Description.Text := 'Syntax: PORT <sp> b0, b1, b2, b3, b4'; {do not localize}
  1614. //PASV <CRLF>
  1615. LCmd := CommandHandlers.Add;
  1616. LCmd.Command := 'PASV'; {Do not Localize}
  1617. LCmd.OnCommand := CommandPASV;
  1618. LCmd.Description.Text := 'Syntax: PASV (set server in passive mode)'; {do not localize}
  1619. //P@SW <CRLF>
  1620. //This is for some routers that replace a PASV with a P@SW
  1621. //as part of a misguided attempt to add a feature.
  1622. //A router would do a replacement so a client would think that
  1623. //PASV wasn't supported and then the client would do a PORT command
  1624. //instead. That doesn't happen so this just caused the client not to work.
  1625. //See: http://www.gbnetwork.co.uk/smcftpd/
  1626. LCmd := CommandHandlers.Add;
  1627. LCmd.Command := 'P@SW'; {Do not Localize}
  1628. LCmd.OnCommand := CommandPASV;
  1629. LCmd.HelpVisible := False; //this is just a workaround
  1630. //TYPE <SP> <type-code> <CRLF>
  1631. LCmd := CommandHandlers.Add;
  1632. LCmd.Command := 'TYPE'; {Do not Localize}
  1633. LCmd.OnCommand := CommandTYPE;
  1634. LCmd.Description.Text := 'Syntax: TYPE <sp> [ A | E | I | L ]'; {do not localize}
  1635. //STRU <SP> <structure-code> <CRLF>
  1636. LCmd := CommandHandlers.Add;
  1637. LCmd.Command := 'STRU'; {Do not Localize}
  1638. LCmd.OnCommand := CommandSTRU;
  1639. LCmd.Description.Text := 'Syntax: STRU (specify file structure)'; {do not localize}
  1640. //MODE <SP> <mode-code> <CRLF>
  1641. LCmd := CommandHandlers.Add;
  1642. LCmd.Command := 'MODE'; {Do not Localize}
  1643. LCmd.OnCommand := CommandMODE;
  1644. LCmd.ExceptionReply.NumericCode := 501;
  1645. LCmd.Description.Text := 'Syntax: MODE (specify transfer mode)'; {do not localize}
  1646. //FTP SERVICE COMMANDS
  1647. //RETR <SP> <pathname> <CRLF>
  1648. LCmd := CommandHandlers.Add;
  1649. LCmd.Command := 'RETR'; {Do not Localize}
  1650. LCmd.OnCommand := CommandRETR;
  1651. LCmd.ExceptionReply.NumericCode := 550;
  1652. LCmd.Description.Text := 'Syntax: RETR <sp> file-name'; {do not localize}
  1653. //STOR <SP> <pathname> <CRLF>
  1654. LCmd := CommandHandlers.Add;
  1655. LCmd.Command := 'STOR'; {Do not Localize}
  1656. LCmd.OnCommand := CommandSSAP;
  1657. LCmd.ExceptionReply.NumericCode := 551;
  1658. LCmd.Description.Text := 'Syntax: STOR <sp> file-name'; {do not localize}
  1659. //STOU <CRLF>
  1660. LCmd := CommandHandlers.Add;
  1661. LCmd.Command := 'STOU'; {Do not Localize}
  1662. LCmd.OnCommand := CommandSSAP;
  1663. LCmd.ExceptionReply.NumericCode := 551;
  1664. LCmd.Description.Text := 'Syntax: STOU <sp> file-name'; {do not localize}
  1665. //APPE <SP> <pathname> <CRLF>
  1666. LCmd := CommandHandlers.Add;
  1667. LCmd.Command := 'APPE'; {Do not Localize}
  1668. LCmd.OnCommand := CommandSSAP;
  1669. LCmd.ExceptionReply.NumericCode := 550;
  1670. LCmd.Description.Text := 'Syntax: APPE <sp> file-name'; {do not localize}
  1671. //ALLO <SP> <decimal-integer>
  1672. // [<SP> R <SP> <decimal-integer>] <CRLF>
  1673. LCmd := CommandHandlers.Add;
  1674. LCmd.Command := 'ALLO'; {Do not Localize}
  1675. LCmd.OnCommand := CommandALLO;
  1676. LCmd.ExceptionReply.NumericCode := 550;
  1677. LCmd.Description.Text := 'Syntax: ALLO allocate storage (vacuously)'; {do not localize}
  1678. //REST <SP> <marker> <CRLF>
  1679. LCmd := CommandHandlers.Add;
  1680. LCmd.Command := 'REST'; {Do not Localize}
  1681. LCmd.OnCommand := CommandREST;
  1682. LCmd.ExceptionReply.NumericCode := 550;
  1683. LCmd.Description.Text := 'Syntax: REST (restart command)'; {do not localize}
  1684. //RNFR <SP> <pathname> <CRLF>
  1685. LCmd := CommandHandlers.Add;
  1686. LCmd.Command := 'RNFR'; {Do not Localize}
  1687. LCmd.OnCommand := CommandRNFR;
  1688. LCmd.ExceptionReply.NumericCode := 550;
  1689. LCmd.Description.Text := 'Syntax: RNFR <sp> file-name'; {do not localize}
  1690. //RNTO <SP> <pathname> <CRLF>
  1691. LCmd := CommandHandlers.Add;
  1692. LCmd.Command := 'RNTO'; {Do not Localize}
  1693. LCmd.OnCommand := CommandRNTO;
  1694. LCmd.ExceptionReply.NumericCode := 550;
  1695. LCmd.Description.Text := 'Syntax: RNTO <sp> file-name'; {do not localize}
  1696. //ABOR <CRLF>
  1697. LCmd := CommandHandlers.Add;
  1698. LCmd.Command := 'ABOR'; {Do not Localize}
  1699. LCmd.OnCommand := CommandABOR;
  1700. LCmd.ExceptionReply.NumericCode := 550;
  1701. LCmd.Description.Text := 'Syntax: ABOR (abort operation)'; {do not localize}
  1702. //DELE <SP> <pathname> <CRLF>
  1703. LCmd := CommandHandlers.Add;
  1704. LCmd.Command := 'DELE'; {Do not Localize}
  1705. LCmd.OnCommand := CommandDELE;
  1706. LCmd.ExceptionReply.NumericCode := 550;
  1707. LCmd.Description.Text := 'Syntax: DELE <sp> file-name'; {do not localize}
  1708. // 'SMNT (structure mount); unimplemented.';
  1709. //RMD <SP> <pathname> <CRLF>
  1710. LCmd := CommandHandlers.Add;
  1711. LCmd.Command := 'RMD'; {Do not Localize}
  1712. LCmd.OnCommand := CommandRMD;
  1713. LCmd.ExceptionReply.NumericCode := 550;
  1714. LCmd.Description.Text := 'Syntax: RMD <sp> path-name'; {do not localize}
  1715. //MKD <SP> <pathname> <CRLF>
  1716. LCmd := CommandHandlers.Add;
  1717. LCmd.Command := 'MKD'; {Do not Localize}
  1718. LCmd.OnCommand := CommandMKD;
  1719. LCmd.ExceptionReply.NumericCode := 550;
  1720. LCmd.Description.Text := 'Syntax: MKD <sp> path-name'; {do not localize}
  1721. //PWD <CRLF>
  1722. LCmd := CommandHandlers.Add;
  1723. LCmd.Command := 'PWD'; {Do not Localize}
  1724. LCmd.OnCommand := CommandPWD;
  1725. LCmd.ExceptionReply.NumericCode := 550;
  1726. LCmd.Description.Text := 'Syntax: PWD (return current directory)'; {do not localize}
  1727. //LIST [<SP> <pathname>] <CRLF>
  1728. LCmd := CommandHandlers.Add;
  1729. LCmd.Command := 'LIST'; {Do not Localize}
  1730. LCmd.OnCommand := CommandLIST;
  1731. LCmd.ExceptionReply.NumericCode := 450;
  1732. LCmd.Description.Text := 'Syntax: LIST [ <sp> path-name ]'; {do not localize}
  1733. //NLST [<SP> <pathname>] <CRLF>
  1734. LCmd := CommandHandlers.Add;
  1735. LCmd.Command := 'NLST'; {Do not Localize}
  1736. LCmd.OnCommand := CommandLIST;
  1737. LCmd.ExceptionReply.NumericCode := 450;
  1738. LCmd.Description.Text := 'Syntax: NLST [ <sp> path-name ]'; {do not localize}
  1739. //SITE <SP> <string> <CRLF>
  1740. LCmd := CommandHandlers.Add;
  1741. LCmd.Command := 'SITE'; {Do not Localize}
  1742. LCmd.OnCommand := CommandSITE;
  1743. LCmd.ExceptionReply.NumericCode := 501;
  1744. LCmd.Description.Text := 'Syntax: SITE (site-specific commands)';
  1745. //SYST <CRLF>
  1746. LCmd := CommandHandlers.Add;
  1747. LCmd.Command := 'SYST'; {Do not Localize}
  1748. LCmd.OnCommand := CommandSYST;
  1749. LCmd.ExceptionReply.NumericCode := 501;
  1750. LCmd.Description.Text := 'Syntax: SYST (get type of operating system)'; {do not localize}
  1751. //STAT [<SP> <pathname>] <CRLF>
  1752. LCmd := CommandHandlers.Add;
  1753. LCmd.Command := 'STAT'; {Do not Localize}
  1754. LCmd.OnCommand := CommandSTAT;
  1755. LCmd.ExceptionReply.NumericCode := 450;
  1756. LCmd.Description.Text := 'Syntax: CWD [ <sp> directory-name ]'; {do not localize}
  1757. //NOOP <CRLF>
  1758. LCmd := CommandHandlers.Add;
  1759. LCmd.Command := 'NOOP'; {Do not Localize}
  1760. LCmd.NormalReply.SetReply(200, IndyFormat(RSFTPCmdSuccessful, ['NOOP'])); {Do not Localize}
  1761. LCmd.ExceptionReply.NumericCode := 550;
  1762. LCmd.Description.Text := 'Syntax: NOOP'; {do not localize}
  1763. //RFC 775
  1764. LCmd := CommandHandlers.Add;
  1765. LCmd.Command := 'XMKD'; {Do not Localize}
  1766. LCmd.OnCommand := CommandMKD;
  1767. LCmd.ExceptionReply.NumericCode := 551; //use the ones in parathensies
  1768. LCmd.Description.Text := 'Syntax: XMKD <sp> path-name'; {do not localize}
  1769. //XCWD <SP> <pathname> <CRLF>
  1770. LCmd := CommandHandlers.Add;
  1771. LCmd.Command := 'XCWD'; {Do not Localize}
  1772. LCmd.OnCommand := CommandCWD;
  1773. LCmd.ExceptionReply.NumericCode := 550;
  1774. LCmd.Description.Text := 'Syntax: XCWD [ <sp> directory-name ]'; {do not localize}
  1775. LCmd := CommandHandlers.Add;
  1776. LCmd.Command := 'XRMD'; {Do not Localize}
  1777. LCmd.OnCommand := CommandRMD;
  1778. LCmd.ExceptionReply.NumericCode := 551; //use the ones in parathensies
  1779. LCmd.Description.Text := 'Syntax: XRMD <sp> path-name'; {do not localize}
  1780. LCmd := CommandHandlers.Add;
  1781. LCmd.Command := 'XPWD'; {Do not Localize}
  1782. LCmd.OnCommand := CommandPWD;
  1783. LCmd.ExceptionReply.NumericCode := 502;
  1784. LCmd.Description.Text := 'Syntax: PWD (return current directory)'; {do not localize}
  1785. LCmd := CommandHandlers.Add;
  1786. LCmd.Command := 'XCUP'; {Do not Localize}
  1787. LCmd.OnCommand := CommandCDUP;
  1788. LCmd.ExceptionReply.NumericCode := 551; //use the ones in parathensies
  1789. LCmd.Description.Text := 'Syntax: XCUP (change to parent directory)'; {do not localize}
  1790. //RFC 2389
  1791. LCmd := CommandHandlers.Add;
  1792. LCmd.Command := 'FEAT'; {Do not Localize}
  1793. LCmd.OnCommand := CommandFEAT;
  1794. SetRFCReplyFormat(LCmd.NormalReply);
  1795. LCmd.ExceptionReply.NumericCode := 501;
  1796. LCmd.Description.Text := 'Syntax: FEAT (returns feature list)'; {do not localize}
  1797. //RFC 2389
  1798. LCmd := CommandHandlers.Add;
  1799. LCmd.Command := 'OPTS'; {Do not Localize}
  1800. LCmd.OnCommand := CommandOPTS;
  1801. LCmd.ExceptionReply.NumericCode := 501;
  1802. LCmd.Description.Text := 'Syntax: OPTS <sp> command [<sp> options]'; {do not localize}
  1803. //SIZE [<FILE>] CRLF
  1804. LCmd := CommandHandlers.Add;
  1805. LCmd.Command := 'SIZE'; {Do not Localize}
  1806. LCmd.OnCommand := CommandSIZE;
  1807. LCmd.ExceptionReply.NumericCode := 550;
  1808. LCmd.Description.Text := 'Syntax: SIZE <sp> path-name'; {do not localize}
  1809. //EPSV [protocol] <CRLF>
  1810. LCmd := CommandHandlers.Add;
  1811. LCmd.Command := 'EPSV'; {Do not Localize}
  1812. LCmd.OnCommand := CommandEPSV;
  1813. LCmd.ExceptionReply.NumericCode := 501;
  1814. LCmd.Description.Text := 'Syntax: EPSV (returns port |||port|)'; {do not localize}
  1815. //EPRT [address/port string] <CRLF>
  1816. LCmd := CommandHandlers.Add;
  1817. LCmd.Command := 'EPRT'; {Do not Localize}
  1818. LCmd.OnCommand := CommandEPRT;
  1819. LCmd.ExceptionReply.NumericCode := 501;
  1820. LCmd.Description.Text := 'Syntax: EPRT <sp> |proto|addr|port|'; {do not localize}
  1821. //MDTM [<FILE>] <CRLF>
  1822. LCmd := CommandHandlers.Add;
  1823. LCmd.Command := 'MDTM'; {Do not Localize}
  1824. LCmd.OnCommand := CommandMDTM;
  1825. LCmd.ExceptionReply.NumericCode := 550;
  1826. LCmd.Description.Text := 'Syntax: MDTM <sp> path-name'; {do not localize}
  1827. //RFC 2228
  1828. //AUTH [Mechanism] <CRLF>
  1829. LCmd := CommandHandlers.Add;
  1830. LCmd.Command := 'AUTH'; {Do not translate}
  1831. LCmd.OnCommand := CommandAUTH;
  1832. LCmd.ExceptionReply.NumericCode := 501;
  1833. LCmd.Description.Text := 'Syntax: AUTH <sp> mechanism-name'; {do not localize}
  1834. //PBSZ [Protection Buffer Size] <CRLF>
  1835. LCmd := CommandHandlers.Add;
  1836. LCmd.Command := 'PBSZ'; {Do not translate}
  1837. LCmd.OnCommand := CommandPBSZ;
  1838. LCmd.ExceptionReply.NumericCode := 501;
  1839. LCmd.Description.Text := 'Syntax: PBSZ <sp> protection buffer size'; {do not localize}
  1840. //PROT Protection Type <CRLF>
  1841. LCmd := CommandHandlers.Add;
  1842. LCmd.Command := 'PROT'; {Do not translate}
  1843. LCmd.OnCommand := CommandPROT;
  1844. LCmd.ExceptionReply.NumericCode := 501;
  1845. LCmd.Description.Text := 'Syntax: PROT <sp> protection code'; {do not localize}
  1846. //CCC Clear Command Channel
  1847. LCmd := CommandHandlers.Add;
  1848. LCmd.Command := 'CCC'; {Do not translate}
  1849. LCmd.OnCommand := CommandCCC;
  1850. LCmd.Description.Text := 'Syntax: CCC (clear command channel)'; {do not localize}
  1851. //MIC Integrity Protected Command
  1852. LCmd := CommandHandlers.Add;
  1853. LCmd.Command := 'MIC'; {Do not translate}
  1854. LCmd.OnCommand := CommandSecRFC;
  1855. LCmd.HelpSuperScript := '*';
  1856. LCmd.Description.Text := 'MIC (integrity protected command); unimplemented.'; {do not localize}
  1857. //CONF Confidentiality protected command
  1858. LCmd := CommandHandlers.Add;
  1859. LCmd.Command := 'CONF'; {Do not translate}
  1860. LCmd.OnCommand := CommandSecRFC;
  1861. LCmd.HelpSuperScript := '*';
  1862. LCmd.Description.Text := 'CONF (confidentiality protected command); unimplemented.'; {do not localize}
  1863. //ENC Privacy Protected command
  1864. LCmd := CommandHandlers.Add;
  1865. LCmd.Command := 'ENC'; {Do not translate}
  1866. LCmd.OnCommand := CommandSecRFC;
  1867. LCmd.HelpSuperScript := '*';
  1868. LCmd.Description.Text := 'ENC (privacy protected command); unimplemented.'; {do not localize}
  1869. //These are from IETF Draft "Extensions to FTP"
  1870. //MLSD [Pathname] <CRLF>
  1871. LCmd := CommandHandlers.Add;
  1872. LCmd.Command := 'MLSD'; {Do not translate}
  1873. LCmd.OnCommand := CommandMLSD;
  1874. LCmd.ExceptionReply.NumericCode := 550;
  1875. LCmd.Description.Text := 'Syntax: MLSD [ <sp> path-name ]'; {do not localize}
  1876. //MLST [Pathname] <CRLF>
  1877. LCmd := CommandHandlers.Add;
  1878. LCmd.Command := 'MLST'; {Do not translate}
  1879. LCmd.OnCommand := CommandMLST;
  1880. SetRFCReplyFormat(LCmd.NormalReply);
  1881. LCmd.ExceptionReply.NumericCode := 550;
  1882. LCmd.Description.Text := 'Syntax: MLST [ <sp> path-name ]'; {do not localize}
  1883. //Defined in http://www.trevezel.com/downloads/draft-somers-ftp-mfxx-00.html
  1884. //Modify File Modification Time
  1885. //MFMT [ATime] [Path-name]<CRLF>
  1886. LCmd := CommandHandlers.Add;
  1887. LCmd.Command := 'MFMT'; {Do not translate}
  1888. LCmd.OnCommand := CommandMFMT;
  1889. LCmd.ExceptionReply.NumericCode := 550;
  1890. LCmd.Description.Text := 'Syntax: MFMT [ATime] [Path-name]<CRLF>'; {do not localize}
  1891. //Defined in http://www.trevezel.com/downloads/draft-somers-ftp-mfxx-00.html
  1892. //Modify File Creation Time
  1893. //MFMT [ATime] [Pathname]<CRLF>
  1894. LCmd := CommandHandlers.Add;
  1895. LCmd.Command := 'MFCT'; {Do not translate}
  1896. LCmd.OnCommand := CommandMFCT;
  1897. LCmd.ExceptionReply.NumericCode := 550;
  1898. LCmd.Description.Text := 'Syntax: MFCT [ATime] [Path-name]'; {do not localize}
  1899. //params are the same format as the MLS output
  1900. LCmd := CommandHandlers.Add;
  1901. LCmd.Command := 'MFF'; {Do not translate}
  1902. LCmd.OnCommand := CommandMFF;
  1903. LCmd.ExceptionReply.NumericCode := 550;
  1904. LCmd.Description.Text := 'Syntax: MFF [ mff-facts ] SP path-name'; {do not localize}
  1905. //From http://www.ietf.org/internet-drafts/draft-twine-ftpmd5-00.txt
  1906. //MD5 [Pathname]
  1907. LCmd := CommandHandlers.Add;
  1908. LCmd.Command := 'MD5'; {Do not translate}
  1909. LCmd.OnCommand := CommandMD5;
  1910. LCmd.ExceptionReply.NumericCode := 504;
  1911. LCmd.Description.Text := 'Syntax: MD5 [Pathname]'; {do not localize}
  1912. //MMD5 [Filepath1], [Filepath2] [...]
  1913. LCmd := CommandHandlers.Add;
  1914. LCmd.Command := 'MMD5'; {Do not translate}
  1915. LCmd.OnCommand := CommandMMD5;
  1916. LCmd.ExceptionReply.NumericCode := 504;
  1917. LCmd.Description.Text := 'Syntax: MMD5 [Filepath1], [Filepath2] [...]'; {do not localize}
  1918. //These two commands are not in RFC's or drafts
  1919. // but are documented in:
  1920. // GlobalSCAPE Secure FTP Server User’s Guide
  1921. //XCRC "[filename]" [start] [finish]
  1922. LCmd := CommandHandlers.Add;
  1923. LCmd.Command := 'XCRC'; {Do not translate}
  1924. LCmd.OnCommand := CommandCheckSum;
  1925. LCmd.ExceptionReply.NumericCode := 550;
  1926. LCmd.Description.Text := 'Syntax: XCRC "[file-name]" [start] [finish]'; {do not localize}
  1927. //COMB "[filename]" [start] [finish]
  1928. LCmd := CommandHandlers.Add;
  1929. LCmd.Command := 'COMB'; {Do not translate}
  1930. LCmd.OnCommand := CommandCOMB;
  1931. LCmd.ExceptionReply.NumericCode := 550;
  1932. LCmd.Description.Text := 'Syntax: COMB "[file-name]" [start] [finish]'; {do not localize}
  1933. //informal but we might want to support this anyway
  1934. //SSCN - specified by:
  1935. //http://www.raidenftpd.com/kb/kb000000037.htm
  1936. LCmd := CommandHandlers.Add;
  1937. LCmd.Command := 'SSCN'; {Do not translate}
  1938. LCmd.OnCommand := CommandSSCN;
  1939. LCmd.ExceptionReply.NumericCode := 550;
  1940. LCmd.NormalReply.NumericCode := 200;
  1941. LCmd.Description.Text := 'Syntax: SSCN [ON|OFF]'; {do not localize}
  1942. //CPSV <CRLF>
  1943. LCmd := CommandHandlers.Add;
  1944. LCmd.Command := 'CPSV'; {Do not Localize}
  1945. LCmd.OnCommand := CommandCPSV;
  1946. LCmd.Description.Text := 'Syntax: CPSV (set server in passive mode with SSL Connect)'; {do not localize}
  1947. //Seen in RaidenFTPD documentation
  1948. //XCRC "[filename]" [start] [finish]
  1949. LCmd := CommandHandlers.Add;
  1950. LCmd.Command := 'XMD5'; {Do not translate}
  1951. LCmd.OnCommand := CommandCheckSum;
  1952. LCmd.ExceptionReply.NumericCode := 550;
  1953. LCmd.Description.Text := 'Syntax: XMD5 "[filename]" [start] [finish]'; {do not localize}
  1954. //Seen in RaidenFTPD documentation
  1955. //XCRC "[filename]" [start] [finish]
  1956. LCmd := CommandHandlers.Add;
  1957. LCmd.Command := 'XSHA1'; {Do not translate}
  1958. LCmd.OnCommand := CommandCheckSum;
  1959. LCmd.ExceptionReply.NumericCode := 550;
  1960. LCmd.Description.Text := 'Syntax: XSHA1 "[filename]" [start] [finish]'; {do not localize}
  1961. LCmd := CommandHandlers.Add;
  1962. LCmd.Command := 'XSHA256'; {Do not translate}
  1963. LCmd.OnCommand := CommandCheckSum;
  1964. LCmd.ExceptionReply.NumericCode := 550;
  1965. LCmd.Description.Text := 'Syntax: XSHA256 "[filename]" [start] [finish]'; {do not localize}
  1966. LCmd := CommandHandlers.Add;
  1967. LCmd.Command := 'XSHA512'; {Do not translate}
  1968. LCmd.OnCommand := CommandCheckSum;
  1969. LCmd.ExceptionReply.NumericCode := 550;
  1970. LCmd.HelpVisible := True;
  1971. LCmd.Description.Text := 'Syntax: XSHA512 "[filename]" [start] [finish]'; {do not localize}
  1972. //commands from
  1973. // draft-peterson-streamlined-ftp-command-extensions-01.txt
  1974. //http://tools.ietf.org/html/draft-peterson-streamlined-ftp-command-extensions-01#section-2.4
  1975. LCmd := CommandHandlers.Add;
  1976. LCmd.Command := 'AVBL'; {Do not localize}
  1977. LCmd.OnCommand := CommandAVBL;
  1978. LCmd.ExceptionReply.NumericCode := 500;
  1979. LCmd.Description.Text := 'Syntax: AVBL [<sp> dirpath] (returns the number of '+
  1980. 'bytes available for uploading in the directory or current working directory)';
  1981. LCmd := CommandHandlers.Add;
  1982. LCmd.Command := 'DSIZ'; {Do not localize}
  1983. LCmd.OnCommand := CommandDSIZ;
  1984. LCmd.ExceptionReply.NumericCode := 500;
  1985. LCmd.Description.Text := 'DSIZ [<sp> dirpath] (returns the number of bytes '+
  1986. 'in the directory or current working directory, including sub directories)';
  1987. LCmd := CommandHandlers.Add;
  1988. LCmd.Command := 'RMDA';
  1989. LCmd.OnCommand := CommandRMDA;
  1990. LCmd.ExceptionReply.NumericCode := 550;
  1991. LCmd.Description.Text := 'RMDA <sp> pathname (deletes (removes) the '+
  1992. 'specified directory and its contents)';
  1993. //informal but we might want to support this anyway
  1994. //CLNT
  1995. LCmd := CommandHandlers.Add;
  1996. LCmd.Command := 'CLNT'; {do not localize}
  1997. LCmd.OnCommand := CommandCLNT;
  1998. LCmd.ExceptionReply.NumericCode := 550;
  1999. LCmd.NormalReply.SetReply(200, RSFTPClntNoted); {Do not Localize}
  2000. LCmd.Description.Text := 'Syntax: CLNT <sp> <clientname> <sp> <clientversion> [ <sp> <platform> ]'; {do not localize}
  2001. //https://www.ietf.org/archive/id/draft-peterson-streamlined-ftp-command-extensions-10.txt
  2002. LCmd := CommandHandlers.Add;
  2003. LCmd.Command := 'CSID'; {Do not localize}
  2004. LCmd.OnCommand := CommandCSID;
  2005. LCmd.ExceptionReply.NumericCode := 550;
  2006. LCmd.Description.Text := 'Syntax: CSID <sp> Name=<clientname>; Version=<clientversion>;'; {Do not localize}
  2007. //Informal - an old proposed solution to IPv6 support in FTP.
  2008. //Mentioned at: http://cr.yp.to/ftp/retr.html
  2009. //and supported by PureFTPD.
  2010. LCmd := CommandHandlers.Add;
  2011. LCmd.Command := 'SPSV'; {do not localize}
  2012. LCmd.OnCommand := CommandSPSV;
  2013. LCmd.Description.Text := 'Syntax: SPSV (set server in passive mode)'; {do not localize}
  2014. LCmd := CommandHandlers.Add;
  2015. LCmd.Command := 'HOST'; {Do not localize}
  2016. LCmd.OnCommand := CommandHOST;
  2017. LCmd.ExceptionReply.NumericCode := 504;
  2018. LCmd.Description.Text := 'Syntax: HOST <sp> domain (select a domain prior to logging in)'; {Do not localize}
  2019. //Note that these commands are mentioned in old RFC's
  2020. //and we will not support them at all. The commands
  2021. //were there because FTP was a predisessor of SMTP
  2022. //These are from RFC 765
  2023. //MLFL [<SP> <ident>] <CRLF>
  2024. LCmd := CommandHandlers.Add;
  2025. LCmd.Command := 'MLFL'; {Do not Localize}
  2026. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MLFL'])); {Do not Localize}
  2027. LCmd.HelpSuperScript := '*';
  2028. LCmd.Description.Text := 'MLFL (mail file); unimplemented.'; {do not localize}
  2029. //MAIL [<SP> <ident>] <CRLF>
  2030. LCmd := CommandHandlers.Add;
  2031. LCmd.Command := 'MAIL'; {Do not Localize}
  2032. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MAIL'])); {Do not Localize}
  2033. LCmd.HelpSuperScript := '*';
  2034. LCmd.Description.Text := 'MAIL (mail to user); unimplemented.'; {do not localize}
  2035. // MSND [<SP> <ident>] <CRLF>
  2036. LCmd := CommandHandlers.Add;
  2037. LCmd.Command := 'MSND'; {Do not Localize}
  2038. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MSND'])); {Do not Localize}
  2039. LCmd.HelpSuperScript := '*';
  2040. LCmd.Description.Text := 'MSND (mail send to terminal); unimplemented.'; {do not localize}
  2041. // MSOM [<SP> <ident>] <CRLF>
  2042. LCmd := CommandHandlers.Add;
  2043. LCmd.Command := 'MSOM'; {Do not Localize}
  2044. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MSOM'])); {Do not Localize}
  2045. LCmd.HelpSuperScript := '*';
  2046. LCmd.Description.Text := 'MSOM (mail send to terminal or mailbox); unimplemented.'; {do not localize}
  2047. // MSAM [<SP> <ident>] <CRLF>
  2048. LCmd := CommandHandlers.Add;
  2049. LCmd.Command := 'MSAM'; {Do not Localize}
  2050. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MSAM'])); {Do not Localize}
  2051. LCmd.HelpSuperScript := '*';
  2052. LCmd.Description.Text := 'MSAM (mail send to terminal and mailbox); unimplemented.'; {do not localize}
  2053. // MRSQ [<SP> <scheme>] <CRLF>
  2054. LCmd := CommandHandlers.Add;
  2055. LCmd.Command := 'MRSQ'; {Do not Localize}
  2056. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MRSQ'])); {Do not Localize}
  2057. LCmd.HelpSuperScript := '*';
  2058. LCmd.Description.Text := 'MRSQ (mail recipient scheme question); unimplemented.'; {do not localize}
  2059. // MRCP <SP> <ident> <CRLF>
  2060. LCmd := CommandHandlers.Add;
  2061. LCmd.Command := 'MRCP'; {Do not Localize}
  2062. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MRCP'])); {Do not Localize}
  2063. LCmd.HelpSuperScript := '*';
  2064. LCmd.Description.Text := 'MRCP (mail recipient); unimplemented.'; {do not localize}
  2065. //
  2066. LCmd := CommandHandlers.Add;
  2067. LCmd.Command := 'HELP'; {Do not Localize}
  2068. LCmd.OnCommand := COmmandHELP;
  2069. LCmd.NormalReply.NumericCode := 214;
  2070. LCmd.Description.Text := 'Syntax: HELP [ <sp> <string> ]'; {do not localize}
  2071. //We use a separate command handler collection for some things which are
  2072. //valid durring the data connection.
  2073. //ABOR <CRLF>
  2074. LCmd := FDataChannelCommands.Add;
  2075. LCmd.Command := 'ABOR'; {Do not Localize}
  2076. LCmd.OnCommand := CommandABOR;
  2077. LCmd.ExceptionReply.NumericCode := 550;
  2078. //STAT [<SP> <pathname>] <CRLF>
  2079. LCmd := FDataChannelCommands.Add;
  2080. LCmd.Command := 'STAT'; {Do not Localize}
  2081. LCmd.OnCommand := CommandSTAT;
  2082. LCmd.ExceptionReply.NumericCode := 450;
  2083. //This is for SITE commands to make it easy for the user to add their own site commands
  2084. //as they wish
  2085. //These are Unix site commands
  2086. LCmd := FSITECommands.Add;
  2087. LCmd.Command := 'HELP'; {Do not localize}
  2088. LCmd.ExceptionReply.NumericCode := 501;
  2089. LCmd.OnCommand := CommandSiteHELP;
  2090. LCmd.Description.Text := 'Syntax: SITE HELP [ <sp> <string> ]'; {do not localize}
  2091. //SITE ATTRIB<SP>Attribs<SP>FileName<CRLF>
  2092. LCmd := FSITECommands.Add;
  2093. LCmd.Command := 'ATTRIB'; {Do not Localize}
  2094. LCmd.OnCommand := CommandSiteATTRIB;
  2095. LCmd.ExceptionReply.NumericCode := 501;
  2096. LCmd.Description.Text := 'Syntax: SITE ATTRIB<SP>Attribs<SP>Filename'; {do not localize}
  2097. //SITE UMASK<SP>[mask]
  2098. LCmd := FSITECommands.Add;
  2099. LCmd.Command := 'UMASK'; {Do not Localize}
  2100. LCmd.OnCommand := CommandSiteUMASK;
  2101. LCmd.ExceptionReply.NumericCode := 501;
  2102. LCmd.Description.Text := 'Syntax: SITE UMASK'; {do not localize}
  2103. //SITE CHMOD<SP>Permission numbers<SP>Filename<CRLF>
  2104. LCmd := FSITECommands.Add;
  2105. LCmd.Command := 'CHMOD'; {Do not Localize}
  2106. LCmd.OnCommand := CommandSiteCHMOD;
  2107. LCmd.ExceptionReply.NumericCode := 501;
  2108. LCmd.Description.Text := 'Syntax: SITE CHMOD<SP>Permission numbers<SP>Filename'; {do not localize}
  2109. //additional Unix server commands that aren't supported but should be supported, IMAO
  2110. //SITE CHOWN<SP>Owner[:Group]<SP>Filename<CRLF>
  2111. LCmd := FSITECommands.Add;
  2112. LCmd.Command := 'CHOWN'; {Do not Localize}
  2113. LCmd.OnCommand := CommandSiteCHOWN;
  2114. LCmd.ExceptionReply.NumericCode := 501;
  2115. LCmd.Description.Text := 'Syntax: SITE CHOWN<SP>Owner[:Group]<SP>Filename<CRLF>'; {do not localize}
  2116. //SITE CHGRP<SP>Group<SP>Filename<CRLF>
  2117. LCmd := FSITECommands.Add;
  2118. LCmd.Command := 'CHGRP'; {Do not Localize}
  2119. LCmd.OnCommand := CommandSiteCHGRP;
  2120. LCmd.ExceptionReply.NumericCode := 501;
  2121. LCmd.Description.Text := 'Syntax: SITE CHGRP<SP>Group<SP>Filename<CRLF>'; {do not localize}
  2122. //Microsoft IIS SITE commands
  2123. //SITE DIRSTYLE
  2124. LCmd := FSITECommands.Add;
  2125. LCmd.Command := 'DIRSTYLE'; {Do not Localize}
  2126. LCmd.ExceptionReply.NumericCode := 501;
  2127. LCmd.OnCommand := CommandSiteDIRSTYLE;
  2128. LCmd.Description.Text := 'Syntax: SITE DIRSTYLE (toggle directory format)'; {do not localize}
  2129. //SITE ZONE
  2130. LCmd := FSITECommands.Add;
  2131. LCmd.Command := 'ZONE'; {Do not localize}
  2132. LCmd.ExceptionReply.NumericCode := 530;
  2133. LCmd.OnCommand := CommandSiteZONE;
  2134. LCmd.Description.Text := 'Syntax: SITE ZONE (returns the server offset from GMT)'; {do not localize}
  2135. //SITE UTIME
  2136. LCmd := FSITECommands.Add;
  2137. LCmd.Command := 'UTIME'; {Do not localize}
  2138. LCmd.NormalReply.NumericCode := 200;
  2139. LCmd.NormalReply.Text.Text := 'Date/time changed okay.';
  2140. LCmd.ExceptionReply.NumericCode := 530;
  2141. LCmd.OnCommand := CommandSiteUTIME;
  2142. LCmd.Description.Text :=
  2143. 'Syntax: SITE UTIME <file> <access-time> <modification-time> <creation time>'+CR+LF+ {do not localize}
  2144. ' Each timestamp must be in the format YYYYMMDDhhmmss'; {do not localize}
  2145. //OPTS MLST
  2146. LCmd := FOPTSCommands.Add;
  2147. LCmd.Command := 'MLST'; {Do not localize}
  2148. LCmd.ExceptionReply.NumericCode := 501;
  2149. LCmd.OnCommand := CommandOptsMLST;
  2150. //OPTS MODE Z
  2151. LCmd := FOPTSCommands.Add;
  2152. LCmd.Command := 'MODE Z'; {Do not localize}
  2153. LCmd.ExceptionReply.NumericCode := 501;
  2154. LCmd.OnCommand := CommandOptsMODEZ;
  2155. // OPTS UTF-8 <NLST>
  2156. LCmd := FOPTSCommands.Add;
  2157. LCmd.Command := 'UTF-8'; {Do not localize}
  2158. LCmd.ExceptionReply.NumericCode := 501;
  2159. LCmd.NormalReply.NumericCode := 200;
  2160. LCmd.OnCommand := CommandOptsUTF8;
  2161. // OPTS UTF8 <ON|OFF>
  2162. LCmd := FOPTSCommands.Add;
  2163. LCmd.Command := 'UTF8'; {Do not localize}
  2164. LCmd.ExceptionReply.NumericCode := 501;
  2165. LCmd.NormalReply.NumericCode := 200;
  2166. LCmd.OnCommand := CommandOptsUTF8;
  2167. //XAUT <SP> <xor encrypted data> <CRLF>
  2168. LCmd := CommandHandlers.Add;
  2169. LCmd.Command := 'XAUT'; {Do not Localize}
  2170. LCmd.OnCommand := CommandXAUT;
  2171. LCmd.Description.Text := 'Syntax: XAUT <sp> 2 <sp> <encrypted username and password>'; {do not localize}
  2172. end;
  2173. procedure TIdFTPServer.ContextCreated(AContext: TIdContext);
  2174. var
  2175. LContext : TIdFTPServerContext;
  2176. begin
  2177. LContext := AContext as TIdFTPServerContext;
  2178. // TODO: TIdFTPServerContext.Server is separate from TIdServerContext.Server.
  2179. // TIdFTPServerContext.Server should be removed and TIdFTPServerContext
  2180. // should be updated to return TIdServerContext.Server casted to TIdFTPServer...
  2181. LContext.Server := Self;
  2182. //from Before run method
  2183. LContext.FDataPort := 0;
  2184. LContext.FPasswordAttempts := 0;
  2185. LContext.FDataPortDenied := False;
  2186. LContext.FUserSecurity.Assign(FTPSecurityOptions);
  2187. if (DirFormat = ftpdfOSDependent) and (GOSType = otWindows) then begin
  2188. LContext.MSDOSMode := True;
  2189. end;
  2190. //
  2191. if mlsdUnixModes in FMLSDFacts then begin
  2192. LContext.MLSOpts := LContext.MLSOpts + [UnixMODE];
  2193. end;
  2194. if mlsdUnixOwner in FMLSDFacts then begin
  2195. LContext.MLSOpts := LContext.MLSOpts + [UnixOwner];
  2196. end;
  2197. if mlsdUnixGroup in FMLSDFacts then begin
  2198. LContext.MLSOpts := LContext.MLSOpts + [UnixGroup];
  2199. end;
  2200. if mlsdFileCreationTime in FMLSDFacts then begin
  2201. LContext.MLSOpts := LContext.MLSOpts + [CreateTime];
  2202. end;
  2203. if mlsdPerms in FMLSDFacts then begin
  2204. LContext.MLSOpts := LContext.MLSOpts + [Perm];
  2205. end;
  2206. if mlsdUniqueID in FMLSDFacts then begin
  2207. LContext.MLSOpts := LContext.MLSOpts + [Unique];
  2208. end;
  2209. if mlsdFileLastAccessTime in FMLSDFacts then begin
  2210. LContext.MLSOpts := LContext.MLSOpts + [LastAccessTime];
  2211. end;
  2212. if mlsdWin32Attributes in FMLSDFacts then begin
  2213. LContext.MLSOpts := LContext.MLSOpts + [WinAttribs];
  2214. end;
  2215. if mlsdWin32DriveType in FMLSDFacts then begin
  2216. LContext.MLSOpts := LContext.MLSOpts + [WinDriveType];
  2217. end;
  2218. if mlstWin32DriveLabel in FMLSDFacts then begin
  2219. LContext.MLSOpts := LContext.MLSOpts + [WinDriveLabel];
  2220. end;
  2221. //MS-DOS mode on for MS-DOS
  2222. if FDirFormat = ftpdfDOS then begin
  2223. LContext.FMSDOSMode := True;
  2224. end;
  2225. inherited ContextCreated(AContext);
  2226. end;
  2227. procedure TIdFTPServer.ListDirectory(ASender: TIdFTPServerContext; ADirectory: string;
  2228. ADirContents: TStrings; ADetails: Boolean; const ACmd : String = 'LIST';
  2229. const ASwitches : String = ''); {do not localize}
  2230. var
  2231. LDirectoryList: TIdFTPListOutput;
  2232. LPathSep: string;
  2233. LIsMLST: Boolean;
  2234. // under ARC, convert a weak reference to a strong reference before working with it
  2235. LFileSystem: TIdFTPBaseFilesystem;
  2236. begin
  2237. LIsMLST := PosInStrArray(ACmd, ['MLSD', 'MLST']) <> -1; {do not localize}
  2238. if (FDirFormat = ftpdfCustom) and (not LIsMLST) then begin
  2239. DoOnCustomListDirectory(ASender, ADirectory, ADirContents, ACmd, ASwitches);
  2240. Exit;
  2241. end;
  2242. LFileSystem := FFTPFileSystem;
  2243. if Assigned(FOnListDirectory) or Assigned(LFileSystem) then begin
  2244. LDirectoryList := TIdFTPListOutput.Create;
  2245. try
  2246. case FDirFormat of
  2247. ftpdfEPLF :
  2248. LDirectoryList.DirFormat := doEPLF;
  2249. ftpdfDOS :
  2250. if ASender.FMSDOSMode then begin
  2251. LDirectoryList.DirFormat := DoWin32;
  2252. end else begin
  2253. LDirectoryList.DirFormat := DoUnix;
  2254. end;
  2255. ftpdfOSDependent :
  2256. if (GOSType = otWindows) and (ASender.FMSDOSMode) then begin
  2257. LDirectoryList.DirFormat := DoWin32;
  2258. end else begin
  2259. LDirectoryList.DirFormat := DoUnix;
  2260. end;
  2261. else
  2262. LDirectoryList.DirFormat := DoUnix;
  2263. end;
  2264. //someone might be using the STAT -l to get a dir without a data channel
  2265. if IndyPos('l', ASwitches) > 0 then begin
  2266. LDirectoryList.Switches := LDirectoryList.Switches + 'l';
  2267. end;
  2268. //we do things this way because the 'a' and 'T' swithces only make sense
  2269. //when listing Unix dirs.
  2270. if SupportTaDirSwitches(ASender) then begin
  2271. if IndyPos('a', ASwitches) > 0 then begin
  2272. LDirectoryList.Switches := LDirectoryList.Switches + 'a';
  2273. end;
  2274. if IndyPos('T', ASwitches) > 0 then begin
  2275. LDirectoryList.Switches := LDirectoryList.Switches + 'T';
  2276. end;
  2277. end;
  2278. LDirectoryList.ExportTotalLine := True;
  2279. // TODO: use FTPPathSeparator here?
  2280. LPathSep := '/'; {Do not Localize}
  2281. if not TextEndsWith(ADirectory, LPathSep) then begin
  2282. ADirectory := ADirectory + LPathSep;
  2283. end;
  2284. if Assigned(LFileSystem) then begin
  2285. LFileSystem.ListDirectory(ASender, ADirectory, LDirectoryList, ACmd, ASwitches);
  2286. end else begin
  2287. FOnListDirectory(ASender, ADirectory, LDirectoryList, ACmd, ASwitches); // Event
  2288. end;
  2289. if LIsMLST then begin {Do not translate}
  2290. LDirectoryList.MLISTOutputDir(ADirContents, ASender.MLSOpts);
  2291. end
  2292. else if ADetails then begin
  2293. LDirectoryList.LISTOutputDir(ADirContents);
  2294. end else begin
  2295. LDirectoryList.NLISTOutputDir(ADirContents);
  2296. end;
  2297. finally
  2298. LDirectoryList.Free;
  2299. end;
  2300. end else begin
  2301. raise EIdFTPServerNoOnListDirectory.Create(RSFTPNoOnDirEvent); {Do not Localize}
  2302. end;
  2303. end;
  2304. procedure TIdFTPServer.SetUserAccounts(const AValue: TIdCustomUserManager);
  2305. var
  2306. // under ARC, convert a weak reference to a strong reference before working with it
  2307. LUserAccounts: TIdCustomUserManager;
  2308. begin
  2309. LUserAccounts := FUserAccounts;
  2310. if LUserAccounts <> AValue then begin
  2311. // under ARC, all weak references to a freed object get nil'ed automatically
  2312. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  2313. if Assigned(LUserAccounts) then begin
  2314. LUserAccounts.RemoveFreeNotification(Self);
  2315. end;
  2316. {$ENDIF}
  2317. FUserAccounts := AValue;
  2318. if Assigned(AValue) then begin
  2319. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  2320. AValue.FreeNotification(Self);
  2321. {$ENDIF}
  2322. FOnUserAccount := nil;
  2323. //XAUT can not work with an account manager that sends
  2324. //a challenge because that command is a USER/PASS rolled into
  2325. //one command.
  2326. if AValue.SendsChallange then begin
  2327. FSupportXAUTH := False;
  2328. end;
  2329. end;
  2330. end;
  2331. end;
  2332. // under ARC, all weak references to a freed object get nil'ed automatically
  2333. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  2334. procedure TIdFTPServer.SetFTPFileSystem(const AValue: TIdFTPBaseFileSystem);
  2335. begin
  2336. if FFTPFileSystem <> AValue then begin
  2337. if Assigned(FFTPFileSystem) then begin
  2338. FFTPFileSystem.RemoveFreeNotification(Self);
  2339. end;
  2340. FFTPFileSystem := AValue;
  2341. if Assigned(AValue) then begin
  2342. AValue.FreeNotification(Self);
  2343. end;
  2344. end;
  2345. end;
  2346. {$ENDIF}
  2347. procedure TIdFTPServer.SetReplyUnknownSITECommand(AValue: TIdReply);
  2348. begin
  2349. FReplyUnknownSITECommand.Assign(AValue);
  2350. end;
  2351. procedure TIdFTPServer.SetServerInfo(const AValue: TIdFTPServerIdentifier);
  2352. begin
  2353. FServerInfo.Assign(AValue);
  2354. end;
  2355. procedure TIdFTPServer.SetSITECommands(AValue: TIdCommandHandlers);
  2356. begin
  2357. FSITECommands.Assign(AValue);
  2358. end;
  2359. // under ARC, all weak references to a freed object get nil'ed automatically
  2360. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  2361. procedure TIdFTPServer.Notification(AComponent: TComponent; Operation: TOperation);
  2362. begin
  2363. if Operation = opRemove then begin
  2364. if AComponent = FUserAccounts then begin
  2365. FUserAccounts := nil;
  2366. end
  2367. else if AComponent = FFTPFileSystem then begin
  2368. FFTPFileSystem := nil;
  2369. end;
  2370. end;
  2371. inherited Notification(AComponent, Operation);
  2372. end;
  2373. {$ENDIF}
  2374. procedure TIdFTPServer.SetAnonymousAccounts(const AValue: TStrings);
  2375. begin
  2376. if Assigned(AValue) then begin
  2377. FAnonymousAccounts.Assign(AValue);
  2378. end;
  2379. end;
  2380. procedure TIdFTPServer.SetSupportXAUTH(AValue : Boolean);
  2381. var
  2382. // under ARC, convert a weak reference to a strong reference before working with it
  2383. LUserAccounts: TIdCustomUserManager;
  2384. begin
  2385. if FSupportXAUTH <> AValue then begin
  2386. LUserAccounts := FUserAccounts;
  2387. if Assigned(LUserAccounts) then begin
  2388. if LUserAccounts.SendsChallange then begin
  2389. Exit;
  2390. end;
  2391. end;
  2392. FSupportXAUTH := AValue;
  2393. end;
  2394. end;
  2395. procedure TIdFTPServer.ThreadException(AThread: TIdThread; AException: Exception);
  2396. begin
  2397. //we do not want to show an exception in a dialog-box
  2398. end;
  2399. //Command Replies/Handling
  2400. procedure TIdFTPServer.CommandUSER(ASender: TIdCommand);
  2401. var
  2402. LSafe: Boolean;
  2403. LChallenge: String;
  2404. LContext: TIdFTPServerContext;
  2405. // under ARC, convert a weak reference to a strong reference before working with it
  2406. LUserAccounts: TIdCustomUserManager;
  2407. begin
  2408. LChallenge := '';
  2409. LContext := ASender.Context as TIdFTPServerContext;
  2410. if (FUseTLS = utUseRequireTLS) and (LContext.AuthMechanism <> 'TLS') then begin {do not localize}
  2411. DisconUser(ASender);
  2412. Exit;
  2413. end;
  2414. LContext.Authenticated := False;
  2415. if (FAnonymousAccounts.IndexOf(LowerCase(ASender.UnparsedParams)) >= 0) and AllowAnonymousLogin then begin
  2416. LContext.UserType := utAnonymousUser;
  2417. LContext.Username := ASender.UnparsedParams;
  2418. ASender.Reply.SetReply(331, RSFTPAnonymousUserOkay);
  2419. end else begin
  2420. LContext.UserType := utNormalUser;
  2421. if ASender.UnparsedParams <> '' then begin
  2422. LContext.Username := ASender.UnparsedParams;
  2423. LUserAccounts := FUserAccounts;
  2424. if Assigned(LUserAccounts) then begin
  2425. LChallenge := LUserAccounts.ChallengeUser(LSafe, LContext.Username);
  2426. {$IFDEF USE_OBJECT_ARC}LUserAccounts := nil;{$ENDIF}
  2427. if not LSafe then begin
  2428. //we do this to prevent a potential race attack
  2429. DisconUser(ASender);
  2430. Exit;
  2431. end;
  2432. end;
  2433. if LChallenge = '' then begin
  2434. ASender.Reply.SetReply(331, RSFTPUserOkay);
  2435. end else begin
  2436. ASender.Reply.SetReply(331, LChallenge);
  2437. end;
  2438. end else begin
  2439. ASender.Reply.SetReply(332, RSFTPNeedAccountForLogin);
  2440. end;
  2441. end;
  2442. end;
  2443. procedure TIdFTPServer.AuthenticateUser(ASender: TIdCommand);
  2444. var
  2445. LValidated: Boolean;
  2446. LContext: TIdFTPServerContext;
  2447. // under ARC, convert a weak reference to a strong reference before working with it
  2448. LUserAccounts: TIdCustomUserManager;
  2449. begin
  2450. LContext := ASender.Context as TIdFTPServerContext;
  2451. try
  2452. LContext.FAuthenticated := False;
  2453. case LContext.FUserType of
  2454. utAnonymousUser:
  2455. begin
  2456. LValidated := LContext.Password <> '';
  2457. if FAnonymousPassStrictCheck and LValidated then begin
  2458. LValidated := False;
  2459. if FindFirstOf('@.', LContext.Password) > 0 then begin {Do not Localize}
  2460. LValidated := True;
  2461. end;
  2462. end;
  2463. if LValidated then begin
  2464. LContext.FAuthenticated := True;
  2465. ASender.Reply.SetReply(230, RSFTPAnonymousUserLogged);
  2466. if Assigned(OnLoginSuccessBanner) then begin
  2467. OnLoginSuccessBanner(LContext, ASender.Reply);
  2468. ASender.Reply.SetReply(230, ASender.Reply.Text.Text);
  2469. end;
  2470. LContext.FPasswordAttempts := 0;
  2471. end else begin
  2472. LContext.FUserType := utNone;
  2473. LContext.FAuthenticated := False;
  2474. LContext.FPassword := ''; {Do not Localize}
  2475. Inc(LContext.FPasswordAttempts);
  2476. if LContext.UserSecurity.InvalidPassDelay > 0 then begin
  2477. //Delay our error response to slow down a dictionary attack
  2478. IndySleep(FFTPSecurityOptions.InvalidPassDelay);
  2479. end;
  2480. if (LContext.UserSecurity.PasswordAttempts > 0) and
  2481. (LContext.FPasswordAttempts >= LContext.UserSecurity.PasswordAttempts) then begin
  2482. DisconUser(ASender);
  2483. Exit;
  2484. end;
  2485. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  2486. end;
  2487. end;
  2488. utNormalUser:
  2489. begin
  2490. LUserAccounts := FUserAccounts;
  2491. if Assigned(LUserAccounts) then begin
  2492. LContext.FAuthenticated := LUserAccounts.AuthenticateUser(LContext.FUsername, ASender.UnparsedParams);
  2493. {$IFDEF USE_OBJECT_ARC}LUserAccounts := nil;{$ENDIF}
  2494. if LContext.FAuthenticated then begin
  2495. LContext.FPasswordAttempts := 0;
  2496. ASender.Reply.SetReply(230, RSFTPUserLogged);
  2497. end else begin
  2498. LContext.FPassword := ''; {Do not Localize}
  2499. Inc(LContext.FPasswordAttempts);
  2500. if LContext.UserSecurity.InvalidPassDelay > 0 then begin
  2501. //Delay our error response to slow down a dictionary attack
  2502. IndySleep(LContext.UserSecurity.InvalidPassDelay);
  2503. end;
  2504. if (LContext.UserSecurity.PasswordAttempts > 0) and
  2505. (LContext.FPasswordAttempts >= LContext.UserSecurity.PasswordAttempts) then
  2506. begin
  2507. //Max login attempts exceeded, close the connection
  2508. DisconUser(ASender);
  2509. Exit;
  2510. end;
  2511. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  2512. end;
  2513. end
  2514. else if Assigned(FOnUserLogin) then begin
  2515. LValidated := False;
  2516. FOnUserLogin(LContext, LContext.FUsername, LContext.Password, LValidated);
  2517. LContext.FAuthenticated := LValidated;
  2518. if LValidated then begin
  2519. if (LContext.AccountNeeded = True) and Assigned(FOnUserAccount) then begin
  2520. LContext.FAuthenticated := False;
  2521. ASender.Reply.SetReply(332,'Need account for login.');
  2522. Exit;
  2523. end else begin
  2524. LContext.FAuthenticated := LValidated;
  2525. ASender.Reply.SetReply(230, RSFTPUserLogged);
  2526. if Assigned(OnLoginSuccessBanner) then begin
  2527. OnLoginSuccessBanner(LContext, ASender.Reply);
  2528. ASender.Reply.SetReply(230, ASender.Reply.Text.Text);
  2529. end;
  2530. LContext.FPasswordAttempts := 0;
  2531. end;
  2532. end else begin
  2533. LContext.FPassword := ''; {Do not Localize}
  2534. Inc(LContext.FPasswordAttempts);
  2535. if (LContext.UserSecurity.PasswordAttempts > 0) and
  2536. (LContext.FPasswordAttempts >= LContext.UserSecurity.PasswordAttempts) then begin
  2537. //Max login attempts exceeded, close the connection
  2538. DisconUser(ASender);
  2539. Exit;
  2540. end;
  2541. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  2542. end;
  2543. end else begin
  2544. //APR 020423
  2545. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn); // user manager not found
  2546. end;
  2547. end;
  2548. else
  2549. ASender.Reply.SetReply(503, RSFTPNeedLoginWithUser);
  2550. end;//case
  2551. except
  2552. on E : Exception do begin
  2553. ASender.Reply.SetReply(503, E.Message);
  2554. end;
  2555. end;
  2556. //After login
  2557. if LContext.FAuthenticated and Assigned(FOnAfterUserLogin) then begin
  2558. FOnAfterUserLogin(LContext);
  2559. end;
  2560. end;
  2561. procedure TIdFTPServer.CommandPASS(ASender: TIdCommand);
  2562. var
  2563. LContext: TIdFTPServerContext;
  2564. begin
  2565. LContext := ASender.Context as TIdFTPServerContext;
  2566. if (FUseTLS = utUseRequireTLS) and (LContext.AuthMechanism <> 'TLS') then begin {do not localize}
  2567. DisconUser(ASender);
  2568. Exit;
  2569. end;
  2570. LContext.FAuthenticated := False;
  2571. LContext.FPassword := ASender.UnparsedParams;
  2572. AuthenticateUser(ASender);
  2573. end;
  2574. procedure TIdFTPServer.CommandXAUT(ASender : TIdCommand);
  2575. var
  2576. LContext : TIdFTPServerContext;
  2577. s : String;
  2578. LPos : Integer;
  2579. begin
  2580. LContext := ASender.Context as TIdFTPServerContext;
  2581. if (FUseTLS = utUseRequireTLS) and (LContext.AuthMechanism <> 'TLS') then begin {do not localize}
  2582. DisconUser(ASender);
  2583. Exit;
  2584. end;
  2585. LContext := ASender.Context as TIdFTPServerContext;
  2586. s := ASender.UnparsedParams;
  2587. s := IdFTPCommon.ExtractAutInfoFromXAUT(s, LContext.FXAUTKey );
  2588. LPos := RPos(':',s);
  2589. if LPos > 1 then begin
  2590. LContext.Username := Copy(s,1,LPos - 1);
  2591. s := Copy(s,LPos + 1,$FF);
  2592. //for some reason, WS-FTP Pro likes to add the string "^vta4r2" to
  2593. //the authentication information if you aren't using anonymous login.
  2594. //I'm not sure what the significance of "^vta4r2" really is.
  2595. // 1234567
  2596. if TextEndsWith(s,'^vta4r2') then begin
  2597. LContext.Password := Copy(s,1,Length(s)-7);
  2598. end;
  2599. end else begin
  2600. LContext.Username := s;
  2601. LContext.Password := '';
  2602. end;
  2603. LContext.Authenticated := False;
  2604. if (FAnonymousAccounts.IndexOf(LowerCase(ASender.UnparsedParams)) >= 0) and AllowAnonymousLogin then begin
  2605. LContext.UserType := utAnonymousUser;
  2606. end else begin
  2607. LContext.UserType := utNormalUser;
  2608. end;
  2609. AuthenticateUser(ASender);
  2610. end;
  2611. procedure TIdFTPServer.CommandACCT(ASender: TIdCommand);
  2612. var
  2613. LContext : TIdFTPServerContext;
  2614. LValidated : Boolean;
  2615. begin
  2616. LValidated := False;
  2617. if Assigned(FOnUserAccount) then begin
  2618. LContext := ASender.Context as TIdFTPServerContext;
  2619. LContext.Account := ASender.UnparsedParams;
  2620. FOnUserAccount(LContext,LContext.Username, LContext.Password, LContext.Account, LValidated);
  2621. LContext.Authenticated := LValidated;
  2622. if LValidated then begin
  2623. LContext.AccountNeeded := False;
  2624. ASender.Reply.SetReply(230, RSFTPUserLogged);
  2625. if Assigned(OnLoginSuccessBanner) then begin
  2626. OnLoginSuccessBanner(LContext, ASender.Reply);
  2627. ASender.Reply.SetReply(230, ASender.Reply.Text.Text);
  2628. LContext.PasswordAttempts := 0;
  2629. end;
  2630. end else begin
  2631. LContext.FPassword := ''; {Do not Localize}
  2632. Inc(LContext.FPasswordAttempts);
  2633. if (LContext.UserSecurity.PasswordAttempts > 0) and
  2634. (LContext.FPasswordAttempts >= LContext.UserSecurity.PasswordAttempts) then begin
  2635. //Max login attempts exceeded, close the connection
  2636. DisconUser(ASender);
  2637. Exit;
  2638. end;
  2639. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  2640. end;
  2641. end else begin
  2642. ASender.Reply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['ACCT'])); {do not localize}
  2643. end;
  2644. end;
  2645. procedure TIdFTPServer.CommandCWD(ASender: TIdCommand);
  2646. var
  2647. s: TIdFTPFileName;
  2648. LContext : TIdFTPServerContext;
  2649. // under ARC, convert a weak reference to a strong reference before working with it
  2650. LFileSystem: TIdFTPBaseFilesystem;
  2651. begin
  2652. LContext := ASender.Context as TIdFTPServerContext;
  2653. s := ASender.UnparsedParams;
  2654. if LContext.IsAuthenticated(ASender) then begin
  2655. s := IgnoreLastPathDelim(s);
  2656. LFileSystem := FFTPFileSystem;
  2657. if Assigned(OnChangeDirectory) or Assigned(LFileSystem) then begin
  2658. if s = '..' then begin {do not localize}
  2659. s := CDUPDir(LContext);
  2660. end
  2661. else if s = '.' then begin {do not localize}
  2662. s := LContext.CurrentDir;
  2663. end else begin
  2664. s := DoProcessPath(LContext, s);
  2665. end;
  2666. s := RemoveDuplicatePathSyms(s);
  2667. DoOnChangeDirectory(LContext, s);
  2668. LContext.CurrentDir := s;
  2669. CmdCommandSuccessful(ASender);
  2670. end else begin
  2671. CmdNotImplemented(ASender);
  2672. end;
  2673. end;
  2674. end;
  2675. procedure TIdFTPServer.CommandCDUP(ASender: TIdCommand);
  2676. var
  2677. s: TIdFTPFileName;
  2678. LContext : TIdFTPServerContext;
  2679. // under ARC, convert a weak reference to a strong reference before working with it
  2680. LFileSystem: TIdFTPBaseFileSystem;
  2681. begin
  2682. LContext := ASender.Context as TIdFTPServerContext;
  2683. if LContext.IsAuthenticated(ASender) then begin
  2684. s := CDUPDir(LContext);
  2685. s := DoProcessPath(LContext, s);
  2686. LFileSystem := FFTPFileSystem;
  2687. if Assigned(FOnChangeDirectory) or Assigned(LFileSystem) then begin
  2688. DoOnChangeDirectory(LContext, s);
  2689. LContext.FCurrentDir := s;
  2690. ASender.Reply.SetReply(250, IndyFormat(RSFTPCurrentDirectoryIs, [LContext.FCurrentDir]));
  2691. end else begin
  2692. CmdNotImplemented(ASender);
  2693. end;
  2694. end;
  2695. end;
  2696. procedure TIdFTPServer.CommandREIN(ASender: TIdCommand);
  2697. var
  2698. LIO : TIdSSLIOHandlerSocketBase;
  2699. LContext : TIdFTPServerContext;
  2700. begin
  2701. LContext := ASender.Context as TIdFTPServerContext;
  2702. if LContext.IsAuthenticated(ASender) then begin
  2703. LContext.ReInitialize;
  2704. LContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
  2705. ASender.Reply.SetReply(220, RSFTPServiceOpen);
  2706. if (FUseTLS in ExplicitTLSVals) then begin
  2707. LIO := ASender.Context.Connection.IOHandler as TIdSSLIOHandlerSocketBase;
  2708. if not LIO.PassThrough then begin
  2709. LIO.PassThrough := True;
  2710. end;
  2711. LContext.FCCC := False;
  2712. end;
  2713. end;
  2714. end;
  2715. procedure TIdFTPServer.CommandPORT(ASender: TIdCommand);
  2716. var
  2717. LLo, LHi : Integer;
  2718. LPort: TIdPort;
  2719. LParm, LIP : string;
  2720. LContext : TIdFTPServerContext;
  2721. LDataChannel: TIdTCPClient;
  2722. begin
  2723. LContext := ASender.Context as TIdFTPServerContext;
  2724. if LContext.IsAuthenticated(ASender) then begin
  2725. if LContext.FEPSVAll then begin
  2726. ASender.Reply.SetReply(501, IndyFormat(RSFTPNotAllowedAfterEPSVAll, [ASender.CommandHandler.Command]));
  2727. Exit;
  2728. end;
  2729. if LContext.UserSecurity.BlockAllPORTTransfers then
  2730. begin
  2731. LContext.FDataPort := 0;
  2732. LContext.FDataPortDenied := True;
  2733. ASender.Reply.SetReply(502, RSFTPPORTDisabled);
  2734. Exit;
  2735. end;
  2736. LContext.FPASV := False;
  2737. LParm := ASender.UnparsedParams;
  2738. LIP := ''; {Do not Localize}
  2739. { h1 }
  2740. LIP := LIP + Fetch(LParm, ',') + '.'; {Do not Localize}
  2741. { h2 }
  2742. LIP := LIP + Fetch(LParm, ',') + '.'; {Do not Localize}
  2743. { h3 }
  2744. LIP := LIP + Fetch(LParm, ',') + '.'; {Do not Localize}
  2745. { h4 }
  2746. LIP := LIP + Fetch(LParm, ','); {Do not Localize}
  2747. { p1 }
  2748. LLo := IndyStrToInt(Fetch(LParm, ',')); {Do not Localize}
  2749. { p2 }
  2750. LHi := IndyStrToInt(LParm);
  2751. LPort := TIdPort((LLo * 256) + LHi);
  2752. if LContext.UserSecurity.NoReservedRangePORT and
  2753. ((LPort > 0) and (LPort <= 1024)) then begin
  2754. LContext.FDataPort := 0;
  2755. LContext.FDataPortDenied := True;
  2756. ASender.Reply.SetReply(504, RSFTPPORTRange);
  2757. Exit;
  2758. end;
  2759. {//BGO}
  2760. if LContext.UserSecurity.FRequirePORTFromSameIP and
  2761. (LIP <> LContext.Binding.PeerIP) then begin
  2762. LContext.FDataPort := 0;
  2763. LContext.FDataPortDenied := True;
  2764. ASender.Reply.SetReply(504, RSFTPSameIPAddress);
  2765. Exit;
  2766. end;
  2767. {//BGO}
  2768. LContext.CreateDataChannel(False);
  2769. LDataChannel := TIdTCPClient(LContext.FDataChannel.FDataChannel);
  2770. LDataChannel.Host := LIP;
  2771. LDataChannel.Port := LPort;
  2772. LDataChannel.IPVersion := Id_IPv4;
  2773. LContext.FDataPort := LPort;
  2774. LContext.FDataPortDenied := False;
  2775. CmdCommandSuccessful(ASender, 200);
  2776. end;
  2777. end;
  2778. procedure TIdFTPServer.CommandPASV(ASender: TIdCommand);
  2779. var
  2780. LParam: string;
  2781. LBPort: Word;
  2782. LIPVersion : TIdIPVersion;
  2783. begin
  2784. //InternalPASV does all of the checking
  2785. if InternalPASV(ASender, LParam, LBPort, LIPVersion) then begin
  2786. DoOnPASVReply(TIdFTPServerContext(ASender.Context), LParam, LBPort, LIPVersion);
  2787. LParam := ReplaceAll(LParam, '.', ','); {Do not Localize}
  2788. LParam := LParam + ',' + IntToStr(LBPort div 256) + ',' + IntToStr(LBPort mod 256); {Do not Localize}
  2789. ASender.Reply.SetReply(227, IndyFormat(RSFTPPassiveMode, [LParam]));
  2790. end;
  2791. end;
  2792. procedure TIdFTPServer.CommandTYPE(ASender: TIdCommand);
  2793. var
  2794. LContext : TIdFTPServerContext;
  2795. s: string;
  2796. begin
  2797. LContext := ASender.Context as TIdFTPServerContext;
  2798. if LContext.IsAuthenticated(ASender) then begin
  2799. s := ASender.UnparsedParams;
  2800. s := UpperCase(Fetch(s));
  2801. if Length(s) = 1 then begin
  2802. //Default data type is ASCII
  2803. case s[1] of
  2804. 'A': LContext.FDataType := ftASCII; {Do not Localize}
  2805. 'I': LContext.FDataType := ftBinary; {Do not Localize}
  2806. else Exit;
  2807. end;
  2808. ASender.Reply.SetReply(200, IndyFormat(RSFTPTYPEChanged, [s]));
  2809. end;
  2810. end;
  2811. end;
  2812. procedure TIdFTPServer.CommandSTRU(ASender: TIdCommand);
  2813. var
  2814. LContext : TIdFTPServerContext;
  2815. s: String;
  2816. begin
  2817. LContext := ASender.Context as TIdFTPServerContext;
  2818. if LContext.IsAuthenticated(ASender) then begin
  2819. s := ASender.UnparsedParams;
  2820. s := UpperCase(Fetch(s));
  2821. if Length(s) = 1 then begin
  2822. //Default structure is file
  2823. case s[1] of
  2824. 'F': LContext.FDataStruct := dsFile; {Do not Localize}
  2825. 'R': LContext.FDataStruct := dsRecord; {Do not Localize}
  2826. 'P': LContext.FDataStruct := dsPage; {Do not Localize}
  2827. else Exit;
  2828. end;
  2829. ASender.Reply.SetReply(200, IndyFormat(RSFTPSTRUChanged, [s]));
  2830. end;
  2831. end;
  2832. end;
  2833. procedure TIdFTPServer.CommandMODE(ASender: TIdCommand);
  2834. var
  2835. LContext : TIdFTPServerContext;
  2836. s: String;
  2837. begin
  2838. LContext := TIdFTPServerContext(ASender.Context);
  2839. if LContext.IsAuthenticated(ASender) then begin
  2840. s := ASender.UnparsedParams;
  2841. s := UpperCase(Fetch(s));
  2842. if Length(s) = 1 then begin
  2843. //Default data mode is stream
  2844. case s[1] of
  2845. 'S' : //stream mode
  2846. begin
  2847. LContext.DataMode := dmStream;
  2848. ASender.Reply.SetReply(200, IndyFormat(RSFTPMODEChanged, [s]));
  2849. Exit;
  2850. end;
  2851. 'Z' : //deflate
  2852. begin
  2853. if Assigned(FCompressor) then begin
  2854. LContext.DataMode := dmDeflate;
  2855. ASender.Reply.SetReply(200, IndyFormat(RSFTPMODEChanged, [s]));
  2856. Exit;
  2857. end;
  2858. end;
  2859. end;
  2860. ASender.Reply.SetReply(504, RSFTPMODENotSupported);
  2861. end;
  2862. end;
  2863. end;
  2864. procedure TIdFTPServer.CommandRETR(ASender: TIdCommand);
  2865. var
  2866. s: string;
  2867. LStream: TStream;
  2868. LContext : TIdFTPServerContext;
  2869. // under ARC, convert a weak reference to a strong reference before working with it
  2870. LFileSystem: TIdFTPBaseFileSystem;
  2871. begin
  2872. LContext := ASender.Context as TIdFTPServerContext;
  2873. if LContext.IsAuthenticated(ASender) then begin
  2874. if (not Assigned(LContext.FDataChannel)) or LContext.FDataPortDenied then begin
  2875. ASender.Reply.SetReply(425, RSFTPCantOpenData);
  2876. Exit;
  2877. end;
  2878. //TODO: Fix reference to /
  2879. s := DoProcessPath(LContext, ASender.UnparsedParams);
  2880. LFileSystem := FFTPFileSystem;
  2881. if Assigned(FOnRetrieveFile) or Assigned(LFileSystem) then begin
  2882. LStream := nil;
  2883. try
  2884. //some file stream creations can fail with an exception so
  2885. //we need to handle this gracefully.
  2886. if Assigned(LFileSystem) then begin
  2887. LFileSystem.RetrieveFile(LContext, s, LStream)
  2888. end else begin
  2889. FOnRetrieveFile(LContext, s, LStream);
  2890. end;
  2891. except
  2892. on E : Exception do begin
  2893. LContext.KillDataChannel;
  2894. ASender.Reply.SetReply(550, E.Message);
  2895. Exit;
  2896. end;
  2897. end;
  2898. if Assigned(LStream) then begin
  2899. try
  2900. LStream.Position := LContext.FRESTPos;
  2901. LContext.FRESTPos := 0;
  2902. //it should be safe to assume that the FDataChannel object exists because
  2903. //we checked it earlier
  2904. LContext.FDataChannel.FFtpOperation := ftpRetr;
  2905. LContext.FDataChannel.FData := LStream;
  2906. LContext.FDataChannel.OKReply.SetReply(226, RSFTPDataConnClosed);
  2907. LContext.FDataChannel.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  2908. ASender.Reply.SetReply(150, RSFTPDataConnToOpen);
  2909. ASender.SendReply;
  2910. DoDataChannelOperation(ASender, LContext.SSCNOn);
  2911. finally
  2912. LStream.Free;
  2913. end;
  2914. end else begin
  2915. //make sure the data connection is closed
  2916. LContext.KillDataChannel;
  2917. CmdFileActionAborted(ASender);
  2918. end;
  2919. end else begin
  2920. //make sure the data connection is closed
  2921. LContext.KillDataChannel;
  2922. CmdNotImplemented(ASender);
  2923. end;
  2924. end;
  2925. end;
  2926. procedure TIdFTPServer.CommandSSAP(ASender: TIdCommand);
  2927. var
  2928. LStream: TStream;
  2929. LTmp1: string;
  2930. LAppend: Boolean;
  2931. LContext : TIdFTPServerContext;
  2932. // under ARC, convert a weak reference to a strong reference before working with it
  2933. LFileSystem: TIdFTPBaseFileSystem;
  2934. begin
  2935. LContext := ASender.Context as TIdFTPServerContext;
  2936. if LContext.IsAuthenticated(ASender) then begin
  2937. if (not Assigned(LContext.FDataChannel)) or LContext.FDataPortDenied then begin
  2938. ASender.Reply.SetReply(425, RSFTPCantOpenData);
  2939. Exit;
  2940. end;
  2941. if TextIsSame(ASender.CommandHandler.Command, 'STOU') then begin {Do not Localize}
  2942. LTmp1 := GetUniqueFileName('', 'Temp', ''); {Do not localize}
  2943. //This is a standardized format
  2944. ASender.Reply.SetReply(150, IndyFormat('FILE: %s', [LTmp1])); {Do not translate}
  2945. end else begin
  2946. LTmp1 := ASender.UnparsedParams;
  2947. ASender.Reply.SetReply(150, RSFTPDataConnToOpen);
  2948. end;
  2949. LTmp1 := DoProcessPath(LContext, LTmp1);
  2950. LAppend := TextIsSame(ASender.CommandHandler.Command, 'APPE'); {Do not Localize}
  2951. LFileSystem := FFTPFileSystem;
  2952. if Assigned(FOnStoreFile) or Assigned(LFileSystem) then begin
  2953. LStream := nil;
  2954. try
  2955. if Assigned(LFileSystem) then begin
  2956. LFileSystem.StoreFile(LContext, LTmp1, LAppend, LStream);
  2957. {$IFDEF USE_OBJECT_ARC}LFileSystem := nil;{$ENDIF}
  2958. end else begin
  2959. FOnStoreFile(LContext, LTmp1, LAppend, LStream);
  2960. end;
  2961. except
  2962. on E : Exception do
  2963. begin
  2964. ASender.Reply.SetReply(550, E.Message);
  2965. LContext.KillDataChannel;
  2966. Exit;
  2967. end;
  2968. end;
  2969. if Assigned(LStream) then begin
  2970. try
  2971. //Issued previously by ALLO cmd
  2972. if LContext.ALLOSize > 0 then begin
  2973. LStream.Size := LContext.FALLOSize;
  2974. end;
  2975. if LAppend then begin
  2976. LStream.Seek(0, soEnd);
  2977. end else begin
  2978. LStream.Position := LContext.FRESTPos;
  2979. LContext.FRESTPos := 0;
  2980. end;
  2981. { Data transfer }
  2982. //it should be safe to assume that the FDataChannel object exists because
  2983. //we checked it earlier
  2984. LContext.FDataChannel.FFtpOperation := ftpStor;
  2985. LContext.FDataChannel.Data := LStream;
  2986. LContext.FDataChannel.OKReply.SetReply(226, RSFTPDataConnClosed);
  2987. LContext.FDataChannel.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  2988. ASender.SendReply;
  2989. DoDataChannelOperation(ASender, LContext.SSCNOn);
  2990. finally
  2991. LStream.Free;
  2992. end;
  2993. end else begin
  2994. //make sure the data connection is closed
  2995. LContext.KillDataChannel;
  2996. CmdFileActionAborted(ASender);
  2997. end;
  2998. end else begin
  2999. //make sure the data connection is closed
  3000. LContext.KillDataChannel;
  3001. CmdNotImplemented(ASender);
  3002. end;
  3003. end;
  3004. end;
  3005. procedure TIdFTPServer.CommandALLO(ASender: TIdCommand);
  3006. var
  3007. LContext: TIdFTPServerContext;
  3008. LALLOSize, s: string;
  3009. begin
  3010. LContext := TIdFTPServerContext(ASender.Context);
  3011. if LContext.IsAuthenticated(ASender) then begin
  3012. LALLOSize := '';
  3013. if ASender.UnparsedParams <> '' then begin
  3014. if TextStartsWith(ASender.UnparsedParams, 'R ') then begin {Do not localize}
  3015. LALLOSize := TrimLeft(Copy(s, 3, MaxInt));
  3016. end else begin
  3017. LALLOSize := TrimLeft(ASender.UnparsedParams);
  3018. end;
  3019. LALLOSize := Fetch(LALLOSize);
  3020. end;
  3021. if LALLOSize <> '' then begin
  3022. LContext.FALLOSize := IndyStrToInt(LALLOSize, 0);
  3023. CmdCommandSuccessful(ASender, 200);
  3024. end else begin
  3025. ASender.Reply.SetReply(504, RSFTPInvalidForParam);
  3026. end;
  3027. end;
  3028. end;
  3029. procedure TIdFTPServer.CommandREST(ASender: TIdCommand);
  3030. var
  3031. LContext: TIdFTPServerContext;
  3032. begin
  3033. LContext := TIdFTPServerContext(ASender.Context);
  3034. if LContext.IsAuthenticated(ASender) then begin
  3035. LContext.FRESTPos := IndyStrToInt(ASender.UnparsedParams, 0);
  3036. ASender.Reply.SetReply(350, RSFTPFileActionPending);
  3037. end;
  3038. end;
  3039. procedure TIdFTPServer.CommandRNFR(ASender: TIdCommand);
  3040. var
  3041. LContext: TIdFTPServerContext;
  3042. s: string;
  3043. begin
  3044. LContext := TIdFTPServerContext(ASender.Context);
  3045. if LContext.IsAuthenticated(ASender) then begin
  3046. s := ASender.UnparsedParams;
  3047. if Assigned(FOnRenameFile) or Assigned(FTPFileSystem) then begin
  3048. ASender.Reply.SetReply(350, RSFTPFileActionPending);
  3049. LContext.FRNFR := DoProcessPath(TIdFTPServerContext(LContext), s);
  3050. end else begin
  3051. CmdNotImplemented(ASender);
  3052. end;
  3053. end;
  3054. end;
  3055. procedure TIdFTPServer.CommandRNTO(ASender: TIdCommand);
  3056. var
  3057. s: string;
  3058. LContext : TIdFTPServerContext;
  3059. // under ARC, convert a weak reference to a strong reference before working with it
  3060. LFileSystem: TIdFTPBaseFileSystem;
  3061. begin
  3062. LContext := ASender.Context as TIdFTPServerContext;
  3063. if LContext.IsAuthenticated(ASender) then begin
  3064. s := ASender.UnparsedParams;
  3065. LFileSystem := FFTPFileSystem;
  3066. if Assigned(LFileSystem) or Assigned(FOnRenameFile) then begin
  3067. DoOnRenameFile(LContext, LContext.FRNFR, DoProcessPath(LContext, s));
  3068. ASender.Reply.NumericCode := 250;
  3069. end else begin
  3070. CmdNotImplemented(ASender);
  3071. end;
  3072. end;
  3073. end;
  3074. procedure TIdFTPServer.CommandABOR(ASender: TIdCommand);
  3075. var
  3076. LContext: TIdFTPServerContext;
  3077. begin
  3078. LContext := TIdFTPServerContext(ASender.Context);
  3079. if LContext.IsAuthenticated(ASender) then begin
  3080. if Assigned(LContext.FDataChannel) then begin
  3081. if not LContext.FDataChannel.Stopped then begin
  3082. LContext.FDataChannel.OkReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  3083. LContext.FDataChannel.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  3084. LContext.KillDataChannel;
  3085. ASender.Reply.SetReply(226, RSFTPDataConnClosed);
  3086. Exit;
  3087. end;
  3088. end;
  3089. CmdCommandSuccessful(ASender, 226);
  3090. end;
  3091. end;
  3092. procedure TIdFTPServer.CommandDELE(ASender: TIdCommand);
  3093. var
  3094. LContext : TIdFTPServerContext;
  3095. // under ARC, convert a weak reference to a strong reference before working with it
  3096. LFileSystem: TIdFTPBaseFileSystem;
  3097. (*
  3098. DELE <SP> <pathname> <CRLF>
  3099. 250 Requested file action okay, completed.
  3100. 450 Requested file action not taken. - File is busy
  3101. 550 Requested action not taken. - File unavailable, no access permitted, etc
  3102. 500 Syntax error, command unrecognized.
  3103. 501 Syntax error in parameters or arguments.
  3104. 502 Command not implemented.
  3105. 421 Service not available, closing control connection. - During server shutdown, etc
  3106. 530 Not logged in.
  3107. *)
  3108. //TODO: Need to set replies when not authenticated and set NormalReply to 250
  3109. // do for all procs, list valid replies in comments. Or maybe default is 550
  3110. begin
  3111. LContext := ASender.Context as TIdFTPServerContext;
  3112. if LContext.IsAuthenticated(ASender) then begin
  3113. LFileSystem := FTPFileSystem;
  3114. if Assigned(FOnDeleteFile) or Assigned(LFileSystem) then begin
  3115. DoOnDeleteFile(LContext, DoProcessPath(LContext, ASender.UnparsedParams));
  3116. ASender.Reply.SetReply(250, RSFTPFileActionCompleted);
  3117. end else begin
  3118. CmdNotImplemented(ASender);
  3119. end;
  3120. end else begin
  3121. ASender.Reply.SetReply(550, RSFTPFileActionNotTaken);
  3122. end;
  3123. end;
  3124. procedure TIdFTPServer.CommandRMD(ASender: TIdCommand);
  3125. var
  3126. s: TIdFTPFileName;
  3127. LContext : TIdFTPServerContext;
  3128. // under ARC, convert a weak reference to a strong reference before working with it
  3129. LFileSystem: TIdFTPBaseFileSystem;
  3130. begin
  3131. LContext := ASender.Context as TIdFTPServerContext;
  3132. if LContext.IsAuthenticated(ASender) then begin
  3133. S := IgnoreLastPathDelim(S);
  3134. s := DoProcessPath(LContext, ASender.UnparsedParams);
  3135. LFileSystem := FFTPFileSystem;
  3136. if Assigned(LFileSystem) or Assigned(FOnRemoveDirectory) then begin
  3137. DoOnRemoveDirectory(LContext, s);
  3138. ASender.Reply.SetReply(250, RSFTPFileActionCompleted);
  3139. end else begin
  3140. CmdNotImplemented(ASender);
  3141. end;
  3142. end;
  3143. end;
  3144. procedure TIdFTPServer.CommandMKD(ASender: TIdCommand);
  3145. var
  3146. S: TIdFTPFileName;
  3147. LContext : TIdFTPServerContext;
  3148. begin
  3149. LContext := ASender.Context as TIdFTPServerContext;
  3150. if LContext.IsAuthenticated(ASender) then begin
  3151. S := IgnoreLastPathDelim(S);
  3152. S := DoProcessPath(LContext, ASender.UnparsedParams);
  3153. DoOnMakeDirectory(LContext, s);
  3154. ASender.Reply.SetReply(257, RSFTPFileActionCompleted);
  3155. end;
  3156. end;
  3157. procedure TIdFTPServer.CommandPWD(ASender: TIdCommand);
  3158. var
  3159. LContext: TIdFTPServerContext;
  3160. begin
  3161. LContext := TIdFTPServerContext(ASender.Context);
  3162. if LContext.IsAuthenticated(ASender) then begin
  3163. ASender.Reply.SetReply(257, IndyFormat(RSFTPCurrentDirectoryIs, [LContext.FCurrentDir]));
  3164. end;
  3165. end;
  3166. procedure TIdFTPServer.CommandLIST(ASender: TIdCommand);
  3167. var
  3168. LStream: TStringList;
  3169. LSendData : Boolean;
  3170. LPath, LSwitches : String;
  3171. LContext : TIdFTPServerContext;
  3172. function DeletRSwitch(const AString : String): String;
  3173. var
  3174. i : Integer;
  3175. begin
  3176. Result := '';
  3177. for i := 1 to Length(AString) do begin
  3178. if AString[i] <> 'R' then begin
  3179. Result := Result + AString[i];
  3180. end;
  3181. end;
  3182. end;
  3183. begin
  3184. LSendData := False;
  3185. LContext := ASender.Context as TIdFTPServerContext;
  3186. if LContext.IsAuthenticated(ASender) then begin
  3187. if (not Assigned(LContext.FDataChannel)) or LContext.FDataPortDenied then begin
  3188. ASender.Reply.SetReply(425, RSFTPCantOpenData);
  3189. Exit;
  3190. end;
  3191. if (not Assigned(FOnListDirectory)) and
  3192. ((FDirFormat = ftpdfCustom) and (not Assigned(FOnCustomListDirectory))) then begin
  3193. LContext.KillDataChannel;
  3194. CmdNotImplemented(ASender);
  3195. Exit;
  3196. end;
  3197. LStream := TStringList.Create;
  3198. try
  3199. LSwitches := '';
  3200. LPath := ASender.UnparsedParams;
  3201. if TextStartsWith(LPath, '-') then begin {Do not Localize}
  3202. LSwitches := Fetch(LPath);
  3203. end;
  3204. //we can't support recursive lists with EPLF
  3205. if DirFormat = ftpdfEPLF then begin
  3206. LSwitches := DeletRSwitch(LSwitches);
  3207. end;
  3208. ListDirectory(LContext, DoProcessPath(LContext, LPath), LStream,
  3209. TextIsSame(ASender.CommandHandler.Command, 'LIST'), ASender.CommandHandler.Command,
  3210. LSwitches);
  3211. LSendData := True;
  3212. finally
  3213. try
  3214. if LSendData then begin
  3215. //it should be safe to assume that the FDataChannel object exists because
  3216. //we checked it earlier
  3217. LContext.FDataChannel.Data := LStream;
  3218. LContext.FDataChannel.FFtpOperation := ftpRetr;
  3219. LContext.FDataChannel.OKReply.SetReply(226, RSFTPDataConnClosed);
  3220. LContext.FDataChannel.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  3221. if FDirFormat = ftpdfEPLF then begin
  3222. ASender.Reply.SetReply(125, RSFTPDataConnToOpen);
  3223. LContext.FDataChannel.OKReply.SetReply(226, RSFTPDataConnClosed);
  3224. end
  3225. else if TextIsSame(ASender.CommandHandler.Command, 'LIST') or (LSwitches <> '') then begin {do not localize}
  3226. ASender.Reply.SetReply(125, RSFTPDataConnList);
  3227. end else begin
  3228. ASender.Reply.SetReply(125, RSFTPDataConnNList);
  3229. end;
  3230. ASender.SendReply;
  3231. DoDataChannelOperation(ASender);
  3232. end else begin
  3233. LContext.KillDataChannel;
  3234. ASender.Reply.SetReply(426, RSFTPDataConnClosedAbnormally);
  3235. end;
  3236. finally
  3237. LStream.Free;
  3238. end;
  3239. end;
  3240. end;
  3241. end;
  3242. procedure TIdFTPServer.DoDataChannelOperation(ASender: TIdCommand; const AConnectMode : Boolean = False);
  3243. const
  3244. DEF_BLOCKSIZE = 10*10240;
  3245. {CH DEF_CHECKCMD_WAIT = 1; }
  3246. var
  3247. LContext : TIdFTPServerContext;
  3248. LCmdQueue : TStringList;
  3249. LLine : String;
  3250. LStrm : TStream;
  3251. procedure CheckControlConnection(AContext : TIdFTPServerContext; ACmdQueue : TStrings);
  3252. var
  3253. LLocalLine : String;
  3254. begin
  3255. // TODO: rewrite this to wait on both control and data sockets at the same
  3256. // time and read a command only if the control socket is actually readable...
  3257. LLocalLine := ReadCommandLine(AContext);
  3258. if LLocalLine <> '' then begin
  3259. if not FDataChannelCommands.HandleCommand(AContext, LLocalLine) then begin
  3260. ACmdQueue.Add(LLocalLine);
  3261. end;
  3262. end;
  3263. end;
  3264. procedure ReadFromStream(AContext : TIdFTPServerContext; ACmdQueue : TStrings; ADestStream : TStream);
  3265. var
  3266. LM : TStream;
  3267. begin
  3268. if AContext.DataMode = dmDeflate then begin
  3269. LM := TMemoryStream.Create;
  3270. end else begin
  3271. LM := ADestStream;
  3272. end;
  3273. try
  3274. repeat
  3275. AContext.FDataChannel.FDataChannel.IOHandler.CheckForDisconnect(False);
  3276. AContext.FDataChannel.FDataChannel.IOHandler.ReadStream(LM, DEF_BLOCKSIZE, True);
  3277. CheckControlConnection(AContext, ACmdQueue);
  3278. until not AContext.FDataChannel.FDataChannel.IOHandler.Connected;
  3279. if AContext.DataMode = dmDeflate then begin
  3280. LM.Position := 0;
  3281. FCompressor.DecompressFTPDeflate(LM, ADestStream, AContext.ZLibWindowBits);
  3282. end;
  3283. finally
  3284. if AContext.DataMode = dmDeflate then begin
  3285. LM.Free;
  3286. end;
  3287. end;
  3288. end;
  3289. procedure WriteToStream(AContext : TIdFTPServerContext; ACmdQueue : TStrings;
  3290. ASrcStream : TStream; const AIgnoreCompression : Boolean = False);
  3291. var
  3292. LBuffer : TIdBytes;
  3293. LBufSize : Int64;
  3294. LOutStream : TStream;
  3295. begin
  3296. if AContext.DataMode = dmDeflate then begin
  3297. LOutStream := TMemoryStream.Create;
  3298. end else begin
  3299. LOutStream := ASrcStream;
  3300. end;
  3301. try
  3302. if AContext.DataMode = dmDeflate then begin
  3303. FCompressor.CompressFTPDeflate(ASrcStream, LOutStream,
  3304. AContext.ZLibCompressionLevel, AContext.ZLibWindowBits,
  3305. AContext.ZLibMemLevel, AContext.ZLibStratagy);
  3306. LOutStream.Position := 0;
  3307. end;
  3308. SetLength(LBuffer, DEF_BLOCKSIZE);
  3309. LBufSize := ReadTIdBytesFromStream(LOutStream, LBuffer, DEF_BLOCKSIZE);
  3310. if LBufSize > 0 then begin
  3311. repeat
  3312. AContext.FDataChannel.FDataChannel.IOHandler.Write(LBuffer, LBufSize);
  3313. LBufSize := ReadTIdBytesFromStream(LOutStream, LBuffer, DEF_BLOCKSIZE);
  3314. if LBufSize > 0 then begin
  3315. CheckControlConnection(AContext, ACmdQueue);
  3316. end;
  3317. until (LBufSize < 1) or (not AContext.FDataChannel.FDataChannel.IOHandler.Connected);
  3318. end;
  3319. finally
  3320. if AContext.DataMode = dmDeflate then begin
  3321. LOutStream.Free;
  3322. end;
  3323. end;
  3324. end;
  3325. procedure WriteStrings(AContext : TIdFTPServerContext; ACmdQueue : TStrings; ASrcStrings : TStrings);
  3326. var
  3327. i : Integer;
  3328. LM : TStream;
  3329. LEncoding: IIdTextEncoding;
  3330. begin
  3331. //for loops will execute at least once triggering an out of range error.
  3332. //write nothing if AStrings is empty.
  3333. if ASrcStrings.Count < 1 then begin
  3334. Exit;
  3335. end;
  3336. {
  3337. IMPORTANT!!!
  3338. If LIST data is sent as 8bit, you have a FTP list that is unparsable by
  3339. some FTP clients. If UTF8 OPTS OFF, you should send the data as 7bit
  3340. for the LIST and NLST commands. That way, unprintable charactors are
  3341. returned as ?. While the file name is not valid, at least, there some
  3342. thing that looks better than binary junk.
  3343. }
  3344. case PosInStrArray(ASender.CommandHandler.Command, ['LIST', 'NLST', 'MLSD'], False) of {do not localize}
  3345. 0, 1: begin
  3346. LEncoding := IndyTextEncoding(NLSTEncType[AContext.NLSTUtf8]);
  3347. end;
  3348. 2: begin
  3349. LEncoding := IndyTextEncoding_UTF8;
  3350. end;
  3351. else begin
  3352. LEncoding := IndyTextEncoding_8Bit;
  3353. end;
  3354. end;
  3355. if AContext.DataMode = dmDeflate then begin
  3356. LM := TMemoryStream.Create;
  3357. try
  3358. for i := 0 to ASrcStrings.Count-1 do begin
  3359. WriteStringToStream(LM, ASrcStrings[i] + EOL, LEncoding);
  3360. end;
  3361. LM.Position := 0;
  3362. WriteToStream(AContext, ACmdQueue, LM, True);
  3363. finally
  3364. LM.Free;
  3365. end;
  3366. Exit;
  3367. end;
  3368. for i := 0 to ASrcStrings.Count-1 do begin
  3369. if AContext.FDataChannel.FDataChannel.IOHandler.Connected then begin
  3370. AContext.FDataChannel.FDataChannel.IOHandler.WriteLn(ASrcStrings[i], LEncoding);
  3371. if ((i mod 10) = 0) and (i <> (ASrcStrings.Count-1)) then begin
  3372. if AContext.FDataChannel.FDataChannel.IOHandler.Connected then begin
  3373. CheckControlConnection(AContext, ACmdQueue);
  3374. end else begin
  3375. Break;
  3376. end;
  3377. end;
  3378. end else begin
  3379. Break;
  3380. end;
  3381. end;
  3382. end;
  3383. begin
  3384. if not Assigned(ASender) then begin
  3385. Exit;
  3386. end;
  3387. if not Assigned(ASender.Context) then begin
  3388. Exit;
  3389. end;
  3390. LContext := ASender.Context as TIdFTPServerContext;
  3391. if not Assigned(LContext.FDataChannel) then begin
  3392. Exit;
  3393. end;
  3394. try
  3395. LCmdQueue := TStringList.Create;
  3396. try
  3397. LContext.FDataChannel.InitOperation(AConnectMode);
  3398. try
  3399. try
  3400. try
  3401. if LContext.FDataChannel.Data is TStream then begin
  3402. LStrm := TStream(LContext.FDataChannel.Data);
  3403. case LContext.FDataChannel.FFtpOperation of
  3404. ftpRetr:
  3405. WriteToStream(LContext, LCmdQueue, LStrm);
  3406. ftpStor:
  3407. ReadFromStream(LContext, LCmdQueue, LStrm);
  3408. end;
  3409. end else begin
  3410. case LContext.FDataChannel.FFtpOperation of
  3411. ftpRetr:
  3412. if Assigned(LContext.FDataChannel.Data) then begin
  3413. WriteStrings(LContext, LCmdQueue, LContext.FDataChannel.Data as TStrings);
  3414. end;
  3415. ftpStor:
  3416. if Assigned(LContext.FDataChannel.Data) then begin
  3417. LStrm := TMemoryStream.Create;
  3418. try
  3419. ReadFromStream(LContext, LCmdQueue, LStrm);
  3420. //TODO;
  3421. // SplitLines(TMemoryStream(LStrm).Memory, LMemStream.Size, LContext.FDataChannel.FData as TStrings);
  3422. finally
  3423. LStrm.Free;
  3424. end;
  3425. end;//ftpStor
  3426. end;//case
  3427. end;
  3428. finally
  3429. if Assigned(LContext.FDataChannel.FDataChannel) then begin
  3430. LContext.FDataChannel.FDataChannel.Disconnect(False);
  3431. end;
  3432. end;
  3433. LContext.FDataChannel.FReply.Assign(LContext.FDataChannel.FOKReply); //226
  3434. except
  3435. on E: Exception do begin
  3436. if not (E is EIdSilentException) then begin
  3437. LContext.FDataChannel.FReply.Assign(LContext.FDataChannel.FErrorReply); //426
  3438. end;
  3439. end;
  3440. end;
  3441. finally
  3442. ASender.Reply.Assign(LContext.FDataChannel.FReply);
  3443. ASender.SendReply;
  3444. //now we have to handle the FIFO queue we had made
  3445. while LCmdQueue.Count > 0 do begin
  3446. LLine := LCmdQueue[0];
  3447. if not FCommandHandlers.HandleCommand(ASender.Context, LLine) then begin
  3448. DoReplyUnknownCommand(ASender.Context, LLine);
  3449. end;
  3450. if Assigned(ASender.Context.Connection) then begin
  3451. if not ASender.Context.Connection.Connected then begin
  3452. Break;
  3453. end;
  3454. end else begin
  3455. Break;
  3456. end;
  3457. LCmdQueue.Delete(0);
  3458. end;
  3459. end;
  3460. finally
  3461. LCmdQueue.Free;
  3462. end;
  3463. finally
  3464. FreeAndNil(LContext.FDataChannel);
  3465. end;
  3466. end;
  3467. procedure TIdFTPServer.CommandSYST(ASender: TIdCommand);
  3468. var
  3469. LContext : TIdFTPServerContext;
  3470. begin
  3471. LContext := ASender.Context as TIdFTPServerContext;
  3472. if LContext.UserSecurity.DisableSYSTCommand then begin
  3473. CmdNotImplemented(ASender);
  3474. Exit;
  3475. end;
  3476. //this should keep CuteFTP Pro 3.0 from stopping there's no custom ID and
  3477. //the Dir format is custonm.
  3478. if (FDirFormat = ftpdfCustom) and (Trim(FCustomSystID) = '') then begin
  3479. CmdNotImplemented(ASender);
  3480. Exit;
  3481. end;
  3482. if LContext.IsAuthenticated(ASender) then begin
  3483. ASender.Reply.SetReply(215, DoSysType(LContext));
  3484. end;
  3485. end;
  3486. procedure TIdFTPServer.CommandSTAT(ASender: TIdCommand);
  3487. var
  3488. LStream: TStringList;
  3489. LActAsList: boolean;
  3490. LSwitches, LPath : String;
  3491. i : Integer;
  3492. LContext : TIdFTPServerContext;
  3493. begin
  3494. LContext := ASender.Context as TIdFTPServerContext;
  3495. LActAsList := (ASender.Params.Count > 0);
  3496. if not LActAsList then begin
  3497. if LContext.UserSecurity.DisableSTATCommand then begin
  3498. if ASender.UnparsedParams = '' then begin
  3499. CmdNotImplemented(ASender);
  3500. Exit;
  3501. end;
  3502. end;
  3503. end;
  3504. if LContext.IsAuthenticated(ASender) then begin
  3505. if Assigned(LContext.DataChannel) then begin
  3506. if not LContext.DataChannel.Stopped then begin
  3507. LActAsList := False;
  3508. end;
  3509. end;
  3510. if not LActAsList then begin
  3511. ASender.Reply.NumericCode := 211;
  3512. ASender.Reply.Text.Clear;
  3513. if Assigned(FOnStat) then begin
  3514. LStream := TStringList.Create;
  3515. try
  3516. SetRFCReplyFormat(ASender.Reply);
  3517. FOnStat(LContext, LStream);
  3518. for i := 0 to LStream.Count - 1 do begin
  3519. ASender.Reply.Text.Add(' ' + TrimLeft(LStream[i])); {Do not Localize}
  3520. end;
  3521. finally
  3522. LStream.Free;
  3523. end;
  3524. end;
  3525. ASender.Reply.Text.Insert(0,RSFTPCmdStartOfStat);
  3526. ASender.Reply.Text.Add(RSFTPCmdEndOfStat);
  3527. end else begin //else act as LIST command without a data channel
  3528. LStream := TStringList.Create;
  3529. try
  3530. LSwitches := '';
  3531. LPath := ASender.UnparsedParams;
  3532. if TextStartsWith(LPath, '-') then begin
  3533. LSwitches := Fetch(LPath);
  3534. end;
  3535. ListDirectory(LContext, DoProcessPath(LContext, LPath), LStream, True, LSwitches);
  3536. //we use IOHandler.WriteLn here because we need better control over what
  3537. //we send than what Reply.SendReply offers. This is important as the dir
  3538. //is written using WriteStrings and I found that with Reply.SetReply, a stat
  3539. //reply could throw off a FTP client.
  3540. LContext.Connection.IOHandler.WriteLn(IndyFormat('213-%s', [RSFTPDataConnToOpen])); {Do not Localize}
  3541. LContext.Connection.IOHandler.Write(LStream, False, IndyTextEncoding(NLSTEncType[LContext.NLSTUtf8]));
  3542. ASender.PerformReply := True;
  3543. ASender.Reply.SetReply(213, RSFTPCmdEndOfStat);
  3544. finally
  3545. LStream.Free;
  3546. end;
  3547. end;
  3548. end;
  3549. end;
  3550. procedure TIdFTPServer.CommandFEAT(ASender: TIdCommand);
  3551. const
  3552. MFFPREFIX = 'MFF '; {Do not Localize}
  3553. var
  3554. LTmp : String;
  3555. LContext: TIdFTPServerContext;
  3556. // under ARC, convert a weak reference to a strong reference before working with it
  3557. LFileSystem: TIdFTPBaseFileSystem;
  3558. begin
  3559. LContext := TIdFTPServerContext(ASender.Context);
  3560. LFileSystem := FTPFileSystem;
  3561. ASender.Reply.Clear;
  3562. SetRFCReplyFormat(ASender.Reply);
  3563. ASender.Reply.NumericCode := 211;
  3564. ASender.Reply.Text.Add(RSFTPCmdExtsSupportedStart); {Do not translate}
  3565. //AUTH
  3566. if IOHandler is TIdServerIOHandlerSSLBase then begin
  3567. if (FUseTLS <> utUseImplicitTLS) then begin
  3568. ASender.Reply.Text.Add('AUTH TLS;AUTH TLS-C;SSL;TLS-P;'); {Do not translate}
  3569. end;
  3570. end;
  3571. //AVBL
  3572. if Assigned(FOnAvailDiskSpace) then begin
  3573. ASender.Reply.Text.Add('AVBL');
  3574. end;
  3575. //CCC
  3576. if (FUseTLS <> utNoTLSSupport) then begin
  3577. ASender.Reply.Text.Add('CCC'); {Do not translate}
  3578. end;
  3579. //CLNT
  3580. if Assigned(FOnClientID) or Assigned(FOnClientIDEx) then begin
  3581. ASender.Reply.Text.Add('CLNT'); {Do not translate}
  3582. end;
  3583. //COMB
  3584. if Assigned(FOnCombineFiles) or Assigned(LFileSystem) then begin
  3585. ASender.Reply.Text.Add('COMB target;source_list'); {Do not translate}
  3586. end;
  3587. //CPSV
  3588. //CPSV is not supported in IPv6 - same problem as PASV
  3589. if (UseTLS <> utNoTLSSupport) and (LContext.Binding.IPVersion = Id_IPv4) then begin
  3590. ASender.Reply.Text.Add('CPSV'); {Do not translate}
  3591. end;
  3592. //CSID
  3593. ASender.Reply.Text.Add('CSID'); {Do not localize}
  3594. //DSIZ
  3595. if Assigned(OnCompleteDirSize) then begin
  3596. ASender.Reply.Text.Add('DSIZ'); {Do not localize}
  3597. end;
  3598. //EPRT
  3599. ASender.Reply.Text.Add('EPRT'); {Do not translate}
  3600. //EPSV
  3601. ASender.Reply.Text.Add('EPSV'); {Do not translate}
  3602. //Host
  3603. if Assigned(FOnHostCheck) then begin
  3604. ASender.Reply.Text.Add('HOST domain'); {Do not localize}
  3605. end;
  3606. //
  3607. //This is not proper but FTP Voyager uses it to determine if the -T parameter
  3608. //will work.
  3609. if Assigned(FOnListDirectory) then begin
  3610. //we do things this way because the 'a' and 'T' swithces only make sense
  3611. //when listing Unix dirs.
  3612. LTmp := 'LIST -l'; {Do not translate}
  3613. if SupportTaDirSwitches(LContext) then begin
  3614. LTmp := LTmp + 'aT'; {Do not translate}
  3615. end;
  3616. ASender.Reply.Text.Add(LTmp); {do not localize}
  3617. end;
  3618. //MDTM
  3619. if Assigned(FOnGetFileDate) or Assigned(LFileSystem) then begin
  3620. ASender.Reply.Text.Add('MDTM'); {Do not translate}
  3621. //MDTM YYYYMMDDHHMMSS filename
  3622. if Assigned(FOnSetModifiedTime) then begin
  3623. // ASender.Reply.Text.Add('MDTM YYYYMMDDHHMMSS[+-TZ];filename');
  3624. //Indicate that we wish to use FTP Voyager's old MDTM variation for seting time.
  3625. //time is returned as local (relative to server's timezone. We do this for compatibility
  3626. ASender.Reply.Text.Add('MDTM YYYYMMDDHHMMSS filename'); {Do not translate}
  3627. end;
  3628. end;
  3629. //MFCT
  3630. if Assigned(FOnSetCreationTime) then begin
  3631. ASender.Reply.Text.Add('MFCT'); {Do not Localize}
  3632. //TODO: The logic for the MMF entry may need to change if we
  3633. //support modifying more facts
  3634. end;
  3635. //MFF
  3636. LTmp := MFFPREFIX; {Do not localize}
  3637. if Assigned(FOnSetCreationTime) and (mlsdFileLastAccessTime in FMLSDFacts) then begin
  3638. LTmp := LTmp + 'Create;'; {Do not Localize}
  3639. end;
  3640. if Assigned(FOnSetModifiedTime) or Assigned(LFileSystem) then begin
  3641. LTmp := LTmp + 'Modify;'; {Do not Localize}
  3642. end;
  3643. if Assigned(FOnSiteCHMOD) then begin
  3644. LTmp := LTmp + 'Unix.mode;';
  3645. end;
  3646. if Assigned(FOnSiteCHOWN) then begin
  3647. LTmp := LTmp + 'Unix.owner;';
  3648. end;
  3649. if Assigned(FOnSiteCHGRP) then begin
  3650. LTmp := LTmp + 'Unix.group;';
  3651. end;
  3652. if Assigned(FOnSiteUTIME) and (mlsdFileLastAccessTime in FMLSDFacts) then begin
  3653. LTmp := LTmp + 'Windows.lastaccesstime;';
  3654. end;
  3655. if Assigned(FOnSetATTRIB) then begin
  3656. LTmp := LTmp + 'Win32.ea;';
  3657. end;
  3658. if LTmp <> MFFPREFIX then begin
  3659. ASender.Reply.Text.Add(LTmp);
  3660. end;
  3661. //MFMT
  3662. if Assigned(FOnSetModifiedTime) or Assigned(LFileSystem) then begin
  3663. ASender.Reply.Text.Add('MFMT'); {Do not Localize}
  3664. end;
  3665. //MLST
  3666. if Assigned(FOnListDirectory) then begin
  3667. ASender.Reply.Text.Add('MLSD'); {Do not translate}
  3668. ASender.Reply.Text.Add(MLSFEATLine(FMLSDFacts, LContext.MLSOpts)); {Do not translate}
  3669. end;
  3670. //MODE Z
  3671. if Assigned(FCompressor) then begin
  3672. ASender.Reply.Text.Add('MODE Z'); {do not localize}
  3673. end;
  3674. //OPTS
  3675. LTmp := 'OPTS ';
  3676. if Assigned(FOnListDirectory) then begin
  3677. LTmp := LTmp + 'MLST;';
  3678. end;
  3679. if Assigned(FCompressor) then begin
  3680. LTmp := LTmp + 'MODE;';
  3681. end;
  3682. LTmp := LTmp + 'UTF8';
  3683. ASender.Reply.Text.Add(LTmp);
  3684. //PBSZ
  3685. if (FUseTLS <> utNoTLSSupport) then begin
  3686. ASender.Reply.Text.Add('PBSZ'); {Do not translate}
  3687. end;
  3688. //PROT
  3689. if (FUseTLS <> utNoTLSSupport) then begin
  3690. ASender.Reply.Text.Add('PROT'); {Do not translate}
  3691. end;
  3692. //REST STREAM
  3693. ASender.Reply.Text.Add('REST STREAM'); {Do not translate}
  3694. //RMDA
  3695. if Assigned(FOnRemoveDirectoryAll) then begin
  3696. ASender.Reply.Text.Add('RMDA directoryname'); {Do not localize}
  3697. end;
  3698. //SITE ZONE
  3699. //Listing a SITE command in feature negotiation is unusual and
  3700. //may be a little off-spec. FTP Voyager scans this looking for
  3701. //SITE ZONE and if it's present, it will use the SITE ZONE
  3702. //to help it convert the time to the user's local time zone.
  3703. //The only other way that FTP Voyager would know is if the initial
  3704. //FTP greeting banner started with "Serv-U FTP-Server v2.5f" which
  3705. //is more problematic because Serve-U is a trademark and we would then
  3706. //then be stuck with a situation where everyone has to use it down the road.
  3707. //This would amount to the same mess we had with "Mozilla" in the HTTP
  3708. //User-Agent header field.
  3709. //also list other supported site commands;
  3710. LTmp := 'SITE ZONE';
  3711. if Assigned(FOnSetATTRIB) then begin
  3712. LTmp := LTmp + ';ATTRIB';
  3713. end;
  3714. if Assigned(FOnSiteUMASK) then begin
  3715. LTmp := LTmp + 'UMASK';
  3716. end;
  3717. if Assigned(FOnSiteCHMOD) then begin
  3718. LTmp := LTmp + ';CHMOD';
  3719. end;
  3720. if (FDirFormat = ftpdfDOS) or
  3721. ((FDirFormat = ftpdfOSDependent) and (GOSType = otWindows)) then begin
  3722. LTmp := LTmp + ';DIRSTYLE';
  3723. end;
  3724. if Assigned(OnSiteUTIME) or Assigned(OnSetModifiedTime) then begin
  3725. LTmp := LTmp + ';UTIME';
  3726. end;
  3727. if Assigned(OnSiteCHOWN) then begin
  3728. LTmp := LTmp + ';CHOWN';
  3729. end;
  3730. if Assigned(OnSiteCHGRP) then begin
  3731. LTmp := LTmp + ';CHGRP';
  3732. end;
  3733. ASender.Reply.Text.Add(LTmp); {do not localize}
  3734. //SIZE
  3735. if Assigned(FOnGetFileSize) or Assigned(LFileSystem) then begin
  3736. ASender.Reply.Text.Add('SIZE'); {do not localize}
  3737. end;
  3738. //SPSV
  3739. ASender.Reply.Text.Add('SPSV'); {do not localize}
  3740. //SSCN
  3741. if UseTLS <> utNoTLSSupport then begin
  3742. ASender.Reply.Text.Add('SSCN'); {do not localize}
  3743. end;
  3744. //STAT -l
  3745. //Some servers such as Microsoft FTP Service, RaidenFTPD, and a few others,
  3746. //treat a STAT -l as a LIST command, only it's sent on the control connection.
  3747. //Some versions of Flash FXP can also use this as an option to improve efficiency.
  3748. if Assigned(FOnListDirectory) then begin
  3749. //we do things this way because the 'a' and 'T' swithces only make sense
  3750. //when listing Unix dirs.
  3751. LTmp := 'STAT -l'; {Do not translate}
  3752. if SupportTaDirSwitches(LContext) then begin
  3753. LTmp := LTmp + 'aT'; {Do not translate}
  3754. end;
  3755. ASender.Reply.Text.Add(LTmp); {do not localize}
  3756. end;
  3757. //TVFS
  3758. if FPathProcessing <> ftppCustom then begin
  3759. //TVFS should not be indicated for custom parsing because
  3760. //we don't know what a person will do.
  3761. ASender.Reply.Text.Add('TVFS'); {Do not localize}
  3762. end;
  3763. // UTF-8
  3764. // RFC 2640 says that "Servers MUST support the UTF-8 feature in response to the FEAT command [RFC2389]."
  3765. // TODO: finish actually implementing UTF-8 support
  3766. ASender.Reply.Text.Add('UTF8'); {Do not localize}
  3767. //XCRC
  3768. if Assigned(FOnCRCFile) or Assigned(LFileSystem) then begin
  3769. if not GetFIPSMode then begin
  3770. ASender.Reply.Text.Add('XCRC "filename" SP EP');//filename;start;end'); {Do not Localize}
  3771. ASender.Reply.Text.Add('XMD5 "filename" SP EP');//filename;start;end'); {Do not Localize}
  3772. end;
  3773. ASender.Reply.Text.Add('XSHA1 "filename" SP EP');//filename;start;end'); {Do not Localize}
  3774. if TIdHashSHA256.IsAvailable then begin
  3775. ASender.Reply.Text.Add('XSHA256 "filename" SP EP'); //file;start/end
  3776. end;
  3777. if TIdHashSHA512.IsAvailable then begin
  3778. ASender.Reply.Text.Add('XSHA512 "filename" SP EP'); //file;start/end
  3779. end;
  3780. end;
  3781. //I'm doing things this way with complience level to match the current
  3782. //version of NcFTPD
  3783. LTmp := 'RFC 959 2389 ';
  3784. if LContext.UserSecurity.FInvalidPassDelay <> 0 then begin
  3785. LTmp := LTmp + '2577 ';
  3786. end;
  3787. LTmp := LTmp + '3659 '; {Do not Localize}
  3788. if IOHandler is TIdServerIOHandlerSSLBase then begin
  3789. if (FUseTLS <> utUseImplicitTLS) then begin
  3790. LTmp := LTmp + '4217 '; {Do not localize}
  3791. end;
  3792. end;
  3793. ASender.Reply.Text.Add(Trim(LTmp)); {Do not Localize}
  3794. ASender.Reply.Text.Add(RSFTPCmdExtsSupportedEnd);
  3795. end;
  3796. procedure TIdFTPServer.CommandOPTS(ASender: TIdCommand);
  3797. var
  3798. LCmd : String;
  3799. begin
  3800. LCmd := ASender.UnparsedParams;
  3801. ASender.Reply.Clear;
  3802. if TextIsSame(Fetch(LCmd, ' ', False), 'MLST') then begin {do not localize}
  3803. //just in case the user doesn't create a ListDirectory event.
  3804. if not Assigned(FOnListDirectory) then begin
  3805. ASender.Reply.SetReply(501, RSFTPOptNotRecog);
  3806. Exit;
  3807. end;
  3808. end;
  3809. if not FOPTSCommands.HandleCommand(ASender.Context, LCmd) then begin
  3810. ASender.Reply.SetReply(501, RSFTPOptNotRecog);
  3811. end else begin
  3812. //we don't want an extra 200 reply.
  3813. ASender.PerformReply := False;
  3814. end;
  3815. end;
  3816. procedure TIdFTPServer.CommandSIZE(ASender: TIdCommand);
  3817. var
  3818. s: string;
  3819. LSize: Int64;
  3820. LContext : TIdFTPServerContext;
  3821. // under ARC, convert a weak reference to a strong reference before working with it
  3822. LFileSystem: TIdFTPBaseFileSystem;
  3823. begin
  3824. LContext := ASender.Context as TIdFTPServerContext;
  3825. if LContext.IsAuthenticated(ASender) then begin
  3826. LFileSystem := FFTPFileSystem;
  3827. if Assigned(FOnGetFileSize) or Assigned(LFileSystem) then begin
  3828. LSize := -1;
  3829. s := DoProcessPath(LContext, ASender.UnparsedParams);
  3830. DoOnGetFileSize(LContext, s, LSize);
  3831. if LSize > -1 then begin
  3832. ASender.Reply.SetReply(213, IntToStr(LSize));
  3833. end else begin
  3834. CmdFileActionAborted(ASender);
  3835. end;
  3836. end else begin
  3837. CmdSyntaxError(ASender);
  3838. end;
  3839. end;
  3840. end;
  3841. procedure TIdFTPServer.DoOnChangeDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  3842. var
  3843. // under ARC, convert a weak reference to a strong reference before working with it
  3844. LFileSystem: TIdFTPBaseFileSystem;
  3845. begin
  3846. LFileSystem := FFTPFileSystem;
  3847. if Assigned(LFileSystem) then begin
  3848. LFileSystem.ChangeDir(AContext, VDirectory);
  3849. end else if Assigned(FOnChangeDirectory) then begin
  3850. FOnChangeDirectory(AContext, VDirectory);
  3851. end;
  3852. end;
  3853. procedure TIdFTPServer.DoOnRemoveDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  3854. var
  3855. // under ARC, convert a weak reference to a strong reference before working with it
  3856. LFileSystem: TIdFTPBaseFileSystem;
  3857. begin
  3858. LFileSystem := FFTPFileSystem;
  3859. if Assigned(LFileSystem) then begin
  3860. LFileSystem.RemoveDirectory(AContext, VDirectory);
  3861. end else if Assigned(FOnRemoveDirectory) then begin
  3862. FOnRemoveDirectory(AContext, VDirectory);
  3863. end;
  3864. end;
  3865. procedure TIdFTPServer.DoOnMakeDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  3866. var
  3867. // under ARC, convert a weak reference to a strong reference before working with it
  3868. LFileSystem: TIdFTPBaseFileSystem;
  3869. begin
  3870. LFileSystem := FFTPFileSystem;
  3871. if Assigned(LFileSystem) then begin
  3872. LFileSystem.MakeDirectory(AContext, VDirectory);
  3873. end else if Assigned(FOnMakeDirectory) then begin
  3874. FOnMakeDirectory(AContext, VDirectory);
  3875. end;
  3876. end;
  3877. procedure TIdFTPServer.CommandEPRT(ASender: TIdCommand);
  3878. var
  3879. LParm, LIP: string;
  3880. LDelim: char;
  3881. LReqIPVersion: TIdIPVersion;
  3882. LContext : TIdFTPServerContext;
  3883. LDataChannel: TIdTCPClient;
  3884. begin
  3885. LContext := ASender.Context as TIdFTPServerContext;
  3886. if LContext.IsAuthenticated(ASender) then begin
  3887. LContext.FPASV := False;
  3888. LParm := ASender.UnparsedParams;
  3889. if LParm = '' then begin
  3890. LContext.FDataPortDenied := True;
  3891. CmdInvalidParamNum(ASender);
  3892. Exit;
  3893. end;
  3894. if FFTPSecurityOptions.BlockAllPORTTransfers then begin
  3895. LContext.FDataPortDenied := True;
  3896. ASender.Reply.SetReply(502, RSFTPPORTDisabled);
  3897. Exit;
  3898. end;
  3899. LDelim := LParm[1];
  3900. Fetch(LParm, LDelim);
  3901. case IndyStrToInt(Fetch(LParm, LDelim), -1) of
  3902. 1: begin
  3903. if not GStack.SupportsIPv4 then begin
  3904. LContext.FDataPort := 0;
  3905. LContext.FDataPortDenied := True;
  3906. ASender.Reply.SetReply(522, IndyFormat(RSFTPNetProtNotSup, ['2'])); {Do not translate}
  3907. Exit;
  3908. end;
  3909. LReqIPVersion := Id_IPv4;
  3910. end;
  3911. 2: begin
  3912. if not GStack.SupportsIPv6 then begin
  3913. LContext.FDataPort := 0;
  3914. LContext.FDataPortDenied := True;
  3915. ASender.Reply.SetReply(522, IndyFormat(RSFTPNetProtNotSup, ['1'])); {Do not translate}
  3916. Exit;
  3917. end;
  3918. LReqIPVersion := Id_IPv6;
  3919. end;
  3920. else
  3921. begin
  3922. LParm := '';
  3923. if GStack.SupportsIPv4 then begin
  3924. LParm := '1'; {Do not translate}
  3925. end;
  3926. if GStack.SupportsIPv6 then begin
  3927. if LParm <> '' then begin
  3928. LParm := LParm + ','; {Do not translate}
  3929. end;
  3930. LParm := LParm + '2'; {Do not translate}
  3931. end;
  3932. LContext.FDataPort := 0;
  3933. LContext.FDataPortDenied := True;
  3934. ASender.Reply.SetReply(522, IndyFormat(RSFTPNetProtNotSup, [LParm])); {Do not translate}
  3935. Exit;
  3936. end;
  3937. end;
  3938. LIP := Fetch(LParm, LDelim);
  3939. if LIP = '' then begin
  3940. LContext.FDataPort := 0;
  3941. LContext.FDataPortDenied := True;
  3942. ASender.Reply.SetReply(500, RSFTPInvalidIP);
  3943. Exit;
  3944. end;
  3945. LContext.FDataPort := TIdPort(IndyStrToInt(Fetch(LParm, LDelim), 0));
  3946. if LContext.FDataPort = 0 then begin
  3947. LContext.FDataPortDenied := True;
  3948. ASender.Reply.SetReply(500, RSFTPInvalidPort);
  3949. Exit;
  3950. end;
  3951. if FFTPSecurityOptions.NoReservedRangePORT and
  3952. ((LContext.FDataPort > 0) and (LContext.FDataPort <= 1024)) then begin
  3953. LContext.FDataPort := 0;
  3954. LContext.FDataPortDenied := True;
  3955. ASender.Reply.SetReply(504, RSFTPPORTRange);
  3956. Exit;
  3957. end;
  3958. if FFTPSecurityOptions.FRequirePORTFromSameIP then begin
  3959. case LReqIPVersion of
  3960. Id_IPv4: LIP := MakeCanonicalIPv4Address(LIP);
  3961. Id_IPv6: LIP := MakeCanonicalIPv6Address(LIP);
  3962. end;
  3963. if LIP <> LContext.Binding.PeerIP then begin
  3964. LContext.FDataPort := 0;
  3965. LContext.FDataPortDenied := True;
  3966. ASender.Reply.SetReply(504, RSFTPSameIPAddress);
  3967. Exit;
  3968. end;
  3969. end;
  3970. LContext.CreateDataChannel(False);
  3971. LDataChannel := TIdTCPClient(LContext.FDataChannel.FDataChannel);
  3972. LDataChannel.Host := LIP;
  3973. LDataChannel.Port := LContext.FDataPort;
  3974. LDataChannel.IPVersion := LReqIPVersion;
  3975. LContext.FDataPortDenied := False;
  3976. CmdCommandSuccessful(ASender, 200);
  3977. end;
  3978. end;
  3979. procedure TIdFTPServer.CommandEPSV(ASender: TIdCommand);
  3980. var
  3981. LParam: string;
  3982. LBPortMin, LBPortMax: Word;
  3983. LIP : String;
  3984. LIPVersion: TIdIPVersion;
  3985. LReqIPVersion: TIdIPVersion;
  3986. LContext : TIdFTPServerContext;
  3987. LDataChannel: TIdSimpleServer;
  3988. begin
  3989. LContext := ASender.Context as TIdFTPServerContext;
  3990. if LContext.IsAuthenticated(ASender) then begin
  3991. LIPVersion := LContext.Binding.IPVersion;
  3992. LReqIPVersion := LIPVersion;
  3993. LParam := ASender.UnparsedParams;
  3994. if LParam <> '' then begin
  3995. case IndyStrToInt(LParam, -1) of
  3996. 1: begin
  3997. if not GStack.SupportsIPv4 then begin
  3998. ASender.Reply.SetReply(522, IndyFormat(RSFTPNetProtNotSup, ['2'])); {do not localize}
  3999. Exit;
  4000. end;
  4001. LReqIPVersion := Id_IPv4;
  4002. end;
  4003. 2: begin
  4004. if not GStack.SupportsIPv6 then begin
  4005. ASender.Reply.SetReply(522, IndyFormat(RSFTPNetProtNotSup, ['1'])); {do not localize}
  4006. Exit;
  4007. end;
  4008. LReqIPVersion := Id_IPv6;
  4009. end;
  4010. else
  4011. begin
  4012. if TextIsSame(LParam, 'ALL') then begin { do not localize }
  4013. LContext.FEPSVAll := True;
  4014. ASender.Reply.SetReply(200, RSFTPEPSVAllEntered);
  4015. end else begin
  4016. LIP := '';
  4017. if GStack.SupportsIPv4 then begin
  4018. LIP := '1'; {do not localize}
  4019. end;
  4020. if GStack.SupportsIPv6 then begin
  4021. if LIP <> '' then begin
  4022. LIP := LIP + ','; {do not localize}
  4023. end;
  4024. LIP := LIP + '2'; {do not localize}
  4025. end;
  4026. ASender.Reply.SetReply(522, IndyFormat(RSFTPNetProtNotSup, [LIP])); {do not localize}
  4027. end;
  4028. Exit;
  4029. end;
  4030. end;
  4031. end;
  4032. if LReqIPVersion = LIPVersion then begin
  4033. LIP := LContext.Binding.IP;
  4034. end;
  4035. if (FPASVBoundPortMin <> 0) and (FPASVBoundPortMax <> 0) then begin
  4036. LBPortMin := FPASVBoundPortMin;
  4037. LBPortMax := FPASVBoundPortMax;
  4038. end else begin
  4039. LBPortMin := FDefaultDataPort;
  4040. LBPortMax := LBPortMin;
  4041. end;
  4042. DoOnPASVBeforeBind(LContext, LIP, LBPortMin, LBPortMax, LReqIPVersion);
  4043. LContext.CreateDataChannel(True);
  4044. LDataChannel := TIdSimpleServer(LContext.FDataChannel.FDataChannel);
  4045. LDataChannel.BoundIP := LIP;
  4046. if LBPortMin = LBPortMax then begin
  4047. LDataChannel.BoundPort := LBPortMin;
  4048. LDataChannel.BoundPortMin := 0;
  4049. LDataChannel.BoundPortMax := 0;
  4050. end else begin
  4051. LDataChannel.BoundPort := 0;
  4052. LDataChannel.BoundPortMin := LBPortMin;
  4053. LDataChannel.BoundPortMax := LBPortMax;
  4054. end;
  4055. LDataChannel.IPVersion := LReqIPVersion;
  4056. LDataChannel.BeginListen;
  4057. LIP := LDataChannel.Binding.IP;
  4058. LBPortMin := LDataChannel.Binding.Port;
  4059. //Note that only one Port can work with EPSV
  4060. DoOnPASVReply(LContext, LIP, LBPortMin, LReqIPVersion);
  4061. LParam := '|||' + IntToStr(LBPortMin) + '|'; {Do not localize}
  4062. ASender.Reply.SetReply(229, IndyFormat(RSFTPEnteringEPSV, [LParam]));
  4063. ASender.SendReply;
  4064. LContext.FPASV := True;
  4065. end;
  4066. end;
  4067. procedure TIdFTPServer.CommandMDTM(ASender: TIdCommand);
  4068. var
  4069. s: string;
  4070. LDate: TDateTime;
  4071. LContext : TIdFTPServerContext;
  4072. LSDate : String;
  4073. LExists : Boolean;
  4074. // under ARC, convert a weak reference to a strong reference before working with it
  4075. LFileSystem: TIdFTPBaseFileSystem;
  4076. {
  4077. I know that this code and design are a mess.
  4078. There are actually two forms of MDTM and they mean different things.
  4079. The formal spec indicates that anything after the space in MDTM <filename>
  4080. is the filename.
  4081. FTP Voyager and some other clients abuse the MDTM command by using it to specify
  4082. a timestamp for the "Modified Time" on a file. The format is like this:
  4083. MDTM YYYYMMDDHHMMSS filename
  4084. Thus, there's an ambiguity.
  4085. Does MDTM 20031229152022 ESBAdDemo.exe mean
  4086. 1) Set the date time on ESBAdDemo.exe to 12/29/2003 3:20:22 PM
  4087. or
  4088. 2) Get the time for a file named 20031229152022 ESBAdDemo.exe
  4089. To resolve this ambiguity, we check specifically for a valid date, and then see
  4090. if a file, 20031229152022 ESBAdDemo.exe really does exist. If not, we interpret
  4091. MDTM as a set date command. Otherwise, we will traat it as a request for the timestamp
  4092. of a file, 20031229152022 ESBAdDemo.exe.
  4093. Note also that the time is sometimes given as either relative to the local server
  4094. or an offset is provided.
  4095. Note from:
  4096. http://www.ftpvoyager.com/releasenotes.asp
  4097. ====
  4098. Added support for RFC change and the MDTM. MDTM requires sending the server
  4099. GMT (UTC) instead of a "fixed" date and time. FTP Voyager supports this with
  4100. Serv-U automatically by checking the Serv-U version number and by checking the
  4101. response to the FEAT command for MDTM. Servers returning "MDTM" or
  4102. "MDTM YYYYMMDDHHMMSS[+-TZ] filename" will use the old method. Servers
  4103. returning "MDTM YYYYMMDDHHMMSS" only will use the new method where the date a
  4104. and time is GMT (UTC).
  4105. ===
  4106. We will use the old form for compatiability with some older FTP Voyager clients
  4107. and because a few servers support the old form as well. I do this even though,
  4108. this is really inconsistant with what MDTM returns for a query request. I might
  4109. consider some type of support for the new form but I do not feel that such
  4110. things are just abuse of the MDTM command. That's why I prefer a separate command for
  4111. modifying file modification dates (MFMT).
  4112. }
  4113. begin
  4114. LFileSystem := FFTPFileSystem;
  4115. if Assigned(FOnGetFileDate) or Assigned(LFileSystem) then
  4116. begin
  4117. LContext := ASender.Context as TIdFTPServerContext;
  4118. if LContext.IsAuthenticated(ASender) then begin
  4119. s := ASender.UnparsedParams;
  4120. LSDate := Fetch(s);
  4121. if IsMDTMDate(LSDate) then begin
  4122. s := DoProcessPath(LContext, ASender.UnparsedParams );
  4123. DoOnFileExistCheck(LContext, s, LExists);
  4124. if not LExists then begin
  4125. s := ASender.UnparsedParams;
  4126. Fetch(s);
  4127. s := DoProcessPath(LContext, s);
  4128. LDate := FTPMDTMToGMTDateTime(LSDate);
  4129. DoOnSetModifiedTime(LContext, s, LDate);
  4130. // Self.DoOnSetModifiedTime(LF,s, LSDate);
  4131. ASender.Reply.SetReply(253, 'Date/time changed okay.'); {do not localize}
  4132. Exit;
  4133. end;
  4134. end;
  4135. s := DoProcessPath(LContext, ASender.UnparsedParams);
  4136. LDate := 0;
  4137. DoOnGetFileDate(LContext, s, LDate);
  4138. if LDate > 0 then begin
  4139. ASender.Reply.SetReply(213, FTPGMTDateTimeToMLS(LDate));
  4140. end else begin
  4141. CmdFileActionAborted(ASender);
  4142. end;
  4143. end;
  4144. end else begin
  4145. CmdSyntaxError(ASender);
  4146. end;
  4147. end;
  4148. procedure TIdFTPServer.SetFTPSecurityOptions(const AValue: TIdFTPSecurityOptions);
  4149. begin
  4150. FFTPSecurityOptions.Assign(AValue);
  4151. end;
  4152. procedure TIdFTPServer.SetOnUserAccount(AValue: TOnFTPUserAccountEvent);
  4153. var
  4154. LCmd : TIdCommandHandler;
  4155. i : Integer;
  4156. begin
  4157. if FUserAccounts = nil then begin
  4158. FOnUserAccount := AValue;
  4159. for i := 0 to CommandHandlers.Count - 1 do begin
  4160. LCmd := CommandHandlers.Items[i];
  4161. if LCmd.Command = 'ACCT' then begin
  4162. if Assigned(AValue) then begin
  4163. LCmd.HelpSuperScript := '';
  4164. LCmd.Description.Text := ACCT_HELP_ENABLED;
  4165. end else begin
  4166. LCmd.HelpSuperScript := '*';
  4167. LCmd.Description.Text := ACCT_HELP_DISABLED;
  4168. end;
  4169. end;
  4170. end;
  4171. end;
  4172. end;
  4173. procedure TIdFTPServer.CommandAUTH(ASender: TIdCommand);
  4174. var
  4175. LContext : TIdFTPServerContext;
  4176. begin
  4177. LContext := ASender.Context as TIdFTPServerContext;
  4178. if (PosInStrArray(ASender.UnparsedParams, TLS_AUTH_NAMES) > -1) and
  4179. (ASender.Context.Connection.IOHandler is TIdSSLIOHandlerSocketBase) and
  4180. (FUseTLS in ExplicitTLSVals) then
  4181. begin
  4182. ASender.Reply.SetReply(234,RSFTPAuthSSL);
  4183. ASender.SendReply;
  4184. (ASender.Context.Connection.IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := False;
  4185. {
  4186. This is from:
  4187. http://www.ford-hutchinson.com/~fh-1-pfh/ftps-ext.html#bad
  4188. and we implement things this way for historical reasons so
  4189. we don't break older and newer clients.
  4190. }
  4191. case PosInStrArray(ASender.UnparsedParams, TLS_AUTH_NAMES) of
  4192. 0,2 : LContext.DataProtection := ftpdpsClear; //AUTH TLS, AUTH TLS-C
  4193. 1,3 : LContext.DataProtection := ftpdpsPrivate; //AUTH SSL, AUTH TLS-P
  4194. end;
  4195. LContext.AuthMechanism := 'TLS'; {Do not localize}
  4196. end else begin
  4197. CmdSyntaxError(ASender);
  4198. end;
  4199. end;
  4200. procedure TIdFTPServer.CommandAVBL(ASender: TIdCommand);
  4201. var
  4202. LContext : TIdFTPServerContext;
  4203. LIsFile : Boolean;
  4204. LSize : Int64;
  4205. LPath : String;
  4206. begin
  4207. LIsFile := True;
  4208. LSize := 0;
  4209. LContext := ASender.Context as TIdFTPServerContext;
  4210. if LContext.IsAuthenticated(ASender) then begin
  4211. if Assigned(FOnAvailDiskSpace) then begin
  4212. LPath := DoProcessPath(LContext, ASender.UnparsedParams);
  4213. FOnAvailDiskSpace(LContext, LPath, LIsFile, LSize);
  4214. if LIsFile then begin
  4215. ASender.Reply.SetReply(550, IndyFormat(RSFTPIsAFile,[LPath]));
  4216. end else begin
  4217. ASender.Reply.SetReply(213, IntToStr(LSize));
  4218. end;
  4219. end else begin
  4220. CmdNotImplemented(ASender);
  4221. end;
  4222. end else begin
  4223. ASender.Reply.SetReply(550, RSFTPFileActionNotTaken);
  4224. end;
  4225. end;
  4226. //FOnCompleteDirSize
  4227. procedure TIdFTPServer.CommandDSIZ(ASender : TIdCommand);
  4228. var
  4229. LContext : TIdFTPServerContext;
  4230. LIsFile : Boolean;
  4231. LSize : Int64;
  4232. LPath : String;
  4233. begin
  4234. LIsFile := True;
  4235. LSize := 0;
  4236. LContext := ASender.Context as TIdFTPServerContext;
  4237. if LContext.IsAuthenticated(ASender) then begin
  4238. if Assigned(FOnCompleteDirSize) then begin
  4239. LPath := DoProcessPath(LContext, ASender.UnparsedParams);
  4240. FOnCompleteDirSize(LContext, LPath, LIsFile, LSize);
  4241. if LIsFile then begin
  4242. ASender.Reply.SetReply(550, IndyFormat(RSFTPIsAFile,[LPath]));
  4243. end else begin
  4244. ASender.Reply.SetReply(213, IntToStr(LSize));
  4245. end;
  4246. end else begin
  4247. CmdNotImplemented(ASender);
  4248. end;
  4249. end else begin
  4250. ASender.Reply.SetReply(550, RSFTPFileActionNotTaken);
  4251. end;
  4252. end;
  4253. procedure TIdFTPServer.CommandRMDA(ASender : TIdCommand);
  4254. var
  4255. LContext : TIdFTPServerContext;
  4256. LPath : TIdFTPFileName;
  4257. begin
  4258. //FOnRemoveDirectoryAll: TOnDirectoryEvent;
  4259. LContext := ASender.Context as TIdFTPServerContext;
  4260. if LContext.IsAuthenticated(ASender) then begin
  4261. if Assigned(FOnRemoveDirectoryAll) then begin
  4262. LPath := DoProcessPath(LContext, ASender.UnparsedParams);
  4263. FOnRemoveDirectoryAll(LContext, LPath);
  4264. ASender.Reply.SetReply(250, RSFTPFileActionCompleted);
  4265. end else begin
  4266. CmdNotImplemented(ASender);
  4267. end;
  4268. end else begin
  4269. ASender.Reply.SetReply(550, RSFTPFileActionNotTaken);
  4270. end;
  4271. end;
  4272. procedure TIdFTPServer.CommandCCC(ASender: TIdCommand);
  4273. var
  4274. LContext : TIdFTPServerContext;
  4275. begin
  4276. LContext := ASender.Context as TIdFTPServerContext;
  4277. if FUseTLS <> utNoTLSSupport then begin
  4278. //Not sure if it's proper to require authentication before a CCC
  4279. //but it is a good idea anyway because you definately want to
  4280. //prevent eavesdropping
  4281. if LContext.IsAuthenticated(ASender) then begin
  4282. if LContext.FUserSecurity.PermitCCC then begin
  4283. ASender.Reply.SetReply(200, RSFTPClearCommandConnection);
  4284. ASender.SendReply;
  4285. (ASender.Context.Connection.IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := True;
  4286. LContext.FCCC := True;
  4287. end else begin
  4288. ASender.Reply.SetReply(534, RSFTPClearCommandNotPermitted);
  4289. end;
  4290. end;
  4291. end else begin
  4292. CmdSyntaxError(ASender);
  4293. end;
  4294. end;
  4295. procedure TIdFTPServer.CommandPBSZ(ASender: TIdCommand);
  4296. {Note that this may have to be expanded and reworked for other AUTH mechanisms}
  4297. var
  4298. LContext : TIdFTPServerContext;
  4299. begin
  4300. LContext := ASender.Context as TIdFTPServerContext;
  4301. if IOHandler is TIdServerIOHandlerSSLBase then begin
  4302. if ASender.UnparsedParams = '' then begin
  4303. CmdInvalidParamNum(ASender);
  4304. Exit;
  4305. end;
  4306. if (LContext.AuthMechanism = '') and (FUseTLS <> utUseImplicitTLS) then begin
  4307. ASender.Reply.SetReply(503, RSFTPPBSZAuthDataRequired);
  4308. Exit;
  4309. end;
  4310. if LContext.FCCC then begin
  4311. ASender.Reply.SetReply(503, RSFTPPBSZNotAfterCCC);
  4312. Exit;
  4313. end;
  4314. if (LContext.AuthMechanism = 'TLS') or (FUseTLS = utUseImplicitTLS) then begin {Do not localize}
  4315. ASender.Reply.SetReply(200,RSFTPDataProtBuffer0);
  4316. LContext.DataPBSZCalled := True;
  4317. end
  4318. else if IsNumeric(ASender.UnparsedParams) then begin
  4319. ASender.Reply.SetReply(200,'PBSZ=0'); {Do not translate}
  4320. LContext.DataPBSZCalled := True;
  4321. end else begin
  4322. CmdInvalidParams(ASender);
  4323. end;
  4324. end else begin
  4325. CmdSyntaxError(ASender);
  4326. end;
  4327. end;
  4328. procedure TIdFTPServer.CommandPROT(ASender: TIdCommand);
  4329. const
  4330. LValidParams : array [0..3] of string = ('C','S','E','P'); {Do not translate}
  4331. {Note that this may have to be expanded and reworked for other AUTH mechanisms}
  4332. var
  4333. LContext : TIdFTPServerContext;
  4334. begin
  4335. LContext := ASender.Context as TIdFTPServerContext;
  4336. if IOHandler is TIdServerIOHandlerSSLBase then begin
  4337. if LContext.FCCC then begin
  4338. ASender.Reply.SetReply(503, RSFTPPBSZNotAfterCCC);
  4339. Exit;
  4340. end;
  4341. if not LContext.DataPBSZCalled then begin
  4342. ASender.Reply.SetReply(503, RSFTPPROTProtBufRequired);
  4343. Exit;
  4344. end;
  4345. case PosInStrArray(ASender.UnparsedParams, LValidParams) of
  4346. 0 : begin
  4347. LContext.FDataProtection := ftpdpsClear;
  4348. ASender.Reply.SetReply(200, RSFTPProtTypeClear);
  4349. end;
  4350. 1, 2 : ASender.Reply.SetReply(536, RSFTPInvalidProtTypeForMechanism);
  4351. 3 : begin
  4352. LContext.FDataProtection := ftpdpsPrivate;
  4353. ASender.Reply.SetReply(200, RSFTPProtTypePrivate);
  4354. end;
  4355. else
  4356. ASender.Reply.SetReply(504, RSFTPInvalidForParam);
  4357. end;
  4358. end else begin
  4359. CmdNotImplemented(ASender);
  4360. end;
  4361. end;
  4362. procedure TIdFTPServer.CommandCOMB(ASender: TIdCommand);
  4363. var
  4364. LFileParts : TStringList;
  4365. LBuf : String;
  4366. LTargetFileName : String;
  4367. LContext : TIdFTPServerContext;
  4368. // under ARC, convert a weak reference to a strong reference before working with it
  4369. LFileSystem: TIdFTPBaseFileSystem;
  4370. begin
  4371. LContext := ASender.Context as TIdFTPServerContext;
  4372. LFileSystem := FTPFileSystem;
  4373. if Assigned(FOnCombineFiles) or Assigned(LFileSystem) then begin
  4374. if LContext.IsAuthenticated(ASender) then begin
  4375. if ASender.UnparsedParams = '' then begin
  4376. CmdInvalidParamNum(ASender);
  4377. Exit;
  4378. end;
  4379. if Pos('"', ASender.UnparsedParams) > 0 then begin
  4380. LBuf := ASender.UnparsedParams;
  4381. Fetch(LBuf,'"');
  4382. LTargetFileName := Fetch(LBuf, '"');
  4383. LTargetFileName := DoProcessPath(LContext, LTargetFileName);
  4384. LBuf := Trim(LBuf);
  4385. LFileParts := TStringList.Create;
  4386. try
  4387. while LBuf <> '' do begin
  4388. Fetch(LBuf,'"');
  4389. LFileParts.Add(DoProcessPath(LContext, Fetch(LBuf,'"')));
  4390. end;
  4391. DoOnCombineFiles(LContext, LTargetFileName, LFileParts);
  4392. ASender.Reply.SetReply(250, RSFTPFileOpSuccess);
  4393. finally
  4394. LFileParts.Free;
  4395. end;
  4396. end else begin
  4397. CmdInvalidParams(ASender);
  4398. end;
  4399. end;
  4400. end else begin
  4401. CmdNotImplemented(ASender);
  4402. end;
  4403. end;
  4404. procedure TIdFTPServer.DoConnect(AContext: TIdContext);
  4405. var
  4406. LGreeting : TIdReplyRFC;
  4407. LContext : TIdFTPServerContext;
  4408. begin
  4409. AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
  4410. // RLebeau 2/2/2021: let the user decide whether to enable SSL in their
  4411. // own event handler. Indy should not be making any assumptions about
  4412. // whether to implicitally force SSL on any given connection. This
  4413. // prevents a single server from handling both SSL and non-SSL connections
  4414. // together. The whole point of the PassThrough property is to allow
  4415. // per-connection SSL handling.
  4416. //
  4417. // TODO: move this new logic into TIdCustomTCPServer directly somehow
  4418. if AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase then begin
  4419. if FUseTLS = utUseImplicitTLS then begin
  4420. TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough :=
  4421. not DoQuerySSLPort(AContext.Binding.Port);
  4422. end;
  4423. end;
  4424. LContext := AContext as TIdFTPServerContext;
  4425. LContext.FXAUTKey := MakeXAUTKey;
  4426. if Assigned(OnGreeting) then begin
  4427. LGreeting := TIdReplyRFC.Create(nil);
  4428. try
  4429. LGreeting.Assign(Greeting);
  4430. OnGreeting(TIdFTPServerContext(AContext), LGreeting);
  4431. ReplyTexts.UpdateText(LGreeting);
  4432. if (not GetFIPSMode) and FSupportXAUTH and (LGreeting.NumericCode = 220) then begin
  4433. LContext.FXAUTKey := IdFTPCommon.MakeXAUTKey;
  4434. XAutGreeting(AContext,LGreeting, GStack.HostName);
  4435. end;
  4436. AContext.Connection.IOHandler.Write(LGreeting.FormattedReply);
  4437. if Assigned(OnConnect) then begin
  4438. OnConnect(AContext);
  4439. end;
  4440. if LGreeting.NumericCode = 421 then begin
  4441. AContext.Connection.Disconnect(False);
  4442. end;
  4443. finally
  4444. LGreeting.Free;
  4445. end;
  4446. end else begin
  4447. if (not GetFIPSMode) and FSupportXAUTH and (Greeting.NumericCode = 220) then begin
  4448. LGreeting := TIdReplyRFC.Create(nil);
  4449. try
  4450. LGreeting.Assign(Greeting);
  4451. XAutGreeting(AContext,LGreeting, GStack.HostName);
  4452. AContext.Connection.IOHandler.Write(LGreeting.FormattedReply);
  4453. if Assigned(OnConnect) then begin
  4454. OnConnect(AContext);
  4455. end;
  4456. if LGreeting.NumericCode = 421 then begin
  4457. AContext.Connection.Disconnect(False);
  4458. end;
  4459. finally
  4460. LGreeting.Free;
  4461. end;
  4462. end else begin
  4463. inherited DoConnect(AContext);
  4464. end;
  4465. end;
  4466. end;
  4467. function TIdFTPServer.DoQuerySSLPort(APort: TIdPort): Boolean;
  4468. begin
  4469. // check for the default FTPS port, but let the user override that if desired...
  4470. Result := (APort = IdPORT_ftps);
  4471. if Assigned(FOnQuerySSLPort) then begin
  4472. FOnQuerySSLPort(APort, Result);
  4473. end;
  4474. end;
  4475. procedure TIdFTPServer.CommandQUIT(ASender: TIdCommand);
  4476. begin
  4477. if Assigned(FOnQuitBanner) then begin
  4478. FOnQuitBanner(TIdFTPServerContext(ASender.Context), ASender.Reply);
  4479. ASender.Disconnect := True;
  4480. end else begin
  4481. ASender.Reply.Assign(ASender.CommandHandler.NormalReply);
  4482. end;
  4483. ASender.Reply.SetReply(221, ASender.Reply.Text.Text);
  4484. end;
  4485. procedure TIdFTPServer.CommandMLSD(ASender: TIdCommand);
  4486. var
  4487. LStream: TStringList;
  4488. LSendData : Boolean;
  4489. LContext : TIdFTPServerContext;
  4490. begin
  4491. if not Assigned(OnListDirectory) then begin
  4492. CmdSyntaxError(ASender);
  4493. Exit;
  4494. end;
  4495. LContext := ASender.Context as TIdFTPServerContext;
  4496. LSendData := False;
  4497. if LContext.IsAuthenticated(ASender) then begin
  4498. if (not Assigned(LContext.FDataChannel)) or LContext.FDataPortDenied then begin
  4499. ASender.Reply.SetReply(425, RSFTPCantOpenData);
  4500. Exit;
  4501. end;
  4502. LStream := TStringList.Create;
  4503. try
  4504. ListDirectory(LContext, DoProcessPath(LContext, ASender.UnparsedParams),
  4505. LStream, TextIsSame(ASender.CommandHandler.Command, 'LIST'), 'MLSD'); {Do not translate}
  4506. LSendData := True;
  4507. finally
  4508. try
  4509. if LSendData then begin
  4510. //it should be safe to assume that the FDataChannel object exists because
  4511. //we checked it earlier
  4512. LContext.FDataChannel.Data := LStream;
  4513. LContext.FDataChannel.OKReply.SetReply(226, RSFTPDataConnClosed);
  4514. LContext.FDataChannel.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  4515. LContext.FDataChannel.FFtpOperation := ftpRetr;
  4516. ASender.Reply.SetReply(125, RSFTPDataConnToOpen);
  4517. ASender.SendReply;
  4518. DoDataChannelOperation(ASender);
  4519. end else begin
  4520. LContext.KillDataChannel;
  4521. ASender.Reply.SetReply(426, RSFTPDataConnClosedAbnormally);
  4522. end;
  4523. finally
  4524. LStream.Free;
  4525. end;
  4526. end;
  4527. end;
  4528. end;
  4529. procedure TIdFTPServer.CommandMLST(ASender: TIdCommand);
  4530. var
  4531. LStream : TStringList;
  4532. i : Integer;
  4533. LContext : TIdFTPServerContext;
  4534. LPath : String;
  4535. LDir : TIdFTPListOutput;
  4536. begin
  4537. if Assigned(OnListDirectory) or Assigned(FOnMLST) then begin
  4538. LContext := ASender.Context as TIdFTPServerContext;
  4539. if LContext.IsAuthenticated(ASender) then begin
  4540. LStream := TStringList.Create;
  4541. try
  4542. LPath := DoProcessPath(LContext, ASender.UnparsedParams);
  4543. if Assigned(FOnMLST) then begin
  4544. LDir := TIdFTPListOutput.Create;
  4545. try
  4546. FOnMLST(LContext, LPath, LDir);
  4547. LDir.MLISTOutputDir(LStream, LContext.MLSOpts);
  4548. finally
  4549. LDir.Free;
  4550. end;
  4551. end else begin
  4552. //this part is kept just for backwards compatibility
  4553. ListDirectory(LContext, LPath, LStream, True, 'MLST'); {Do not translate}
  4554. end;
  4555. ASender.Reply.Clear;
  4556. SetRFCReplyFormat(ASender.Reply);
  4557. ASender.Reply.NumericCode := 250;
  4558. ASender.Reply.Text.Add('Begin'); {do not localize}
  4559. for i := 0 to LStream.Count -1 do begin
  4560. ASender.Reply.Text.Add(' ' + LStream[i]);
  4561. end;
  4562. ASender.Reply.Text.Add('End'); {do not localize}
  4563. ASender.SendReply;
  4564. finally
  4565. LStream.Free;
  4566. end;
  4567. end;
  4568. end else begin
  4569. CmdSyntaxError(ASender);
  4570. end;
  4571. end;
  4572. procedure TIdFTPServer.DoOnSetModifiedTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime);
  4573. var
  4574. // under ARC, convert a weak reference to a strong reference before working with it
  4575. LFileSystem: TIdFTPBaseFileSystem;
  4576. begin
  4577. LFileSystem := FTPFileSystem;
  4578. if Assigned(LFileSystem) then begin
  4579. LFileSystem.SetModifiedFileDate(AContext, AFileName, VDateTime);
  4580. end else if Assigned(FOnSetModifiedTime) then begin
  4581. FOnSetModifiedTime(AContext, AFileName, VDateTime);
  4582. end;
  4583. end;
  4584. procedure TIdFTPServer.DoOnSetModifiedTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTimeStr : String);
  4585. var
  4586. LTime : TDateTime;
  4587. begin
  4588. LTime := FTPMLSToGMTDateTime(VDateTimeStr);
  4589. DoOnSetModifiedTime(AContext, AFileName, LTime);
  4590. VDateTimeStr := FTPGMTDateTimeToMLS(LTime);
  4591. end;
  4592. procedure TIdFTPServer.DoOnSetCreationTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime);
  4593. var
  4594. // under ARC, convert a weak reference to a strong reference before working with it
  4595. LFileSystem: TIdFTPBaseFileSystem;
  4596. begin
  4597. LFileSystem := FTPFileSystem;
  4598. if Assigned(LFileSystem) then begin
  4599. //LFileSystem.SetCreationFileDate(AContext,AFileName,VDateTime);
  4600. end else if Assigned(FOnSetCreationTime) then begin
  4601. FOnSetCreationTime(AContext, AFileName, VDateTime);
  4602. end;
  4603. end;
  4604. procedure TIdFTPServer.DoOnSetCreationTimeGMT(AContext: TIdFTPServerContext;
  4605. const AFileName: String; var VDateTime: TDateTime);
  4606. begin
  4607. if Assigned(FOnSetCreationTime) then begin
  4608. FOnSetCreationTime(AContext, AFileName, VDateTime);
  4609. end;
  4610. end;
  4611. procedure TIdFTPServer.DoOnSetModifiedTimeGMT(AContext: TIdFTPServerContext;
  4612. const AFileName: String; var VDateTime: TDateTime);
  4613. begin
  4614. if Assigned(FOnSetModifiedTime) then begin
  4615. FOnSetModifiedTime(AContext, AFileName, VDateTime);
  4616. end;
  4617. end;
  4618. procedure TIdFTPServer.DoOnSetCreationTime(AContext: TIdFTPServerContext;
  4619. const AFileName : String; var VDateTimeStr : String);
  4620. var
  4621. LTime : TDateTime;
  4622. begin
  4623. LTime := FTPMLSToLocalDateTime(VDateTimeStr);
  4624. DoOnSetCreationTime(AContext, AFileName, LTime);
  4625. VDateTimeStr := FTPLocalDateTimeToMLS(LTime);
  4626. end;
  4627. procedure TIdFTPServer.CommandMFMT(ASender: TIdCommand);
  4628. var
  4629. LTimeStr, LFileName : String;
  4630. LContext : TIdFTPServerContext;
  4631. // under ARC, convert a weak reference to a strong reference before working with it
  4632. LFileSystem: TIdFTPBaseFileSystem;
  4633. begin
  4634. LContext := ASender.Context as TIdFTPServerContext;
  4635. if LContext.IsAuthenticated(ASender) then begin
  4636. LFilesystem := FTPFileSystem;
  4637. if Assigned(FOnSetModifiedTime) or Assigned(LFileSystem) then begin
  4638. LFileName := ASender.UnparsedParams;
  4639. LTimeStr := Fetch(LFileName);
  4640. LFileName := DoProcessPath(LContext, LFileName);
  4641. DoOnSetModifiedTime(LContext, LFileName, LTimeStr);
  4642. ASender.Reply.SetReply(213, IndyFormat('Modify=%s %s', [LTimeStr, LFileName])); {Do not translate}
  4643. end else begin
  4644. CmdSyntaxError(ASender);
  4645. end;
  4646. end;
  4647. end;
  4648. procedure TIdFTPServer.CommandMFCT(ASender: TIdCommand);
  4649. var
  4650. LTimeStr, LFileName : String;
  4651. LContext : TIdFTPServerContext;
  4652. // under ARC, convert a weak reference to a strong reference before working with it
  4653. LFileSystem: TIdFTPBaseFileSystem;
  4654. begin
  4655. LContext := TIdFTPServerContext(ASender.Context);
  4656. if LContext.IsAuthenticated(ASender) then begin
  4657. LFileSystem := FTPFileSystem;
  4658. if Assigned(FOnSetCreationTime) or Assigned(LFileSystem) then begin
  4659. LFileName := ASender.UnparsedParams;
  4660. LTimeStr := Fetch(LFileName);
  4661. LFileName := DoProcessPath(LContext, LFileName);
  4662. DoOnSetCreationTime(LContext, LFileName, LTimeStr);
  4663. ASender.Reply.SetReply(213, IndyFormat('CreateTime=%s %s', [LTimeStr, LFileName])); {Do not translate}
  4664. end else begin
  4665. CmdSyntaxError(ASender);
  4666. end;
  4667. end;
  4668. end;
  4669. procedure TIdFTPServer.CommandMFF(ASender: TIdCommand);
  4670. var
  4671. LFacts : TStringList;
  4672. LFileName : String;
  4673. LValue : String;
  4674. s : String;
  4675. LContext : TIdFTPServerContext;
  4676. LAttrib : UInt32;
  4677. LAuth : Boolean;
  4678. LDummyDate1, LDummyDate2 : TDateTime;
  4679. LDate : TDateTime;
  4680. LCHMOD : Integer;
  4681. LDummy : String;
  4682. begin
  4683. LAuth := True;
  4684. LDummy := ''; //empty value for passing a var in case we need to do that
  4685. LContext := TIdFTPServerContext(ASender.Context);
  4686. //this may need to change if we make more facts to modify
  4687. if not Assigned(FOnSetModifiedTime) and not Assigned(FOnSetCreationTime) then begin
  4688. CmdSyntaxError(ASender);
  4689. Exit;
  4690. end;
  4691. s := '';
  4692. if ASender.UnparsedParams = '' then begin
  4693. CmdInvalidParamNum(ASender);
  4694. Exit;
  4695. end;
  4696. if LContext.IsAuthenticated(ASender) then begin
  4697. LFacts := TStringList.Create;
  4698. try
  4699. LFileName := ParseFacts(ASender.UnparsedParams, LFacts);
  4700. LFileName := DoProcessPath(LContext, LFileName);
  4701. if LFacts.Values['Modify'] <> '' then begin {Do not translate}
  4702. if Assigned(FOnSetModifiedTime) then begin
  4703. LValue := LFacts.Values['Modify']; {Do not translate}
  4704. DoOnSetModifiedTime(LContext, LFileName, LValue);
  4705. s := s + IndyFormat('Modify=%s;', [LValue]); {Do not translate}
  4706. end;
  4707. end;
  4708. if LFacts.Values['Create'] <> '' then begin {Do not translate}
  4709. if Assigned(FOnSetCreationTime) then begin
  4710. LValue := LFacts.Values['Create']; {Do not translate}
  4711. DoOnSetCreationTime(LContext, LFileName, LValue);
  4712. s := s + IndyFormat('Create=%s;', [LValue]); {Do not translate}
  4713. end;
  4714. end;
  4715. if LFacts.Values['Win32.ea'] <> '' then begin
  4716. if Assigned(FOnSetATTRIB) then begin
  4717. LValue := LFacts.Values['Win32.ea']; {Do not localize}
  4718. LAttrib := IndyStrToInt(LValue);
  4719. DoOnSetAttrib(LContext, LAttrib, LFileName, LAuth);
  4720. LValue := '0x' + IntToHex(LAttrib, 8);
  4721. s := s + IndyFormat('Win32.ea=%s;', [LValue]); {Do not translate}
  4722. end;
  4723. end;
  4724. if LFacts.Values['Unix.mode'] <> '' then begin
  4725. LValue := LFacts.Values['Unix.mode']; {Do not localize}
  4726. if Assigned(FOnSiteCHMOD) then begin
  4727. If IsValidPermNumbers(LValue) then begin
  4728. LCHMOD := IndyStrToInt(LValue);
  4729. DoOnSiteCHMOD(LContext, LCHMOD, LFileName, LAuth);
  4730. LValue := IndyFormat('%.4d', [LCHMOD]);
  4731. s := s + IndyFormat('Unix.mode=%s;', [LValue]); {Do not translate}
  4732. end;
  4733. end;
  4734. end;
  4735. if LFacts.Values['Unix.owner'] <> '' then begin {Do not localize}
  4736. LValue := LFacts.Values['Unix.owner']; {Do not localize}
  4737. if Assigned(FOnSiteCHOWN) then begin
  4738. DoOnSiteCHOWN(LContext, LValue, LDummy, LFileName, LAuth);
  4739. s := s + IndyFormat('Unix.owner=%s;', [LValue]); {Do not localize}
  4740. end;
  4741. end;
  4742. if LFacts.Values['Unix.group'] <> '' then begin {Do not localize}
  4743. LValue := LFacts.Values['Unix.group']; {Do not localize}
  4744. if Assigned(FOnSiteCHGRP) then begin
  4745. DoOnSiteCHGRP(LContext, LValue, LFileName, LAuth);
  4746. s := s + IndyFormat('Unix.group=%s;', [LValue]); {Do not localize}
  4747. end;
  4748. end;
  4749. if LFacts.Values['Windows.lastaccesstime'] <> '' then begin
  4750. LValue := LFacts.Values['Windows.lastaccesstime'];
  4751. if Assigned(FOnSiteUTIME) and (mlsdFileLastAccessTime in FMLSDFacts) then begin
  4752. LDate := FTPMLSToGMTDateTime(LValue);
  4753. LDummyDate1 := 0;
  4754. LDummyDate2 := 0;
  4755. FOnSiteUTIME(LContext, LFileName, LDate, LDummyDate1, LDummyDate2, LAuth);
  4756. LValue := FTPGMTDateTimeToMLS(LDate);
  4757. s := s + IndyFormat('Windows.lastaccesstime=%s;', [LValue]);
  4758. end;
  4759. end;
  4760. if s <> '' then begin
  4761. ASender.Reply.SetReply(213, s + ' ' + LFileName);
  4762. end else begin
  4763. ASender.Reply.SetReply(504, IndyFormat(RSFTPParamError, ['MFF'])); {Do not translate}
  4764. end;
  4765. finally
  4766. LFacts.Free;
  4767. end;
  4768. end;
  4769. end;
  4770. function TIdFTPServer.GetMD5Checksum(ASender : TIdFTPServerContext; const AFileName : String ) : String;
  4771. var
  4772. LCalcStream : TStream;
  4773. begin
  4774. Result := '';
  4775. DoOnMD5Cache(ASender, AFileName, Result);
  4776. if Result = '' then begin
  4777. LCalcStream := nil;
  4778. DoOnCRCFile(ASender, AFileName, LCalcStream);
  4779. if Assigned(LCalcStream) then try
  4780. LCalcStream.Position := 0;
  4781. Result := CalculateCheckSum(TIdHashMessageDigest5, LCalcStream, 0, LCalcStream.Size);
  4782. DoOnMD5Verify(ASender, AFileName, Result);
  4783. finally
  4784. LCalcStream.Free;
  4785. end;
  4786. end;
  4787. end;
  4788. procedure TIdFTPServer.CommandMMD5(ASender: TIdCommand);
  4789. var
  4790. LChecksum : String;
  4791. LRes : String;
  4792. LFiles : TStringList;
  4793. LError : Boolean;
  4794. i : Integer;
  4795. LContext : TIdFTPServerContext;
  4796. // under ARC, convert a weak reference to a strong reference before working with it
  4797. LFileSystem: TIdFTPBaseFileSystem;
  4798. begin
  4799. LContext := ASender.Context as TIdFTPServerContext;
  4800. if GetFIPSMode then begin
  4801. CmdSyntaxError(ASender);
  4802. Exit;
  4803. end;
  4804. LError := False;
  4805. LChecksum := '';
  4806. LRes := '';
  4807. if LContext.IsAuthenticated(ASender) then begin
  4808. LFileSystem := FTPFileSystem;
  4809. if Assigned(FOnCRCFile) or Assigned(FOnMD5Cache) or Assigned(LFileSystem) then begin
  4810. LFiles := TStringList.Create;
  4811. try
  4812. ParseQuotedArgs(ASender.UnparsedParams, LFiles);
  4813. for i := 0 to LFiles.Count -1 do begin
  4814. LChecksum := GetMD5Checksum(LContext, DoProcessPath(LContext, UnquotedStr(LFiles[i])));
  4815. if LChecksum = '' then begin
  4816. LError := True;
  4817. Break;
  4818. end;
  4819. LRes := LRes + ',' + LFiles[i] + ' '+ LChecksum;
  4820. end;
  4821. IdDelete(LRes,1,1);
  4822. finally
  4823. LFiles.Free;
  4824. end;
  4825. if LError then begin
  4826. //The http://www.potaroo.net/ietf/idref/draft-twine-ftpmd5/
  4827. //draft didn't specify 550 as an error.
  4828. CmdTwineFileActionAborted(ASender);
  4829. end else begin
  4830. ASender.Reply.SetReply(252, LRes);
  4831. end;
  4832. end else begin
  4833. CmdSyntaxError(ASender);
  4834. end;
  4835. end;
  4836. end;
  4837. procedure TIdFTPServer.CommandMD5(ASender: TIdCommand);
  4838. var
  4839. LChecksum : String;
  4840. LContext : TIdFTPServerContext;
  4841. // under ARC, convert a weak reference to a strong reference before working with it
  4842. LFileSystem: TIdFTPBaseFileSystem;
  4843. begin
  4844. LContext := TIdFTPServerContext(ASender.Context);
  4845. if GetFIPSMode then begin
  4846. CmdSyntaxError(ASender);
  4847. Exit;
  4848. end;
  4849. LChecksum := '';
  4850. if LContext.IsAuthenticated(ASender) then begin
  4851. LFileSystem := FTPFileSystem;
  4852. if Assigned(FOnCRCFile) or Assigned(LFileSystem) then begin
  4853. LChecksum := GetMD5Checksum(LContext, DoProcessPath(LContext, ASender.UnparsedParams));
  4854. if LChecksum = '' then begin
  4855. CmdTwineFileActionAborted(ASender);
  4856. end else begin
  4857. ASender.Reply.SetReply(251, LChecksum);
  4858. end;
  4859. end else begin
  4860. CmdSyntaxError(ASender);
  4861. end;
  4862. end;
  4863. end;
  4864. procedure TIdFTPServer.DoOnMD5Verify(ASender: TIdFTPServerContext;
  4865. const AFileName, ACheckSum: String);
  4866. begin
  4867. if Assigned(OnMD5Verify) then begin
  4868. OnMD5Verify(ASender, AFileName, AChecksum);
  4869. end;
  4870. end;
  4871. procedure TIdFTPServer.DoOnMD5Cache(ASender: TIdFTPServerContext;
  4872. const AFileName: String; var VCheckSum: String);
  4873. begin
  4874. if Assigned(OnMD5Cache) then begin
  4875. OnMD5Cache(ASender, AFileName, VCheckSum);
  4876. end;
  4877. end;
  4878. procedure TIdFTPServer.DoDisconnect(AContext: TIdContext);
  4879. var
  4880. // under ARC, convert a weak reference to a strong reference before working with it
  4881. LUserAccounts: TIdCustomUserManager;
  4882. begin
  4883. LUserAccounts := FUserAccounts;
  4884. if Assigned(LUserAccounts) then begin
  4885. LUserAccounts.UserDisconnected(TIdFTPServerContext(AContext).UserName);
  4886. {$IFDEF USE_OBJECT_ARC}LUserAccounts := nil;{$ENDIF}
  4887. end;
  4888. inherited DoDisconnect(AContext);
  4889. end;
  4890. procedure TIdFTPServer.DoOnCRCFile(ASender: TIdFTPServerContext;
  4891. const AFileName: String; var VStream: TStream);
  4892. var
  4893. // under ARC, convert a weak reference to a strong reference before working with it
  4894. LFileSystem: TIdFTPBaseFileSystem;
  4895. begin
  4896. LFileSystem := FTPFileSystem;
  4897. if Assigned(LFileSystem) then begin
  4898. LFileSystem.GetCRCCalcStream(ASender, AFileName, VStream);
  4899. end else if Assigned(FOnCRCFile) then begin
  4900. FOnCRCFile(ASender, AFileName, VStream);
  4901. end;
  4902. end;
  4903. procedure TIdFTPServer.DoOnCombineFiles(ASender: TIdFTPServerContext;
  4904. const ATargetFileName: string; AParts: TStrings);
  4905. var
  4906. // under ARC, convert a weak reference to a strong reference before working with it
  4907. LFileSystem: TIdFTPBaseFileSystem;
  4908. begin
  4909. LFileSystem := FTPFileSystem;
  4910. if Assigned(LFileSystem) then begin
  4911. LFileSystem.CombineFiles(ASender, ATargetFileName, AParts);
  4912. end else if Assigned(FOnCombineFiles) then begin
  4913. FOnCombineFiles(ASender, ATargetFileName, AParts);
  4914. end;
  4915. end;
  4916. procedure TIdFTPServer.DoOnRenameFile(ASender: TIdFTPServerContext;
  4917. const ARenameFromFile, ARenameToFile: string);
  4918. var
  4919. // under ARC, convert a weak reference to a strong reference before working with it
  4920. LFileSystem: TIdFTPBaseFileSystem;
  4921. begin
  4922. LFileSystem := FTPFileSystem;
  4923. if Assigned(LFileSystem) then begin
  4924. LFileSystem.RenameFile(ASender, ARenameToFile);
  4925. end else if Assigned(FOnRenameFile) then begin
  4926. FOnRenameFile(ASender, ARenameFromFile, ARenameToFile);
  4927. end;
  4928. end;
  4929. procedure TIdFTPServer.DoOnGetFileDate(ASender: TIdFTPServerContext;
  4930. const AFilename: string; var VFileDate: TDateTime);
  4931. var
  4932. // under ARC, convert a weak reference to a strong reference before working with it
  4933. LFileSystem: TIdFTPBaseFileSystem;
  4934. begin
  4935. LFileSystem := FTPFileSystem;
  4936. if Assigned(LFileSystem) then begin
  4937. LFileSystem.GetFileDate(ASender, AFileName, VFileDate);
  4938. VFileDate := LocalTimeToUTCTime(VFileDate);
  4939. end else if Assigned(FOnGetFileDate) then begin
  4940. FOnGetFileDate(ASender, AFileName, VFileDate);
  4941. end;
  4942. end;
  4943. procedure TIdFTPServer.DoOnGetFileSize(ASender: TIdFTPServerContext;
  4944. const AFilename: string; var VFileSize: Int64);
  4945. var
  4946. // under ARC, convert a weak reference to a strong reference before working with it
  4947. LFileSystem: TIdFTPBaseFileSystem;
  4948. begin
  4949. LFileSystem := FTPFileSystem;
  4950. if Assigned(LFileSystem) then begin
  4951. LFileSystem.GetFileSize(ASender, AFileName, VFileSize);
  4952. end else if Assigned(FOnGetFileSize) then begin
  4953. FOnGetFileSize(ASender, AFileName, VFileSize);
  4954. end;
  4955. end;
  4956. procedure TIdFTPServer.DoOnDeleteFile(ASender: TIdFTPServerContext;
  4957. const APathName: string);
  4958. var
  4959. // under ARC, convert a weak reference to a strong reference before working with it
  4960. LFileSystem: TIdFTPBaseFileSystem;
  4961. begin
  4962. LFileSystem := FTPFileSystem;
  4963. if Assigned(LFileSystem) then begin
  4964. LFileSystem.DeleteFile(ASender, APathName);
  4965. end else if Assigned(FOnDeleteFile) then begin
  4966. FOnDeleteFile(ASender, APathName);
  4967. end;
  4968. end;
  4969. procedure TIdFTPServer.SetUseTLS(AValue: TIdUseTLS);
  4970. begin
  4971. inherited SetUseTLS(AValue);
  4972. if AValue = utUseImplicitTLS then
  4973. begin
  4974. if DefaultDataPort = IdPORT_FTP_DATA then begin
  4975. DefaultDataPort := IdPORT_ftps_data;
  4976. end;
  4977. end
  4978. else if DefaultDataPort = IdPORT_ftps_data then begin
  4979. DefaultDataPort := IdPORT_FTP_DATA;
  4980. end;
  4981. end;
  4982. procedure TIdFTPServer.DisconUser(ASender: TIdCommand);
  4983. begin
  4984. ASender.Disconnect := True;
  4985. ASender.Reply.SetReply(421, RSFTPClosingConnection);
  4986. if Assigned(OnLoginFailureBanner) then begin
  4987. OnLoginFailureBanner(TIdFTPServerContext(ASender.Context), ASender.Reply);
  4988. ASender.Reply.SetReply(421, ASender.Reply.Text.Text);
  4989. end;
  4990. end;
  4991. procedure TIdFTPServer.SetRFCReplyFormat(AReply: TIdReply);
  4992. begin
  4993. if AReply is TIdReplyFTP then begin
  4994. TIdReplyFTP(AReply).ReplyFormat := rfIndentMidLines;
  4995. end;
  4996. end;
  4997. procedure TIdFTPServer.CommandSiteATTRIB(ASender : TIdCommand);
  4998. var
  4999. LContext : TIdFTPServerContext;
  5000. LFileName,
  5001. LAttrs : String;
  5002. LAttrVal : UInt32;
  5003. LPermitted : Boolean;
  5004. function ValidAttribStr(const AAttrib : String) : Boolean;
  5005. var i : Integer;
  5006. begin
  5007. Result := TextStartsWith(AAttrib, '+');
  5008. if Result then begin
  5009. Result := Length(AAttrib)>1;
  5010. if result then begin
  5011. if AAttrib = '+N' then begin
  5012. Exit;
  5013. end;
  5014. for i := 2 to Length(AAttrib) do begin
  5015. if not CharIsInSet(AAttrib,i,'RASH') then begin
  5016. Result := False;
  5017. break;
  5018. end;
  5019. end;
  5020. end;
  5021. end;
  5022. end;
  5023. begin
  5024. LContext := ASender.Context as TIdFTPServerContext;
  5025. if LContext.IsAuthenticated(ASender) then begin
  5026. if Assigned(OnSetAttrib) then begin
  5027. LFileName := ASender.UnparsedParams;
  5028. LAttrs := Fetch(LFileName);
  5029. LPermitted := True;
  5030. LAttrs := UpperCase(LAttrs);
  5031. if TextStartsWith(LAttrs, '+') then begin
  5032. if ValidAttribStr(LAttrs) then begin
  5033. LAttrVal := 0;
  5034. ASender.Reply.Clear;
  5035. ASender.Reply.SetReply(220,'');
  5036. if IndyPos('R', LATTRS) > 0 then begin
  5037. LAttrVal := LAttrVal or IdFILE_ATTRIBUTE_READONLY;
  5038. ASender.Reply.Text.Add(RSFTPSiteATTRIBMsg+' : +FILE_ATTRIBUTE_READONLY'); {Do not localize}
  5039. end;
  5040. if IndyPos('A', LATTRS) > 0 then begin
  5041. LAttrVal := LAttrVal or IdFILE_ATTRIBUTE_ARCHIVE;
  5042. ASender.Reply.Text.Add(RSFTPSiteATTRIBMsg+' : +FILE_ATTRIBUTE_ARCHIVE'); {Do not localize}
  5043. end;
  5044. if IndyPos('S', LATTRS) > 0 then begin
  5045. LAttrVal := LAttrVal or IdFILE_ATTRIBUTE_SYSTEM;
  5046. ASender.Reply.Text.Add(RSFTPSiteATTRIBMsg+' : +FILE_ATTRIBUTE_SYSTEM'); {Do not localize}
  5047. end;
  5048. if IndyPos('H', LATTRS) > 0 then begin
  5049. LAttrVal := LAttrVal or IdFILE_ATTRIBUTE_HIDDEN;
  5050. ASender.Reply.Text.Add(RSFTPSiteATTRIBMsg+' : +FILE_ATTRIBUTE_HIDDEN'); {Do not localize}
  5051. end;
  5052. if IndyPos('N', LATTRS) > 0 then begin
  5053. LAttrVal := LAttrVal or IdFILE_ATTRIBUTE_NORMAL;
  5054. ASender.Reply.Text.Add(RSFTPSiteATTRIBMsg+' : +FILE_ATTRIBUTE_NORMAL'); {Do not localize}
  5055. end;
  5056. ASender.Reply.Text.Add(RSFTPSiteATTRIBMsg + IndyFormat(RSFTPSiteATTRIBDone, [IntToStr(Length(LAttrs)-1)]));
  5057. LFileName := DoProcessPath(LContext, LFileName);
  5058. DoOnSetATTRIB(LContext, LAttrVal, LFileName, LPermitted);
  5059. end else begin
  5060. ASender.Reply.SetReply(550,RSFTPSiteATTRIBInvalid);
  5061. Exit;
  5062. end;
  5063. if not LPermitted then begin
  5064. ASender.Reply.SetReply(553, RSFTPPermissionDenied);
  5065. end;
  5066. end else begin
  5067. ASender.Reply.SetReply(550,RSFTPSiteATTRIBInvalid);
  5068. Exit;
  5069. end;
  5070. end else begin
  5071. ASender.Reply.Assign(FReplyUnknownSITECommand);
  5072. end;
  5073. end;
  5074. end;
  5075. procedure TIdFTPServer.CommandSiteUTIME(ASender: TIdCommand);
  5076. procedure TryNewFTPSyntax(AContext: TIdFTPServerContext; ALSender: TIdCommand);
  5077. var
  5078. LgMTime : TDateTime;
  5079. LgPermitted : Boolean;
  5080. LFileName : String;
  5081. LDummy1, LDummy2 : TDateTime;
  5082. begin
  5083. //this is for gFTP Syntax
  5084. //such as: "SITE UTIME 20050815041129 /.bashrc"
  5085. LgPermitted := True;
  5086. if ALSender.Params.Count = 0 then begin
  5087. CmdSyntaxError(ALSender);
  5088. Exit;
  5089. end;
  5090. if IsValidTimeStamp(ALSender.Params[0]) then begin
  5091. LFileName := ALSender.UnparsedParams;
  5092. //This is local Time
  5093. LgMTime := UTCTimeToLocalTime(FTPMLSToGMTDateTime(Fetch(LFileName)));
  5094. LFileName := DoProcessPath(AContext, LFileName);
  5095. if Assigned(FOnSiteUTIME) then
  5096. begin
  5097. //indicate that both creation time and last access time should not be set
  5098. LDummy1 := 0;
  5099. LDummy2 := 0;
  5100. FOnSiteUTIME(AContext, LFileName, LDummy1, LgMTime, LDummy2, LgPermitted);
  5101. end
  5102. else if Assigned(FOnSetModifiedTime) then begin
  5103. FOnSetModifiedTime(AContext, LFileName, LgMTime);
  5104. end;
  5105. if LgPermitted then begin
  5106. ALSender.Reply.SetReply(200, RSFTPCHMODSuccessful);
  5107. end else begin
  5108. ALSender.Reply.SetReply(553, RSFTPPermissionDenied);
  5109. end;
  5110. end else
  5111. begin
  5112. CmdSyntaxError(ALSender);
  5113. end;
  5114. end;
  5115. var
  5116. LContext : TIdFTPServerContext;
  5117. LPermitted : Boolean;
  5118. LFileName : String;
  5119. LIdx : Integer;
  5120. LDateCount : Integer;
  5121. LAccessTime, LModTime, LCreateTime : TDateTime;
  5122. i : Integer;
  5123. begin
  5124. {
  5125. This is used by NcFTP like this:
  5126. SITE UTIME test.txt 20050731224504 20050731041205 20050731035940 UTC
  5127. where the first date is the "Last Access Time"
  5128. the second date is the "Last Modified Time"
  5129. and the final date is the "Creation File Time"
  5130. I think the third parameter is optional.
  5131. The final parameter is "UTC"
  5132. gFTP does something different. It does something like:
  5133. SITE UTIME 20050815041129 /.bashrc
  5134. where the timestamp is probably in based on the local time.
  5135. }
  5136. LPermitted := True;
  5137. LAccessTime := 0;
  5138. LModTime := 0;
  5139. LCreateTime := 0;
  5140. LContext := ASender.Context as TIdFTPServerContext;
  5141. if LContext.IsAuthenticated(ASender) then
  5142. begin
  5143. if Assigned(OnSiteUTIME) or Assigned(OnSetModifiedTime) or Assigned(OnSetCreationTime) then begin
  5144. LDateCount := 0;
  5145. LIdx := ASender.Params.Count - 1;
  5146. if ASender.Params.Count > 2 then begin
  5147. LPermitted := True;
  5148. if TextIsSame(ASender.Params[LIdx], 'UTC') then begin
  5149. //figure out how many dates we have and where the end of the filename is
  5150. Dec(LIdx);
  5151. Inc(LDateCount);
  5152. if IsValidTimeStamp(ASender.Params[LIdx]) then begin
  5153. Dec(LIdx);
  5154. Inc(LDateCount);
  5155. if IsValidTimeStamp(ASender.Params[LIdx]) then begin
  5156. Dec(LIdx);
  5157. Inc(LDateCount);
  5158. end;
  5159. end else begin
  5160. TryNewFTPSyntax(LContext, ASender);
  5161. Exit;
  5162. end;
  5163. //now extract the date
  5164. LAccessTime := FTPMLSToGMTDateTime(ASender.Params[LIdx]);
  5165. if LDateCount > 1 then begin
  5166. LModTime := FTPMLSToGMTDateTime(ASender.Params[LIdx+1]);
  5167. end;
  5168. if LDateCount > 2 then begin
  5169. LCreateTime := FTPMLSToGMTDateTime(ASender.Params[LIdx+2]);
  5170. end;
  5171. //extract filename including any spaces
  5172. LFileName := '';
  5173. for i := 0 to LIdx-1 do begin
  5174. LFileName := LFileName + ' ' + ASender.Params[i];
  5175. end;
  5176. IdDelete(LFileName,1,1);
  5177. LFileName := DoProcessPath(LContext,LFileName);
  5178. //now do it
  5179. if Assigned(FOnSiteUTIME) then begin
  5180. FOnSiteUTIME(LContext, LFileName, LAccessTime, LModTime, LCreateTime, LPermitted);
  5181. end else begin
  5182. if (LModTime <> 0) and Assigned(FOnSetModifiedTime) then begin
  5183. FOnSetModifiedTime(LContext, LFileName, LModTime);
  5184. end;
  5185. if (LCreateTime <> 0) and Assigned(FOnSetCreationTime) then begin
  5186. FOnSetCreationTime(LContext, LFileName, LCreateTime);
  5187. end;
  5188. end;
  5189. if LPermitted then begin
  5190. ASender.Reply.SetReply(200, RSFTPCHMODSuccessful);
  5191. end else begin
  5192. ASender.Reply.SetReply(553, RSFTPPermissionDenied);
  5193. end;
  5194. Exit;
  5195. end;
  5196. end;
  5197. end;
  5198. TryNewFTPSyntax(LContext, ASender);
  5199. // CmdNotImplemented(ASender);
  5200. end;
  5201. end;
  5202. procedure TIdFTPServer.DoOnSiteCHGRP(ASender: TIdFTPServerContext;
  5203. var AGroup: String; const AFileName: String; var VAUth: Boolean);
  5204. begin
  5205. if Assigned(FOnSiteCHGRP) then begin
  5206. FOnSiteCHGRP(ASender, AGroup, AFileName, VAuth);
  5207. end;
  5208. end;
  5209. procedure TIdFTPServer.DoOnSiteCHOWN(ASender: TIdFTPServerContext; var AOwner,
  5210. AGroup: String; const AFileName: String; var VAUth: Boolean);
  5211. begin
  5212. if Assigned(FOnSiteCHOWN) then begin
  5213. OnSiteCHOWN(ASender, AOwner, AGroup, AFileName, VAuth);
  5214. end;
  5215. end;
  5216. procedure TIdFTPServer.CommandSiteCHOWN(ASender: TIdCommand);
  5217. var
  5218. LContext : TIdFTPServerContext;
  5219. LPermitted : Boolean;
  5220. LFileName : String;
  5221. LOwner, LGroup : string;
  5222. begin
  5223. LContext := ASender.Context as TIdFTPServerContext;
  5224. if LContext.IsAuthenticated(ASender) then begin
  5225. if Assigned(OnSiteCHOWN) then begin
  5226. LPermitted := True;
  5227. LFileName := ASender.UnparsedParams;
  5228. LGroup := Fetch(LFileName);
  5229. LOwner := Fetch(LGroup,':');
  5230. DoOnSiteCHOWN(LContext, LOwner, LGroup, DoProcessPath(LContext, LFileName), LPermitted);
  5231. if LPermitted then begin
  5232. ASender.Reply.SetReply(220, IndyFormat(RSFTPCmdSuccessful, [ASender.RawLine]));
  5233. end else begin
  5234. ASender.Reply.SetReply(553, RSFTPPermissionDenied);
  5235. end;
  5236. end;
  5237. end;
  5238. end;
  5239. procedure TIdFTPServer.CommandSiteCHGRP(ASender: TIdCommand);
  5240. var
  5241. LContext : TIdFTPServerContext;
  5242. LPermitted : Boolean;
  5243. LFileName : String;
  5244. LGroup : String;
  5245. begin
  5246. LContext := ASender.Context as TIdFTPServerContext;
  5247. if LContext.IsAuthenticated(ASender) then begin
  5248. if Assigned(FOnSiteCHGRP) then begin
  5249. LPermitted := True;
  5250. LFileName := ASender.UnparsedParams;
  5251. LGroup := Fetch(LFileName);
  5252. DoOnSiteCHGRP(LContext, LGroup, DoProcessPath(LContext, LFileName), LPermitted);
  5253. if LPermitted then begin
  5254. ASender.Reply.SetReply(200, IndyFormat(RSFTPCmdSuccessful, [ASender.RawLine]));
  5255. end else begin
  5256. ASender.Reply.SetReply(553, RSFTPPermissionDenied);
  5257. end;
  5258. end;
  5259. end;
  5260. end;
  5261. procedure TIdFTPServer.CommandSiteCHMOD(ASender: TIdCommand);
  5262. var
  5263. LContext : TIdFTPServerContext;
  5264. LPermitted : Boolean;
  5265. LFileName : String;
  5266. LPerms : String;
  5267. LPermNo : Integer;
  5268. // under ARC, convert a weak reference to a strong reference before working with it
  5269. LFileSystem: TIdFTPBaseFileSystem;
  5270. begin
  5271. LContext := ASender.Context as TIdFTPServerContext;
  5272. if LContext.IsAuthenticated(ASender) then begin
  5273. LFileSystem := FTPFileSystem;
  5274. if Assigned(OnSiteCHMOD ) or Assigned(LFileSystem) then begin
  5275. LFileName := ASender.UnparsedParams;
  5276. LPerms := Fetch(LFileName);
  5277. If IsValidPermNumbers(LPerms) then begin
  5278. LPermitted := True;
  5279. LPermNo := IndyStrToInt(LPerms, 0);
  5280. DoOnSiteCHMOD(LContext, LPermNo, DoProcessPath(LContext, LFileName), LPermitted);
  5281. if LPermitted then begin
  5282. ASender.Reply.SetReply(220, RSFTPCHMODSuccessful);
  5283. end else begin
  5284. ASender.Reply.SetReply(553, RSFTPPermissionDenied);
  5285. end;
  5286. end else begin
  5287. CmdNotImplemented(ASender);
  5288. end;
  5289. end else begin
  5290. ASender.Reply.Assign(FReplyUnknownSITECommand);
  5291. end;
  5292. end;
  5293. end;
  5294. procedure TIdFTPServer.CommandSiteUMASK(ASender: TIdCommand);
  5295. var
  5296. LContext : TIdFTPServerContext;
  5297. LOldMask, LNewMask : Integer;
  5298. LPermitted : Boolean;
  5299. begin
  5300. LContext := ASender.Context as TIdFTPServerContext;
  5301. if LContext.IsAuthenticated(ASender) then begin
  5302. if Assigned(FOnSiteUMASK) then begin
  5303. if ASender.Params.Count > 0 then begin
  5304. If IsValidPermNumbers(ASender.Params[0]) then begin
  5305. LPermitted := True;
  5306. LNewMask := IndyStrToInt(ASender.Params[0], 0);
  5307. DoOnSiteUMASK(LContext, LNewMask, LPermitted);
  5308. if LPermitted then begin
  5309. LOldMask := LContext.FUMask;
  5310. LContext.FUMask := LNewMask;
  5311. ASender.Reply.SetReply(200, IndyFormat(RSFTPUMaskSet, [LNewMask, LOldMask]));
  5312. end else begin
  5313. ASender.Reply.SetReply(553, RSFTPPermissionDenied);
  5314. end;
  5315. end else begin
  5316. CmdNotImplemented(ASender);
  5317. end;
  5318. end else begin
  5319. ASender.Reply.SetReply(200, IndyFormat(RSFTPUMaskIs, [LContext.FUMask]));
  5320. end;
  5321. end else begin
  5322. CmdNotImplemented(ASender);
  5323. end;
  5324. end;
  5325. end;
  5326. function TIdFTPServer.IsValidPermNumbers(const APermNos: String): Boolean;
  5327. const
  5328. PERMDIGITS = '01234567';
  5329. var
  5330. i: Integer;
  5331. begin
  5332. Result := False;
  5333. for i := 1 to Length(APermNos) do begin
  5334. if not CharIsInSet(APermNos, i, PERMDIGITS) then begin
  5335. Exit;
  5336. end;
  5337. end;
  5338. Result := True;
  5339. end;
  5340. procedure TIdFTPServer.DoOnSiteUMASK(ASender: TIdFTPServerContext;
  5341. var VUMASK: Integer; var VAUth: Boolean);
  5342. begin
  5343. if Assigned(FOnSiteUMASK) then begin
  5344. FOnSiteUMASK(ASender,VUMASK,VAUth);
  5345. end;
  5346. end;
  5347. procedure TIdFTPServer.DoOnSetATTRIB(ASender: TIdFTPServerContext; var VAttr : UInt32; const AFileName : String; var VAUth : Boolean);
  5348. begin
  5349. if Assigned( FOnSetATTRIB) then begin
  5350. FOnSetATTRIB(ASender, VAttr, AFileName, VAUth);
  5351. end;
  5352. end;
  5353. procedure TIdFTPServer.DoOnSiteCHMOD(ASender: TIdFTPServerContext;
  5354. var APermissions: Integer; const AFileName: String; var VAUth: Boolean);
  5355. begin
  5356. if Assigned(FOnSiteCHMOD) then begin
  5357. FOnSiteCHMOD(ASender,APermissions,AFileName,VAUth);
  5358. end;
  5359. end;
  5360. procedure TIdFTPServer.CommandSiteDIRSTYLE(ASender: TIdCommand);
  5361. //FMSDOSMode
  5362. var
  5363. LContext : TIdFTPServerContext;
  5364. //SITE DIRSTYLE is only for MS-DOS formatted directory lists so
  5365. //a program can flip to Unix directory list format. This is
  5366. //for compatability purposes, ONLY.
  5367. begin
  5368. LContext := ASender.Context as TIdFTPServerContext;
  5369. if (FDirFormat = ftpdfDOS) or
  5370. ((FDirFormat = ftpdfOSDependent) and (GOSType = otWindows)) then begin
  5371. if LContext.IsAuthenticated(ASender) then begin
  5372. if ASender.Params.Count = 0 then begin
  5373. LContext.FMSDOSMode := not LContext.FMSDOSMode;
  5374. if LContext.FMSDOSMode then begin
  5375. ASender.Reply.SetReply(200, IndyFormat(RSFTPDirStyle, [RSFTPOn]));
  5376. end else begin
  5377. ASender.Reply.SetReply(200, IndyFormat(RSFTPDirStyle, [RSFTPOff]));
  5378. end;
  5379. end;
  5380. end;
  5381. end else begin
  5382. ASender.Reply.Assign(FReplyUnknownSITECommand);
  5383. end;
  5384. end;
  5385. procedure TIdFTPServer.CommandSiteHELP(ASender: TIdCommand);
  5386. var
  5387. s : String;
  5388. LCmds : TStringList;
  5389. LContext : TIdFTPServerContext;
  5390. begin
  5391. LContext := ASender.Context as TIdFTPServerContext;
  5392. if LContext.IsAuthenticated(ASender) then begin
  5393. s := RSFTPSITECmdsSupported+EOL;
  5394. LCmds := TStringList.Create;
  5395. try
  5396. if Assigned(OnSetAttrib) then begin
  5397. LCmds.Add('ATTRIB'); {Do not translate}
  5398. end;
  5399. if Assigned(OnSiteCHMOD) then begin
  5400. LCmds.Add('CHMOD'); {Do not translate}
  5401. end;
  5402. if (FDirFormat = ftpdfDOS) or
  5403. ((FDirFormat = ftpdfOSDependent) and (GOSType = otWindows)) then begin
  5404. LCmds.Add('DIRSTYLE'); {Do not translate}
  5405. end;
  5406. if Assigned(OnSiteUMASK) then begin
  5407. LCmds.Add('UMASK'); {Do not translate}
  5408. end;
  5409. LCmds.Add('ZONE'); {Do not translate}
  5410. s := s + HelpText(LCmds) + FEndOfHelpLine;
  5411. ASender.Reply.SetReply(214, s);
  5412. finally
  5413. LCmds.Free;
  5414. end;
  5415. end;
  5416. end;
  5417. function TIdFTPServer.HelpText(Cmds: TStrings): String;
  5418. var
  5419. LRows : Integer;
  5420. LMod : Integer;
  5421. i : Integer;
  5422. begin
  5423. Result := '';
  5424. if Cmds.Count =0 then begin
  5425. Exit;
  5426. end;
  5427. LRows := Cmds.Count div 6;
  5428. LMod := Cmds.Count mod 6;
  5429. if Cmds.Count < 6 then begin
  5430. Result := ' ';
  5431. for i := 0 to Cmds.Count -1 do begin
  5432. Result := Result + IndyFormat('%-10s', [Cmds[i]]);
  5433. end;
  5434. Result := Result + CR;
  5435. end else begin
  5436. for i := 0 to (LRows -1) do begin
  5437. if (i <= LMod-1) and (LMod<>0) then begin
  5438. Result := Result + IndyFormat(' %-10s%-10s%-10s%-10s%-10s%-10s%-10s', {Do not translate}
  5439. [ Cmds[i],Cmds[i+LRows],Cmds[(LRows*2)+i],
  5440. Cmds[(LRows*3)+i],Cmds[(LRows*4)+i],Cmds[(LRows*5)+i],
  5441. Cmds[(LRows*6)+i]])+CR;
  5442. end else begin
  5443. Result := Result + IndyFormat(' %-10s%-10s%-10s%-10s%-10s%-10s', {Do not translate}
  5444. [ Cmds[i],Cmds[i+LRows],Cmds[(LRows*2)+i],
  5445. Cmds[(LRows*3)+i],Cmds[(LRows*4)+i],Cmds[(LRows*5)+i]])+CR;
  5446. end;
  5447. end;
  5448. end;
  5449. end;
  5450. procedure TIdFTPServer.CommandSITE(ASender: TIdCommand);
  5451. var
  5452. LCmd : String;
  5453. begin
  5454. LCmd := ASender.UnparsedParams;
  5455. ASender.Reply.Clear;
  5456. ASender.PerformReply := False;
  5457. if not FSITECommands.HandleCommand(ASender.Context, LCmd) then begin
  5458. ASender.Reply.NumericCode := 500;
  5459. CmdSyntaxError(ASender.Context, ASender.CommandHandler.Command + ' ' + LCmd, ASender.Reply);
  5460. ASender.PerformReply := False;
  5461. end;
  5462. end;
  5463. function TIdFTPServer.MLSFEATLine(const AFactMask: TIdMLSDAttrs;
  5464. const AFacts: TIdFTPFactOutputs): String;
  5465. begin
  5466. Result := 'MLST size'; {Do not translate}
  5467. //the * indicates if the option is selected for MLST
  5468. if Size in AFacts then begin {Do not translate}
  5469. Result := Result + '*;';
  5470. end else begin
  5471. Result := Result + ';'
  5472. end;
  5473. Result := Result + 'Type'; {Do not translate}
  5474. if ItemType in AFacts then begin {Do not translate}
  5475. Result := Result + '*;'; {Do not translate}
  5476. end else begin
  5477. Result := Result + ';';
  5478. end;
  5479. if mlsdPerms in FMLSDFacts then begin
  5480. Result := Result + 'Perm'; {Do not translate}
  5481. if Perm in AFacts then begin {Do not translate}
  5482. Result := Result + '*;'; {Do not translate}
  5483. end else begin
  5484. Result := Result + ';';
  5485. end;
  5486. end;
  5487. if mlsdFileCreationTime in FMLSDFacts then begin
  5488. Result := Result + 'Create'; {Do not translate}
  5489. if CreateTime in AFacts then begin {Do not translate}
  5490. Result := Result + '*;'; {Do not translate}
  5491. end else begin
  5492. Result := Result + ';';
  5493. end;
  5494. end;
  5495. Result := Result + 'Modify'; {Do not translate}
  5496. if Modify in AFacts then begin
  5497. Result := Result + '*;';
  5498. end else begin
  5499. Result := Result + ';';
  5500. end;
  5501. if mlsdUnixModes in FMLSDFacts then begin
  5502. Result := Result + 'UNIX.mode'; {Do not translate}
  5503. if UnixMODE in AFacts then begin {Do not translate}
  5504. Result := Result + '*;'; {Do not translate}
  5505. end else begin
  5506. Result := Result + ';';
  5507. end;
  5508. end;
  5509. if mlsdUnixOwner in FMLSDFacts then
  5510. begin
  5511. Result := Result + 'UNIX.owner'; {Do not translate}
  5512. if UnixOwner in AFacts then begin {Do not translate}
  5513. Result := Result + '*;'; {Do not translate}
  5514. end else begin
  5515. Result := Result + ';';
  5516. end;
  5517. end;
  5518. if mlsdUnixGroup in FMLSDFacts then begin
  5519. Result := Result + 'UNIX.group'; {Do not translate}
  5520. if UnixGroup in AFacts then begin {Do not translate}
  5521. Result := Result + '*;'; {Do not translate}
  5522. end else begin
  5523. Result := Result + ';';
  5524. end;
  5525. end;
  5526. if mlsdUniqueID in FMLSDFacts then begin
  5527. Result := Result + 'Unique'; {Do not translate}
  5528. if Unique in AFacts then begin {Do not translate}
  5529. Result := Result + '*;'; {Do not translate}
  5530. end else begin
  5531. Result := Result + ';';
  5532. end;
  5533. end;
  5534. if mlsdFileLastAccessTime in FMLSDFacts then begin
  5535. Result := Result + 'Windows.lastaccesstime'; {Do not translate}
  5536. if CreateTime in AFacts then begin {Do not translate}
  5537. Result := Result + '*;'; {Do not translate}
  5538. end else begin
  5539. Result := Result + ';';
  5540. end;
  5541. end;
  5542. if mlsdWin32Attributes in FMLSDFacts then begin
  5543. Result := Result + 'Win32.ea'; {Do not translate}
  5544. if WinAttribs in AFacts then begin {Do not translate}
  5545. Result := Result + '*;'; {Do not translate}
  5546. end else begin
  5547. Result := Result + ';';
  5548. end;
  5549. end;
  5550. if mlsdWin32DriveType in FMLSDFacts then begin
  5551. Result := Result + 'Win32.dt';
  5552. if WinDriveType in AFacts then begin
  5553. Result := Result + '*;'; {Do not localize}
  5554. end else begin
  5555. Result := Result + ';'; {Do not localize}
  5556. end;
  5557. end;
  5558. if mlstWin32DriveLabel in FMLSDFacts then begin
  5559. Result := Result + 'Win32.dl';
  5560. if WinDriveLabel in AFacts then begin
  5561. Result := Result + '*;'; {Do not localize}
  5562. end else begin
  5563. Result := Result + ';'; {Do not localize}
  5564. end;
  5565. end;
  5566. if Result <> '' then begin
  5567. SetLength(Result, Length(Result) - 1);
  5568. end;
  5569. end;
  5570. procedure TIdFTPServer.CommandCLNT(ASender: TIdCommand);
  5571. var
  5572. LClientInfo : TIdFTPClientIdentifier;
  5573. LContext: TIdFTPServerContext;
  5574. begin
  5575. LContext := ASender.Context as TIdFTPServerContext;
  5576. // TODO: store the client's info in LContext?
  5577. if ASender.UnparsedParams <> '' then begin
  5578. if Assigned(FOnClientID) then begin
  5579. FOnClientID(LContext, ASender.UnparsedParams);
  5580. end;
  5581. if Assigned(FOnClientIDEx) then begin
  5582. LClientInfo := TIdFTPClientIdentifier.Create;
  5583. try
  5584. LClientInfo.CLNTParams := ASender.UnparsedParams;
  5585. FOnClientIDEx(LContext, LClientInfo);
  5586. finally
  5587. LClientInfo.Free;
  5588. end;
  5589. end;
  5590. end else begin
  5591. CmdInvalidParams(ASender);
  5592. end;
  5593. end;
  5594. procedure TIdFTPServer.CommandCSID(ASender: TIdCommand);
  5595. var
  5596. LContext : TIdFTPServerContext;
  5597. LClientInfo : TIdFTPClientIdentifier;
  5598. LServerInfo: TIdFTPServerIdentifier;
  5599. begin
  5600. LContext := ASender.Context as TIdFTPServerContext;
  5601. if LContext.IsAuthenticated(ASender) then begin
  5602. // TODO: store the client's info in LContext?
  5603. if Assigned(FOnClientID) or Assigned(FOnClientIDEx) then begin
  5604. LClientInfo := TIdFTPClientIdentifier.Create;
  5605. try
  5606. LClientInfo.CSIDParams := ASender.UnparsedParams;
  5607. if (LClientInfo.ClientName = '') or
  5608. (LClientInfo.ClientVersion = '') then
  5609. begin
  5610. CmdInvalidParams(ASender);
  5611. Exit;
  5612. end;
  5613. if Assigned(FOnClientID) then begin
  5614. FOnClientID(LContext, LClientInfo.CLNTParams);
  5615. end;
  5616. if Assigned(FOnClientIDEx) then begin
  5617. FOnClientIDEx(LContext, LClientInfo);
  5618. end;
  5619. finally
  5620. LClientInfo.Free;
  5621. end;
  5622. end;
  5623. if FPathProcessing <> ftppCustom then begin
  5624. LServerInfo := TIdFTPServerIdentifier.Create;
  5625. try
  5626. LServerInfo.Assign(FServerInfo);
  5627. LServerInfo.CaseSensitive := FTPIsCaseSensitive;
  5628. LServerInfo.DirSeparator := FTPPathSeparator;
  5629. ASender.Reply.SetReply(200, LServerInfo.CSIDParams);
  5630. finally
  5631. LServerInfo.Free;
  5632. end;
  5633. end else begin
  5634. ASender.Reply.SetReply(200, FServerInfo.CSIDParams);
  5635. end;
  5636. end;
  5637. end;
  5638. procedure TIdFTPServer.SetPASVBoundPortMax(const AValue: TIdPort);
  5639. begin
  5640. if FPASVBoundPortMin <> 0 then begin
  5641. if AValue <= FPASVBoundPortMin then begin
  5642. raise EIdFTPBoundPortMaxGreater.Create(RSFTPPASVBoundPortMaxMustBeGreater);
  5643. end;
  5644. end;
  5645. FPASVBoundPortMax := AValue;
  5646. end;
  5647. procedure TIdFTPServer.SetPASVBoundPortMin(const AValue: TIdPort);
  5648. begin
  5649. if FPASVBoundPortMax <> 0 then begin
  5650. if FPASVBoundPortMax <= AValue then begin
  5651. raise EIdFTPBoundPortMinLess.Create(RSFTPPASVBoundPortMinMustBeLess);
  5652. end;
  5653. end;
  5654. FPASVBoundPortMin := AValue;
  5655. end;
  5656. procedure TIdFTPServer.DoOnDataPortAfterBind(ASender: TIdFTPServerContext);
  5657. begin
  5658. if Assigned(FOnDataPortAfterBind) then begin
  5659. FOnDataPortAfterBind(ASender);
  5660. end;
  5661. end;
  5662. procedure TIdFTPServer.DoOnDataPortBeforeBind(ASender: TIdFTPServerContext);
  5663. begin
  5664. if Assigned(FOnDataPortBeforeBind) then begin
  5665. FOnDataPortBeforeBind(ASender);
  5666. end;
  5667. end;
  5668. function TIdFTPServer.FTPPathSeparator : Char;
  5669. begin
  5670. case FPathProcessing of
  5671. ftppDOS: Result := '\'; {do not localize}
  5672. ftpOSDependent:
  5673. begin
  5674. if (GOSType = otWindows) then begin
  5675. Result := '\'; {do not localize}
  5676. end else begin
  5677. Result := '/'; {do not localize}
  5678. end;
  5679. end;
  5680. ftppUnix: Result := '/'; {do not localize}
  5681. ftppCustom: Result := FServerInfo.DirSeparator;
  5682. else
  5683. Result := '/'; {do not localize}
  5684. end;
  5685. end;
  5686. function TIdFTPServer.FTPIsCaseSensitive: Boolean;
  5687. begin
  5688. case FPathProcessing of
  5689. ftppDOS : Result := False;
  5690. ftpOSDependent : Result := (GOSType <> otWindows);
  5691. ftppCustom : Result := FServerInfo.CaseSensitive;
  5692. else
  5693. Result := True;
  5694. end;
  5695. end;
  5696. function TIdFTPServer.FTPNormalizePath(const APath: String): String;
  5697. {
  5698. Microsoft IIS accepts both a "/" and a "\" as path/file name separators.
  5699. We have to flatten this out so that our FTP server can behave like Microsoft IIS.
  5700. In Unix, a "\" is a valid filename character so we don't anything there.
  5701. This WILL cause a "\" to be treated differently in Unix and Win32. I submit that
  5702. this is really desirable as both file systems are like apples and oranges.
  5703. }
  5704. begin
  5705. case FPathProcessing of
  5706. ftppDOS : Result := ReplaceAll(APath, '\', '/');
  5707. ftpOSDependent :
  5708. begin
  5709. if GOSType = otWindows then begin
  5710. Result := ReplaceAll(APath, '\', '/');
  5711. end else begin
  5712. Result := APath;
  5713. end;
  5714. end;
  5715. else
  5716. Result := APath;
  5717. end;
  5718. end;
  5719. function TIdFTPServer.DoProcessPath(ASender: TIdFTPServerContext; const APath: TIdFTPFileName): TIdFTPFileName;
  5720. begin
  5721. if FPathProcessing <> ftppCustom then begin
  5722. Result := FTPNormalizePath(APath);
  5723. Result := ProcessPath(ASender.CurrentDir, Result); {Do not Localize}
  5724. end else begin
  5725. Result := APath;
  5726. if Assigned(FOnCustomPathProcess) then begin
  5727. FOnCustomPathProcess(ASender, Result);
  5728. end;
  5729. end;
  5730. end;
  5731. function TIdFTPServer.CDUPDir(AContext : TIdFTPServerContext) : String;
  5732. const
  5733. LCDUP_DOS = '..\';
  5734. CDUP_UNIX = '../';
  5735. begin
  5736. case FPathProcessing of
  5737. ftppDOS : Result := LCDUP_DOS;
  5738. ftpOSDependent :
  5739. if GOSType = otWindows then begin
  5740. Result := LCDUP_DOS;
  5741. end else begin
  5742. Result := CDUP_UNIX;
  5743. end;
  5744. ftppCustom : Result := DoProcessPath(AContext, '..');
  5745. else
  5746. Result := CDUP_UNIX;
  5747. end;
  5748. end;
  5749. function TIdFTPServer.DoSysType(ASender: TIdFTPServerContext): String;
  5750. begin
  5751. //We tie the SYST descriptor to the directory style for compatability
  5752. //reasons. Some FTP clients use the SYST descriptor to determine what
  5753. //type of FTP directory list parsing to do. I think TurboPower IPros does this.
  5754. //Note that I personally do not find this to be sound as a general rule.
  5755. case FDirFormat of
  5756. ftpdfOSDependent :
  5757. begin
  5758. if GOSType = otWindows then begin
  5759. Result := SYST_ID_NT;
  5760. end else begin
  5761. Result := SYST_ID_UNIX;
  5762. end;
  5763. end;
  5764. ftpdfUnix, ftpdfEPLF : Result := SYST_ID_UNIX;
  5765. ftpdfDOS : Result := SYST_ID_NT;
  5766. ftpdfCustom : Result := FCustomSystID;
  5767. end;
  5768. end;
  5769. procedure TIdFTPServer.DoOnCustomListDirectory(
  5770. ASender: TIdFTPServerContext; const APath: string;
  5771. ADirectoryListing: TStrings; const ACmd, ASwitches: String);
  5772. begin
  5773. if Assigned(OnCustomListDirectory) then begin
  5774. OnCustomListDirectory(ASender,APath,ADirectoryListing,ACmd,ASwitches);
  5775. end;
  5776. end;
  5777. procedure TIdFTPServer.CmdNotImplemented(ASender: TIdCommand);
  5778. begin
  5779. ASender.Reply.SetReply(550, IndyFormat(RSFTPCmdNotImplemented, [ASender.CommandHandler.Command ]));
  5780. end;
  5781. procedure TIdFTPServer.CmdFileActionAborted(ASender: TIdCommand);
  5782. begin
  5783. ASender.Reply.SetReply(550, RSFTPFileActionAborted);
  5784. end;
  5785. //This is for where the client didn't provide a valid number of parameters for a command
  5786. procedure TIdFTPServer.CmdInvalidParamNum(ASender: TIdCommand);
  5787. begin
  5788. ASender.Reply.SetReply(501, IndyFormat(RSFTPInvalidNumberArgs, [ASender.CommandHandler.Command]));
  5789. end;
  5790. //This is for other command syntax issues.
  5791. procedure TIdFTPServer.CmdInvalidParams(ASender: TIdCommand);
  5792. begin
  5793. ASender.Reply.SetReply(501, IndyFormat(RSFTPParamError, [ASender.CommandHandler.Command]));
  5794. end;
  5795. procedure TIdFTPServer.CmdTwineFileActionAborted(ASender: TIdCommand);
  5796. begin
  5797. ASender.Reply.SetReply(504, RSFTPFileActionAborted);
  5798. end;
  5799. procedure TIdFTPServer.CmdCommandSuccessful(ASender: TIdCOmmand; const AReplyCode : Integer = 250);
  5800. begin
  5801. ASender.Reply.SetReply(AReplyCode, IndyFormat(RSFTPCmdSuccessful, [ASender.CommandHandler.Command]));
  5802. end;
  5803. procedure TIdFTPServer.CommandSSCN(ASender: TIdCommand);
  5804. const
  5805. REPLY_SSCN_ON = 'SSCN:CLIENT METHOD'; {do not localize}
  5806. REPLY_SSCN_OFF = 'SSCN:SERVER METHOD'; {do not localize}
  5807. var
  5808. LContext : TIdFTPServerContext;
  5809. begin
  5810. if UseTLS = utNoTLSSupport then begin
  5811. CmdNotImplemented(ASender);
  5812. Exit;
  5813. end;
  5814. LContext := ASender.Context as TIdFTPServerContext;
  5815. if LContext.IsAuthenticated(ASender) then begin
  5816. if ASender.Params.Count = 0 then begin
  5817. //check state
  5818. if LContext.SSCNOn then begin
  5819. ASender.Reply.SetReply(200, REPLY_SSCN_ON);
  5820. end else begin
  5821. ASender.Reply.SetReply(200, REPLY_SSCN_OFF);
  5822. end;
  5823. end else begin
  5824. //set state
  5825. case PosInStrArray(ASender.Params[0], OnOffStates, False) of
  5826. 0 : //'ON'
  5827. begin
  5828. LContext.SSCNOn := True;
  5829. ASender.Reply.SetReply(200, REPLY_SSCN_ON);
  5830. end;
  5831. 1 : //'OFF'
  5832. begin
  5833. LContext.SSCNOn := False;
  5834. ASender.Reply.SetReply(200, REPLY_SSCN_OFF);
  5835. end;
  5836. else
  5837. ASender.Reply.SetReply(504, RSFTPInvalidForParam);
  5838. end;
  5839. end;
  5840. end;
  5841. end;
  5842. procedure TIdFTPServer.CommandCPSV(ASender: TIdCommand);
  5843. var
  5844. LContext : TIdFTPServerContext;
  5845. LIO : TIdSSLIOHandlerSocketBase;
  5846. begin
  5847. //CPSV must be used with SSL and can only be used with IPv4
  5848. if (UseTLS = utNoTLSSupport) or
  5849. (ASender.Context.Binding.IPVersion <> Id_IPv4) then begin
  5850. CmdSyntaxError(ASender);
  5851. Exit;
  5852. end;
  5853. CommandPASV(ASender);
  5854. LContext := TIdFTPServerContext(ASender.Context);
  5855. LIO := LContext.DataChannel.FDataChannel.IOHandler as TIdSSLIOHandlerSocketBase;
  5856. //tell IOHandler to use ssl_Conntect
  5857. LIO.IsPeer := False;
  5858. end;
  5859. procedure TIdFTPServer.CommandSiteZONE(ASender: TIdCommand);
  5860. var
  5861. LMin : Integer;
  5862. LFmt: string;
  5863. begin
  5864. LMin := MinutesFromGMT;
  5865. //plus must always be displayed for positive numbers
  5866. if LMin < 0 then begin
  5867. LFmt := 'UTC%d'; {do not localize}
  5868. end else begin
  5869. LFmt := 'UTC+%d'; {do not localize}
  5870. end;
  5871. ASender.Reply.SetReply(210, IndyFormat(LFmt, [LMin]));
  5872. end;
  5873. procedure TIdFTPServer.CommandCheckSum(ASender: TIdCommand);
  5874. const
  5875. HashTypes: array[0..4] of TIdHashClass = (TIdHashCRC32, TIdHashMessageDigest5, TIdHashSHA1, TIdHashSHA256, TIdHashSHA512);
  5876. var
  5877. LCalcStream : TStream;
  5878. LFileName, LCheckSum, LBuf : String;
  5879. LBeginPos, LEndPos : Int64;
  5880. LContext : TIdFTPServerContext;
  5881. LHashIdx: Integer;
  5882. // under ARC, convert a weak reference to a strong reference before working with it
  5883. LFileSystem: TIdFTPBaseFileSystem;
  5884. begin
  5885. if GetFIPSMode and
  5886. (PosInStrArray(ASender.CommandHandler.Command, ['XCRC', 'XMD5']) > -1) then begin
  5887. CmdSyntaxError(ASender);
  5888. Exit;
  5889. end;
  5890. LFileSystem := FTPFileSystem;
  5891. if Assigned(FOnCRCFile) or Assigned(LFileSystem) then begin
  5892. LContext := TIdFTPServerContext(ASender.Context);
  5893. if LContext.IsAuthenticated(ASender) then begin
  5894. LBuf := ASender.UnparsedParams;
  5895. if Pos('"', LBuf) > 0 then begin {do not localize}
  5896. Fetch(LBuf, '"'); {do not localize}
  5897. LFileName := Fetch(LBuf, '"'); {do not localize}
  5898. end else begin
  5899. LFileName := Fetch(LBuf);
  5900. end;
  5901. if LFileName = '' then begin
  5902. CmdInvalidParamNum(ASender);
  5903. Exit;
  5904. end;
  5905. LBuf := Trim(LBuf);
  5906. if LBuf <> '' then begin
  5907. LBeginPos := IndyStrToStreamSize(Fetch(LBuf), -1);
  5908. if LBeginPos < 0 then begin
  5909. CmdInvalidParams(ASender);
  5910. Exit;
  5911. end;
  5912. LBuf := Trim(LBuf);
  5913. if LBuf <> '' then begin
  5914. LEndPos := IndyStrToStreamSize(Fetch(LBuf), -1);
  5915. if LEndPos < 0 then begin
  5916. CmdInvalidParams(ASender);
  5917. Exit;
  5918. end;
  5919. end else begin
  5920. LEndPos := -1;
  5921. end;
  5922. end else begin
  5923. LBeginPos := 0;
  5924. LEndPos := -1;
  5925. end;
  5926. LCalcStream := nil;
  5927. LFileName := DoProcessPath(LContext, LFileName);
  5928. DoOnCRCFile(LContext, LFileName, LCalcStream);
  5929. if Assigned(LCalcStream) then begin
  5930. if LEndPos = -1 then begin
  5931. LEndPos := LCalcStream.Size;
  5932. end;
  5933. try
  5934. LCalcStream.Position := 0;
  5935. LHashIdx := PosInStrArray(ASender.CommandHandler.Command, ['XCRC', 'XMD5', 'XSHA1','XSHA256','XSHA512'], False); {do not localize}
  5936. LCheckSum := CalculateCheckSum(HashTypes[LHashIdx], LCalcStream, LBeginPos, LEndPos);
  5937. ASender.Reply.SetReply(250, LCheckSum);
  5938. finally
  5939. LCalcStream.Free;
  5940. end;
  5941. end else begin
  5942. CmdFileActionAborted(ASender);
  5943. end;
  5944. end;
  5945. end else begin
  5946. CmdSyntaxError(ASender);
  5947. end;
  5948. end;
  5949. procedure TIdFTPServer.DoOnFileExistCheck(AContext: TIdFTPServerContext;
  5950. const AFileName: String; var VExist: Boolean);
  5951. begin
  5952. if Assigned(FOnFileExistCheck) then begin
  5953. FOnFileExistCheck(AContext, AFileName, VExist);
  5954. end;
  5955. end;
  5956. procedure TIdFTPServer.CommandSPSV(ASender: TIdCommand);
  5957. var
  5958. LIP : String;
  5959. LBPort : Word;
  5960. LIPVer : TIdIPVersion;
  5961. begin
  5962. //just to keep the compiler happy
  5963. LBPort := 0;
  5964. if InternalPASV(ASender, LIP, LBPort, LIPVer) then begin
  5965. ASender.Reply.SetReply(227, IntToStr(LBPort));
  5966. end;
  5967. end;
  5968. function TIdFTPServer.InternalPASV(ASender: TIdCommand; var VIP : String;
  5969. var VPort: TIdPort; var VIPVersion : TIdIPVersion): Boolean;
  5970. var
  5971. LContext : TIdFTPServerContext;
  5972. LBPortMin, LBPortMax: TIdPort;
  5973. LDataChannel: TIdSimpleServer;
  5974. begin
  5975. Result := False;
  5976. LContext := ASender.Context as TIdFTPServerContext;
  5977. if LContext.IsAuthenticated(ASender) then begin
  5978. if LContext.FEPSVAll then begin
  5979. ASender.Reply.SetReply(501, IndyFormat(RSFTPNotAllowedAfterEPSVAll, [ASender.CommandHandler.Command]));
  5980. Exit;
  5981. end;
  5982. VIP := LContext.Binding.IP;
  5983. VIPVersion := LContext.Binding.IPVersion;
  5984. if (FPASVBoundPortMin <> 0) and (FPASVBoundPortMax <> 0) then begin
  5985. LBPortMin := FPASVBoundPortMin;
  5986. LBPortMax := FPASVBoundPortMax;
  5987. end else begin
  5988. LBPortMin := FDefaultDataPort;
  5989. LBPortMax := LBPortMin;
  5990. end;
  5991. DoOnPASVBeforeBind(LContext, VIP, LBPortMin, LBPortMax, VIPVersion);
  5992. LContext.CreateDataChannel(True);
  5993. LDataChannel := TIdSimpleServer(LContext.FDataChannel.FDataChannel);
  5994. LDataChannel.BoundIP := VIP;
  5995. if LBPortMin = LBPortMax then begin
  5996. LDataChannel.BoundPort := LBPortMin;
  5997. LDataChannel.BoundPortMin := 0;
  5998. LDataChannel.BoundPortMax := 0;
  5999. end else begin
  6000. LDataChannel.BoundPort := 0;
  6001. LDataChannel.BoundPortMin := LBPortMin;
  6002. LDataChannel.BoundPortMax := LBPortMax;
  6003. end;
  6004. LDataChannel.IPVersion := VIPVersion;
  6005. LDataChannel.BeginListen;
  6006. VIP := LDataChannel.Binding.IP;
  6007. VPort := LDataChannel.Binding.Port;
  6008. LContext.FPASV := True;
  6009. LContext.FDataPortDenied := False;
  6010. Result := True;
  6011. end;
  6012. end;
  6013. procedure TIdFTPServer.DoOnPASVBeforeBind(ASender: TIdFTPServerContext;
  6014. var VIP: String; var VPortMin, VPortMax: TIdPort; const AIPVersion: TIdIPVersion);
  6015. begin
  6016. if Assigned(FOnPASVBeforeBind) then begin
  6017. FOnPASVBeforeBind(ASender, VIP, VPortMin, VPortMax, AIPVersion);
  6018. end;
  6019. end;
  6020. procedure TIdFTPServer.DoOnPASVReply(ASender: TIdFTPServerContext;
  6021. var VIP: String; var VPort: TIdPort; const AIPVersion: TIdIPVersion);
  6022. begin
  6023. if Assigned(FOnPASVReply) then begin
  6024. FOnPASVReply(ASender, VIP, VPort, AIPVersion);
  6025. end;
  6026. end;
  6027. function TIdFTPServer.ReadCommandLine(AContext: TIdContext): string;
  6028. var
  6029. i : Integer;
  6030. State: TIdFTPTelnetState;
  6031. lb : Byte;
  6032. LContext: TIdFTPServerContext;
  6033. { Receive the line in 8-bit initially so that .NET can then
  6034. decode any UTF-8 data into a Unicode string afterwards if
  6035. needed }
  6036. LLine: TIdBytes;
  6037. LReply: TIdBytes;
  6038. Finished: Boolean;
  6039. begin
  6040. Result := '';
  6041. LContext := AContext as TIdFTPServerContext;
  6042. //we do it this way in case there's no data. We don't want to stop
  6043. //a data channel operation if that's the case.
  6044. AContext.Connection.IOHandler.CheckForDataOnSource(1);
  6045. if AContext.Connection.IOHandler.InputBufferIsEmpty then begin
  6046. Exit;
  6047. end;
  6048. //
  6049. SetLength(LLine, 0);
  6050. SetLength(LReply, 0);
  6051. Finished := False;
  6052. State := tsData;
  6053. repeat
  6054. lb := AContext.Connection.IOHandler.ReadByte;
  6055. case State of
  6056. tsData:
  6057. begin
  6058. case lb of
  6059. $FF: //is a command
  6060. begin
  6061. State := tsIAC;
  6062. end;
  6063. $0D: //wait for the next character to see what to do
  6064. begin
  6065. State := tsCheckCR;
  6066. end;
  6067. else
  6068. AppendByte(LLine, lb);
  6069. end;
  6070. end;
  6071. tsCheckCR:
  6072. begin
  6073. case lb of
  6074. $0: // preserve CR
  6075. begin
  6076. AppendByte(LLine, $0D);
  6077. State := tsData;
  6078. end;
  6079. $0A:
  6080. begin
  6081. State := tsData;
  6082. Finished := True;
  6083. end;
  6084. $FF: //unexpected IAC, just in case
  6085. begin
  6086. AppendByte(LLine, $0D);
  6087. State := tsIAC;
  6088. end;
  6089. else
  6090. ExpandBytes(LLine, Length(LLine), 2);
  6091. LLine[Length(LLine)-2] := $0D;
  6092. LLine[Length(LLine)-1] := lb;
  6093. State := tsData;
  6094. end;
  6095. end;
  6096. tsIAC:
  6097. begin
  6098. case lb of
  6099. $F1, //no-operation - do nothing
  6100. $F3: //break - do nothing for now
  6101. begin
  6102. State := tsData;
  6103. end;
  6104. $F4: //interrupt process - clear result and wait for data mark
  6105. begin
  6106. SetLength(LLine, 0);
  6107. State := tsInterrupt;
  6108. end;
  6109. $F5: //abort output
  6110. begin
  6111. // note - the DM needs to be sent as OOB "Urgent" data
  6112. SetLength(LReply, 4);
  6113. // TELNET_IP
  6114. LReply[0] := $FF;
  6115. LReply[1] := $F4;
  6116. // TELNET_DM
  6117. LReply[2] := $FF;
  6118. LReply[3] := $F2;
  6119. AContext.Connection.IOHandler.Write(LReply);
  6120. SetLength(LReply, 0);
  6121. State := tsData;
  6122. end;
  6123. $F6: //are you there - do nothing for now
  6124. begin
  6125. State := tsData;
  6126. end;
  6127. $F7: //erase character
  6128. begin
  6129. i := Length(LLine);
  6130. if i > 0 then begin
  6131. SetLength(LLine, i-1);
  6132. end;
  6133. State := tsData;
  6134. end;
  6135. $F8 : //erase line
  6136. begin
  6137. SetLength(LLine, 0);
  6138. State := tsData;
  6139. end;
  6140. $F9 : //go ahead - do nothing for now
  6141. begin
  6142. State := tsData;
  6143. end;
  6144. $FA : //begin sub-negotiation
  6145. begin
  6146. State := tsNegotiate;
  6147. end;
  6148. $FB : //I will use
  6149. begin
  6150. State := tsWill;
  6151. end;
  6152. $FC : //you won't use
  6153. begin
  6154. State := tsWont;
  6155. end;
  6156. $FD : //please, you use option
  6157. begin
  6158. State := tsDo;
  6159. end;
  6160. $FE : //please, you stop option
  6161. begin
  6162. State := tsDont;
  6163. end;
  6164. $FF : //data $FF
  6165. begin
  6166. AppendByte(LLine, $FF);
  6167. State := tsData;
  6168. end;
  6169. else
  6170. // unknown command, ignore
  6171. State := tsData;
  6172. end;
  6173. end;
  6174. tsWill:
  6175. begin
  6176. SetLength(LReply, 3);
  6177. // TELNET_WONT
  6178. LReply[0] := $FF;
  6179. LReply[1] := $FC;
  6180. LReply[2] := lb;
  6181. AContext.Connection.IOHandler.Write(LReply);
  6182. SetLength(LReply, 0);
  6183. State := tsData;
  6184. end;
  6185. tsDo:
  6186. begin
  6187. SetLength(LReply, 3);
  6188. // TELNET_DONT
  6189. LReply[0] := $FF;
  6190. LReply[1] := $FE;
  6191. LReply[2] := lb;
  6192. AContext.Connection.IOHandler.Write(LReply);
  6193. SetLength(LReply, 0);
  6194. State := tsData;
  6195. end;
  6196. tsWont,
  6197. tsDont:
  6198. begin
  6199. State := tsData;
  6200. end;
  6201. tsNegotiate:
  6202. begin
  6203. State := tsNegotiateData;
  6204. end;
  6205. tsNegotiateData:
  6206. begin
  6207. case lb of
  6208. $FF: //is a command?
  6209. begin
  6210. State := tsNegotiateIAC;
  6211. end;
  6212. end;
  6213. end;
  6214. tsNegotiateIAC:
  6215. begin
  6216. case lb of
  6217. $F0: //end sub-negotiation
  6218. begin
  6219. State := tsData;
  6220. end;
  6221. else
  6222. State := tsNegotiateData;
  6223. end;
  6224. end;
  6225. tsInterrupt:
  6226. begin
  6227. case lb of
  6228. $FF: //is a command?
  6229. begin
  6230. State := tsInterruptIAC;
  6231. end;
  6232. end;
  6233. end;
  6234. tsInterruptIAC:
  6235. begin
  6236. case lb of
  6237. $F2: //data mark
  6238. begin
  6239. State := tsData;
  6240. end;
  6241. end;
  6242. end;
  6243. else
  6244. State := tsData;
  6245. end;
  6246. until Finished or (not AContext.Connection.IOHandler.Connected);
  6247. //The last char was #13, we have to make sure that we remove a trailing
  6248. //#10 if it exists so that it doesn't appear in the next line.
  6249. if (lb = $0D) and (State = tsData) then
  6250. begin
  6251. i := AContext.Connection.IOHandler.InputBuffer.Size;
  6252. if i > 0 then begin
  6253. lb := AContext.Connection.IOHandler.InputBuffer.PeekByte(i - 1);
  6254. if lb = $0A then begin
  6255. AContext.Connection.IOHandler.ReadByte;
  6256. end;
  6257. end;
  6258. end;
  6259. Result := BytesToString(LLine, 0, MaxInt, LContext.Connection.IOHandler.DefStringEncoding);
  6260. end;
  6261. procedure TIdFTPServer.DoReplyUnknownCommand(AContext: TIdContext; ALine: string);
  6262. begin
  6263. CmdSyntaxError(AContext, ALine);
  6264. end;
  6265. procedure TIdFTPServer.DoTerminateContext(AContext: TIdContext);
  6266. begin
  6267. try
  6268. TIdFTPServerContext(AContext).KillDataChannel;
  6269. finally
  6270. inherited DoTerminateContext(AContext);
  6271. end;
  6272. end;
  6273. procedure TIdFTPServer.CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply : TIdReply = nil);
  6274. var
  6275. LTmp : String;
  6276. LReply : TIdReply;
  6277. begin
  6278. //First make the first word upper-case
  6279. LTmp := UpCaseFirstWord(ALine);
  6280. if Assigned(AReply) then begin
  6281. LReply := AReply;
  6282. end else begin
  6283. LReply := FReplyClass.CreateWithReplyTexts(nil, ReplyTexts);
  6284. end;
  6285. try
  6286. if not Assigned(AReply) then begin
  6287. LReply.Assign(ReplyUnknownCommand);
  6288. end;
  6289. LReply.Text.Clear;
  6290. LReply.Text.Add(IndyFormat(RSFTPCmdNotRecognized, [LTmp]));
  6291. AContext.Connection.IOHandler.Write(LReply.FormattedReply);
  6292. finally
  6293. if not Assigned(AReply) then begin
  6294. LReply.Free;
  6295. end;
  6296. end;
  6297. end;
  6298. procedure TIdFTPServer.CmdSyntaxError(ASender: TIdCommand);
  6299. begin
  6300. CmdSyntaxError(ASender.Context, ASender.RawLine, FReplyUnknownCommand );
  6301. ASender.PerformReply := False;
  6302. end;
  6303. procedure TIdFTPServer.CommandSecRFC(ASender: TIdCommand);
  6304. //stub for RFC 2228 commands that we don't implement as
  6305. //part of the SSL framework.
  6306. begin
  6307. if IOHandler is TIdServerIOHandlerSSLBase then begin
  6308. CmdNotImplemented(ASender);
  6309. end else begin
  6310. CmdSyntaxError(ASender);
  6311. end;
  6312. end;
  6313. procedure TIdFTPServer.CommandOptsMLST(ASender: TIdCommand);
  6314. const
  6315. LVALIDOPTS : array [0..12] of string =
  6316. ('type', 'size', 'modify',
  6317. 'UNIX.mode', 'UNIX.owner', 'UNIX.group',
  6318. 'unique', 'perm', 'create',
  6319. 'windows.lastaccesstime','win32.ea','win32.dt','win32.dl'); {Do not localize}
  6320. var
  6321. s: string;
  6322. LContext : TIdFTPServerContext;
  6323. function ParseMLSParms(ASvr : TIdFTPServer; const AParms : String) : TIdFTPFactOutputs;
  6324. var
  6325. Ls : String;
  6326. begin
  6327. Result := [];
  6328. Ls := UpperCase(AParms);
  6329. while Ls <> '' do begin
  6330. case PosInStrArray(Fetch(Ls,';'), LVALIDOPTS, False) of
  6331. 0 : Result := Result + [ItemType]; //type
  6332. 1 : Result := Result + [Size]; //size
  6333. 2 : Result := Result + [Modify]; //modify
  6334. 3 : if mlsdUnixModes in ASvr.FMLSDFacts then begin
  6335. Result := Result + [UnixMODE]; //UnixMode
  6336. end;
  6337. 4 : if mlsdUnixOwner in ASvr.FMLSDFacts then begin
  6338. Result := Result + [UnixOwner]; //UNIX.owner
  6339. end;
  6340. 5 : if mlsdUnixGroup in ASvr.FMLSDFacts then begin
  6341. Result := Result + [UnixGroup]; //UNIX.group
  6342. end;
  6343. 6 : if mlsdUniqueID in ASvr.FMLSDFacts then begin //Unique
  6344. Result := Result + [Unique];
  6345. end;
  6346. 7 : if mlsdPerms in ASvr.FMLSDFacts then begin //perm
  6347. Result := Result + [Perm];
  6348. end;
  6349. 8 : if mlsdFileCreationTime in ASvr.FMLSDFacts then begin
  6350. Result := Result + [CreateTime];
  6351. end;
  6352. 9 : if mlsdFileLastAccessTime in ASvr.FMLSDFacts then begin
  6353. Result := Result + [LastAccessTime];
  6354. end;
  6355. 10 : if mlsdWin32Attributes in ASvr.FMLSDFacts then begin
  6356. Result := Result + [WinAttribs];
  6357. end;
  6358. 11 : if mlsdWin32DriveType in ASvr.MLSDFacts then begin
  6359. Result := Result + [WinDriveType];
  6360. end;
  6361. 12 : if mlstWin32DriveLabel in ASvr.MLSDFacts then begin
  6362. Result := Result + [WinDriveLabel];
  6363. end;
  6364. end;
  6365. end;
  6366. end;
  6367. function SetToOptsStr(AFacts : TIdFTPFactOutputs) : String;
  6368. begin
  6369. Result := '';
  6370. if Size in AFacts then begin {Do not translate}
  6371. Result := Result + 'size;'; {Do not localize}
  6372. end;
  6373. if ItemType in AFacts then begin {Do not translate}
  6374. Result := Result + 'type;'; {Do not translate}
  6375. end;
  6376. if Perm in AFacts then begin {Do not translate}
  6377. Result := Result + 'perm;'; {Do not translate}
  6378. end;
  6379. if CreateTime in AFacts then begin {Do not translate}
  6380. Result := Result + 'create;'; {Do not translate}
  6381. end;
  6382. if Modify in AFacts then begin
  6383. Result := Result + 'modify;'; {Do not translate}
  6384. end;
  6385. if UnixMODE in AFacts then begin {Do not translate}
  6386. Result := Result + 'UNIX.mode;'; {Do not translate}
  6387. end;
  6388. if UnixOwner in AFacts then begin{Do not translate}
  6389. Result := Result + 'UNIX.owner;'; {Do not translate}
  6390. end;
  6391. if UnixGroup in AFacts then begin {Do not translate}
  6392. Result := Result + 'UNIX.group;'; {Do not translate}
  6393. end;
  6394. if Unique in AFacts then begin {Do not translate}
  6395. Result := Result + 'unique;'; {Do not translate}
  6396. end;
  6397. if LastAccessTime in AFacts then begin
  6398. Result := Result + 'windows.lastaccesstime;'; {Do not translate}
  6399. end;
  6400. if IdFTPListOutput.WinAttribs in AFacts then begin
  6401. Result := Result + 'win32.ea;'; {Do not translate}
  6402. end;
  6403. if IdFTPListOutput.WinDriveType in AFacts then begin
  6404. Result := Result + 'Win32.dt;'; {Do not localize}
  6405. end;
  6406. if IdFTPListOutput.WinDriveLabel in AFacts then begin
  6407. Result := Result + 'Win32.dl;'; {Do not localize}
  6408. end;
  6409. end;
  6410. begin
  6411. LContext := ASender.Context as TIdFTPServerContext;
  6412. s := ASender.UnparsedParams;
  6413. if IndyPos(' ', s) = 0 then begin
  6414. LContext.MLSOpts := ParseMLSParms(Self, Trim(s));
  6415. //the string is standardized format
  6416. ASender.Reply.SetReply(200, Trim(IndyFormat('MLST OPTS %s', [SetToOptsStr(LContext.MLSOpts)]))); {Do not Localize}
  6417. end else begin
  6418. ASender.Reply.SetReply(501, IndyFormat(RSFTPInvalidOps, ['MLST'])); {Do not Localize}
  6419. end;
  6420. end;
  6421. procedure TIdFTPServer.CommandOptsMODEZ(ASender: TIdCommand);
  6422. const
  6423. OPT_NAMES : Array[0..4] of String =
  6424. ('ENGINE','LEVEL','METHOD','BLOCKSIZE','EXTRA'); {do not localize}
  6425. var
  6426. s: string;
  6427. LOptName, LOptVal : String;
  6428. LContext : TIdFTPServerContext;
  6429. LFirstPar : Boolean;
  6430. LError : Boolean;
  6431. LNoVal : Integer;
  6432. LReset : Boolean;
  6433. procedure ReportSettings(ACxt : TIdFTPServerContext; AReply : TIdReply);
  6434. begin
  6435. AReply.NumericCode := 200;
  6436. AReply.Text.Clear;
  6437. AReply.Text.Add('MODE Z ENGINE set to ZLIB.'); {do not localize}
  6438. AReply.Text.Add('MODE Z LEVEL set to ' + IntToStr(ACxt.FZLibCompressionLevel) + '.'); {do not localize}
  6439. AReply.Text.Add('MODE Z METHOD set to ' + IntToStr(DEF_ZLIB_METHOD) + '.'); {do not localize}
  6440. end;
  6441. procedure SyntaxError(AReply : TIdCommand);
  6442. var
  6443. LOpts : String;
  6444. begin
  6445. //drop the OPTS part of the command for display
  6446. LOpts := ASender.RawLine;
  6447. Fetch(LOpts);
  6448. LOpts := TrimLeft(LOpts);
  6449. ASender.Reply.SetReply(501, IndyFormat(RSFTPInvalidOps, [LOpts]));
  6450. end;
  6451. begin
  6452. LFirstPar := True;
  6453. LReset := True;
  6454. LError := True;
  6455. LContext := ASender.Context as TIdFTPServerContext;
  6456. s := Trim(ASender.UnparsedParams);
  6457. if s = '' then begin
  6458. LContext.ResetZLibSettings;
  6459. ReportSettings(LContext, ASender.Reply);
  6460. end;
  6461. repeat
  6462. LOptName := Fetch(s);
  6463. if s = '' then begin
  6464. if LFirstPar then begin
  6465. SyntaxError(ASender);
  6466. Exit;
  6467. end;
  6468. end;
  6469. LOptVal := Fetch(s);
  6470. if Trim(s) <> '' then begin
  6471. //if there's more, than we see if there's a valid option.
  6472. LFirstPar := False;
  6473. end;
  6474. if LFirstPar and (PosInStrArray(LOptName, OPT_NAMES, False) = -1) then begin
  6475. SyntaxError(ASender);
  6476. Exit;
  6477. end;
  6478. LFirstPar := False;
  6479. case PosInStrArray(LOptName, OPT_NAMES, False) of
  6480. 0 : //'ENGINE' - we only support ZLIB
  6481. begin
  6482. LError := False;
  6483. end;
  6484. 1 : begin //,'LEVEL', - implemented
  6485. LNoVal := IndyStrToInt(LOptVal, -1);
  6486. if (LNoVal > -1) and (LNoVal < 8) then begin
  6487. LContext.FZLibCompressionLevel := LNoVal;
  6488. LReset := False;
  6489. LError := False;
  6490. end;
  6491. end;
  6492. 2 : begin //'METHOD', - not implemented - jst do syntax check
  6493. LNoVal := IndyStrToInt(LOptVal, -1);
  6494. if LNoVal <> -1 then begin
  6495. LError := False;
  6496. end;
  6497. end;
  6498. 3 : begin ///'BLOCKSIZE', -not implemented - just do syntax check
  6499. LNoVal := IndyStrToInt(LOptVal, -1);
  6500. if LNoVal <> -1 then begin
  6501. LError := False;
  6502. end;
  6503. end;
  6504. 4 : begin //'EXTRA') - not implemented - just do syntax check
  6505. if PosInStrArray(LOptVal, OnOffStates, False) > -1 then begin
  6506. LError := False;
  6507. end;
  6508. end;
  6509. end;
  6510. until (s = '');
  6511. if LError then begin
  6512. SyntaxError(ASender);
  6513. Exit;
  6514. end;
  6515. if LReset then begin
  6516. LContext.ResetZLibSettings;
  6517. end;
  6518. ReportSettings(LContext, ASender.Reply);
  6519. end;
  6520. procedure TIdFTPServer.CommandOptsUTF8(ASender: TIdCommand);
  6521. var
  6522. s: String;
  6523. LContext: TIdFTPServerContext;
  6524. procedure SyntaxError(AReply : TIdCommand);
  6525. var
  6526. LOpts : String;
  6527. begin
  6528. //drop the OPTS part of the command for display
  6529. LOpts := ASender.RawLine;
  6530. Fetch(LOpts);
  6531. LOpts := TrimLeft(LOpts);
  6532. ASender.Reply.SetReply(501, IndyFormat(RSFTPInvalidOps, [LOpts]));
  6533. end;
  6534. begin
  6535. LContext := ASender.Context as TIdFTPServerContext;
  6536. s := Trim(ASender.UnparsedParams);
  6537. if TextIsSame(ASender.CommandHandler.Command, 'UTF-8') then begin
  6538. // OPTS UTF-8 <NLST>
  6539. // http://www.ietf.org/proceedings/02nov/I-D/draft-ietf-ftpext-utf-8-option-00.txt
  6540. if s = '' then begin
  6541. LContext.NLSTUtf8 := False; // disable UTF-8 over data connection
  6542. end
  6543. else if TextIsSame(s, 'NLST') then begin
  6544. LContext.NLSTUtf8 := True; // enable UTF-8 over data connection
  6545. end else begin
  6546. SyntaxError(ASender);
  6547. Exit;
  6548. end;
  6549. // enable UTF-8 over control connection
  6550. LContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
  6551. end else begin
  6552. // OPTS UTF8 <ON|OFF>
  6553. // non-standard Microsoft IE implementation!!!!
  6554. case PosInStrArray(s, OnOffStates, False) of
  6555. 0: begin // 'ON'
  6556. LContext.NLSTUtf8 := True;
  6557. LContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
  6558. end;
  6559. 1: begin // 'OFF'
  6560. LContext.NLSTUtf8 := False;
  6561. LContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
  6562. end;
  6563. else
  6564. begin
  6565. SyntaxError(ASender);
  6566. Exit;
  6567. end;
  6568. end;
  6569. end;
  6570. ASender.Reply.NumericCode := 200;
  6571. end;
  6572. function TIdFTPServer.IgnoreLastPathDelim(const APath: String): String;
  6573. //This internal function is needed because path processing is different in Windows
  6574. //than in Linux. The path separators on a FTP server on either system will be different.
  6575. //
  6576. //On Windows machines, both '/' and '\'
  6577. //
  6578. //On a Linux machine, a FTP server would probably only use '/' because '\' is a valid
  6579. //filename char.
  6580. var
  6581. i : Integer;
  6582. LPathProcessing : TIdFTPPathProcessing;
  6583. begin
  6584. Result := APath;
  6585. i := Length(Result);
  6586. if FPathProcessing <> ftpOSDependent then begin
  6587. LPathProcessing := FPathProcessing;
  6588. end else begin
  6589. case GOSType of
  6590. otUnix :
  6591. begin
  6592. LPathProcessing := ftppUnix;
  6593. end;
  6594. otUnknown :
  6595. begin
  6596. LPathProcessing := ftppCustom;
  6597. end
  6598. else
  6599. LPathProcessing := ftppDOS;
  6600. end;
  6601. end;
  6602. case LPathProcessing of
  6603. ftppDOS :
  6604. begin
  6605. if Result <>'' then begin
  6606. if CharIsInSet(Result, i, '/\') then begin
  6607. IdDelete(Result, i, 1);
  6608. end;
  6609. end;
  6610. end;
  6611. ftppUnix :
  6612. begin
  6613. if Result <>'' then begin
  6614. if TextEndsWith(Result, '/') then begin
  6615. IdDelete(Result, i, 1);
  6616. end;
  6617. end;
  6618. end;
  6619. ftppCustom :
  6620. begin
  6621. Exit;
  6622. end;
  6623. end;
  6624. //Done so that something like "cd /" or "cd \" will go to
  6625. //the main directory
  6626. if Result = '' then begin
  6627. Result := '/';
  6628. end;
  6629. end;
  6630. function TIdFTPServer.SupportTaDirSwitches(AContext : TIdFTPServerContext): Boolean;
  6631. begin
  6632. Result := True;
  6633. case FDirFormat of
  6634. ftpdfCustom, ftpdfEPLF:
  6635. Result := False;
  6636. ftpdfDOS:
  6637. Result := not AContext.FMSDOSMode;
  6638. ftpdfOSDependent:
  6639. if GOSType = otWindows then begin
  6640. Result := not AContext.FMSDOSMode;
  6641. end;
  6642. end;
  6643. end;
  6644. function TIdFTPServer.GetCaseSensitive: Boolean;
  6645. begin
  6646. Result := FServerInfo.CaseSensitive;
  6647. end;
  6648. procedure TIdFTPServer.SetCaseSensitive(const AValue : Boolean);
  6649. begin
  6650. FServerInfo.CaseSensitive := AValue;
  6651. end;
  6652. function TIdFTPServer.GetDirSeparator : Char;
  6653. begin
  6654. Result := FServerInfo.DirSeparator;
  6655. end;
  6656. procedure TIdFTPServer.SetDirSeparator(const AValue : Char);
  6657. begin
  6658. FServerInfo.DirSeparator := AValue;
  6659. end;
  6660. { TIdFTPSecurityOptions }
  6661. procedure TIdFTPSecurityOptions.Assign(Source: TPersistent);
  6662. var
  6663. LSrc : TIdFTPSecurityOptions;
  6664. begin
  6665. if Source is TIdFTPSecurityOptions then begin
  6666. LSrc := Source as TIdFTPSecurityOptions;
  6667. BlockAllPORTTransfers := LSrc.BlockAllPORTTransfers;
  6668. DisableSTATCommand := LSrc.DisableSTATCommand;
  6669. DisableSYSTCommand := LSrc.DisableSYSTCommand;
  6670. PasswordAttempts := LSrc.PasswordAttempts;
  6671. InvalidPassDelay := LSrc.InvalidPassDelay;
  6672. NoReservedRangePORT := LSrc.NoReservedRangePORT;
  6673. RequirePASVFromSameIP := LSrc.RequirePASVFromSameIP;
  6674. RequirePORTFromSameIP := LSrc.RequirePORTFromSameIP;
  6675. PermitCCC := LSrc.PermitCCC;
  6676. end else begin
  6677. inherited Assign(Source);
  6678. end;
  6679. end;
  6680. constructor TIdFTPSecurityOptions.Create;
  6681. begin
  6682. inherited Create;
  6683. //limit login attempts - some hackers will try guessing passwords from a dictionary
  6684. PasswordAttempts := DEF_FTP_PASSWORDATTEMPTS;
  6685. //should slow-down a password guessing attack - note those dictionaries
  6686. InvalidPassDelay := DEF_FTP_INVALIDPASS_DELAY;
  6687. //client IP Address is the only one that we will accept a PASV
  6688. //transfer from
  6689. //http://cr.yp.to/ftp/security.html
  6690. RequirePASVFromSameIP := DEF_FTP_PASV_SAME_IP;
  6691. //Accept port transfers from the same IP address as the client -
  6692. //should prevent bounce attacks
  6693. RequirePORTFromSameIP := DEF_FTP_PORT_SAME_IP;
  6694. //Do not accept port requests to ports in the reserved range. That is dangerous on some systems
  6695. NoReservedRangePORT := DEF_FTP_NO_RESERVED_PORTS;
  6696. //Do not accept any PORT transfers at all. This is a little extreme but reduces troubles further.
  6697. //This will break the the Win32 console clients and a number of other programs.
  6698. BlockAllPORTTransfers := DEF_FTP_BLOCK_ALL_PORTS;
  6699. //Disable SYST command. SYST usually gives the system description.
  6700. //Disabling it may make it harder for a trouble maker to know about your computer
  6701. //but will not be a complete security solution. See http://www.sans.org/rr/infowar/fingerprint.php for details
  6702. //On the other hand, disabling it will break RFC 959 complience and may break some FTP programs.
  6703. DisableSYSTCommand := DEF_FTP_DISABLE_SYST;
  6704. //Disable STAT command. STAT gives freeform information about the connection status.
  6705. // http://www.sans.org/rr/infowar/fingerprint.php advises administrators to disable this
  6706. //because servers tend to give distinct patterns of information and some trouble makers
  6707. //can figure out what type of server you are running simply with this.
  6708. DisableSTATCommand := DEF_FTP_DISABLE_STAT;
  6709. //Permit CCC command when using TLS with FTP to clear the control connection.
  6710. //That may be helpful for someone behind a NAT where an IP address can NOT be altered by the NAT
  6711. //when using SSL. On the other hand, some administrators may NOT permit this for security reasons.
  6712. //That's a debate I'll leave up to the programmer in hopes that they will pass it to the user.
  6713. PermitCCC := DEF_FTP_PERMIT_CCC;
  6714. end;
  6715. { TIdDataChannel }
  6716. constructor TIdDataChannel.Create(APASV: Boolean; AControlContext: TIdFTPServerContext;
  6717. const ARequirePASVFromSameIP: Boolean; AServer: TIdFTPServer);
  6718. var
  6719. LIO: TIdIOHandlerSocket;
  6720. LDataChannelSvr: TIdSimpleServer;
  6721. LDataChannelCli: TIdTCPClient;
  6722. begin
  6723. inherited Create;
  6724. FNegotiateTLS := False;
  6725. FOKReply := TIdReplyRFC.Create(nil);
  6726. FErrorReply := TIdReplyRFC.Create(nil);
  6727. FReply := TIdReplyRFC.Create(nil);
  6728. FRequirePASVFromSameIP := ARequirePASVFromSameIP;
  6729. FControlContext := AControlContext;
  6730. FServer := AServer;
  6731. // RLebeau: do not set both BoundPortMin/Max and BoundPort at the same time.
  6732. // If they are all non-zero, BoundPort will take priority in TIdSocketHandle.
  6733. // The DefaultDataPort property should not be assigned to zero in order to
  6734. // support Active-mode transfers, but doing so will cause BoundPortMin/Max
  6735. // to be ignored for Passive-mode transfers. So assign them in an either-or
  6736. // manner.
  6737. if APASV then begin
  6738. FDataChannel := TIdSimpleServer.Create(nil);
  6739. LDataChannelSvr := TIdSimpleServer(FDataChannel);
  6740. LDataChannelSvr.BoundIP := FControlContext.Binding.IP;
  6741. if (AServer.PASVBoundPortMin <> 0) and (AServer.PASVBoundPortMax <> 0) then begin
  6742. LDataChannelSvr.BoundPortMin := AServer.PASVBoundPortMin;
  6743. LDataChannelSvr.BoundPortMax := AServer.PASVBoundPortMax;
  6744. end else begin
  6745. LDataChannelSvr.BoundPort := AServer.DefaultDataPort;
  6746. end;
  6747. LDataChannelSvr.IPVersion := FControlContext.Binding.IPVersion;
  6748. LDataChannelSvr.OnBeforeBind := AControlContext.PortOnBeforeBind;
  6749. LDataChannelSvr.OnAfterBind := AControlContext.PortOnAfterBind;
  6750. end else begin
  6751. FDataChannel := TIdTCPClient.Create(nil);
  6752. //the TCPClient for the dataport must be bound to a default port
  6753. LDataChannelCli := TIdTCPClient(FDataChannel);
  6754. LDataChannelCli.BoundIP := FControlContext.Binding.IP;
  6755. LDataChannelCli.BoundPort := AServer.DefaultDataPort;
  6756. LDataChannelCli.IPVersion := FControlContext.Binding.IPVersion;
  6757. end;
  6758. if AControlContext.Server.IOHandler is TIdServerIOHandlerSSLBase then begin
  6759. if APASV then begin
  6760. LIO := TIdServerIOHandlerSSLBase(AServer.IOHandler).MakeFTPSvrPasv;
  6761. end else begin
  6762. LIO := TIdServerIOHandlerSSLBase(AServer.IOHandler).MakeFTPSvrPort;
  6763. end;
  6764. TIdSSLIOHandlerSocketBase(LIO).PassThrough := True;
  6765. // always uses a ssl iohandler, but passthrough is true...
  6766. end else begin
  6767. LIO := FServer.IOHandler.MakeClientIOHandler(nil) as TIdIOHandlerSocket;
  6768. end;
  6769. // under ARC, the TIdTCPConnection.IOHandler property is a weak/unsafe reference.
  6770. // MakeFTPSvrPasv(), MakeFTPSvrPort(), and MakeClientIOHandler() return an
  6771. // IOHandler with no Owner assigned, so lets make the TIdTCPConnection become
  6772. // the Owner in order to keep the IOHandler alive when this method exits.
  6773. //
  6774. // Let's assign Ownership unconditionally on all platforms...
  6775. //
  6776. // TODO: add an AOwner parameter to MakeFTPSvrPasv(), MakeFTPSvrPort() and
  6777. // MakeClientIOHandler
  6778. //
  6779. FDataChannel.InsertComponent(LIO);
  6780. FDataChannel.IOHandler := LIO;
  6781. LIO.OnBeforeBind := AControlContext.PortOnBeforeBind;
  6782. LIO.OnAfterBind := AControlContext.PortOnAfterBind;
  6783. if LIO is TIdSSLIOHandlerSocketBase then begin
  6784. case AControlContext.DataProtection of
  6785. ftpdpsClear: begin
  6786. TIdSSLIOHandlerSocketBase(LIO).PassThrough := True;
  6787. end;
  6788. ftpdpsPrivate: begin
  6789. FNegotiateTLS := True;
  6790. end;
  6791. end;
  6792. end;
  6793. end;
  6794. destructor TIdDataChannel.Destroy;
  6795. begin
  6796. FOKReply.Free;
  6797. FErrorReply.Free;
  6798. FReply.Free;
  6799. if Assigned(FDataChannel) then begin
  6800. FDataChannel.IOHandler := nil;
  6801. end;
  6802. FDataChannel.Free;
  6803. inherited Destroy;
  6804. end;
  6805. function GetBinding(AConnection: TIdTCPConnection): TIdSocketHandle;
  6806. var
  6807. // under ARC, convert a weak reference to a strong reference before working with it
  6808. LSocket: TIdIOHandlerSocket;
  6809. begin
  6810. Result := nil;
  6811. if Assigned(AConnection) then begin
  6812. LSocket := AConnection.Socket;
  6813. if Assigned(LSocket) then begin
  6814. Result := LSocket.Binding;
  6815. end;
  6816. end;
  6817. end;
  6818. function TIdDataChannel.GetPeerIP: String;
  6819. var
  6820. LBinding: TIdSocketHandle;
  6821. begin
  6822. LBinding := GetBinding(FDataChannel);
  6823. if Assigned(LBinding) then begin
  6824. Result := LBinding.PeerIP;
  6825. end else begin
  6826. Result := '';
  6827. end;
  6828. end;
  6829. function TIdDataChannel.GetPeerPort: TIdPort;
  6830. var
  6831. LBinding: TIdSocketHandle;
  6832. begin
  6833. LBinding := GetBinding(FDataChannel);
  6834. if Assigned(LBinding) then begin
  6835. Result := LBinding.PeerPort;
  6836. end else begin
  6837. Result := 0;
  6838. end;
  6839. end;
  6840. function TIdDataChannel.GetLocalIP: String;
  6841. var
  6842. LBinding: TIdSocketHandle;
  6843. begin
  6844. LBinding := GetBinding(FDataChannel);
  6845. if Assigned(LBinding) then begin
  6846. Result := LBinding.IP;
  6847. end else begin
  6848. Result := '';
  6849. end;
  6850. end;
  6851. function TIdDataChannel.GetLocalPort: TIdPort;
  6852. var
  6853. LBinding: TIdSocketHandle;
  6854. begin
  6855. LBinding := GetBinding(FDataChannel);
  6856. if Assigned(LBinding) then begin
  6857. Result := LBinding.Port;
  6858. end else begin
  6859. Result := 0;
  6860. end;
  6861. end;
  6862. procedure TIdDataChannel.InitOperation(const AConnectMode : Boolean = False);
  6863. var
  6864. LIO : TIdSSLIOHandlerSocketBase;
  6865. begin
  6866. try
  6867. if FDataChannel is TIdSimpleServer then begin
  6868. TIdSimpleServer(FDataChannel).Listen;
  6869. if FRequirePASVFromSameIP then begin
  6870. {//BGO}
  6871. if FControlContext.Binding.PeerIP <> TIdSimpleServer(FDataChannel).Binding.PeerIP then begin
  6872. TIdFTPServerContext(FControlContext).FDataPortDenied := True;
  6873. ErrorReply.SetReply(504, RSFTPSameIPAddress);
  6874. FControlContext.Connection.IOHandler.Write(ErrorReply.FormattedReply);
  6875. TIdSimpleServer(FDataChannel).Disconnect(False);
  6876. Exit;
  6877. end;
  6878. end;
  6879. {//BGO}
  6880. if FNegotiateTLS then begin
  6881. LIO := FDataChannel.IOHandler as TIdSSLIOHandlerSocketBase;
  6882. if AConnectMode then begin
  6883. LIO.IsPeer := False;
  6884. end;
  6885. LIO.PassThrough := False;
  6886. end;
  6887. end
  6888. else if FDataChannel is TIdTCPClient then begin
  6889. TIdTCPClient(FDataChannel).Connect;
  6890. if FNegotiateTLS then begin
  6891. LIO := FDataChannel.IOHandler as TIdSSLIOHandlerSocketBase;
  6892. if AConnectMode then begin
  6893. LIO.IsPeer := False;
  6894. end;
  6895. LIO.PassThrough := False;
  6896. end;
  6897. end;
  6898. except
  6899. FControlContext.Connection.IOHandler.Write(FErrorReply.FormattedReply); //426
  6900. raise;
  6901. end;
  6902. end;
  6903. procedure TIdDataChannel.SetErrorReply(const AValue: TIdReplyRFC);
  6904. begin
  6905. FErrorReply.Assign(AValue);
  6906. end;
  6907. procedure TIdDataChannel.SetOKReply(const AValue: TIdReplyRFC);
  6908. begin
  6909. FOKReply.Assign(AValue);
  6910. end;
  6911. procedure TIdFTPServerContext.PortOnAfterBind(ASender: TObject);
  6912. begin
  6913. FServer.DoOnDataPortAfterBind(Self);
  6914. end;
  6915. procedure TIdFTPServerContext.PortOnBeforeBind(ASender: TObject);
  6916. begin
  6917. FServer.DoOnDataPortBeforeBind(Self);
  6918. end;
  6919. procedure TIdFTPServerContext.ResetZLibSettings;
  6920. begin
  6921. //Settings specified by
  6922. // http://www.ietf.org/internet-drafts/draft-preston-ftpext-deflate-00.txt
  6923. FZLibCompressionLevel := DEF_ZLIB_COMP_LEVEL;
  6924. FZLibWindowBits := DEF_ZLIB_WINDOW_BITS; //-15 - no extra headers
  6925. FZLibMemLevel := DEF_ZLIB_MEM_LEVEL;
  6926. FZLibStratagy := DEF_ZLIB_STRATAGY; // - default
  6927. end;
  6928. end.