IdFTPServer.pas 273 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529
  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. {$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FUserAccounts: TIdCustomUserManager;
  766. FOnUserAccount : TOnFTPUserAccountEvent;
  767. FOnAfterUserLogin: TOnAfterUserLoginEvent;
  768. FOnUserLogin: TOnFTPUserLoginEvent;
  769. FOnChangeDirectory: TOnDirectoryEvent;
  770. FOnGetFileSize: TOnGetFileSizeEvent;
  771. FOnGetFileDate:TOnGetFileDateEvent;
  772. FOnListDirectory: TOnListDirectoryEvent;
  773. FOnCustomListDirectory : TOnCustomListDirectoryEvent;
  774. FOnRenameFile: TOnRenameFileEvent;
  775. FOnDeleteFile: TOnFileEvent;
  776. FOnRetrieveFile: TOnRetrieveFileEvent;
  777. FOnStoreFile: TOnStoreFileEvent;
  778. FOnMakeDirectory: TOnDirectoryEvent;
  779. FOnRemoveDirectory: TOnDirectoryEvent;
  780. FOnStat : TIdOnFTPStatEvent;
  781. FFTPSecurityOptions : TIdFTPSecurityOptions;
  782. FServerInfo : TIdFTPServerIdentifier;
  783. FOnCRCFile : TOnCheckSumFile;
  784. FOnCombineFiles : TOnCombineFiles;
  785. FOnSetModifiedTime : TOnSetFileDateEvent;
  786. FOnFileExistCheck : TOnCheckFileEvent; //for MDTM variation to set the file time
  787. FOnSetCreationTime : TOnSetFileDateEvent;
  788. FOnMD5Cache : TOnCacheChecksum;
  789. FOnMD5Verify : TOnVerifyChecksum;
  790. FOnGreeting : TIdOnBanner;
  791. FOnLoginSuccessBanner : TIdOnBanner;
  792. FOnLoginFailureBanner : TIdOnBanner;
  793. FOnQuitBanner : TIdOnBanner;
  794. FOnSetATTRIB : TOnSetATTRIB;
  795. FOnSiteUMASK : TOnSiteUMASK;
  796. FOnSiteCHMOD : TOnSiteCHMOD;
  797. FOnSiteCHOWN : TOnSiteCHOWN;
  798. FOnSiteCHGRP : TOnSiteCHGRP;
  799. FOnAvailDiskSpace : TIdOnDirSizeInfo;
  800. FOnCompleteDirSize : TIdOnDirSizeInfo;
  801. FOnRemoveDirectoryAll: TOnDirectoryEvent;
  802. FOnCustomPathProcess : TOnCustomPathProcess;
  803. FOnDataPortBeforeBind : TOnDataPortBind;
  804. FOnDataPortAfterBind : TOnDataPortBind;
  805. FOnPASVBeforeBind : TIdOnPASVRange;
  806. FOnPASVReply : TIdOnPASV;
  807. {$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FFTPFileSystem: TIdFTPBaseFileSystem;
  808. FEndOfHelpLine : String;
  809. FCustomSystID : String;
  810. FReplyUnknownSITECommand : TIdReply;
  811. FCompressor : TIdZLibCompressorBase;
  812. FOnMLST : TIdOnMLST;
  813. FOnSiteUTIME : TOnSiteUTIME;
  814. FOnHostCheck : TOnHostCheck;
  815. FOnQuerySSLPort: TIdOnQuerySSLPort;
  816. procedure SetOnUserAccount(AValue : TOnFTPUserAccountEvent);
  817. procedure AuthenticateUser(ASender: TIdCommand);
  818. function SupportTaDirSwitches(AContext : TIdFTPServerContext) : Boolean;
  819. function IgnoreLastPathDelim(const APath : String) : String;
  820. procedure DoOnPASVBeforeBind(ASender : TIdFTPServerContext; var VIP : String;
  821. var VPortMin, VPortMax : TIdPort; const AIPVersion : TIdIPVersion);
  822. procedure DoOnPASVReply(ASender : TIdFTPServerContext; var VIP : String;
  823. var VPort : TIdPort; const AIPVersion : TIdIPVersion);
  824. function InternalPASV(ASender: TIdCommand; var VIP : String;
  825. var VPort: TIdPort; var VIPVersion : TIdIPVersion): Boolean;
  826. function DoSysType(ASender : TIdFTPServerContext) : String;
  827. function DoProcessPath(ASender : TIdFTPServerContext; const APath: TIdFTPFileName): TIdFTPFileName;
  828. function FTPNormalizePath(const APath: String) : String;
  829. function FTPPathSeparator : Char;
  830. function FTPIsCaseSensitive : Boolean;
  831. function MLSFEATLine(const AFactMask : TIdMLSDAttrs; const AFacts : TIdFTPFactOutputs) : String;
  832. function HelpText(Cmds : TStrings) : String;
  833. function IsValidPermNumbers(const APermNos : String) : Boolean;
  834. procedure SetRFCReplyFormat(AReply : TIdReply);
  835. function CDUPDir(AContext : TIdFTPServerContext) : String;
  836. procedure DisconUser(ASender: TIdCommand);
  837. //command reply common code
  838. procedure CmdNotImplemented(ASender : TIdCommand);
  839. procedure CmdFileActionAborted(ASender : TIdCommand);
  840. procedure CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply : TIdReply = nil); overload;
  841. procedure CmdSyntaxError(ASender : TIdCommand); overload;
  842. procedure CmdInvalidParams(ASender: TIdCommand);
  843. procedure CmdInvalidParamNum(ASender:TIdCommand);
  844. //The http://www.potaroo.net/ietf/idref/draft-twine-ftpmd5/
  845. //draft didn't specify 550 as an error. It said use 504.
  846. procedure CmdTwineFileActionAborted(ASender : TIdCommand);
  847. //success reply codes can vary amoung commands
  848. procedure CmdCommandSuccessful(ASender: TIdCommand; const AReplyCode : Integer = 250);
  849. //Command replies
  850. procedure CommandQUIT(ASender:TIdCommand);
  851. procedure CommandUSER(ASender: TIdCommand);
  852. procedure CommandPASS(ASender: TIdCommand);
  853. procedure CommandACCT(ASender: TIdCommand);
  854. procedure CommandXAUT(ASender : TIdCommand);
  855. procedure CommandCWD(ASender: TIdCommand);
  856. procedure CommandCDUP(ASender: TIdCommand);
  857. procedure CommandREIN(ASender: TIdCommand);
  858. procedure CommandPORT(ASender: TIdCommand);
  859. procedure CommandPASV(ASender: TIdCommand);
  860. procedure CommandTYPE(ASender: TIdCommand);
  861. procedure CommandSTRU(ASender: TIdCommand);
  862. procedure CommandMODE(ASender: TIdCommand);
  863. procedure CommandRETR(ASender: TIdCommand);
  864. procedure CommandSSAP(ASender: TIdCommand);
  865. procedure CommandALLO(ASender: TIdCommand);
  866. procedure CommandREST(ASender: TIdCommand);
  867. procedure CommandRNFR(ASender: TIdCommand);
  868. procedure CommandRNTO(ASender: TIdCommand);
  869. procedure CommandABOR(ASender: TIdCommand);
  870. //AVBL from Streamlined FTP Command Extensions
  871. // draft-peterson-streamlined-ftp-command-extensions-01.txt
  872. procedure CommandAVBL(ASender: TIdCommand);
  873. procedure CommandDELE(ASender: TIdCommand);
  874. //DSIZ from Streamlined FTP Command Extensions
  875. // draft-peterson-streamlined-ftp-command-extensions-01.txt
  876. procedure CommandDSIZ(ASender : TIdCommand);
  877. procedure CommandRMDA(ASender : TIdCommand);
  878. procedure CommandRMD(ASender: TIdCommand);
  879. procedure CommandMKD(ASender: TIdCommand);
  880. procedure CommandPWD(ASender: TIdCommand);
  881. procedure CommandLIST(ASender: TIdCommand);
  882. procedure CommandSYST(ASender: TIdCommand);
  883. procedure CommandSTAT(ASender: TIdCommand);
  884. procedure CommandSIZE(ASender: TIdCommand);
  885. procedure CommandFEAT(ASender: TIdCommand);
  886. procedure CommandOPTS(ASender: TIdCommand);
  887. procedure CommandAUTH(ASender: TIdCommand);
  888. procedure CommandCCC(ASender: TIdCommand);
  889. // rfc 2428:
  890. procedure CommandEPSV(ASender: TIdCommand);
  891. procedure CommandEPRT(ASender: TIdCommand);
  892. //
  893. procedure CommandMDTM(ASender: TIdCommand);
  894. procedure CommandMFF(ASender: TIdCommand);
  895. //
  896. procedure CommandMD5(ASender: TIdCommand);
  897. procedure CommandMMD5(ASender: TIdCommand);
  898. //
  899. procedure CommandPROT(ASender: TIdCommand);
  900. procedure CommandPBSZ(ASender: TIdCommand);
  901. procedure CommandMFMT(ASender: TIdCommand);
  902. procedure CommandMFCT(ASender: TIdCommand);
  903. procedure CommandMLSD(ASender: TIdCommand);
  904. procedure CommandMLST(ASender: TIdCommand);
  905. procedure CommandCheckSum(ASender: TIdCommand);
  906. procedure CommandCOMB(ASender: TIdCommand);
  907. procedure CommandCLNT(ASender: TIdCommand);
  908. procedure CommandCSID(ASender: TIdCommand);
  909. //SSCN Secure FTPX - http://www.raidenftpd.com/kb/kb000000037.htm
  910. procedure CommandSSCN(ASender: TIdCommand);
  911. //Informal - like PASV accept SSL is in client mode - used by FlashXP
  912. procedure CommandCPSV(ASender: TIdCommand);
  913. //Informal - like PASV except that only the port is communicated.
  914. //
  915. procedure CommandSPSV(ASender: TIdCommand);
  916. procedure CommandHOST(ASender : TIdCommand);
  917. procedure CommandSecRFC(ASender : TIdCommand); //stub for some commands in 2228
  918. procedure CommandSITE(ASender: TIdCommand);
  919. procedure CommandSiteHELP(ASender : TIdCommand);
  920. //site commands - Unix
  921. procedure CommandSiteUMASK(ASender : TIdCommand);
  922. procedure CommandSiteCHMOD(ASender : TIdCommand);
  923. //SITE CHOWN - supported by some Unix servers
  924. procedure CommandSiteCHOWN(ASender : TIdCommand);
  925. //SITE CHGRP - supported by some Unix servers
  926. procedure CommandSiteCHGRP(ASender : TIdCommand);
  927. //site commans - MS IIS
  928. procedure CommandSiteDIRSTYLE(ASender : TIdCommand);
  929. //used by FTP Voyager
  930. procedure CommandSiteZONE(ASender : TIdCommand);
  931. //supported by RaidenFTP - http://www.raidenftpd.com/kb/kb000000049.htm
  932. procedure CommandSiteATTRIB(ASender : TIdCommand);
  933. //McFTP client uses this to set the time stamps for a file.
  934. procedure CommandSiteUTIME(ASender : TIdCommand);
  935. // end site commands
  936. procedure CommandOptsMLST(ASender : TIdCommand);
  937. procedure CommandOptsMODEZ(ASender : TIdCommand);
  938. procedure CommandOptsUTF8(ASender: TIdCommand);
  939. procedure CommandHELP(ASender: TIdCommand);
  940. //
  941. procedure DoOnRenameFile(ASender: TIdFTPServerContext; const ARenameFromFile, ARenameToFile: string);
  942. procedure DoOnDeleteFile(ASender: TIdFTPServerContext; const APathName: string);
  943. procedure DoOnChangeDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  944. procedure DoOnMakeDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  945. procedure DoOnRemoveDirectory(AContext: TIdFTPServerContext; var VDirectory: TIdFTPFileName);
  946. procedure DoOnGetFileSize(ASender: TIdFTPServerContext; const AFilename: string; var VFileSize: Int64);
  947. procedure DoOnGetFileDate(ASender: TIdFTPServerContext; const AFilename: string; var VFileDate: TDateTime);
  948. procedure DoOnSetModifiedTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime); overload;
  949. procedure DoOnSetModifiedTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTimeStr : String); overload;
  950. procedure DoOnFileExistCheck(AContext: TIdFTPServerContext; const AFileName : String; var VExist : Boolean);
  951. procedure DoOnSetModifiedTimeGMT(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime);
  952. procedure DoOnSetCreationTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime); overload;
  953. procedure DoOnSetCreationTime(AContext: TIdFTPServerContext; const AFileName : String; var VDateTimeStr : String); overload;
  954. procedure DoOnSetCreationTimeGMT(AContext: TIdFTPServerContext; const AFileName : String; var VDateTime: TDateTime);
  955. procedure DoOnCRCFile(ASender: TIdFTPServerContext; const AFileName : String; var VStream : TStream);
  956. procedure DoOnMD5Verify(ASender: TIdFTPServerContext; const AFileName : String; const ACheckSum : String);
  957. procedure DoOnMD5Cache(ASender: TIdFTPServerContext; const AFileName : String; var VCheckSum : String);
  958. procedure DoOnCombineFiles(ASender: TIdFTPServerContext; const ATargetFileName: string; AParts : TStrings);
  959. procedure DoOnSetATTRIB(ASender: TIdFTPServerContext; var VAttr : UInt32; const AFileName : String; var VAUth : Boolean);
  960. procedure DoOnSiteUMASK(ASender: TIdFTPServerContext; var VUMASK : Integer; var VAUth : Boolean);
  961. procedure DoOnSiteCHMOD(ASender: TIdFTPServerContext; var APermissions : Integer; const AFileName : String; var VAUth : Boolean);
  962. procedure DoOnSiteCHOWN(ASender: TIdFTPServerContext; var AOwner, AGroup : String; const AFileName : String; var VAUth : Boolean);
  963. procedure DoOnSiteCHGRP(ASender: TIdFTPServerContext; var AGroup : String; const AFileName : String; var VAUth : Boolean);
  964. procedure SetUseTLS(AValue: TIdUseTLS); override;
  965. procedure SetSupportXAUTH(AValue : Boolean);
  966. procedure InitializeCommandHandlers; override;
  967. procedure ListDirectory(ASender: TIdFTPServerContext; ADirectory: string;
  968. ADirContents: TStrings; ADetails: Boolean; const ACmd : String = 'LIST';
  969. const ASwitches : String = ''); {do not localize}
  970. {$IFNDEF USE_OBJECT_ARC}
  971. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  972. {$ENDIF}
  973. procedure SetAnonymousAccounts(const AValue: TStrings);
  974. procedure SetUserAccounts(const AValue: TIdCustomUserManager);
  975. procedure SetFTPSecurityOptions(const AValue: TIdFTPSecurityOptions);
  976. procedure SetServerInfo(const AValue: TIdFTPServerIdentifier);
  977. procedure SetPASVBoundPortMax(const AValue: TIdPort);
  978. procedure SetPASVBoundPortMin(const AValue: TIdPort);
  979. procedure SetReplyUnknownSITECommand(AValue: TIdReply);
  980. procedure SetSITECommands(AValue: TIdCommandHandlers);
  981. procedure ThreadException(AThread: TIdThread; AException: Exception);
  982. procedure SetFTPFileSystem(const AValue: TIdFTPBaseFileSystem);
  983. function GetMD5Checksum(ASender : TIdFTPServerContext; const AFileName : String) : String;
  984. //overrides from TIdTCPServer
  985. procedure DoConnect(AContext:TIdContext); override;
  986. procedure DoDisconnect(AContext:TIdContext); override;
  987. procedure ContextCreated(AContext:TIdContext); override;
  988. procedure DoOnDataPortBeforeBind(ASender : TIdFTPServerContext); virtual;
  989. procedure DoDataChannelOperation(ASender: TIdCommand; const AConnectMode : Boolean = False);virtual;
  990. procedure DoOnDataPortAfterBind(ASender : TIdFTPServerContext); virtual;
  991. procedure DoOnCustomListDirectory(ASender: TIdFTPServerContext; const APath: string;
  992. ADirectoryListing: TStrings; const ACmd : String; const ASwitches : String);
  993. function DoQuerySSLPort(APort: TIdPort): Boolean; virtual;
  994. function GetReplyClass: TIdReplyClass; override;
  995. function GetRepliesClass: TIdRepliesClass; override;
  996. procedure InitComponent; override;
  997. procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); override;
  998. // overriden so we can close active transfers during a shutdown
  999. procedure DoTerminateContext(AContext: TIdContext); override;
  1000. //overriden so we can handle telnet sequences
  1001. function ReadCommandLine(AContext: TIdContext): string; override;
  1002. function GetCaseSensitive: Boolean;
  1003. procedure SetCaseSensitive(const AValue : Boolean);
  1004. function GetDirSeparator : Char;
  1005. procedure SetDirSeparator(const AValue : Char);
  1006. public
  1007. destructor Destroy; override;
  1008. property SupportXAUTH : Boolean read FSupportXAUTH write SetSupportXAUTH;
  1009. published
  1010. {This is an object that can compress and decompress HTTP Deflate encoding}
  1011. property Compressor : TIdZLibCompressorBase read FCompressor write FCompressor;
  1012. property CustomSystID : String read FCustomSystID write FCustomSystID;
  1013. property DirFormat : TIdFTPDirFormat read FDirFormat write FDirFormat default DEF_DIRFORMAT;
  1014. property PathProcessing : TIdFTPPathProcessing read FPathProcessing write FPathProcessing default DEF_PATHPROCESSING;
  1015. {Only used if PathProcessing is ftppCustom }
  1016. property CaseSensitive : Boolean read GetCaseSensitive write SetCaseSensitive default DEF_CASE_SENSITIVE;
  1017. property DirSeparator : Char read GetDirSeparator write SetDirSeparator;
  1018. property UseTLS;
  1019. property DefaultPort default IDPORT_FTP;
  1020. property AllowAnonymousLogin: Boolean read FAllowAnonymousLogin write FAllowAnonymousLogin default Id_DEF_AllowAnon;
  1021. property AnonymousAccounts: TStrings read FAnonymousAccounts write SetAnonymousAccounts;
  1022. property AnonymousPassStrictCheck: Boolean read FAnonymousPassStrictCheck
  1023. write FAnonymousPassStrictCheck default Id_DEF_PassStrictCheck;
  1024. property DefaultDataPort : TIdPort read FDefaultDataPort write FDefaultDataPort default IdPORT_FTP_DATA;
  1025. property FTPFileSystem:TIdFTPBaseFileSystem read FFTPFileSystem write SetFTPFileSystem;
  1026. property FTPSecurityOptions : TIdFTPSecurityOptions read FFTPSecurityOptions write SetFTPSecurityOptions;
  1027. property EndOfHelpLine : String read FEndOfHelpLine write FEndOfHelpLine;
  1028. property PASVBoundPortMin : TIdPort read FPASVBoundPortMin write SetPASVBoundPortMin default DEF_PASV_BOUND_MIN;
  1029. property PASVBoundPortMax : TIdPort read FPASVBoundPortMax write SetPASVBoundPortMax default DEF_PASV_BOUND_MAX;
  1030. property UserAccounts: TIdCustomUserManager read FUserAccounts write SetUserAccounts;
  1031. property ServerInfo : TIdFTPServerIdentifier read FServerInfo write SetServerInfo;
  1032. property SystemType: string read FSystemType write FSystemType;
  1033. property OnGreeting : TIdOnBanner read FOnGreeting write FOnGreeting;
  1034. property OnLoginSuccessBanner : TIdOnBanner read FOnLoginSuccessBanner write FOnLoginSuccessBanner;
  1035. property OnLoginFailureBanner : TIdOnBanner read FOnLoginFailureBanner write FOnLoginFailureBanner;
  1036. //for retreiving MD5 Checksums from a cache
  1037. property OnMD5Cache : TOnCacheChecksum read FOnMD5Cache write FOnMD5Cache;
  1038. property OnMD5Verify : TOnVerifyChecksum read FOnMD5Verify write FOnMD5Verify;
  1039. property OnQuitBanner : TIdOnBanner read FOnQuitBanner write FOnQuitBanner;
  1040. property OnCustomListDirectory : TOnCustomListDirectoryEvent read FOnCustomListDirectory write FOnCustomListDirectory;
  1041. property OnCustomPathProcess : TOnCustomPathProcess read FOnCustomPathProcess write FOnCustomPathProcess;
  1042. property OnAfterUserLogin: TOnAfterUserLoginEvent read FOnAfterUserLogin write FOnAfterUserLogin;
  1043. property OnChangeDirectory: TOnDirectoryEvent read FOnChangeDirectory write FOnChangeDirectory;
  1044. property OnGetFileSize: TOnGetFileSizeEvent read FOnGetFileSize write FOnGetFileSize;
  1045. property OnGetFileDate: TOnGetFileDateEvent read FOnGetFileDate write FOnGetFileDate;
  1046. property OnUserLogin: TOnFTPUserLoginEvent read FOnUserLogin write FOnUserLogin;
  1047. property OnUserAccount : TOnFTPUserAccountEvent read FOnUserAccount write SetOnUserAccount;
  1048. property OnListDirectory: TOnListDirectoryEvent read FOnListDirectory write FOnListDirectory;
  1049. property OnDataPortBeforeBind : TOnDataPortBind read FOnDataPortBeforeBind write FOnDataPortBeforeBind;
  1050. property OnDataPortAfterBind : TOnDataPortBind read FOnDataPortAfterBind write FOnDataPortAfterBind;
  1051. property OnRenameFile: TOnRenameFileEvent read FOnRenameFile write FOnRenameFile;
  1052. property OnDeleteFile: TOnFileEvent read FOnDeleteFile write FOnDeleteFile;
  1053. property OnRetrieveFile: TOnRetrieveFileEvent read FOnRetrieveFile write FOnRetrieveFile;
  1054. property OnStoreFile: TOnStoreFileEvent read FOnStoreFile write FOnStoreFile;
  1055. property OnMakeDirectory: TOnDirectoryEvent read FOnMakeDirectory write FOnMakeDirectory;
  1056. property OnRemoveDirectory: TOnDirectoryEvent read FOnRemoveDirectory write FOnRemoveDirectory;
  1057. property OnStat : TIdOnFTPStatEvent read FOnStat write FOnStat;
  1058. property OnCombineFiles : TOnCombineFiles read FOnCombineFiles write FOnCombineFiles;
  1059. property OnCRCFile : TOnCheckSumFile read FOnCRCFile write FOnCRCFile;
  1060. property OnSetCreationTime : TOnSetFileDateEvent read FOnSetCreationTime write FOnSetCreationTime;
  1061. property OnSetModifiedTime : TOnSetFileDateEvent read FOnSetModifiedTime write FOnSetModifiedTime;
  1062. property OnFileExistCheck : TOnCheckFileEvent read FOnFileExistCheck write FOnFileExistCheck;
  1063. property OnHostCheck : TOnHostCheck read FOnHostCheck write FOnHostCheck;
  1064. property OnSetATTRIB : TOnSetATTRIB read FOnSetATTRIB write FOnSetATTRIB;
  1065. property OnSiteUMASK : TOnSiteUMASK read FOnSiteUMASK write FOnSiteUMASK;
  1066. property OnSiteCHMOD : TOnSiteCHMOD read FOnSiteCHMOD write FOnSiteCHMOD;
  1067. property OnSiteCHOWN : TOnSiteCHOWN read FOnSiteCHOWN write FOnSiteCHOWN;
  1068. property OnSiteCHGRP : TOnSiteCHGRP read FOnSiteCHGRP write FOnSiteCHGRP;
  1069. {
  1070. READ THIS!!!
  1071. Do not change values in the OnPASV event unless you have a compelling reason to do so.
  1072. In SPSV, the PORT is the only thing that can work because that's all which is
  1073. given as a reply. The server IP is the same one that the client connects to.
  1074. In EPSV, the PORT is the only thing that can work because that's all which is
  1075. given as a reply. The server IP is the same one that the client connects to.
  1076. }
  1077. property OnPASVBeforeBind : TIdOnPASVRange read FOnPASVBeforeBind write FOnPASVBeforeBind;
  1078. property OnPASVReply : TIdOnPASV read FOnPASVReply write FOnPASVReply;
  1079. property OnMLST : TIdOnMLST read FOnMLST write FOnMLST;
  1080. property OnSiteUTIME : TOnSiteUTIME read FOnSiteUTIME write FOnSiteUTIME;
  1081. property OnAvailDiskSpace : TIdOnDirSizeInfo read FOnAvailDiskSpace write FOnAvailDiskSpace;
  1082. property OnCompleteDirSize : TIdOnDirSizeInfo read FOnCompleteDirSize write FOnCompleteDirSize;
  1083. property SITECommands: TIdCommandHandlers read FSITECommands write SetSITECommands;
  1084. property MLSDFacts : TIdMLSDAttrs read FMLSDFacts write FMLSDFacts;
  1085. property OnClientID : TIdOnClientID read FOnClientID write FOnClientID;
  1086. property OnClientIDEx : TIdOnClientIDEx read FOnClientIDEx write FOnClientIDEx;
  1087. property ReplyUnknownSITCommand: TIdReply read FReplyUnknownSITECommand write SetReplyUnknownSITECommand;
  1088. property OnQuerySSLPort: TIdOnQuerySSLPort read FOnQuerySSLPort write FOnQuerySSLPort;
  1089. end;
  1090. {This is used internally for some Telnet sequence parsing}
  1091. type
  1092. TIdFTPTelnetState = (tsData, tsCheckCR, tsIAC, tsWill, tsDo, tsWont, tsDont,
  1093. tsNegotiate, tsNegotiateData, tsNegotiateIAC, tsInterrupt, tsInterruptIAC);
  1094. implementation
  1095. uses
  1096. {$IFDEF DOTNET}
  1097. {$IFDEF USE_INLINE}
  1098. System.Threading,
  1099. {$ENDIF}
  1100. {$ENDIF}
  1101. {$IFDEF USE_VCL_POSIX}
  1102. Posix.SysSelect,
  1103. Posix.SysTime,
  1104. {$ENDIF}
  1105. IdFIPS,
  1106. IdHash, IdHashCRC, IdHashMessageDigest, IdHashSHA, IdIOHandlerSocket,
  1107. IdResourceStringsProtocols, IdGlobalProtocols, IdSimpleServer, IdSSL,
  1108. IdIOHandlerStack, IdSocketHandle, IdTCPClient, IdEMailAddress,
  1109. IdStack, IdFTPListTypes, IdStream;
  1110. const
  1111. //THese commands need some special treatment in the Indy 10 FTP Server help system
  1112. //as they will not always work
  1113. HELP_SPEC_CMDS : array [0..25] of string =
  1114. ('SIZE','MDTM', {do not localize}
  1115. 'AUTH','PBSZ','PROT','CCC','MIC','CONF','ENC', 'SSCN','CPSV', {do not localize}
  1116. 'MFMT','MFF',
  1117. 'MD5','MMD5','XCRC','XMD5','XSHA1','XSHA256','XSHA512', {do not localize}
  1118. 'COMB','AVBL','DSIZ','RMDA','HOST','XAUT'); {do not localize}
  1119. //These commands must always be present even if not implemented
  1120. //alt help topics and superscripts should be used sometimes.
  1121. //These are mandated by RFC 1123
  1122. HELP_ALT_MD_CMD : array [0..17] of string =
  1123. ('RETR', {do not localize}
  1124. 'STOR','STOU', {do not localize}
  1125. 'APPE', {do not localize}
  1126. 'RNFR', 'RNTO', {do not localize}
  1127. 'DELE', {do not localize}
  1128. 'LIST','NLST', {do not localize}
  1129. 'CWD','XCWD', {do not localize}
  1130. 'CDUP','XCUP', {do not localize}
  1131. 'RMD','XRMD', {do not localize}
  1132. 'MKD', 'XMKD', {do not localize}
  1133. 'SYST'); {do not localize}
  1134. HELP_ALT_MD_TP : array [0..17] of string =
  1135. ('RETR (retrieve); unimplemented.', {do not localize}
  1136. 'STOR (store); unimplemented.', {do not localize}
  1137. 'STOU (store unique); unimplemented.', {do not localize}
  1138. 'APPE (append); unimplemented.', {do not localize}
  1139. 'RNFR (rename from); unimplemented.', {do not localize}
  1140. 'RNTO (rename to); unimplemented.', {do not localize}
  1141. 'DELE (delete); unimplemented.', {do not localize}
  1142. 'LIST (list); unimplemented.', {do not localize}
  1143. 'NLIST (name-list); unimplemented.', {do not localize}
  1144. 'CWD (change working directory); unimplemented.', {do not localize}
  1145. 'XCWD (change working directory); unimplemented.', {do not localize}
  1146. 'CDUP (change to parent directory); unimplemented.', {do not localize}
  1147. 'XCDUP (change to parent directory); unimplemented.', {do not localize}
  1148. 'RMD (remove Directory); unimplemented.', {do not localize}
  1149. 'XRMD (remove Directory); unimplemented.', {do not localize}
  1150. 'MKD (make Directory); unimplemented.', {do not localize}
  1151. 'XMKD (make Directory); unimplemented.', {do not localize}
  1152. 'SYST (system); unimplemented.' {do not localize}
  1153. );
  1154. //SSCN, OPTS MODE Z EXTRA, and OPTS UTF8 states
  1155. OnOffStates : array [0..1] of string =
  1156. ('ON', {do not localize}
  1157. 'OFF' {do not localize}
  1158. );
  1159. const
  1160. //%s = host
  1161. //%n = xauth key
  1162. XAUTHBANNER = '%s X2 WS_FTP Server Compatible(%d)';
  1163. ACCT_HELP_DISABLED = 'ACCT (specify account); unimplemented.'; {do not localize}
  1164. ACCT_HELP_ENABLED = 'Syntax: ACCT <SP> <account-information> <CRLF>';
  1165. const
  1166. NLSTEncType: array[Boolean] of IdTextEncodingType = (encASCII, encUTF8);
  1167. function CalculateCheckSum(AHashClass: TIdHashClass; AStrm: TStream; ABeginPos, AEndPos: TIdStreamSize): String;
  1168. {$IFDEF USE_INLINE} inline; {$ENDIF}
  1169. var
  1170. LHash: TIdHash;
  1171. begin
  1172. LHash := AHashClass.Create;
  1173. try
  1174. Result := LHash.HashStreamAsHex(AStrm, ABeginPos, AEndPos-ABeginPos);
  1175. finally
  1176. LHash.Free;
  1177. end;
  1178. end;
  1179. procedure XAutGreeting(AContext: TIdContext; AGreeting : TIdReply; const AHostName : String);
  1180. {$IFDEF USE_INLINE} inline; {$ENDIF}
  1181. var
  1182. s : String;
  1183. begin
  1184. //for XAUT to work with WS-FTP Pro, you need a banner mentioning "WS_FTP Server"
  1185. //and that banner can only be one line in length.
  1186. s := IndyFormat(XAUTHBANNER,
  1187. [ GStack.HostName, (AContext as TIdFTPServerContext).FXAUTKey]) + ' '+AGreeting.Text.Text;
  1188. s := Fetch(s,CR);
  1189. s := Fetch(s,LF);
  1190. AGreeting.Text.Text := s;
  1191. end;
  1192. { TIdFTPServer }
  1193. constructor TIdFTPServerContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn;
  1194. AList: TIdContextThreadList = nil);
  1195. begin
  1196. inherited Create(AConnection, AYarn, AList);
  1197. FUserSecurity := TIdFTPSecurityOptions.Create;
  1198. //we don't initialize FCCC flag here because that shouldn't be cleared with implicit SSL
  1199. FCCC := False;
  1200. FDataMode := dmStream;
  1201. FMLSOpts := [ItemType, Modify, Size];
  1202. //no write permissions for group and others
  1203. FUMask := 22;
  1204. ResetZLibSettings;
  1205. ReInitialize;
  1206. end;
  1207. procedure TIdFTPServerContext.SetUserSecurity(const Value: TIdFTPSecurityOptions);
  1208. begin
  1209. FUserSecurity.Assign( Value);
  1210. end;
  1211. destructor TIdFTPServerContext.Destroy;
  1212. begin
  1213. KillDataChannel;
  1214. FreeAndNil(FUserSecurity);
  1215. inherited Destroy;
  1216. end;
  1217. procedure TIdFTPServerContext.CreateDataChannel(APASV: Boolean = False);
  1218. begin
  1219. KillDataChannel; //let the old one terminate
  1220. FDataChannel := TIdDataChannel.Create(APASV, Self, UserSecurity.RequirePASVFromSameIP, Server);
  1221. end;
  1222. procedure TIdFTPServerContext.KillDataChannel;
  1223. begin
  1224. if Assigned(FDataChannel) then begin
  1225. if not FDataChannel.Stopped then begin
  1226. FDataChannel.Stopped := True;
  1227. FDataChannel.FDataChannel.Disconnect(False);
  1228. end;
  1229. FreeAndNil(FDataChannel);
  1230. end;
  1231. end;
  1232. procedure TIdFTPServerContext.ReInitialize;
  1233. begin
  1234. inherited;
  1235. FDataType := ftASCII;
  1236. // FDataMode := dmStream;
  1237. FDataPort := 0;
  1238. FDataStruct := dsFile;
  1239. FPASV := False;
  1240. FEPSVAll := False;
  1241. FDataProtection := ftpdpsClear;
  1242. DataPBSZCalled := False;
  1243. FDataProtBufSize := 0;
  1244. end;
  1245. function TIdFTPServerContext.IsAuthenticated(ASender: TIdCommand): Boolean;
  1246. begin
  1247. Result := FAuthenticated;
  1248. if not Result then begin
  1249. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  1250. end;
  1251. end;
  1252. { TIdFTPServer }
  1253. procedure TIdFTPServer.InitComponent;
  1254. begin
  1255. inherited InitComponent;
  1256. HelpReply.Code := ''; //we will handle the help ourselves
  1257. FDataChannelCommands := TIdCommandHandlers.Create(Self, FReplyClass, ReplyTexts, ExceptionReply);
  1258. FSITECommands := TIdCommandHandlers.Create(Self, FReplyClass, ReplyTexts, ExceptionReply);
  1259. FOPTSCommands := TIdCommandHandlers.Create(Self, FReplyClass, ReplyTexts, ExceptionReply);
  1260. //inherited from TLS classes
  1261. FRegularProtPort := IdPORT_FTP;
  1262. FImplicitTLSProtPort := IdPORT_ftps;
  1263. FExplicitTLSProtPort := IdPORT_FTP;
  1264. //
  1265. FAnonymousAccounts := TStringList.Create;
  1266. // By default these user names will be treated as anonymous.
  1267. FAnonymousAccounts.Add('anonymous'); { do not localize }
  1268. FAnonymousAccounts.Add('ftp'); { do not localize }
  1269. FAnonymousAccounts.Add('guest'); { do not localize }
  1270. FAllowAnonymousLogin := Id_DEF_AllowAnon;
  1271. FAnonymousPassStrictCheck := Id_DEF_PassStrictCheck;
  1272. DefaultPort := IDPORT_FTP;
  1273. DefaultDataPort := IdPORT_FTP_DATA;
  1274. // FEmulateSystem := Id_DEF_SystemType;
  1275. Greeting.SetReply(220, RSFTPDefaultGreeting);
  1276. FContextClass := TIdFTPServerContext;
  1277. ReplyUnknownCommand.SetReply(500, 'Unknown Command'); {do not localize}
  1278. FReplyUnknownSITECommand := FReplyClass.Create(nil);
  1279. FReplyUnknownSITECommand.SetReply(500, 'Invalid SITE command.'); {do not localize}
  1280. FFTPSecurityOptions := TIdFTPSecurityOptions.Create;
  1281. FServerInfo := TIdFTPServerIdentifier.Create;
  1282. FPASVBoundPortMin := DEF_PASV_BOUND_MIN;
  1283. FPASVBoundPortMax := DEF_PASV_BOUND_MAX;
  1284. FPathProcessing := DEF_PATHPROCESSING;
  1285. FServerInfo.CaseSensitive := DEF_CASE_SENSITIVE;
  1286. FServerInfo.DirSeparator := DEF_DIRSEPARATOR;
  1287. FDirFormat := DEF_DIRFORMAT;
  1288. end;
  1289. function TIdFTPServer.GetReplyClass: TIdReplyClass;
  1290. begin
  1291. Result := TIdReplyFTP;
  1292. end;
  1293. function TIdFTPServer.GetRepliesClass: TIdRepliesClass;
  1294. begin
  1295. Result := TIdRepliesFTP;
  1296. end;
  1297. procedure TIdFTPServer.CommandHELP(ASender: TIdCommand);
  1298. var
  1299. s : String;
  1300. LCmds : TStringList;
  1301. i : Integer;
  1302. LExp : String;
  1303. function ShouldShowCommand(const ACommand : String) : Boolean;
  1304. begin
  1305. Result := False;
  1306. case PosInStrArray(ACommand, HELP_SPEC_CMDS, False) of
  1307. -1 :
  1308. Result := True;
  1309. 0 : //'SIZE'
  1310. if Assigned(FOnGetFileSize) then begin
  1311. Result := True;
  1312. end;
  1313. 1 :// 'MDTM',
  1314. if Assigned(FOnGetFileDate) or Assigned(FTPFileSystem) then begin
  1315. Result := True;
  1316. end;
  1317. 2 : // 'AUTH'
  1318. if (FUseTLS in ExplicitTLSVals) then begin
  1319. Result := True;
  1320. end;
  1321. 3,4,5,6,7,8,9,10 : //'PBSZ','PROT', 'CCC','MIC','CONF','ENC','SSCN','CPSV',
  1322. if (FUseTLS <> utNoTLSSupport) then begin
  1323. Result := True;
  1324. end;
  1325. 11,12 : // 'MFMT','MFF',
  1326. if Assigned(FOnSetModifiedTime) or Assigned(FTPFileSystem) then begin
  1327. Result := True;
  1328. end;
  1329. 13,14, 15,16 : //'MD5','MMD5','XCRC','XMD5',
  1330. begin
  1331. Result := False;
  1332. if not GetFIPSMode then begin
  1333. if Assigned(FOnCRCFile) or Assigned(FTPFileSystem) then begin
  1334. Result := True;
  1335. end;
  1336. end;
  1337. end;
  1338. 17 : // 'XSHA1',
  1339. if Assigned(FOnCRCFile) or Assigned(FTPFileSystem) then begin
  1340. Result := True;
  1341. end;
  1342. 18 : //'XSHA256'
  1343. if (Assigned(FOnCRCFile) or Assigned(FTPFileSystem))
  1344. and TIdHashSHA256.IsAvailable then begin
  1345. Result := True;
  1346. end;
  1347. 19 : //'XSHA512'
  1348. if (Assigned(FOnCRCFile) or Assigned(FTPFileSystem)) and
  1349. TIdHashSHA512.IsAvailable then begin
  1350. Result := True;
  1351. end;
  1352. 20 : // 'COMB');
  1353. if Assigned(OnCombineFiles) or Assigned(FTPFileSystem) then begin
  1354. Result := True;
  1355. end;
  1356. 21 : // AVBL
  1357. if Assigned(FOnAvailDiskSpace) then begin
  1358. Result := True;
  1359. end;
  1360. 22 : // DSIZ
  1361. if Assigned(FOnCompleteDirSize) then begin
  1362. Result := True;
  1363. end;
  1364. 23 : // RMDA
  1365. if Assigned(FOnRemoveDirectoryAll) then begin
  1366. Result := True;
  1367. end;
  1368. 24 : // HOST
  1369. if Assigned( FOnHostCheck ) then begin
  1370. Result := True;
  1371. end;
  1372. 25 : // XAUT
  1373. if (not GetFIPSMode) and Self.FSupportXAUTH then begin
  1374. Result := True;
  1375. end;
  1376. end;
  1377. end;
  1378. function IsNotImplemented(const ACommand : String; var VHelp : String) : Boolean;
  1379. var
  1380. idx : Integer;
  1381. begin
  1382. Result := False; //presume that the command is implemented
  1383. idx := PosInStrArray(ACommand, HELP_ALT_MD_CMD, False);
  1384. if idx = -1 then begin
  1385. Exit;
  1386. end;
  1387. case idx of
  1388. 0 : // 'RETR'
  1389. begin
  1390. if (not Assigned(FOnRetrieveFile)) and (not Assigned(FFTPFileSystem)) then begin
  1391. Result := True;
  1392. end;
  1393. end;
  1394. 1,2,3 : //'STOR','STOU', 'APPE',
  1395. begin
  1396. if (not Assigned(FOnStoreFile)) and (not Assigned(FFTPFileSystem)) then begin
  1397. Result := True;
  1398. end;
  1399. end;
  1400. 4,5 : // 'RNFR', 'RNTO',
  1401. begin
  1402. if (not Assigned(FOnRenameFile)) and (not Assigned(FFTPFileSystem)) then begin
  1403. Result := True;
  1404. end;
  1405. end;
  1406. 6 : // 'DELE',
  1407. begin
  1408. if (not Assigned(FOnDeleteFile)) and (not Assigned(FFTPFileSystem)) then begin
  1409. Result := True;
  1410. end;
  1411. end;
  1412. 7,8 :// 'LIST','NLST',
  1413. begin
  1414. if (not Assigned(FOnListDirectory)) or
  1415. ((FDirFormat = ftpdfCustom) and (not Assigned(OnCustomListDirectory))) then begin
  1416. Result := True;
  1417. end;
  1418. end;
  1419. 9, 10, //'CWD','XCWD',
  1420. 11, 12 : // 'CDUP','XCUP',
  1421. begin
  1422. if (not Assigned(FOnChangeDirectory)) and (not Assigned(FFTPFileSystem)) then begin
  1423. Result := True;
  1424. end;
  1425. end;
  1426. 13, 14 : //'RMD','XRMD',
  1427. begin
  1428. if (not Assigned(FOnRemoveDirectory)) and (not Assigned(FFTPFileSystem)) then begin
  1429. Result := True;
  1430. end;
  1431. end;
  1432. 15,16 : //'MKD', 'XMKD',
  1433. begin
  1434. if (not Assigned(FOnMakeDirectory)) and (not Assigned(FFTPFileSystem)) then begin
  1435. Result := True;
  1436. end;
  1437. end;
  1438. 17 :// 'SYST',
  1439. begin
  1440. if (not Assigned(FOnMakeDirectory)) and (not Assigned(FFTPFileSystem)) then begin
  1441. Result := True;
  1442. end;
  1443. end;
  1444. end;
  1445. if Result then begin
  1446. LExp := HELP_ALT_MD_TP[idx];
  1447. end;
  1448. end;
  1449. begin
  1450. if ASender.Params.Count > 0 then begin
  1451. for i := 0 to CommandHandlers.Count-1 do begin
  1452. if TextIsSame(ASender.Params[0], CommandHandlers.Items[i].Command) then begin
  1453. if CommandHandlers.Items[i].HelpVisible and ShouldShowCommand(ASender.Params[0]) then begin
  1454. if IsNotImplemented(CommandHandlers.Items[i].Command, LExp) then begin
  1455. ASender.Reply.SetReply(214, LExp);
  1456. end else begin
  1457. ASender.Reply.SetReply(214, CommandHandlers.Items[i].Description.Text);
  1458. end;
  1459. end else begin
  1460. ASender.Reply.SetReply(502, IndyFormat(RSFTPCmdHelpNotKnown, [UpperCase(ASender.Params[0])]));
  1461. end;
  1462. Exit;
  1463. end;
  1464. end;
  1465. ASender.Reply.SetReply(502, IndyFormat(RSFTPCmdHelpNotKnown, [UpperCase(ASender.Params[0])]));
  1466. end else begin
  1467. s := RSFTPHelpBegining + EOL;
  1468. LCmds := TStringList.Create;
  1469. try
  1470. //
  1471. for i := 0 to CommandHandlers.Count -1 do begin
  1472. if CommandHandlers.Items[i].HelpVisible and ShouldShowCommand(CommandHandlers.Items[i].Command) then begin
  1473. if IsNotImplemented(CommandHandlers.Items[i].Command, LExp) then begin
  1474. LCmds.Add(CommandHandlers.Items[i].Command + '*'); {do not localize}
  1475. end else begin
  1476. LCmds.Add(CommandHandlers.Items[i].Command + CommandHandlers.Items[i].HelpSuperScript);
  1477. end;
  1478. end;
  1479. end;
  1480. LCmds.Sort;
  1481. s := s + HelpText(LCmds) + FEndOfHelpLine;
  1482. if FEndOfHelpLine = '' then begin
  1483. s := s + EOL; //prevent ugliness if last row out of alignment with the rest
  1484. end;
  1485. ASender.Reply.SetReply(214, s);
  1486. finally
  1487. FreeAndNil(LCmds);
  1488. end;
  1489. end;
  1490. end;
  1491. procedure TIdFTPServer.CommandHOST(ASender: TIdCommand);
  1492. var LTmp : String;
  1493. LValid : Boolean;
  1494. LContext : TIdFTPServerContext;
  1495. begin
  1496. LContext := TIdFTPServerContext(ASender.Context);
  1497. if Assigned(OnHostCheck) then begin
  1498. if LContext.Username <> '' then begin
  1499. ASender.Reply.SetReply(530, RSFTPNotAfterAuthentication );
  1500. Exit;
  1501. end;
  1502. if (ASender.Params.Count > 0) then begin
  1503. LTmp := ASender.Params[0];
  1504. if Copy(LTmp,1,1)='[' then begin
  1505. Delete(LTmp,1,1);
  1506. end;
  1507. LTmp := Fetch(LTmp,']');
  1508. LValid := False;
  1509. FOnHostCheck(LContext,LTmp,LValid);
  1510. if LValid then begin
  1511. LContext.Host := LTmp;
  1512. if Assigned(OnGreeting) then begin
  1513. OnGreeting(LContext,ASender.Reply);
  1514. end;
  1515. if ASender.Reply.NumericCode = 421 then begin
  1516. ASender.Disconnect := True;
  1517. end else begin
  1518. if not GetFIPSMode then begin
  1519. //setting the reply code number directly causes the text to be cleared
  1520. if FSupportXAUTH and (ASender.Reply.NumericCode = 220) then begin
  1521. XAutGreeting(LContext,ASender.Reply, LTmp);
  1522. end;
  1523. end;
  1524. ASender.Reply.SetReply(220,ASender.Reply.Text.Text);
  1525. end;
  1526. ASender.SendReply;
  1527. end else begin
  1528. ASender.Reply.SetReply(530,RSFTPHostNotFound);
  1529. end;
  1530. end;
  1531. end else begin
  1532. CmdSyntaxError(ASender);
  1533. end;
  1534. end;
  1535. procedure TIdFTPServer.InitializeCommandHandlers;
  1536. var
  1537. LCmd : TIdCommandHandler;
  1538. begin
  1539. inherited InitializeCommandHandlers;
  1540. //ACCESS CONTROL COMMANDS
  1541. //USER <SP> <username> <CRLF>
  1542. LCmd := CommandHandlers.Add;
  1543. LCmd.Command := 'USER'; {Do not Localize}
  1544. LCmd.OnCommand := CommandUSER;
  1545. LCmd.Description.Text := 'Syntax: USER <sp> username'; {do not localize}
  1546. //PASS <SP> <password> <CRLF>
  1547. LCmd := CommandHandlers.Add;
  1548. LCmd.Command := 'PASS'; {Do not Localize}
  1549. LCmd.OnCommand := CommandPASS;
  1550. LCmd.Description.Text := 'Syntax: PASS <sp> password'; {do not localize}
  1551. //ACCT <SP> <account-information> <CRLF>
  1552. LCmd := CommandHandlers.Add;
  1553. LCmd.Command := 'ACCT'; {Do not Localize}
  1554. // LCMd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['ACCT'])); {do not localize}
  1555. LCmd.OnCommand := CommandACCT;
  1556. if Assigned(Self.FOnUserAccount) then begin
  1557. LCmd.HelpSuperScript := ''; //not supported
  1558. LCmd.Description.Text := ACCT_HELP_ENABLED;
  1559. end else begin
  1560. LCmd.HelpSuperScript := '*'; //not supported
  1561. LCmd.Description.Text := ACCT_HELP_DISABLED;
  1562. end;
  1563. // 'ACCT (specify account); unimplemented.'; {do not localize}
  1564. {
  1565. LCmd.NormalReply.SetReply(502, Format(RSFTPCmdNotImplemented, ['ACCT'])); {Do not Localize}
  1566. //CWD <SP> <pathname> <CRLF>
  1567. LCmd := CommandHandlers.Add;
  1568. LCmd.Command := 'CWD'; {Do not Localize}
  1569. LCmd.OnCommand := CommandCWD;
  1570. LCmd.ExceptionReply.NumericCode := 550;
  1571. LCmd.Description.Text := 'Syntax: CWD [ <sp> directory-name ]'; {do not localize}
  1572. //CDUP <CRLF>
  1573. LCmd := CommandHandlers.Add;
  1574. LCmd.Command := 'CDUP'; {Do not Localize}
  1575. LCmd.OnCommand := CommandCDUP;
  1576. LCmd.ExceptionReply.NumericCode := 550;
  1577. LCmd.Description.Text := 'Syntax: CDUP (change to parent directory)'; {do not localize}
  1578. //SMNT <SP> <pathname> <CRLF>
  1579. LCmd := CommandHandlers.Add;
  1580. LCmd.Command := 'SMNT'; {Do not Localize}
  1581. LCmd.NormalReply.SetReply(502, RSFTPFileActionCompleted);//250 for success
  1582. LCmd.HelpSuperScript := '*';
  1583. LCmd.Description.Text := 'SMNT (structure mount); unimplemented.'; {do not localize}
  1584. //QUIT <CRLF>
  1585. LCmd := CommandHandlers.Add;
  1586. LCmd.Command := 'QUIT'; {Do not Localize}
  1587. LCmd.OnCommand := CommandQUIT;
  1588. LCmd.Disconnect := True;
  1589. LCmd.NormalReply.SetReply(221, RSFTPQuitGoodby); {Do not Localize}
  1590. LCmd.Description.Text := 'Syntax: QUIT (terminate service)'; {do not localize}
  1591. //REIN <CRLF>
  1592. LCmd := CommandHandlers.Add;
  1593. LCmd.Command := 'REIN'; {Do not Localize}
  1594. LCmd.OnCommand := CommandREIN;
  1595. LCmd.Description.Text := 'Syntax: REIN (reinitialize server state)'; {do not localize}
  1596. //PORT <SP> <host-port> <CRLF>
  1597. LCmd := CommandHandlers.Add;
  1598. LCmd.Command := 'PORT'; {Do not Localize}
  1599. LCmd.OnCommand := CommandPORT;
  1600. LCmd.Description.Text := 'Syntax: PORT <sp> b0, b1, b2, b3, b4'; {do not localize}
  1601. //PASV <CRLF>
  1602. LCmd := CommandHandlers.Add;
  1603. LCmd.Command := 'PASV'; {Do not Localize}
  1604. LCmd.OnCommand := CommandPASV;
  1605. LCmd.Description.Text := 'Syntax: PASV (set server in passive mode)'; {do not localize}
  1606. //P@SW <CRLF>
  1607. //This is for some routers that replace a PASV with a P@SW
  1608. //as part of a misguided attempt to add a feature.
  1609. //A router would do a replacement so a client would think that
  1610. //PASV wasn't supported and then the client would do a PORT command
  1611. //instead. That doesn't happen so this just caused the client not to work.
  1612. //See: http://www.gbnetwork.co.uk/smcftpd/
  1613. LCmd := CommandHandlers.Add;
  1614. LCmd.Command := 'P@SW'; {Do not Localize}
  1615. LCmd.OnCommand := CommandPASV;
  1616. LCmd.HelpVisible := False; //this is just a workaround
  1617. //TYPE <SP> <type-code> <CRLF>
  1618. LCmd := CommandHandlers.Add;
  1619. LCmd.Command := 'TYPE'; {Do not Localize}
  1620. LCmd.OnCommand := CommandTYPE;
  1621. LCmd.Description.Text := 'Syntax: TYPE <sp> [ A | E | I | L ]'; {do not localize}
  1622. //STRU <SP> <structure-code> <CRLF>
  1623. LCmd := CommandHandlers.Add;
  1624. LCmd.Command := 'STRU'; {Do not Localize}
  1625. LCmd.OnCommand := CommandSTRU;
  1626. LCmd.Description.Text := 'Syntax: STRU (specify file structure)'; {do not localize}
  1627. //MODE <SP> <mode-code> <CRLF>
  1628. LCmd := CommandHandlers.Add;
  1629. LCmd.Command := 'MODE'; {Do not Localize}
  1630. LCmd.OnCommand := CommandMODE;
  1631. LCmd.ExceptionReply.NumericCode := 501;
  1632. LCmd.Description.Text := 'Syntax: MODE (specify transfer mode)'; {do not localize}
  1633. //FTP SERVICE COMMANDS
  1634. //RETR <SP> <pathname> <CRLF>
  1635. LCmd := CommandHandlers.Add;
  1636. LCmd.Command := 'RETR'; {Do not Localize}
  1637. LCmd.OnCommand := CommandRETR;
  1638. LCmd.ExceptionReply.NumericCode := 550;
  1639. LCmd.Description.Text := 'Syntax: RETR <sp> file-name'; {do not localize}
  1640. //STOR <SP> <pathname> <CRLF>
  1641. LCmd := CommandHandlers.Add;
  1642. LCmd.Command := 'STOR'; {Do not Localize}
  1643. LCmd.OnCommand := CommandSSAP;
  1644. LCmd.ExceptionReply.NumericCode := 551;
  1645. LCmd.Description.Text := 'Syntax: STOR <sp> file-name'; {do not localize}
  1646. //STOU <CRLF>
  1647. LCmd := CommandHandlers.Add;
  1648. LCmd.Command := 'STOU'; {Do not Localize}
  1649. LCmd.OnCommand := CommandSSAP;
  1650. LCmd.ExceptionReply.NumericCode := 551;
  1651. LCmd.Description.Text := 'Syntax: STOU <sp> file-name'; {do not localize}
  1652. //APPE <SP> <pathname> <CRLF>
  1653. LCmd := CommandHandlers.Add;
  1654. LCmd.Command := 'APPE'; {Do not Localize}
  1655. LCmd.OnCommand := CommandSSAP;
  1656. LCmd.ExceptionReply.NumericCode := 550;
  1657. LCmd.Description.Text := 'Syntax: APPE <sp> file-name'; {do not localize}
  1658. //ALLO <SP> <decimal-integer>
  1659. // [<SP> R <SP> <decimal-integer>] <CRLF>
  1660. LCmd := CommandHandlers.Add;
  1661. LCmd.Command := 'ALLO'; {Do not Localize}
  1662. LCmd.OnCommand := CommandALLO;
  1663. LCmd.ExceptionReply.NumericCode := 550;
  1664. LCmd.Description.Text := 'Syntax: ALLO allocate storage (vacuously)'; {do not localize}
  1665. //REST <SP> <marker> <CRLF>
  1666. LCmd := CommandHandlers.Add;
  1667. LCmd.Command := 'REST'; {Do not Localize}
  1668. LCmd.OnCommand := CommandREST;
  1669. LCmd.ExceptionReply.NumericCode := 550;
  1670. LCmd.Description.Text := 'Syntax: REST (restart command)'; {do not localize}
  1671. //RNFR <SP> <pathname> <CRLF>
  1672. LCmd := CommandHandlers.Add;
  1673. LCmd.Command := 'RNFR'; {Do not Localize}
  1674. LCmd.OnCommand := CommandRNFR;
  1675. LCmd.ExceptionReply.NumericCode := 550;
  1676. LCmd.Description.Text := 'Syntax: RNFR <sp> file-name'; {do not localize}
  1677. //RNTO <SP> <pathname> <CRLF>
  1678. LCmd := CommandHandlers.Add;
  1679. LCmd.Command := 'RNTO'; {Do not Localize}
  1680. LCmd.OnCommand := CommandRNTO;
  1681. LCmd.ExceptionReply.NumericCode := 550;
  1682. LCmd.Description.Text := 'Syntax: RNTO <sp> file-name'; {do not localize}
  1683. //ABOR <CRLF>
  1684. LCmd := CommandHandlers.Add;
  1685. LCmd.Command := 'ABOR'; {Do not Localize}
  1686. LCmd.OnCommand := CommandABOR;
  1687. LCmd.ExceptionReply.NumericCode := 550;
  1688. LCmd.Description.Text := 'Syntax: ABOR (abort operation)'; {do not localize}
  1689. //DELE <SP> <pathname> <CRLF>
  1690. LCmd := CommandHandlers.Add;
  1691. LCmd.Command := 'DELE'; {Do not Localize}
  1692. LCmd.OnCommand := CommandDELE;
  1693. LCmd.ExceptionReply.NumericCode := 550;
  1694. LCmd.Description.Text := 'Syntax: DELE <sp> file-name'; {do not localize}
  1695. // 'SMNT (structure mount); unimplemented.';
  1696. //RMD <SP> <pathname> <CRLF>
  1697. LCmd := CommandHandlers.Add;
  1698. LCmd.Command := 'RMD'; {Do not Localize}
  1699. LCmd.OnCommand := CommandRMD;
  1700. LCmd.ExceptionReply.NumericCode := 550;
  1701. LCmd.Description.Text := 'Syntax: RMD <sp> path-name'; {do not localize}
  1702. //MKD <SP> <pathname> <CRLF>
  1703. LCmd := CommandHandlers.Add;
  1704. LCmd.Command := 'MKD'; {Do not Localize}
  1705. LCmd.OnCommand := CommandMKD;
  1706. LCmd.ExceptionReply.NumericCode := 550;
  1707. LCmd.Description.Text := 'Syntax: MKD <sp> path-name'; {do not localize}
  1708. //PWD <CRLF>
  1709. LCmd := CommandHandlers.Add;
  1710. LCmd.Command := 'PWD'; {Do not Localize}
  1711. LCmd.OnCommand := CommandPWD;
  1712. LCmd.ExceptionReply.NumericCode := 550;
  1713. LCmd.Description.Text := 'Syntax: PWD (return current directory)'; {do not localize}
  1714. //LIST [<SP> <pathname>] <CRLF>
  1715. LCmd := CommandHandlers.Add;
  1716. LCmd.Command := 'LIST'; {Do not Localize}
  1717. LCmd.OnCommand := CommandLIST;
  1718. LCmd.ExceptionReply.NumericCode := 450;
  1719. LCmd.Description.Text := 'Syntax: LIST [ <sp> path-name ]'; {do not localize}
  1720. //NLST [<SP> <pathname>] <CRLF>
  1721. LCmd := CommandHandlers.Add;
  1722. LCmd.Command := 'NLST'; {Do not Localize}
  1723. LCmd.OnCommand := CommandLIST;
  1724. LCmd.ExceptionReply.NumericCode := 450;
  1725. LCmd.Description.Text := 'Syntax: NLST [ <sp> path-name ]'; {do not localize}
  1726. //SITE <SP> <string> <CRLF>
  1727. LCmd := CommandHandlers.Add;
  1728. LCmd.Command := 'SITE'; {Do not Localize}
  1729. LCmd.OnCommand := CommandSITE;
  1730. LCmd.ExceptionReply.NumericCode := 501;
  1731. LCmd.Description.Text := 'Syntax: SITE (site-specific commands)';
  1732. //SYST <CRLF>
  1733. LCmd := CommandHandlers.Add;
  1734. LCmd.Command := 'SYST'; {Do not Localize}
  1735. LCmd.OnCommand := CommandSYST;
  1736. LCmd.ExceptionReply.NumericCode := 501;
  1737. LCmd.Description.Text := 'Syntax: SYST (get type of operating system)'; {do not localize}
  1738. //STAT [<SP> <pathname>] <CRLF>
  1739. LCmd := CommandHandlers.Add;
  1740. LCmd.Command := 'STAT'; {Do not Localize}
  1741. LCmd.OnCommand := CommandSTAT;
  1742. LCmd.ExceptionReply.NumericCode := 450;
  1743. LCmd.Description.Text := 'Syntax: CWD [ <sp> directory-name ]'; {do not localize}
  1744. //NOOP <CRLF>
  1745. LCmd := CommandHandlers.Add;
  1746. LCmd.Command := 'NOOP'; {Do not Localize}
  1747. LCmd.NormalReply.SetReply(200, IndyFormat(RSFTPCmdSuccessful, ['NOOP'])); {Do not Localize}
  1748. LCmd.ExceptionReply.NumericCode := 550;
  1749. LCmd.Description.Text := 'Syntax: NOOP'; {do not localize}
  1750. //RFC 775
  1751. LCmd := CommandHandlers.Add;
  1752. LCmd.Command := 'XMKD'; {Do not Localize}
  1753. LCmd.OnCommand := CommandMKD;
  1754. LCmd.ExceptionReply.NumericCode := 551; //use the ones in parathensies
  1755. LCmd.Description.Text := 'Syntax: XMKD <sp> path-name'; {do not localize}
  1756. //XCWD <SP> <pathname> <CRLF>
  1757. LCmd := CommandHandlers.Add;
  1758. LCmd.Command := 'XCWD'; {Do not Localize}
  1759. LCmd.OnCommand := CommandCWD;
  1760. LCmd.ExceptionReply.NumericCode := 550;
  1761. LCmd.Description.Text := 'Syntax: XCWD [ <sp> directory-name ]'; {do not localize}
  1762. LCmd := CommandHandlers.Add;
  1763. LCmd.Command := 'XRMD'; {Do not Localize}
  1764. LCmd.OnCommand := CommandRMD;
  1765. LCmd.ExceptionReply.NumericCode := 551; //use the ones in parathensies
  1766. LCmd.Description.Text := 'Syntax: XRMD <sp> path-name'; {do not localize}
  1767. LCmd := CommandHandlers.Add;
  1768. LCmd.Command := 'XPWD'; {Do not Localize}
  1769. LCmd.OnCommand := CommandPWD;
  1770. LCmd.ExceptionReply.NumericCode := 502;
  1771. LCmd.Description.Text := 'Syntax: PWD (return current directory)'; {do not localize}
  1772. LCmd := CommandHandlers.Add;
  1773. LCmd.Command := 'XCUP'; {Do not Localize}
  1774. LCmd.OnCommand := CommandCDUP;
  1775. LCmd.ExceptionReply.NumericCode := 551; //use the ones in parathensies
  1776. LCmd.Description.Text := 'Syntax: XCUP (change to parent directory)'; {do not localize}
  1777. //RFC 2389
  1778. LCmd := CommandHandlers.Add;
  1779. LCmd.Command := 'FEAT'; {Do not Localize}
  1780. LCmd.OnCommand := CommandFEAT;
  1781. SetRFCReplyFormat(LCmd.NormalReply);
  1782. LCmd.ExceptionReply.NumericCode := 501;
  1783. LCmd.Description.Text := 'Syntax: FEAT (returns feature list)'; {do not localize}
  1784. //RFC 2389
  1785. LCmd := CommandHandlers.Add;
  1786. LCmd.Command := 'OPTS'; {Do not Localize}
  1787. LCmd.OnCommand := CommandOPTS;
  1788. LCmd.ExceptionReply.NumericCode := 501;
  1789. LCmd.Description.Text := 'Syntax: OPTS <sp> command [<sp> options]'; {do not localize}
  1790. //SIZE [<FILE>] CRLF
  1791. LCmd := CommandHandlers.Add;
  1792. LCmd.Command := 'SIZE'; {Do not Localize}
  1793. LCmd.OnCommand := CommandSIZE;
  1794. LCmd.ExceptionReply.NumericCode := 550;
  1795. LCmd.Description.Text := 'Syntax: SIZE <sp> path-name'; {do not localize}
  1796. //EPSV [protocol] <CRLF>
  1797. LCmd := CommandHandlers.Add;
  1798. LCmd.Command := 'EPSV'; {Do not Localize}
  1799. LCmd.OnCommand := CommandEPSV;
  1800. LCmd.ExceptionReply.NumericCode := 501;
  1801. LCmd.Description.Text := 'Syntax: EPSV (returns port |||port|)'; {do not localize}
  1802. //EPRT [address/port string] <CRLF>
  1803. LCmd := CommandHandlers.Add;
  1804. LCmd.Command := 'EPRT'; {Do not Localize}
  1805. LCmd.OnCommand := CommandEPRT;
  1806. LCmd.ExceptionReply.NumericCode := 501;
  1807. LCmd.Description.Text := 'Syntax: EPRT <sp> |proto|addr|port|'; {do not localize}
  1808. //MDTM [<FILE>] <CRLF>
  1809. LCmd := CommandHandlers.Add;
  1810. LCmd.Command := 'MDTM'; {Do not Localize}
  1811. LCmd.OnCommand := CommandMDTM;
  1812. LCmd.ExceptionReply.NumericCode := 550;
  1813. LCmd.Description.Text := 'Syntax: MDTM <sp> path-name'; {do not localize}
  1814. //RFC 2228
  1815. //AUTH [Mechanism] <CRLF>
  1816. LCmd := CommandHandlers.Add;
  1817. LCmd.Command := 'AUTH'; {Do not translate}
  1818. LCmd.OnCommand := CommandAUTH;
  1819. LCmd.ExceptionReply.NumericCode := 501;
  1820. LCmd.Description.Text := 'Syntax: AUTH <sp> mechanism-name'; {do not localize}
  1821. //PBSZ [Protection Buffer Size] <CRLF>
  1822. LCmd := CommandHandlers.Add;
  1823. LCmd.Command := 'PBSZ'; {Do not translate}
  1824. LCmd.OnCommand := CommandPBSZ;
  1825. LCmd.ExceptionReply.NumericCode := 501;
  1826. LCmd.Description.Text := 'Syntax: PBSZ <sp> protection buffer size'; {do not localize}
  1827. //PROT Protection Type <CRLF>
  1828. LCmd := CommandHandlers.Add;
  1829. LCmd.Command := 'PROT'; {Do not translate}
  1830. LCmd.OnCommand := CommandPROT;
  1831. LCmd.ExceptionReply.NumericCode := 501;
  1832. LCmd.Description.Text := 'Syntax: PROT <sp> protection code'; {do not localize}
  1833. //CCC Clear Command Channel
  1834. LCmd := CommandHandlers.Add;
  1835. LCmd.Command := 'CCC'; {Do not translate}
  1836. LCmd.OnCommand := CommandCCC;
  1837. LCmd.Description.Text := 'Syntax: CCC (clear command channel)'; {do not localize}
  1838. //MIC Integrity Protected Command
  1839. LCmd := CommandHandlers.Add;
  1840. LCmd.Command := 'MIC'; {Do not translate}
  1841. LCmd.OnCommand := CommandSecRFC;
  1842. LCmd.HelpSuperScript := '*';
  1843. LCmd.Description.Text := 'MIC (integrity protected command); unimplemented.'; {do not localize}
  1844. //CONF Confidentiality protected command
  1845. LCmd := CommandHandlers.Add;
  1846. LCmd.Command := 'CONF'; {Do not translate}
  1847. LCmd.OnCommand := CommandSecRFC;
  1848. LCmd.HelpSuperScript := '*';
  1849. LCmd.Description.Text := 'CONF (confidentiality protected command); unimplemented.'; {do not localize}
  1850. //ENC Privacy Protected command
  1851. LCmd := CommandHandlers.Add;
  1852. LCmd.Command := 'ENC'; {Do not translate}
  1853. LCmd.OnCommand := CommandSecRFC;
  1854. LCmd.HelpSuperScript := '*';
  1855. LCmd.Description.Text := 'ENC (privacy protected command); unimplemented.'; {do not localize}
  1856. //These are from IETF Draft "Extensions to FTP"
  1857. //MLSD [Pathname] <CRLF>
  1858. LCmd := CommandHandlers.Add;
  1859. LCmd.Command := 'MLSD'; {Do not translate}
  1860. LCmd.OnCommand := CommandMLSD;
  1861. LCmd.ExceptionReply.NumericCode := 550;
  1862. LCmd.Description.Text := 'Syntax: MLSD [ <sp> path-name ]'; {do not localize}
  1863. //MLST [Pathname] <CRLF>
  1864. LCmd := CommandHandlers.Add;
  1865. LCmd.Command := 'MLST'; {Do not translate}
  1866. LCmd.OnCommand := CommandMLST;
  1867. SetRFCReplyFormat(LCmd.NormalReply);
  1868. LCmd.ExceptionReply.NumericCode := 550;
  1869. LCmd.Description.Text := 'Syntax: MLST [ <sp> path-name ]'; {do not localize}
  1870. //Defined in http://www.trevezel.com/downloads/draft-somers-ftp-mfxx-00.html
  1871. //Modify File Modification Time
  1872. //MFMT [ATime] [Path-name]<CRLF>
  1873. LCmd := CommandHandlers.Add;
  1874. LCmd.Command := 'MFMT'; {Do not translate}
  1875. LCmd.OnCommand := CommandMFMT;
  1876. LCmd.ExceptionReply.NumericCode := 550;
  1877. LCmd.Description.Text := 'Syntax: MFMT [ATime] [Path-name]<CRLF>'; {do not localize}
  1878. //Defined in http://www.trevezel.com/downloads/draft-somers-ftp-mfxx-00.html
  1879. //Modify File Creation Time
  1880. //MFMT [ATime] [Pathname]<CRLF>
  1881. LCmd := CommandHandlers.Add;
  1882. LCmd.Command := 'MFCT'; {Do not translate}
  1883. LCmd.OnCommand := CommandMFCT;
  1884. LCmd.ExceptionReply.NumericCode := 550;
  1885. LCmd.Description.Text := 'Syntax: MFCT [ATime] [Path-name]'; {do not localize}
  1886. //params are the same format as the MLS output
  1887. LCmd := CommandHandlers.Add;
  1888. LCmd.Command := 'MFF'; {Do not translate}
  1889. LCmd.OnCommand := CommandMFF;
  1890. LCmd.ExceptionReply.NumericCode := 550;
  1891. LCmd.Description.Text := 'Syntax: MFF [ mff-facts ] SP path-name'; {do not localize}
  1892. //From http://www.ietf.org/internet-drafts/draft-twine-ftpmd5-00.txt
  1893. //MD5 [Pathname]
  1894. LCmd := CommandHandlers.Add;
  1895. LCmd.Command := 'MD5'; {Do not translate}
  1896. LCmd.OnCommand := CommandMD5;
  1897. LCmd.ExceptionReply.NumericCode := 504;
  1898. LCmd.Description.Text := 'Syntax: MD5 [Pathname]'; {do not localize}
  1899. //MMD5 [Filepath1], [Filepath2] [...]
  1900. LCmd := CommandHandlers.Add;
  1901. LCmd.Command := 'MMD5'; {Do not translate}
  1902. LCmd.OnCommand := CommandMMD5;
  1903. LCmd.ExceptionReply.NumericCode := 504;
  1904. LCmd.Description.Text := 'Syntax: MMD5 [Filepath1], [Filepath2] [...]'; {do not localize}
  1905. //These two commands are not in RFC's or drafts
  1906. // but are documented in:
  1907. // GlobalSCAPE Secure FTP Server User’s Guide
  1908. //XCRC "[filename]" [start] [finish]
  1909. LCmd := CommandHandlers.Add;
  1910. LCmd.Command := 'XCRC'; {Do not translate}
  1911. LCmd.OnCommand := CommandCheckSum;
  1912. LCmd.ExceptionReply.NumericCode := 550;
  1913. LCmd.Description.Text := 'Syntax: XCRC "[file-name]" [start] [finish]'; {do not localize}
  1914. //COMB "[filename]" [start] [finish]
  1915. LCmd := CommandHandlers.Add;
  1916. LCmd.Command := 'COMB'; {Do not translate}
  1917. LCmd.OnCommand := CommandCOMB;
  1918. LCmd.ExceptionReply.NumericCode := 550;
  1919. LCmd.Description.Text := 'Syntax: COMB "[file-name]" [start] [finish]'; {do not localize}
  1920. //informal but we might want to support this anyway
  1921. //SSCN - specified by:
  1922. //http://www.raidenftpd.com/kb/kb000000037.htm
  1923. LCmd := CommandHandlers.Add;
  1924. LCmd.Command := 'SSCN'; {Do not translate}
  1925. LCmd.OnCommand := CommandSSCN;
  1926. LCmd.ExceptionReply.NumericCode := 550;
  1927. LCmd.NormalReply.NumericCode := 200;
  1928. LCmd.Description.Text := 'Syntax: SSCN [ON|OFF]'; {do not localize}
  1929. //CPSV <CRLF>
  1930. LCmd := CommandHandlers.Add;
  1931. LCmd.Command := 'CPSV'; {Do not Localize}
  1932. LCmd.OnCommand := CommandCPSV;
  1933. LCmd.Description.Text := 'Syntax: CPSV (set server in passive mode with SSL Connect)'; {do not localize}
  1934. //Seen in RaidenFTPD documentation
  1935. //XCRC "[filename]" [start] [finish]
  1936. LCmd := CommandHandlers.Add;
  1937. LCmd.Command := 'XMD5'; {Do not translate}
  1938. LCmd.OnCommand := CommandCheckSum;
  1939. LCmd.ExceptionReply.NumericCode := 550;
  1940. LCmd.Description.Text := 'Syntax: XMD5 "[filename]" [start] [finish]'; {do not localize}
  1941. //Seen in RaidenFTPD documentation
  1942. //XCRC "[filename]" [start] [finish]
  1943. LCmd := CommandHandlers.Add;
  1944. LCmd.Command := 'XSHA1'; {Do not translate}
  1945. LCmd.OnCommand := CommandCheckSum;
  1946. LCmd.ExceptionReply.NumericCode := 550;
  1947. LCmd.Description.Text := 'Syntax: XSHA1 "[filename]" [start] [finish]'; {do not localize}
  1948. LCmd := CommandHandlers.Add;
  1949. LCmd.Command := 'XSHA256'; {Do not translate}
  1950. LCmd.OnCommand := CommandCheckSum;
  1951. LCmd.ExceptionReply.NumericCode := 550;
  1952. LCmd.Description.Text := 'Syntax: XSHA256 "[filename]" [start] [finish]'; {do not localize}
  1953. LCmd := CommandHandlers.Add;
  1954. LCmd.Command := 'XSHA512'; {Do not translate}
  1955. LCmd.OnCommand := CommandCheckSum;
  1956. LCmd.ExceptionReply.NumericCode := 550;
  1957. LCmd.HelpVisible := True;
  1958. LCmd.Description.Text := 'Syntax: XSHA512 "[filename]" [start] [finish]'; {do not localize}
  1959. //commands from
  1960. // draft-peterson-streamlined-ftp-command-extensions-01.txt
  1961. //http://tools.ietf.org/html/draft-peterson-streamlined-ftp-command-extensions-01#section-2.4
  1962. LCmd := CommandHandlers.Add;
  1963. LCmd.Command := 'AVBL'; {Do not localize}
  1964. LCmd.OnCommand := CommandAVBL;
  1965. LCmd.ExceptionReply.NumericCode := 500;
  1966. LCmd.Description.Text := 'Syntax: AVBL [<sp> dirpath] (returns the number of '+
  1967. 'bytes available for uploading in the directory or current working directory)';
  1968. LCmd := CommandHandlers.Add;
  1969. LCmd.Command := 'DSIZ'; {Do not localize}
  1970. LCmd.OnCommand := CommandDSIZ;
  1971. LCmd.ExceptionReply.NumericCode := 500;
  1972. LCmd.Description.Text := 'DSIZ [<sp> dirpath] (returns the number of bytes '+
  1973. 'in the directory or current working directory, including sub directories)';
  1974. LCmd := CommandHandlers.Add;
  1975. LCmd.Command := 'RMDA';
  1976. LCmd.OnCommand := CommandRMDA;
  1977. LCmd.ExceptionReply.NumericCode := 550;
  1978. LCmd.Description.Text := 'RMDA <sp> pathname (deletes (removes) the '+
  1979. 'specified directory and its contents)';
  1980. //informal but we might want to support this anyway
  1981. //CLNT
  1982. LCmd := CommandHandlers.Add;
  1983. LCmd.Command := 'CLNT'; {do not localize}
  1984. LCmd.OnCommand := CommandCLNT;
  1985. LCmd.ExceptionReply.NumericCode := 550;
  1986. LCmd.NormalReply.SetReply(200, RSFTPClntNoted); {Do not Localize}
  1987. LCmd.Description.Text := 'Syntax: CLNT <sp> <clientname> <sp> <clientversion> [ <sp> <platform> ]'; {do not localize}
  1988. //https://www.ietf.org/archive/id/draft-peterson-streamlined-ftp-command-extensions-10.txt
  1989. LCmd := CommandHandlers.Add;
  1990. LCmd.Command := 'CSID'; {Do not localize}
  1991. LCmd.OnCommand := CommandCSID;
  1992. LCmd.ExceptionReply.NumericCode := 550;
  1993. LCmd.Description.Text := 'Syntax: CSID <sp> Name=<clientname>; Version=<clientversion>;'; {Do not localize}
  1994. //Informal - an old proposed solution to IPv6 support in FTP.
  1995. //Mentioned at: http://cr.yp.to/ftp/retr.html
  1996. //and supported by PureFTPD.
  1997. LCmd := CommandHandlers.Add;
  1998. LCmd.Command := 'SPSV'; {do not localize}
  1999. LCmd.OnCommand := CommandSPSV;
  2000. LCmd.Description.Text := 'Syntax: SPSV (set server in passive mode)'; {do not localize}
  2001. LCmd := CommandHandlers.Add;
  2002. LCmd.Command := 'HOST'; {Do not localize}
  2003. LCmd.OnCommand := CommandHOST;
  2004. LCmd.ExceptionReply.NumericCode := 504;
  2005. LCmd.Description.Text := 'Syntax: HOST <sp> domain (select a domain prior to logging in)'; {Do not localize}
  2006. //Note that these commands are mentioned in old RFC's
  2007. //and we will not support them at all. The commands
  2008. //were there because FTP was a predisessor of SMTP
  2009. //These are from RFC 765
  2010. //MLFL [<SP> <ident>] <CRLF>
  2011. LCmd := CommandHandlers.Add;
  2012. LCmd.Command := 'MLFL'; {Do not Localize}
  2013. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MLFL'])); {Do not Localize}
  2014. LCmd.HelpSuperScript := '*';
  2015. LCmd.Description.Text := 'MLFL (mail file); unimplemented.'; {do not localize}
  2016. //MAIL [<SP> <ident>] <CRLF>
  2017. LCmd := CommandHandlers.Add;
  2018. LCmd.Command := 'MAIL'; {Do not Localize}
  2019. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MAIL'])); {Do not Localize}
  2020. LCmd.HelpSuperScript := '*';
  2021. LCmd.Description.Text := 'MAIL (mail to user); unimplemented.'; {do not localize}
  2022. // MSND [<SP> <ident>] <CRLF>
  2023. LCmd := CommandHandlers.Add;
  2024. LCmd.Command := 'MSND'; {Do not Localize}
  2025. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MSND'])); {Do not Localize}
  2026. LCmd.HelpSuperScript := '*';
  2027. LCmd.Description.Text := 'MSND (mail send to terminal); unimplemented.'; {do not localize}
  2028. // MSOM [<SP> <ident>] <CRLF>
  2029. LCmd := CommandHandlers.Add;
  2030. LCmd.Command := 'MSOM'; {Do not Localize}
  2031. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MSOM'])); {Do not Localize}
  2032. LCmd.HelpSuperScript := '*';
  2033. LCmd.Description.Text := 'MSOM (mail send to terminal or mailbox); unimplemented.'; {do not localize}
  2034. // MSAM [<SP> <ident>] <CRLF>
  2035. LCmd := CommandHandlers.Add;
  2036. LCmd.Command := 'MSAM'; {Do not Localize}
  2037. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MSAM'])); {Do not Localize}
  2038. LCmd.HelpSuperScript := '*';
  2039. LCmd.Description.Text := 'MSAM (mail send to terminal and mailbox); unimplemented.'; {do not localize}
  2040. // MRSQ [<SP> <scheme>] <CRLF>
  2041. LCmd := CommandHandlers.Add;
  2042. LCmd.Command := 'MRSQ'; {Do not Localize}
  2043. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MRSQ'])); {Do not Localize}
  2044. LCmd.HelpSuperScript := '*';
  2045. LCmd.Description.Text := 'MRSQ (mail recipient scheme question); unimplemented.'; {do not localize}
  2046. // MRCP <SP> <ident> <CRLF>
  2047. LCmd := CommandHandlers.Add;
  2048. LCmd.Command := 'MRCP'; {Do not Localize}
  2049. LCmd.NormalReply.SetReply(502, IndyFormat(RSFTPCmdNotImplemented, ['MRCP'])); {Do not Localize}
  2050. LCmd.HelpSuperScript := '*';
  2051. LCmd.Description.Text := 'MRCP (mail recipient); unimplemented.'; {do not localize}
  2052. //
  2053. LCmd := CommandHandlers.Add;
  2054. LCmd.Command := 'HELP'; {Do not Localize}
  2055. LCmd.OnCommand := COmmandHELP;
  2056. LCmd.NormalReply.NumericCode := 214;
  2057. LCmd.Description.Text := 'Syntax: HELP [ <sp> <string> ]'; {do not localize}
  2058. //We use a separate command handler collection for some things which are
  2059. //valid durring the data connection.
  2060. //ABOR <CRLF>
  2061. LCmd := FDataChannelCommands.Add;
  2062. LCmd.Command := 'ABOR'; {Do not Localize}
  2063. LCmd.OnCommand := CommandABOR;
  2064. LCmd.ExceptionReply.NumericCode := 550;
  2065. //STAT [<SP> <pathname>] <CRLF>
  2066. LCmd := FDataChannelCommands.Add;
  2067. LCmd.Command := 'STAT'; {Do not Localize}
  2068. LCmd.OnCommand := CommandSTAT;
  2069. LCmd.ExceptionReply.NumericCode := 450;
  2070. //This is for SITE commands to make it easy for the user to add their own site commands
  2071. //as they wish
  2072. //These are Unix site commands
  2073. LCmd := FSITECommands.Add;
  2074. LCmd.Command := 'HELP'; {Do not localize}
  2075. LCmd.ExceptionReply.NumericCode := 501;
  2076. LCmd.OnCommand := CommandSiteHELP;
  2077. LCmd.Description.Text := 'Syntax: SITE HELP [ <sp> <string> ]'; {do not localize}
  2078. //SITE ATTRIB<SP>Attribs<SP>FileName<CRLF>
  2079. LCmd := FSITECommands.Add;
  2080. LCmd.Command := 'ATTRIB'; {Do not Localize}
  2081. LCmd.OnCommand := CommandSiteATTRIB;
  2082. LCmd.ExceptionReply.NumericCode := 501;
  2083. LCmd.Description.Text := 'Syntax: SITE ATTRIB<SP>Attribs<SP>Filename'; {do not localize}
  2084. //SITE UMASK<SP>[mask]
  2085. LCmd := FSITECommands.Add;
  2086. LCmd.Command := 'UMASK'; {Do not Localize}
  2087. LCmd.OnCommand := CommandSiteUMASK;
  2088. LCmd.ExceptionReply.NumericCode := 501;
  2089. LCmd.Description.Text := 'Syntax: SITE UMASK'; {do not localize}
  2090. //SITE CHMOD<SP>Permission numbers<SP>Filename<CRLF>
  2091. LCmd := FSITECommands.Add;
  2092. LCmd.Command := 'CHMOD'; {Do not Localize}
  2093. LCmd.OnCommand := CommandSiteCHMOD;
  2094. LCmd.ExceptionReply.NumericCode := 501;
  2095. LCmd.Description.Text := 'Syntax: SITE CHMOD<SP>Permission numbers<SP>Filename'; {do not localize}
  2096. //additional Unix server commands that aren't supported but should be supported, IMAO
  2097. //SITE CHOWN<SP>Owner[:Group]<SP>Filename<CRLF>
  2098. LCmd := FSITECommands.Add;
  2099. LCmd.Command := 'CHOWN'; {Do not Localize}
  2100. LCmd.OnCommand := CommandSiteCHOWN;
  2101. LCmd.ExceptionReply.NumericCode := 501;
  2102. LCmd.Description.Text := 'Syntax: SITE CHOWN<SP>Owner[:Group]<SP>Filename<CRLF>'; {do not localize}
  2103. //SITE CHGRP<SP>Group<SP>Filename<CRLF>
  2104. LCmd := FSITECommands.Add;
  2105. LCmd.Command := 'CHGRP'; {Do not Localize}
  2106. LCmd.OnCommand := CommandSiteCHGRP;
  2107. LCmd.ExceptionReply.NumericCode := 501;
  2108. LCmd.Description.Text := 'Syntax: SITE CHGRP<SP>Group<SP>Filename<CRLF>'; {do not localize}
  2109. //Microsoft IIS SITE commands
  2110. //SITE DIRSTYLE
  2111. LCmd := FSITECommands.Add;
  2112. LCmd.Command := 'DIRSTYLE'; {Do not Localize}
  2113. LCmd.ExceptionReply.NumericCode := 501;
  2114. LCmd.OnCommand := CommandSiteDIRSTYLE;
  2115. LCmd.Description.Text := 'Syntax: SITE DIRSTYLE (toggle directory format)'; {do not localize}
  2116. //SITE ZONE
  2117. LCmd := FSITECommands.Add;
  2118. LCmd.Command := 'ZONE'; {Do not localize}
  2119. LCmd.ExceptionReply.NumericCode := 530;
  2120. LCmd.OnCommand := CommandSiteZONE;
  2121. LCmd.Description.Text := 'Syntax: SITE ZONE (returns the server offset from GMT)'; {do not localize}
  2122. //SITE UTIME
  2123. LCmd := FSITECommands.Add;
  2124. LCmd.Command := 'UTIME'; {Do not localize}
  2125. LCmd.NormalReply.NumericCode := 200;
  2126. LCmd.NormalReply.Text.Text := 'Date/time changed okay.';
  2127. LCmd.ExceptionReply.NumericCode := 530;
  2128. LCmd.OnCommand := CommandSiteUTIME;
  2129. LCmd.Description.Text :=
  2130. 'Syntax: SITE UTIME <file> <access-time> <modification-time> <creation time>'+CR+LF+ {do not localize}
  2131. ' Each timestamp must be in the format YYYYMMDDhhmmss'; {do not localize}
  2132. //OPTS MLST
  2133. LCmd := FOPTSCommands.Add;
  2134. LCmd.Command := 'MLST'; {Do not localize}
  2135. LCmd.ExceptionReply.NumericCode := 501;
  2136. LCmd.OnCommand := CommandOptsMLST;
  2137. //OPTS MODE Z
  2138. LCmd := FOPTSCommands.Add;
  2139. LCmd.Command := 'MODE Z'; {Do not localize}
  2140. LCmd.ExceptionReply.NumericCode := 501;
  2141. LCmd.OnCommand := CommandOptsMODEZ;
  2142. // OPTS UTF-8 <NLST>
  2143. LCmd := FOPTSCommands.Add;
  2144. LCmd.Command := 'UTF-8'; {Do not localize}
  2145. LCmd.ExceptionReply.NumericCode := 501;
  2146. LCmd.NormalReply.NumericCode := 200;
  2147. LCmd.OnCommand := CommandOptsUTF8;
  2148. // OPTS UTF8 <ON|OFF>
  2149. LCmd := FOPTSCommands.Add;
  2150. LCmd.Command := 'UTF8'; {Do not localize}
  2151. LCmd.ExceptionReply.NumericCode := 501;
  2152. LCmd.NormalReply.NumericCode := 200;
  2153. LCmd.OnCommand := CommandOptsUTF8;
  2154. //XAUT <SP> <xor encrypted data> <CRLF>
  2155. LCmd := CommandHandlers.Add;
  2156. LCmd.Command := 'XAUT'; {Do not Localize}
  2157. LCmd.OnCommand := CommandXAUT;
  2158. LCmd.Description.Text := 'Syntax: XAUT <sp> 2 <sp> <encrypted username and password>'; {do not localize}
  2159. end;
  2160. procedure TIdFTPServer.ContextCreated(AContext: TIdContext);
  2161. var
  2162. LContext : TIdFTPServerContext;
  2163. begin
  2164. LContext := AContext as TIdFTPServerContext;
  2165. // TODO: TIdFTPServerContext.Server is separate from TIdServerContext.Server.
  2166. // TIdFTPServerContext.Server should be removed and TIdFTPServerContext
  2167. // should be updated to return TIdServerContext.Server casted to TIdFTPServer...
  2168. LContext.Server := Self;
  2169. //from Before run method
  2170. LContext.FDataPort := 0;
  2171. LContext.FPasswordAttempts := 0;
  2172. LContext.FDataPortDenied := False;
  2173. LContext.FUserSecurity.Assign(FTPSecurityOptions);
  2174. if (DirFormat = ftpdfOSDependent) and (GOSType = otWindows) then begin
  2175. LContext.MSDOSMode := True;
  2176. end;
  2177. //
  2178. if mlsdUnixModes in FMLSDFacts then begin
  2179. LContext.MLSOpts := LContext.MLSOpts + [UnixMODE];
  2180. end;
  2181. if mlsdUnixOwner in FMLSDFacts then begin
  2182. LContext.MLSOpts := LContext.MLSOpts + [UnixOwner];
  2183. end;
  2184. if mlsdUnixGroup in FMLSDFacts then begin
  2185. LContext.MLSOpts := LContext.MLSOpts + [UnixGroup];
  2186. end;
  2187. if mlsdFileCreationTime in FMLSDFacts then begin
  2188. LContext.MLSOpts := LContext.MLSOpts + [CreateTime];
  2189. end;
  2190. if mlsdPerms in FMLSDFacts then begin
  2191. LContext.MLSOpts := LContext.MLSOpts + [Perm];
  2192. end;
  2193. if mlsdUniqueID in FMLSDFacts then begin
  2194. LContext.MLSOpts := LContext.MLSOpts + [Unique];
  2195. end;
  2196. if mlsdFileLastAccessTime in FMLSDFacts then begin
  2197. LContext.MLSOpts := LContext.MLSOpts + [LastAccessTime];
  2198. end;
  2199. if mlsdWin32Attributes in FMLSDFacts then begin
  2200. LContext.MLSOpts := LContext.MLSOpts + [WinAttribs];
  2201. end;
  2202. if mlsdWin32DriveType in FMLSDFacts then begin
  2203. LContext.MLSOpts := LContext.MLSOpts + [WinDriveType];
  2204. end;
  2205. if mlstWin32DriveLabel in FMLSDFacts then begin
  2206. LContext.MLSOpts := LContext.MLSOpts + [WinDriveLabel];
  2207. end;
  2208. //MS-DOS mode on for MS-DOS
  2209. if FDirFormat = ftpdfDOS then begin
  2210. LContext.FMSDOSMode := True;
  2211. end;
  2212. inherited ContextCreated(AContext);
  2213. end;
  2214. destructor TIdFTPServer.Destroy;
  2215. begin
  2216. FreeAndNil(FAnonymousAccounts);
  2217. FreeAndNil(FFTPSecurityOptions);
  2218. FreeAndNil(FServerInfo);
  2219. FreeAndNil(FOPTSCommands);
  2220. FreeAndNil(FDataChannelCommands);
  2221. FreeAndNil(FSITECommands);
  2222. FreeAndNil(FReplyUnknownSITECommand);
  2223. inherited Destroy;
  2224. end;
  2225. procedure TIdFTPServer.ListDirectory(ASender: TIdFTPServerContext; ADirectory: string;
  2226. ADirContents: TStrings; ADetails: Boolean; const ACmd : String = 'LIST';
  2227. const ASwitches : String = ''); {do not localize}
  2228. var
  2229. LDirectoryList: TIdFTPListOutput;
  2230. LPathSep: string;
  2231. LIsMLST: Boolean;
  2232. // under ARC, convert a weak reference to a strong reference before working with it
  2233. LFileSystem: TIdFTPBaseFilesystem;
  2234. begin
  2235. LIsMLST := PosInStrArray(ACmd, ['MLSD', 'MLST']) <> -1; {do not localize}
  2236. if (FDirFormat = ftpdfCustom) and (not LIsMLST) then begin
  2237. DoOnCustomListDirectory(ASender, ADirectory, ADirContents, ACmd, ASwitches);
  2238. Exit;
  2239. end;
  2240. LFileSystem := FFTPFileSystem;
  2241. if Assigned(FOnListDirectory) or Assigned(LFileSystem) then begin
  2242. LDirectoryList := TIdFTPListOutput.Create;
  2243. try
  2244. case FDirFormat of
  2245. ftpdfEPLF :
  2246. LDirectoryList.DirFormat := doEPLF;
  2247. ftpdfDOS :
  2248. if ASender.FMSDOSMode then begin
  2249. LDirectoryList.DirFormat := DoWin32;
  2250. end else begin
  2251. LDirectoryList.DirFormat := DoUnix;
  2252. end;
  2253. ftpdfOSDependent :
  2254. if (GOSType = otWindows) and (ASender.FMSDOSMode) then begin
  2255. LDirectoryList.DirFormat := DoWin32;
  2256. end else begin
  2257. LDirectoryList.DirFormat := DoUnix;
  2258. end;
  2259. else
  2260. LDirectoryList.DirFormat := DoUnix;
  2261. end;
  2262. //someone might be using the STAT -l to get a dir without a data channel
  2263. if IndyPos('l', ASwitches) > 0 then begin
  2264. LDirectoryList.Switches := LDirectoryList.Switches + 'l';
  2265. end;
  2266. //we do things this way because the 'a' and 'T' swithces only make sense
  2267. //when listing Unix dirs.
  2268. if SupportTaDirSwitches(ASender) then begin
  2269. if IndyPos('a', ASwitches) > 0 then begin
  2270. LDirectoryList.Switches := LDirectoryList.Switches + 'a';
  2271. end;
  2272. if IndyPos('T', ASwitches) > 0 then begin
  2273. LDirectoryList.Switches := LDirectoryList.Switches + 'T';
  2274. end;
  2275. end;
  2276. LDirectoryList.ExportTotalLine := True;
  2277. // TODO: use FTPPathSeparator here?
  2278. LPathSep := '/'; {Do not Localize}
  2279. if not TextEndsWith(ADirectory, LPathSep) then begin
  2280. ADirectory := ADirectory + LPathSep;
  2281. end;
  2282. if Assigned(LFileSystem) then begin
  2283. LFileSystem.ListDirectory(ASender, ADirectory, LDirectoryList, ACmd, ASwitches);
  2284. end else begin
  2285. FOnListDirectory(ASender, ADirectory, LDirectoryList, ACmd, ASwitches); // Event
  2286. end;
  2287. if LIsMLST then begin {Do not translate}
  2288. LDirectoryList.MLISTOutputDir(ADirContents, ASender.MLSOpts);
  2289. end
  2290. else if ADetails then begin
  2291. LDirectoryList.LISTOutputDir(ADirContents);
  2292. end else begin
  2293. LDirectoryList.NLISTOutputDir(ADirContents);
  2294. end;
  2295. finally
  2296. FreeAndNil(LDirectoryList);
  2297. end;
  2298. end else begin
  2299. raise EIdFTPServerNoOnListDirectory.Create(RSFTPNoOnDirEvent); {Do not Localize}
  2300. end;
  2301. end;
  2302. procedure TIdFTPServer.SetUserAccounts(const AValue: TIdCustomUserManager);
  2303. var
  2304. // under ARC, convert a weak reference to a strong reference before working with it
  2305. LUserAccounts: TIdCustomUserManager;
  2306. begin
  2307. LUserAccounts := FUserAccounts;
  2308. if LUserAccounts <> AValue then begin
  2309. // under ARC, all weak references to a freed object get nil'ed automatically
  2310. {$IFNDEF USE_OBJECT_ARC}
  2311. if Assigned(LUserAccounts) then begin
  2312. LUserAccounts.RemoveFreeNotification(Self);
  2313. end;
  2314. {$ENDIF}
  2315. FUserAccounts := AValue;
  2316. if Assigned(AValue) then begin
  2317. {$IFNDEF USE_OBJECT_ARC}
  2318. AValue.FreeNotification(Self);
  2319. {$ENDIF}
  2320. FOnUserAccount := nil;
  2321. //XAUT can not work with an account manager that sends
  2322. //a challange because that command is a USER/PASS rolled into
  2323. //one command.
  2324. if AValue.SendsChallange then begin
  2325. FSupportXAUTH := False;
  2326. end;
  2327. end;
  2328. end;
  2329. end;
  2330. procedure TIdFTPServer.SetFTPFileSystem(const AValue: TIdFTPBaseFileSystem);
  2331. begin
  2332. {$IFDEF USE_OBJECT_ARC}
  2333. // under ARC, all weak references to a freed object get nil'ed automatically
  2334. FFTPFileSystem := AValue;
  2335. {$ELSE}
  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. {$ENDIF}
  2346. end;
  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. {$IFNDEF USE_OBJECT_ARC}
  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 Length(ASender.UnparsedParams) > 0 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 := Length(LContext.Password ) > 0;
  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. TIdStreamHelper.Seek(LStream, 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 Length(ASender.UnparsedParams) > 0 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. FreeAndNil(LM);
  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 : TIdStreamSize;
  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. FreeAndNil(LOutStream);
  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. FreeAndNil(LM);
  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. FreeAndNil(LCmdQueue);
  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. FreeAndNil(LStream);
  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. FreeAndNil(LStream);
  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 Length(LParm) = 0 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 Length(LIP) = 0 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 Length(LParam) > 0 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. FreeAndNil(LFileParts);
  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. FreeAndNil(LGreeting);
  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. FreeAndNil(LGreeting);
  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. FreeAndNil(LDir);
  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. FreeAndNil(LStream);
  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. FreeAndNil(LFacts);
  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. FreeAndNil(LCalcStream);
  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. FreeAndNil(LFiles);
  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. 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. ASender.Reply.SetReply(200, IndyFormat(RSFTPUMaskSet, [LNewMask, LContext.FUMask]));
  5310. LContext.FUMask := LNewMask;
  5311. end else begin
  5312. ASender.Reply.SetReply(553, RSFTPPermissionDenied);
  5313. end;
  5314. end else begin
  5315. CmdNotImplemented(ASender);
  5316. end;
  5317. end else begin
  5318. ASender.Reply.SetReply(200, IndyFormat(RSFTPUMaskIs, [LContext.FUMask]));
  5319. end;
  5320. end else begin
  5321. CmdNotImplemented(ASender);
  5322. end;
  5323. end;
  5324. end;
  5325. function TIdFTPServer.IsValidPermNumbers(const APermNos: String): Boolean;
  5326. const
  5327. PERMDIGITS = '01234567';
  5328. var
  5329. i: Integer;
  5330. begin
  5331. Result := False;
  5332. for i := 1 to Length(APermNos) do begin
  5333. if not CharIsInSet(APermNos, i, PERMDIGITS) then begin
  5334. Exit;
  5335. end;
  5336. end;
  5337. Result := True;
  5338. end;
  5339. procedure TIdFTPServer.DoOnSiteUMASK(ASender: TIdFTPServerContext;
  5340. var VUMASK: Integer; var VAUth: Boolean);
  5341. begin
  5342. if Assigned(FOnSiteUMASK) then begin
  5343. FOnSiteUMASK(ASender,VUMASK,VAUth);
  5344. end;
  5345. end;
  5346. procedure TIdFTPServer.DoOnSetATTRIB(ASender: TIdFTPServerContext; var VAttr : UInt32; const AFileName : String; var VAUth : Boolean);
  5347. begin
  5348. if Assigned( FOnSetATTRIB) then begin
  5349. FOnSetATTRIB(ASender, VAttr, AFileName, VAUth);
  5350. end;
  5351. end;
  5352. procedure TIdFTPServer.DoOnSiteCHMOD(ASender: TIdFTPServerContext;
  5353. var APermissions: Integer; const AFileName: String; var VAUth: Boolean);
  5354. begin
  5355. if Assigned(FOnSiteCHMOD) then begin
  5356. FOnSiteCHMOD(ASender,APermissions,AFileName,VAUth);
  5357. end;
  5358. end;
  5359. procedure TIdFTPServer.CommandSiteDIRSTYLE(ASender: TIdCommand);
  5360. //FMSDOSMode
  5361. var
  5362. LContext : TIdFTPServerContext;
  5363. //SITE DIRSTYLE is only for MS-DOS formatted directory lists so
  5364. //a program can flip to Unix directory list format. This is
  5365. //for compatability purposes, ONLY.
  5366. begin
  5367. LContext := ASender.Context as TIdFTPServerContext;
  5368. if (FDirFormat = ftpdfDOS) or
  5369. ((FDirFormat = ftpdfOSDependent) and (GOSType = otWindows)) then begin
  5370. if LContext.IsAuthenticated(ASender) then begin
  5371. if ASender.Params.Count = 0 then begin
  5372. LContext.FMSDOSMode := not LContext.FMSDOSMode;
  5373. if LContext.FMSDOSMode then begin
  5374. ASender.Reply.SetReply(200, IndyFormat(RSFTPDirStyle, [RSFTPOn]));
  5375. end else begin
  5376. ASender.Reply.SetReply(200, IndyFormat(RSFTPDirStyle, [RSFTPOff]));
  5377. end;
  5378. end;
  5379. end;
  5380. end else begin
  5381. ASender.Reply.Assign(FReplyUnknownSITECommand);
  5382. end;
  5383. end;
  5384. procedure TIdFTPServer.CommandSiteHELP(ASender: TIdCommand);
  5385. var
  5386. s : String;
  5387. LCmds : TStringList;
  5388. LContext : TIdFTPServerContext;
  5389. begin
  5390. LContext := ASender.Context as TIdFTPServerContext;
  5391. if LContext.IsAuthenticated(ASender) then begin
  5392. s := RSFTPSITECmdsSupported+EOL;
  5393. LCmds := TStringList.Create;
  5394. try
  5395. if Assigned(OnSetAttrib) then begin
  5396. LCmds.Add('ATTRIB'); {Do not translate}
  5397. end;
  5398. if Assigned(OnSiteCHMOD) then begin
  5399. LCmds.Add('CHMOD'); {Do not translate}
  5400. end;
  5401. if (FDirFormat = ftpdfDOS) or
  5402. ((FDirFormat = ftpdfOSDependent) and (GOSType = otWindows)) then begin
  5403. LCmds.Add('DIRSTYLE'); {Do not translate}
  5404. end;
  5405. if Assigned(OnSiteUMASK) then begin
  5406. LCmds.Add('UMASK'); {Do not translate}
  5407. end;
  5408. LCmds.Add('ZONE'); {Do not translate}
  5409. s := s + HelpText(LCmds) + FEndOfHelpLine;
  5410. ASender.Reply.SetReply(214, s);
  5411. finally
  5412. FreeAndNil(LCmds);
  5413. end;
  5414. end;
  5415. end;
  5416. function TIdFTPServer.HelpText(Cmds: TStrings): String;
  5417. var
  5418. LRows : Integer;
  5419. LMod : Integer;
  5420. i : Integer;
  5421. begin
  5422. Result := '';
  5423. if Cmds.Count =0 then begin
  5424. Exit;
  5425. end;
  5426. LRows := Cmds.Count div 6;
  5427. LMod := Cmds.Count mod 6;
  5428. if Cmds.Count < 6 then begin
  5429. Result := ' ';
  5430. for i := 0 to Cmds.Count -1 do begin
  5431. Result := Result + IndyFormat('%-10s', [Cmds[i]]);
  5432. end;
  5433. Result := Result + CR;
  5434. end else begin
  5435. for i := 0 to (LRows -1) do begin
  5436. if (i <= LMod-1) and (LMod<>0) then begin
  5437. Result := Result + IndyFormat(' %-10s%-10s%-10s%-10s%-10s%-10s%-10s', {Do not translate}
  5438. [ Cmds[i],Cmds[i+LRows],Cmds[(LRows*2)+i],
  5439. Cmds[(LRows*3)+i],Cmds[(LRows*4)+i],Cmds[(LRows*5)+i],
  5440. Cmds[(LRows*6)+i]])+CR;
  5441. end else begin
  5442. Result := Result + IndyFormat(' %-10s%-10s%-10s%-10s%-10s%-10s', {Do not translate}
  5443. [ Cmds[i],Cmds[i+LRows],Cmds[(LRows*2)+i],
  5444. Cmds[(LRows*3)+i],Cmds[(LRows*4)+i],Cmds[(LRows*5)+i]])+CR;
  5445. end;
  5446. end;
  5447. end;
  5448. end;
  5449. procedure TIdFTPServer.CommandSITE(ASender: TIdCommand);
  5450. var
  5451. LCmd : String;
  5452. begin
  5453. LCmd := ASender.UnparsedParams;
  5454. ASender.Reply.Clear;
  5455. ASender.PerformReply := False;
  5456. if not FSITECommands.HandleCommand(ASender.Context, LCmd) then begin
  5457. ASender.Reply.NumericCode := 500;
  5458. CmdSyntaxError(ASender.Context, ASender.CommandHandler.Command + ' ' + LCmd, ASender.Reply);
  5459. ASender.PerformReply := False;
  5460. end;
  5461. end;
  5462. function TIdFTPServer.MLSFEATLine(const AFactMask: TIdMLSDAttrs;
  5463. const AFacts: TIdFTPFactOutputs): String;
  5464. begin
  5465. Result := 'MLST size'; {Do not translate}
  5466. //the * indicates if the option is selected for MLST
  5467. if Size in AFacts then begin {Do not translate}
  5468. Result := Result + '*;';
  5469. end else begin
  5470. Result := Result + ';'
  5471. end;
  5472. Result := Result + 'Type'; {Do not translate}
  5473. if ItemType in AFacts then begin {Do not translate}
  5474. Result := Result + '*;'; {Do not translate}
  5475. end else begin
  5476. Result := Result + ';';
  5477. end;
  5478. if mlsdPerms in FMLSDFacts then begin
  5479. Result := Result + 'Perm'; {Do not translate}
  5480. if Perm in AFacts then begin {Do not translate}
  5481. Result := Result + '*;'; {Do not translate}
  5482. end else begin
  5483. Result := Result + ';';
  5484. end;
  5485. end;
  5486. if mlsdFileCreationTime in FMLSDFacts then begin
  5487. Result := Result + 'Create'; {Do not translate}
  5488. if CreateTime in AFacts then begin {Do not translate}
  5489. Result := Result + '*;'; {Do not translate}
  5490. end else begin
  5491. Result := Result + ';';
  5492. end;
  5493. end;
  5494. Result := Result + 'Modify'; {Do not translate}
  5495. if Modify in AFacts then begin
  5496. Result := Result + '*;';
  5497. end else begin
  5498. Result := Result + ';';
  5499. end;
  5500. if mlsdUnixModes in FMLSDFacts then begin
  5501. Result := Result + 'UNIX.mode'; {Do not translate}
  5502. if UnixMODE in AFacts then begin {Do not translate}
  5503. Result := Result + '*;'; {Do not translate}
  5504. end else begin
  5505. Result := Result + ';';
  5506. end;
  5507. end;
  5508. if mlsdUnixOwner in FMLSDFacts then
  5509. begin
  5510. Result := Result + 'UNIX.owner'; {Do not translate}
  5511. if UnixOwner in AFacts then begin {Do not translate}
  5512. Result := Result + '*;'; {Do not translate}
  5513. end else begin
  5514. Result := Result + ';';
  5515. end;
  5516. end;
  5517. if mlsdUnixGroup in FMLSDFacts then begin
  5518. Result := Result + 'UNIX.group'; {Do not translate}
  5519. if UnixGroup in AFacts then begin {Do not translate}
  5520. Result := Result + '*;'; {Do not translate}
  5521. end else begin
  5522. Result := Result + ';';
  5523. end;
  5524. end;
  5525. if mlsdUniqueID in FMLSDFacts then begin
  5526. Result := Result + 'Unique'; {Do not translate}
  5527. if Unique in AFacts then begin {Do not translate}
  5528. Result := Result + '*;'; {Do not translate}
  5529. end else begin
  5530. Result := Result + ';';
  5531. end;
  5532. end;
  5533. if mlsdFileLastAccessTime in FMLSDFacts then begin
  5534. Result := Result + 'Windows.lastaccesstime'; {Do not translate}
  5535. if CreateTime in AFacts then begin {Do not translate}
  5536. Result := Result + '*;'; {Do not translate}
  5537. end else begin
  5538. Result := Result + ';';
  5539. end;
  5540. end;
  5541. if mlsdWin32Attributes in FMLSDFacts then begin
  5542. Result := Result + 'Win32.ea'; {Do not translate}
  5543. if WinAttribs in AFacts then begin {Do not translate}
  5544. Result := Result + '*;'; {Do not translate}
  5545. end else begin
  5546. Result := Result + ';';
  5547. end;
  5548. end;
  5549. if mlsdWin32DriveType in FMLSDFacts then begin
  5550. Result := Result + 'Win32.dt';
  5551. if WinDriveType in AFacts then begin
  5552. Result := Result + '*;'; {Do not localize}
  5553. end else begin
  5554. Result := Result + ';'; {Do not localize}
  5555. end;
  5556. end;
  5557. if mlstWin32DriveLabel in FMLSDFacts then begin
  5558. Result := Result + 'Win32.dl';
  5559. if WinDriveLabel in AFacts then begin
  5560. Result := Result + '*;'; {Do not localize}
  5561. end else begin
  5562. Result := Result + ';'; {Do not localize}
  5563. end;
  5564. end;
  5565. if Length(Result) > 0 then begin
  5566. SetLength(Result, Length(Result) - 1);
  5567. end;
  5568. end;
  5569. procedure TIdFTPServer.CommandCLNT(ASender: TIdCommand);
  5570. var
  5571. LClientInfo : TIdFTPClientIdentifier;
  5572. LContext: TIdFTPServerContext;
  5573. begin
  5574. LContext := ASender.Context as TIdFTPServerContext;
  5575. // TODO: store the client's info in LContext?
  5576. if Length(ASender.UnparsedParams) > 0 then begin
  5577. if Assigned(FOnClientID) then begin
  5578. FOnClientID(LContext, ASender.UnparsedParams);
  5579. end;
  5580. if Assigned(FOnClientIDEx) then begin
  5581. LClientInfo := TIdFTPClientIdentifier.Create;
  5582. try
  5583. LClientInfo.CLNTParams := ASender.UnparsedParams;
  5584. FOnClientIDEx(LContext, LClientInfo);
  5585. finally
  5586. LClientInfo.Free;
  5587. end;
  5588. end;
  5589. end else begin
  5590. CmdInvalidParams(ASender);
  5591. end;
  5592. end;
  5593. procedure TIdFTPServer.CommandCSID(ASender: TIdCommand);
  5594. var
  5595. LContext : TIdFTPServerContext;
  5596. LClientInfo : TIdFTPClientIdentifier;
  5597. LServerInfo: TIdFTPServerIdentifier;
  5598. begin
  5599. LContext := ASender.Context as TIdFTPServerContext;
  5600. if LContext.IsAuthenticated(ASender) then begin
  5601. // TODO: store the client's info in LContext?
  5602. if Assigned(FOnClientID) or Assigned(FOnClientIDEx) then begin
  5603. LClientInfo := TIdFTPClientIdentifier.Create;
  5604. try
  5605. LClientInfo.CSIDParams := ASender.UnparsedParams;
  5606. if (Length(LClientInfo.ClientName) = 0) or
  5607. (Length(LClientInfo.ClientVersion) = 0) then
  5608. begin
  5609. CmdInvalidParams(ASender);
  5610. Exit;
  5611. end;
  5612. if Assigned(FOnClientID) then begin
  5613. FOnClientID(LContext, LClientInfo.CLNTParams);
  5614. end;
  5615. if Assigned(FOnClientIDEx) then begin
  5616. FOnClientIDEx(LContext, LClientInfo);
  5617. end;
  5618. finally
  5619. LClientInfo.Free;
  5620. end;
  5621. end;
  5622. if FPathProcessing <> ftppCustom then begin
  5623. LServerInfo := TIdFTPServerIdentifier.Create;
  5624. try
  5625. LServerInfo.Assign(FServerInfo);
  5626. LServerInfo.CaseSensitive := FTPIsCaseSensitive;
  5627. LServerInfo.DirSeparator := FTPPathSeparator;
  5628. ASender.Reply.SetReply(200, LServerInfo.CSIDParams);
  5629. finally
  5630. LServerInfo.Free;
  5631. end;
  5632. end else begin
  5633. ASender.Reply.SetReply(200, FServerInfo.CSIDParams);
  5634. end;
  5635. end;
  5636. end;
  5637. procedure TIdFTPServer.SetPASVBoundPortMax(const AValue: TIdPort);
  5638. begin
  5639. if FPASVBoundPortMin <> 0 then begin
  5640. if AValue <= FPASVBoundPortMin then begin
  5641. raise EIdFTPBoundPortMaxGreater.Create(RSFTPPASVBoundPortMaxMustBeGreater);
  5642. end;
  5643. end;
  5644. FPASVBoundPortMax := AValue;
  5645. end;
  5646. procedure TIdFTPServer.SetPASVBoundPortMin(const AValue: TIdPort);
  5647. begin
  5648. if FPASVBoundPortMax <> 0 then begin
  5649. if FPASVBoundPortMax <= AValue then begin
  5650. raise EIdFTPBoundPortMinLess.Create(RSFTPPASVBoundPortMinMustBeLess);
  5651. end;
  5652. end;
  5653. FPASVBoundPortMin := AValue;
  5654. end;
  5655. procedure TIdFTPServer.DoOnDataPortAfterBind(ASender: TIdFTPServerContext);
  5656. begin
  5657. if Assigned(FOnDataPortAfterBind) then begin
  5658. FOnDataPortAfterBind(ASender);
  5659. end;
  5660. end;
  5661. procedure TIdFTPServer.DoOnDataPortBeforeBind(ASender: TIdFTPServerContext);
  5662. begin
  5663. if Assigned(FOnDataPortBeforeBind) then begin
  5664. FOnDataPortBeforeBind(ASender);
  5665. end;
  5666. end;
  5667. function TIdFTPServer.FTPPathSeparator : Char;
  5668. begin
  5669. case FPathProcessing of
  5670. ftppDOS: Result := '\'; {do not localize}
  5671. ftpOSDependent:
  5672. begin
  5673. if (GOSType = otWindows) then begin
  5674. Result := '\'; {do not localize}
  5675. end else begin
  5676. Result := '/'; {do not localize}
  5677. end;
  5678. end;
  5679. ftppUnix: Result := '/'; {do not localize}
  5680. ftppCustom: Result := FServerInfo.DirSeparator;
  5681. else
  5682. Result := '/'; {do not localize}
  5683. end;
  5684. end;
  5685. function TIdFTPServer.FTPIsCaseSensitive: Boolean;
  5686. begin
  5687. case FPathProcessing of
  5688. ftppDOS : Result := False;
  5689. ftpOSDependent : Result := (GOSType <> otWindows);
  5690. ftppCustom : Result := FServerInfo.CaseSensitive;
  5691. else
  5692. Result := True;
  5693. end;
  5694. end;
  5695. function TIdFTPServer.FTPNormalizePath(const APath: String): String;
  5696. {
  5697. Microsoft IIS accepts both a "/" and a "\" as path/file name separators.
  5698. We have to flatten this out so that our FTP server can behave like Microsoft IIS.
  5699. In Unix, a "\" is a valid filename character so we don't anything there.
  5700. This WILL cause a "\" to be treated differently in Unix and Win32. I submit that
  5701. this is really desirable as both file systems are like apples and oranges.
  5702. }
  5703. begin
  5704. case FPathProcessing of
  5705. ftppDOS : Result := ReplaceAll(APath, '\', '/');
  5706. ftpOSDependent :
  5707. begin
  5708. if GOSType = otWindows then begin
  5709. Result := ReplaceAll(APath, '\', '/');
  5710. end else begin
  5711. Result := APath;
  5712. end;
  5713. end;
  5714. else
  5715. Result := APath;
  5716. end;
  5717. end;
  5718. function TIdFTPServer.DoProcessPath(ASender: TIdFTPServerContext; const APath: TIdFTPFileName): TIdFTPFileName;
  5719. begin
  5720. if FPathProcessing <> ftppCustom then begin
  5721. Result := FTPNormalizePath(APath);
  5722. Result := ProcessPath(ASender.CurrentDir, Result); {Do not Localize}
  5723. end else begin
  5724. Result := APath;
  5725. if Assigned(FOnCustomPathProcess) then begin
  5726. FOnCustomPathProcess(ASender, Result);
  5727. end;
  5728. end;
  5729. end;
  5730. function TIdFTPServer.CDUPDir(AContext : TIdFTPServerContext) : String;
  5731. const
  5732. LCDUP_DOS = '..\';
  5733. CDUP_UNIX = '../';
  5734. begin
  5735. case FPathProcessing of
  5736. ftppDOS : Result := LCDUP_DOS;
  5737. ftpOSDependent :
  5738. if GOSType = otWindows then begin
  5739. Result := LCDUP_DOS;
  5740. end else begin
  5741. Result := CDUP_UNIX;
  5742. end;
  5743. ftppCustom : Result := DoProcessPath(AContext, '..');
  5744. else
  5745. Result := CDUP_UNIX;
  5746. end;
  5747. end;
  5748. function TIdFTPServer.DoSysType(ASender: TIdFTPServerContext): String;
  5749. begin
  5750. //We tie the SYST descriptor to the directory style for compatability
  5751. //reasons. Some FTP clients use the SYST descriptor to determine what
  5752. //type of FTP directory list parsing to do. I think TurboPower IPros does this.
  5753. //Note that I personally do not find this to be sound as a general rule.
  5754. case FDirFormat of
  5755. ftpdfOSDependent :
  5756. begin
  5757. if GOSType = otWindows then begin
  5758. Result := SYST_ID_NT;
  5759. end else begin
  5760. Result := SYST_ID_UNIX;
  5761. end;
  5762. end;
  5763. ftpdfUnix, ftpdfEPLF : Result := SYST_ID_UNIX;
  5764. ftpdfDOS : Result := SYST_ID_NT;
  5765. ftpdfCustom : Result := FCustomSystID;
  5766. end;
  5767. end;
  5768. procedure TIdFTPServer.DoOnCustomListDirectory(
  5769. ASender: TIdFTPServerContext; const APath: string;
  5770. ADirectoryListing: TStrings; const ACmd, ASwitches: String);
  5771. begin
  5772. if Assigned(OnCustomListDirectory) then begin
  5773. OnCustomListDirectory(ASender,APath,ADirectoryListing,ACmd,ASwitches);
  5774. end;
  5775. end;
  5776. procedure TIdFTPServer.CmdNotImplemented(ASender: TIdCommand);
  5777. begin
  5778. ASender.Reply.SetReply(550, IndyFormat(RSFTPCmdNotImplemented, [ASender.CommandHandler.Command ]));
  5779. end;
  5780. procedure TIdFTPServer.CmdFileActionAborted(ASender: TIdCommand);
  5781. begin
  5782. ASender.Reply.SetReply(550, RSFTPFileActionAborted);
  5783. end;
  5784. //This is for where the client didn't provide a valid number of parameters for a command
  5785. procedure TIdFTPServer.CmdInvalidParamNum(ASender: TIdCommand);
  5786. begin
  5787. ASender.Reply.SetReply(501, IndyFormat(RSFTPInvalidNumberArgs, [ASender.CommandHandler.Command]));
  5788. end;
  5789. //This is for other command syntax issues.
  5790. procedure TIdFTPServer.CmdInvalidParams(ASender: TIdCommand);
  5791. begin
  5792. ASender.Reply.SetReply(501, IndyFormat(RSFTPParamError, [ASender.CommandHandler.Command]));
  5793. end;
  5794. procedure TIdFTPServer.CmdTwineFileActionAborted(ASender: TIdCommand);
  5795. begin
  5796. ASender.Reply.SetReply(504, RSFTPFileActionAborted);
  5797. end;
  5798. procedure TIdFTPServer.CmdCommandSuccessful(ASender: TIdCOmmand; const AReplyCode : Integer = 250);
  5799. begin
  5800. ASender.Reply.SetReply(AReplyCode, IndyFormat(RSFTPCmdSuccessful, [ASender.CommandHandler.Command]));
  5801. end;
  5802. procedure TIdFTPServer.CommandSSCN(ASender: TIdCommand);
  5803. const
  5804. REPLY_SSCN_ON = 'SSCN:CLIENT METHOD'; {do not localize}
  5805. REPLY_SSCN_OFF = 'SSCN:SERVER METHOD'; {do not localize}
  5806. var
  5807. LContext : TIdFTPServerContext;
  5808. begin
  5809. if UseTLS = utNoTLSSupport then begin
  5810. CmdNotImplemented(ASender);
  5811. Exit;
  5812. end;
  5813. LContext := ASender.Context as TIdFTPServerContext;
  5814. if LContext.IsAuthenticated(ASender) then begin
  5815. if ASender.Params.Count = 0 then begin
  5816. //check state
  5817. if LContext.SSCNOn then begin
  5818. ASender.Reply.SetReply(200, REPLY_SSCN_ON);
  5819. end else begin
  5820. ASender.Reply.SetReply(200, REPLY_SSCN_OFF);
  5821. end;
  5822. end else begin
  5823. //set state
  5824. case PosInStrArray(ASender.Params[0], OnOffStates, False) of
  5825. 0 : //'ON'
  5826. begin
  5827. LContext.SSCNOn := True;
  5828. ASender.Reply.SetReply(200, REPLY_SSCN_ON);
  5829. end;
  5830. 1 : //'OFF'
  5831. begin
  5832. LContext.SSCNOn := False;
  5833. ASender.Reply.SetReply(200, REPLY_SSCN_OFF);
  5834. end;
  5835. else
  5836. ASender.Reply.SetReply(504, RSFTPInvalidForParam);
  5837. end;
  5838. end;
  5839. end;
  5840. end;
  5841. procedure TIdFTPServer.CommandCPSV(ASender: TIdCommand);
  5842. var
  5843. LContext : TIdFTPServerContext;
  5844. LIO : TIdSSLIOHandlerSocketBase;
  5845. begin
  5846. //CPSV must be used with SSL and can only be used with IPv4
  5847. if (UseTLS = utNoTLSSupport) or
  5848. (ASender.Context.Binding.IPVersion <> Id_IPv4) then begin
  5849. CmdSyntaxError(ASender);
  5850. Exit;
  5851. end;
  5852. CommandPASV(ASender);
  5853. LContext := TIdFTPServerContext(ASender.Context);
  5854. LIO := LContext.DataChannel.FDataChannel.IOHandler as TIdSSLIOHandlerSocketBase;
  5855. //tell IOHandler to use ssl_Conntect
  5856. LIO.IsPeer := False;
  5857. end;
  5858. procedure TIdFTPServer.CommandSiteZONE(ASender: TIdCommand);
  5859. var
  5860. LMin : Integer;
  5861. LFmt: string;
  5862. begin
  5863. LMin := MinutesFromGMT;
  5864. //plus must always be displayed for positive numbers
  5865. if LMin < 0 then begin
  5866. LFmt := 'UTC%d'; {do not localize}
  5867. end else begin
  5868. LFmt := 'UTC+%d'; {do not localize}
  5869. end;
  5870. ASender.Reply.SetReply(210, IndyFormat(LFmt, [LMin]));
  5871. end;
  5872. procedure TIdFTPServer.CommandCheckSum(ASender: TIdCommand);
  5873. const
  5874. HashTypes: array[0..4] of TIdHashClass = (TIdHashCRC32, TIdHashMessageDigest5, TIdHashSHA1, TIdHashSHA256, TIdHashSHA512);
  5875. var
  5876. LCalcStream : TStream;
  5877. LFileName, LCheckSum, LBuf : String;
  5878. LBeginPos, LEndPos : TIdStreamSize;
  5879. LContext : TIdFTPServerContext;
  5880. LHashIdx: Integer;
  5881. // under ARC, convert a weak reference to a strong reference before working with it
  5882. LFileSystem: TIdFTPBaseFileSystem;
  5883. begin
  5884. if GetFIPSMode and
  5885. (PosInStrArray(ASender.CommandHandler.Command, ['XCRC', 'XMD5']) > -1) then begin
  5886. CmdSyntaxError(ASender);
  5887. Exit;
  5888. end;
  5889. LFileSystem := FTPFileSystem;
  5890. if Assigned(FOnCRCFile) or Assigned(LFileSystem) then begin
  5891. LContext := TIdFTPServerContext(ASender.Context);
  5892. if LContext.IsAuthenticated(ASender) then begin
  5893. LBuf := ASender.UnparsedParams;
  5894. if Pos('"', LBuf) > 0 then begin {do not localize}
  5895. Fetch(LBuf, '"'); {do not localize}
  5896. LFileName := Fetch(LBuf, '"'); {do not localize}
  5897. end else begin
  5898. LFileName := Fetch(LBuf);
  5899. end;
  5900. if LFileName = '' then begin
  5901. CmdInvalidParamNum(ASender);
  5902. Exit;
  5903. end;
  5904. LBuf := Trim(LBuf);
  5905. if LBuf <> '' then begin
  5906. LBeginPos := IndyStrToStreamSize(Fetch(LBuf), -1);
  5907. if LBeginPos < 0 then begin
  5908. CmdInvalidParams(ASender);
  5909. Exit;
  5910. end;
  5911. LBuf := Trim(LBuf);
  5912. if LBuf <> '' then begin
  5913. LEndPos := IndyStrToStreamSize(Fetch(LBuf), -1);
  5914. if LEndPos < 0 then begin
  5915. CmdInvalidParams(ASender);
  5916. Exit;
  5917. end;
  5918. end else begin
  5919. LEndPos := -1;
  5920. end;
  5921. end else begin
  5922. LBeginPos := 0;
  5923. LEndPos := -1;
  5924. end;
  5925. LCalcStream := nil;
  5926. LFileName := DoProcessPath(LContext, LFileName);
  5927. DoOnCRCFile(LContext, LFileName, LCalcStream);
  5928. if Assigned(LCalcStream) then begin
  5929. if LEndPos = -1 then begin
  5930. LEndPos := LCalcStream.Size;
  5931. end;
  5932. try
  5933. LCalcStream.Position := 0;
  5934. LHashIdx := PosInStrArray(ASender.CommandHandler.Command, ['XCRC', 'XMD5', 'XSHA1','XSHA256','XSHA512'], False); {do not localize}
  5935. LCheckSum := CalculateCheckSum(HashTypes[LHashIdx], LCalcStream, LBeginPos, LEndPos);
  5936. ASender.Reply.SetReply(250, LCheckSum);
  5937. finally
  5938. FreeAndNil(LCalcStream);
  5939. end;
  5940. end else begin
  5941. CmdFileActionAborted(ASender);
  5942. end;
  5943. end;
  5944. end else begin
  5945. CmdSyntaxError(ASender);
  5946. end;
  5947. end;
  5948. procedure TIdFTPServer.DoOnFileExistCheck(AContext: TIdFTPServerContext;
  5949. const AFileName: String; var VExist: Boolean);
  5950. begin
  5951. if Assigned(FOnFileExistCheck) then begin
  5952. FOnFileExistCheck(AContext, AFileName, VExist);
  5953. end;
  5954. end;
  5955. procedure TIdFTPServer.CommandSPSV(ASender: TIdCommand);
  5956. var
  5957. LIP : String;
  5958. LBPort : Word;
  5959. LIPVer : TIdIPVersion;
  5960. begin
  5961. //just to keep the compiler happy
  5962. LBPort := 0;
  5963. if InternalPASV(ASender, LIP, LBPort, LIPVer) then begin
  5964. ASender.Reply.SetReply(227, IntToStr(LBPort));
  5965. end;
  5966. end;
  5967. function TIdFTPServer.InternalPASV(ASender: TIdCommand; var VIP : String;
  5968. var VPort: TIdPort; var VIPVersion : TIdIPVersion): Boolean;
  5969. var
  5970. LContext : TIdFTPServerContext;
  5971. LBPortMin, LBPortMax: TIdPort;
  5972. LDataChannel: TIdSimpleServer;
  5973. begin
  5974. Result := False;
  5975. LContext := ASender.Context as TIdFTPServerContext;
  5976. if LContext.IsAuthenticated(ASender) then begin
  5977. if LContext.FEPSVAll then begin
  5978. ASender.Reply.SetReply(501, IndyFormat(RSFTPNotAllowedAfterEPSVAll, [ASender.CommandHandler.Command]));
  5979. Exit;
  5980. end;
  5981. VIP := LContext.Binding.IP;
  5982. VIPVersion := LContext.Binding.IPVersion;
  5983. if (FPASVBoundPortMin <> 0) and (FPASVBoundPortMax <> 0) then begin
  5984. LBPortMin := FPASVBoundPortMin;
  5985. LBPortMax := FPASVBoundPortMax;
  5986. end else begin
  5987. LBPortMin := FDefaultDataPort;
  5988. LBPortMax := LBPortMin;
  5989. end;
  5990. DoOnPASVBeforeBind(LContext, VIP, LBPortMin, LBPortMax, VIPVersion);
  5991. LContext.CreateDataChannel(True);
  5992. LDataChannel := TIdSimpleServer(LContext.FDataChannel.FDataChannel);
  5993. LDataChannel.BoundIP := VIP;
  5994. if LBPortMin = LBPortMax then begin
  5995. LDataChannel.BoundPort := LBPortMin;
  5996. LDataChannel.BoundPortMin := 0;
  5997. LDataChannel.BoundPortMax := 0;
  5998. end else begin
  5999. LDataChannel.BoundPort := 0;
  6000. LDataChannel.BoundPortMin := LBPortMin;
  6001. LDataChannel.BoundPortMax := LBPortMax;
  6002. end;
  6003. LDataChannel.IPVersion := VIPVersion;
  6004. LDataChannel.BeginListen;
  6005. VIP := LDataChannel.Binding.IP;
  6006. VPort := LDataChannel.Binding.Port;
  6007. LContext.FPASV := True;
  6008. LContext.FDataPortDenied := False;
  6009. Result := True;
  6010. end;
  6011. end;
  6012. procedure TIdFTPServer.DoOnPASVBeforeBind(ASender: TIdFTPServerContext;
  6013. var VIP: String; var VPortMin, VPortMax: TIdPort; const AIPVersion: TIdIPVersion);
  6014. begin
  6015. if Assigned(FOnPASVBeforeBind) then begin
  6016. FOnPASVBeforeBind(ASender, VIP, VPortMin, VPortMax, AIPVersion);
  6017. end;
  6018. end;
  6019. procedure TIdFTPServer.DoOnPASVReply(ASender: TIdFTPServerContext;
  6020. var VIP: String; var VPort: TIdPort; const AIPVersion: TIdIPVersion);
  6021. begin
  6022. if Assigned(FOnPASVReply) then begin
  6023. FOnPASVReply(ASender, VIP, VPort, AIPVersion);
  6024. end;
  6025. end;
  6026. function TIdFTPServer.ReadCommandLine(AContext: TIdContext): string;
  6027. var
  6028. i : Integer;
  6029. State: TIdFTPTelnetState;
  6030. lb : Byte;
  6031. LContext: TIdFTPServerContext;
  6032. { Receive the line in 8-bit initially so that .NET can then
  6033. decode any UTF-8 data into a Unicode string afterwards if
  6034. needed }
  6035. LLine: TIdBytes;
  6036. LReply: TIdBytes;
  6037. Finished: Boolean;
  6038. begin
  6039. Result := '';
  6040. LContext := AContext as TIdFTPServerContext;
  6041. //we do it this way in case there's no data. We don't want to stop
  6042. //a data channel operation if that's the case.
  6043. AContext.Connection.IOHandler.CheckForDataOnSource(1);
  6044. if AContext.Connection.IOHandler.InputBufferIsEmpty then begin
  6045. Exit;
  6046. end;
  6047. //
  6048. SetLength(LLine, 0);
  6049. SetLength(LReply, 0);
  6050. Finished := False;
  6051. State := tsData;
  6052. repeat
  6053. lb := AContext.Connection.IOHandler.ReadByte;
  6054. case State of
  6055. tsData:
  6056. begin
  6057. case lb of
  6058. $FF: //is a command
  6059. begin
  6060. State := tsIAC;
  6061. end;
  6062. $0D: //wait for the next character to see what to do
  6063. begin
  6064. State := tsCheckCR;
  6065. end;
  6066. else
  6067. AppendByte(LLine, lb);
  6068. end;
  6069. end;
  6070. tsCheckCR:
  6071. begin
  6072. case lb of
  6073. $0: // preserve CR
  6074. begin
  6075. AppendByte(LLine, $0D);
  6076. State := tsData;
  6077. end;
  6078. $0A:
  6079. begin
  6080. State := tsData;
  6081. Finished := True;
  6082. end;
  6083. $FF: //unexpected IAC, just in case
  6084. begin
  6085. AppendByte(LLine, $0D);
  6086. State := tsIAC;
  6087. end;
  6088. else
  6089. ExpandBytes(LLine, Length(LLine), 2);
  6090. LLine[Length(LLine)-2] := $0D;
  6091. LLine[Length(LLine)-1] := lb;
  6092. State := tsData;
  6093. end;
  6094. end;
  6095. tsIAC:
  6096. begin
  6097. case lb of
  6098. $F1, //no-operation - do nothing
  6099. $F3: //break - do nothing for now
  6100. begin
  6101. State := tsData;
  6102. end;
  6103. $F4: //interrupt process - clear result and wait for data mark
  6104. begin
  6105. SetLength(LLine, 0);
  6106. State := tsInterrupt;
  6107. end;
  6108. $F5: //abort output
  6109. begin
  6110. // note - the DM needs to be sent as OOB "Urgent" data
  6111. SetLength(LReply, 4);
  6112. // TELNET_IP
  6113. LReply[0] := $FF;
  6114. LReply[1] := $F4;
  6115. // TELNET_DM
  6116. LReply[2] := $FF;
  6117. LReply[3] := $F2;
  6118. AContext.Connection.IOHandler.Write(LReply);
  6119. SetLength(LReply, 0);
  6120. State := tsData;
  6121. end;
  6122. $F6: //are you there - do nothing for now
  6123. begin
  6124. State := tsData;
  6125. end;
  6126. $F7: //erase character
  6127. begin
  6128. i := Length(LLine);
  6129. if i > 0 then begin
  6130. SetLength(LLine, i-1);
  6131. end;
  6132. State := tsData;
  6133. end;
  6134. $F8 : //erase line
  6135. begin
  6136. SetLength(LLine, 0);
  6137. State := tsData;
  6138. end;
  6139. $F9 : //go ahead - do nothing for now
  6140. begin
  6141. State := tsData;
  6142. end;
  6143. $FA : //begin sub-negotiation
  6144. begin
  6145. State := tsNegotiate;
  6146. end;
  6147. $FB : //I will use
  6148. begin
  6149. State := tsWill;
  6150. end;
  6151. $FC : //you won't use
  6152. begin
  6153. State := tsWont;
  6154. end;
  6155. $FD : //please, you use option
  6156. begin
  6157. State := tsDo;
  6158. end;
  6159. $FE : //please, you stop option
  6160. begin
  6161. State := tsDont;
  6162. end;
  6163. $FF : //data $FF
  6164. begin
  6165. AppendByte(LLine, $FF);
  6166. State := tsData;
  6167. end;
  6168. else
  6169. // unknown command, ignore
  6170. State := tsData;
  6171. end;
  6172. end;
  6173. tsWill:
  6174. begin
  6175. SetLength(LReply, 3);
  6176. // TELNET_WONT
  6177. LReply[0] := $FF;
  6178. LReply[1] := $FC;
  6179. LReply[2] := lb;
  6180. AContext.Connection.IOHandler.Write(LReply);
  6181. SetLength(LReply, 0);
  6182. State := tsData;
  6183. end;
  6184. tsDo:
  6185. begin
  6186. SetLength(LReply, 3);
  6187. // TELNET_DONT
  6188. LReply[0] := $FF;
  6189. LReply[1] := $FE;
  6190. LReply[2] := lb;
  6191. AContext.Connection.IOHandler.Write(LReply);
  6192. SetLength(LReply, 0);
  6193. State := tsData;
  6194. end;
  6195. tsWont,
  6196. tsDont:
  6197. begin
  6198. State := tsData;
  6199. end;
  6200. tsNegotiate:
  6201. begin
  6202. State := tsNegotiateData;
  6203. end;
  6204. tsNegotiateData:
  6205. begin
  6206. case lb of
  6207. $FF: //is a command?
  6208. begin
  6209. State := tsNegotiateIAC;
  6210. end;
  6211. end;
  6212. end;
  6213. tsNegotiateIAC:
  6214. begin
  6215. case lb of
  6216. $F0: //end sub-negotiation
  6217. begin
  6218. State := tsData;
  6219. end;
  6220. else
  6221. State := tsNegotiateData;
  6222. end;
  6223. end;
  6224. tsInterrupt:
  6225. begin
  6226. case lb of
  6227. $FF: //is a command?
  6228. begin
  6229. State := tsInterruptIAC;
  6230. end;
  6231. end;
  6232. end;
  6233. tsInterruptIAC:
  6234. begin
  6235. case lb of
  6236. $F2: //data mark
  6237. begin
  6238. State := tsData;
  6239. end;
  6240. end;
  6241. end;
  6242. else
  6243. State := tsData;
  6244. end;
  6245. until Finished or (not AContext.Connection.IOHandler.Connected);
  6246. //The last char was #13, we have to make sure that we remove a trailing
  6247. //#10 if it exists so that it doesn't appear in the next line.
  6248. if (lb = $0D) and (State = tsData) then
  6249. begin
  6250. i := AContext.Connection.IOHandler.InputBuffer.Size;
  6251. if i > 0 then begin
  6252. lb := AContext.Connection.IOHandler.InputBuffer.PeekByte(i - 1);
  6253. if lb = $0A then begin
  6254. AContext.Connection.IOHandler.ReadByte;
  6255. end;
  6256. end;
  6257. end;
  6258. Result := BytesToString(LLine, 0, MaxInt, LContext.Connection.IOHandler.DefStringEncoding);
  6259. end;
  6260. procedure TIdFTPServer.DoReplyUnknownCommand(AContext: TIdContext; ALine: string);
  6261. begin
  6262. CmdSyntaxError(AContext, ALine);
  6263. end;
  6264. procedure TIdFTPServer.DoTerminateContext(AContext: TIdContext);
  6265. begin
  6266. try
  6267. TIdFTPServerContext(AContext).KillDataChannel;
  6268. finally
  6269. inherited DoTerminateContext(AContext);
  6270. end;
  6271. end;
  6272. procedure TIdFTPServer.CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply : TIdReply = nil);
  6273. var
  6274. LTmp : String;
  6275. LReply : TIdReply;
  6276. begin
  6277. //First make the first word upper-case
  6278. LTmp := UpCaseFirstWord(ALine);
  6279. try
  6280. if Assigned(AReply) then begin
  6281. LReply := AReply;
  6282. end else begin
  6283. LReply := FReplyClass.CreateWithReplyTexts(nil, ReplyTexts);
  6284. LReply.Assign(ReplyUnknownCommand);
  6285. end;
  6286. LReply.Text.Clear;
  6287. LReply.Text.Add(IndyFormat(RSFTPCmdNotRecognized, [LTmp]));
  6288. AContext.Connection.IOHandler.Write(LReply.FormattedReply);
  6289. finally
  6290. if not Assigned(AReply) then begin
  6291. FreeAndNil(LReply);
  6292. end;
  6293. end;
  6294. end;
  6295. procedure TIdFTPServer.CmdSyntaxError(ASender: TIdCommand);
  6296. begin
  6297. CmdSyntaxError(ASender.Context, ASender.RawLine, FReplyUnknownCommand );
  6298. ASender.PerformReply := False;
  6299. end;
  6300. procedure TIdFTPServer.CommandSecRFC(ASender: TIdCommand);
  6301. //stub for RFC 2228 commands that we don't implement as
  6302. //part of the SSL framework.
  6303. begin
  6304. if IOHandler is TIdServerIOHandlerSSLBase then begin
  6305. CmdNotImplemented(ASender);
  6306. end else begin
  6307. CmdSyntaxError(ASender);
  6308. end;
  6309. end;
  6310. procedure TIdFTPServer.CommandOptsMLST(ASender: TIdCommand);
  6311. const
  6312. LVALIDOPTS : array [0..12] of string =
  6313. ('type', 'size', 'modify',
  6314. 'UNIX.mode', 'UNIX.owner', 'UNIX.group',
  6315. 'unique', 'perm', 'create',
  6316. 'windows.lastaccesstime','win32.ea','win32.dt','win32.dl'); {Do not localize}
  6317. var
  6318. s: string;
  6319. LContext : TIdFTPServerContext;
  6320. function ParseMLSParms(ASvr : TIdFTPServer; const AParms : String) : TIdFTPFactOutputs;
  6321. var
  6322. Ls : String;
  6323. begin
  6324. Result := [];
  6325. Ls := UpperCase(AParms);
  6326. while Ls <> '' do begin
  6327. case PosInStrArray(Fetch(Ls,';'), LVALIDOPTS, False) of
  6328. 0 : Result := Result + [ItemType]; //type
  6329. 1 : Result := Result + [Size]; //size
  6330. 2 : Result := Result + [Modify]; //modify
  6331. 3 : if mlsdUnixModes in ASvr.FMLSDFacts then begin
  6332. Result := Result + [UnixMODE]; //UnixMode
  6333. end;
  6334. 4 : if mlsdUnixOwner in ASvr.FMLSDFacts then begin
  6335. Result := Result + [UnixOwner]; //UNIX.owner
  6336. end;
  6337. 5 : if mlsdUnixGroup in ASvr.FMLSDFacts then begin
  6338. Result := Result + [UnixGroup]; //UNIX.group
  6339. end;
  6340. 6 : if mlsdUniqueID in ASvr.FMLSDFacts then begin //Unique
  6341. Result := Result + [Unique];
  6342. end;
  6343. 7 : if mlsdPerms in ASvr.FMLSDFacts then begin //perm
  6344. Result := Result + [Perm];
  6345. end;
  6346. 8 : if mlsdFileCreationTime in ASvr.FMLSDFacts then begin
  6347. Result := Result + [CreateTime];
  6348. end;
  6349. 9 : if mlsdFileLastAccessTime in ASvr.FMLSDFacts then begin
  6350. Result := Result + [LastAccessTime];
  6351. end;
  6352. 10 : if mlsdWin32Attributes in ASvr.FMLSDFacts then begin
  6353. Result := Result + [WinAttribs];
  6354. end;
  6355. 11 : if mlsdWin32DriveType in ASvr.MLSDFacts then begin
  6356. Result := Result + [WinDriveType];
  6357. end;
  6358. 12 : if mlstWin32DriveLabel in ASvr.MLSDFacts then begin
  6359. Result := Result + [WinDriveLabel];
  6360. end;
  6361. end;
  6362. end;
  6363. end;
  6364. function SetToOptsStr(AFacts : TIdFTPFactOutputs) : String;
  6365. begin
  6366. Result := '';
  6367. if Size in AFacts then begin {Do not translate}
  6368. Result := Result + 'size;'; {Do not localize}
  6369. end;
  6370. if ItemType in AFacts then begin {Do not translate}
  6371. Result := Result + 'type;'; {Do not translate}
  6372. end;
  6373. if Perm in AFacts then begin {Do not translate}
  6374. Result := Result + 'perm;'; {Do not translate}
  6375. end;
  6376. if CreateTime in AFacts then begin {Do not translate}
  6377. Result := Result + 'create;'; {Do not translate}
  6378. end;
  6379. if Modify in AFacts then begin
  6380. Result := Result + 'modify;'; {Do not translate}
  6381. end;
  6382. if UnixMODE in AFacts then begin {Do not translate}
  6383. Result := Result + 'UNIX.mode;'; {Do not translate}
  6384. end;
  6385. if UnixOwner in AFacts then begin{Do not translate}
  6386. Result := Result + 'UNIX.owner;'; {Do not translate}
  6387. end;
  6388. if UnixGroup in AFacts then begin {Do not translate}
  6389. Result := Result + 'UNIX.group;'; {Do not translate}
  6390. end;
  6391. if Unique in AFacts then begin {Do not translate}
  6392. Result := Result + 'unique;'; {Do not translate}
  6393. end;
  6394. if LastAccessTime in AFacts then begin
  6395. Result := Result + 'windows.lastaccesstime;'; {Do not translate}
  6396. end;
  6397. if IdFTPListOutput.WinAttribs in AFacts then begin
  6398. Result := Result + 'win32.ea;'; {Do not translate}
  6399. end;
  6400. if IdFTPListOutput.WinDriveType in AFacts then begin
  6401. Result := Result + 'Win32.dt;'; {Do not localize}
  6402. end;
  6403. if IdFTPListOutput.WinDriveLabel in AFacts then begin
  6404. Result := Result + 'Win32.dl;'; {Do not localize}
  6405. end;
  6406. end;
  6407. begin
  6408. LContext := ASender.Context as TIdFTPServerContext;
  6409. s := ASender.UnparsedParams;
  6410. if IndyPos(' ', s) = 0 then begin
  6411. LContext.MLSOpts := ParseMLSParms(Self, Trim(s));
  6412. //the string is standardized format
  6413. ASender.Reply.SetReply(200, Trim(IndyFormat('MLST OPTS %s', [SetToOptsStr(LContext.MLSOpts)]))); {Do not Localize}
  6414. end else begin
  6415. ASender.Reply.SetReply(501, IndyFormat(RSFTPInvalidOps, ['MLST'])); {Do not Localize}
  6416. end;
  6417. end;
  6418. procedure TIdFTPServer.CommandOptsMODEZ(ASender: TIdCommand);
  6419. const
  6420. OPT_NAMES : Array[0..4] of String =
  6421. ('ENGINE','LEVEL','METHOD','BLOCKSIZE','EXTRA'); {do not localize}
  6422. var
  6423. s: string;
  6424. LOptName, LOptVal : String;
  6425. LContext : TIdFTPServerContext;
  6426. LFirstPar : Boolean;
  6427. LError : Boolean;
  6428. LNoVal : Integer;
  6429. LReset : Boolean;
  6430. procedure ReportSettings(ACxt : TIdFTPServerContext; AReply : TIdReply);
  6431. begin
  6432. AReply.NumericCode := 200;
  6433. AReply.Text.Clear;
  6434. AReply.Text.Add('MODE Z ENGINE set to ZLIB.'); {do not localize}
  6435. AReply.Text.Add('MODE Z LEVEL set to ' + IntToStr(ACxt.FZLibCompressionLevel) + '.'); {do not localize}
  6436. AReply.Text.Add('MODE Z METHOD set to ' + IntToStr(DEF_ZLIB_METHOD) + '.'); {do not localize}
  6437. end;
  6438. procedure SyntaxError(AReply : TIdCommand);
  6439. var
  6440. LOpts : String;
  6441. begin
  6442. //drop the OPTS part of the command for display
  6443. LOpts := ASender.RawLine;
  6444. Fetch(LOpts);
  6445. LOpts := TrimLeft(LOpts);
  6446. ASender.Reply.SetReply(501, IndyFormat(RSFTPInvalidOps, [LOpts]));
  6447. end;
  6448. begin
  6449. LFirstPar := True;
  6450. LReset := True;
  6451. LError := True;
  6452. LContext := ASender.Context as TIdFTPServerContext;
  6453. s := Trim(ASender.UnparsedParams);
  6454. if s = '' then begin
  6455. LContext.ResetZLibSettings;
  6456. ReportSettings(LContext, ASender.Reply);
  6457. end;
  6458. repeat
  6459. LOptName := Fetch(s);
  6460. if s = '' then begin
  6461. if LFirstPar then begin
  6462. SyntaxError(ASender);
  6463. Exit;
  6464. end;
  6465. end;
  6466. LOptVal := Fetch(s);
  6467. if Trim(s) <> '' then begin
  6468. //if there's more, than we see if there's a valid option.
  6469. LFirstPar := False;
  6470. end;
  6471. if LFirstPar and (PosInStrArray(LOptName, OPT_NAMES, False) = -1) then begin
  6472. SyntaxError(ASender);
  6473. Exit;
  6474. end;
  6475. LFirstPar := False;
  6476. case PosInStrArray(LOptName, OPT_NAMES, False) of
  6477. 0 : //'ENGINE' - we only support ZLIB
  6478. begin
  6479. LError := False;
  6480. end;
  6481. 1 : begin //,'LEVEL', - implemented
  6482. LNoVal := IndyStrToInt(LOptVal, -1);
  6483. if (LNoVal > -1) and (LNoVal < 8) then begin
  6484. LContext.FZLibCompressionLevel := LNoVal;
  6485. LReset := False;
  6486. LError := False;
  6487. end;
  6488. end;
  6489. 2 : begin //'METHOD', - not implemented - jst do syntax check
  6490. LNoVal := IndyStrToInt(LOptVal, -1);
  6491. if LNoVal <> -1 then begin
  6492. LError := False;
  6493. end;
  6494. end;
  6495. 3 : begin ///'BLOCKSIZE', -not implemented - just do syntax check
  6496. LNoVal := IndyStrToInt(LOptVal, -1);
  6497. if LNoVal <> -1 then begin
  6498. LError := False;
  6499. end;
  6500. end;
  6501. 4 : begin //'EXTRA') - not implemented - just do syntax check
  6502. if PosInStrArray(LOptVal, OnOffStates, False) > -1 then begin
  6503. LError := False;
  6504. end;
  6505. end;
  6506. end;
  6507. until (s = '');
  6508. if LError then begin
  6509. SyntaxError(ASender);
  6510. Exit;
  6511. end;
  6512. if LReset then begin
  6513. LContext.ResetZLibSettings;
  6514. end;
  6515. ReportSettings(LContext, ASender.Reply);
  6516. end;
  6517. procedure TIdFTPServer.CommandOptsUTF8(ASender: TIdCommand);
  6518. var
  6519. s: String;
  6520. LContext: TIdFTPServerContext;
  6521. procedure SyntaxError(AReply : TIdCommand);
  6522. var
  6523. LOpts : String;
  6524. begin
  6525. //drop the OPTS part of the command for display
  6526. LOpts := ASender.RawLine;
  6527. Fetch(LOpts);
  6528. LOpts := TrimLeft(LOpts);
  6529. ASender.Reply.SetReply(501, IndyFormat(RSFTPInvalidOps, [LOpts]));
  6530. end;
  6531. begin
  6532. LContext := ASender.Context as TIdFTPServerContext;
  6533. s := Trim(ASender.UnparsedParams);
  6534. if TextIsSame(ASender.CommandHandler.Command, 'UTF-8') then begin
  6535. // OPTS UTF-8 <NLST>
  6536. // http://www.ietf.org/proceedings/02nov/I-D/draft-ietf-ftpext-utf-8-option-00.txt
  6537. if s = '' then begin
  6538. LContext.NLSTUtf8 := False; // disable UTF-8 over data connection
  6539. end
  6540. else if TextIsSame(s, 'NLST') then begin
  6541. LContext.NLSTUtf8 := True; // enable UTF-8 over data connection
  6542. end else begin
  6543. SyntaxError(ASender);
  6544. Exit;
  6545. end;
  6546. // enable UTF-8 over control connection
  6547. LContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
  6548. end else begin
  6549. // OPTS UTF8 <ON|OFF>
  6550. // non-standard Microsoft IE implementation!!!!
  6551. case PosInStrArray(s, OnOffStates, False) of
  6552. 0: begin // 'ON'
  6553. LContext.NLSTUtf8 := True;
  6554. LContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
  6555. end;
  6556. 1: begin // 'OFF'
  6557. LContext.NLSTUtf8 := False;
  6558. LContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
  6559. end;
  6560. else
  6561. begin
  6562. SyntaxError(ASender);
  6563. Exit;
  6564. end;
  6565. end;
  6566. end;
  6567. ASender.Reply.NumericCode := 200;
  6568. end;
  6569. function TIdFTPServer.IgnoreLastPathDelim(const APath: String): String;
  6570. //This internal function is needed because path processing is different in Windows
  6571. //than in Linux. The path separators on a FTP server on either system will be different.
  6572. //
  6573. //On Windows machines, both '/' and '\'
  6574. //
  6575. //On a Linux machine, a FTP server would probably only use '/' because '\' is a valid
  6576. //filename char.
  6577. var
  6578. i : Integer;
  6579. LPathProcessing : TIdFTPPathProcessing;
  6580. begin
  6581. Result := APath;
  6582. i := Length(Result);
  6583. if FPathProcessing <> ftpOSDependent then begin
  6584. LPathProcessing := FPathProcessing;
  6585. end else begin
  6586. case GOSType of
  6587. otUnix :
  6588. begin
  6589. LPathProcessing := ftppUnix;
  6590. end;
  6591. otUnknown :
  6592. begin
  6593. LPathProcessing := ftppCustom;
  6594. end
  6595. else
  6596. LPathProcessing := ftppDOS;
  6597. end;
  6598. end;
  6599. case LPathProcessing of
  6600. ftppDOS :
  6601. begin
  6602. if Result <>'' then begin
  6603. if CharIsInSet(Result, i, '/\') then begin
  6604. IdDelete(Result, i, 1);
  6605. end;
  6606. end;
  6607. end;
  6608. ftppUnix :
  6609. begin
  6610. if Result <>'' then begin
  6611. if TextEndsWith(Result, '/') then begin
  6612. IdDelete(Result, i, 1);
  6613. end;
  6614. end;
  6615. end;
  6616. ftppCustom :
  6617. begin
  6618. Exit;
  6619. end;
  6620. end;
  6621. //Done so that something like "cd /" or "cd \" will go to
  6622. //the main directory
  6623. if Result = '' then begin
  6624. Result := '/';
  6625. end;
  6626. end;
  6627. function TIdFTPServer.SupportTaDirSwitches(AContext : TIdFTPServerContext): Boolean;
  6628. begin
  6629. Result := True;
  6630. case FDirFormat of
  6631. ftpdfCustom, ftpdfEPLF:
  6632. Result := False;
  6633. ftpdfDOS:
  6634. Result := not AContext.FMSDOSMode;
  6635. ftpdfOSDependent:
  6636. if (GOSType = otWindows) or (GOSType = otDotNET) then begin
  6637. Result := not AContext.FMSDOSMode;
  6638. end;
  6639. end;
  6640. end;
  6641. function TIdFTPServer.GetCaseSensitive: Boolean;
  6642. begin
  6643. Result := FServerInfo.CaseSensitive;
  6644. end;
  6645. procedure TIdFTPServer.SetCaseSensitive(const AValue : Boolean);
  6646. begin
  6647. FServerInfo.CaseSensitive := AValue;
  6648. end;
  6649. function TIdFTPServer.GetDirSeparator : Char;
  6650. begin
  6651. Result := FServerInfo.DirSeparator;
  6652. end;
  6653. procedure TIdFTPServer.SetDirSeparator(const AValue : Char);
  6654. begin
  6655. FServerInfo.DirSeparator := AValue;
  6656. end;
  6657. { TIdFTPSecurityOptions }
  6658. procedure TIdFTPSecurityOptions.Assign(Source: TPersistent);
  6659. var
  6660. LSrc : TIdFTPSecurityOptions;
  6661. begin
  6662. if Source is TIdFTPSecurityOptions then begin
  6663. LSrc := Source as TIdFTPSecurityOptions;
  6664. BlockAllPORTTransfers := LSrc.BlockAllPORTTransfers;
  6665. DisableSTATCommand := LSrc.DisableSTATCommand;
  6666. DisableSYSTCommand := LSrc.DisableSYSTCommand;
  6667. PasswordAttempts := LSrc.PasswordAttempts;
  6668. InvalidPassDelay := LSrc.InvalidPassDelay;
  6669. NoReservedRangePORT := LSrc.NoReservedRangePORT;
  6670. RequirePASVFromSameIP := LSrc.RequirePASVFromSameIP;
  6671. RequirePORTFromSameIP := LSrc.RequirePORTFromSameIP;
  6672. PermitCCC := LSrc.PermitCCC;
  6673. end else begin
  6674. inherited Assign(Source);
  6675. end;
  6676. end;
  6677. constructor TIdFTPSecurityOptions.Create;
  6678. begin
  6679. inherited Create;
  6680. //limit login attempts - some hackers will try guessing passwords from a dictionary
  6681. PasswordAttempts := DEF_FTP_PASSWORDATTEMPTS;
  6682. //should slow-down a password guessing attack - note those dictionaries
  6683. InvalidPassDelay := DEF_FTP_INVALIDPASS_DELAY;
  6684. //client IP Address is the only one that we will accept a PASV
  6685. //transfer from
  6686. //http://cr.yp.to/ftp/security.html
  6687. RequirePASVFromSameIP := DEF_FTP_PASV_SAME_IP;
  6688. //Accept port transfers from the same IP address as the client -
  6689. //should prevent bounce attacks
  6690. RequirePORTFromSameIP := DEF_FTP_PORT_SAME_IP;
  6691. //Do not accept port requests to ports in the reserved range. That is dangerous on some systems
  6692. NoReservedRangePORT := DEF_FTP_NO_RESERVED_PORTS;
  6693. //Do not accept any PORT transfers at all. This is a little extreme but reduces troubles further.
  6694. //This will break the the Win32 console clients and a number of other programs.
  6695. BlockAllPORTTransfers := DEF_FTP_BLOCK_ALL_PORTS;
  6696. //Disable SYST command. SYST usually gives the system description.
  6697. //Disabling it may make it harder for a trouble maker to know about your computer
  6698. //but will not be a complete security solution. See http://www.sans.org/rr/infowar/fingerprint.php for details
  6699. //On the other hand, disabling it will break RFC 959 complience and may break some FTP programs.
  6700. DisableSYSTCommand := DEF_FTP_DISABLE_SYST;
  6701. //Disable STAT command. STAT gives freeform information about the connection status.
  6702. // http://www.sans.org/rr/infowar/fingerprint.php advises administrators to disable this
  6703. //because servers tend to give distinct patterns of information and some trouble makers
  6704. //can figure out what type of server you are running simply with this.
  6705. DisableSTATCommand := DEF_FTP_DISABLE_STAT;
  6706. //Permit CCC command when using TLS with FTP to clear the control connection.
  6707. //That may be helpful for someone behind a NAT where an IP address can NOT be altered by the NAT
  6708. //when using SSL. On the other hand, some administrators may NOT permit this for security reasons.
  6709. //That's a debate I'll leave up to the programmer in hopes that they will pass it to the user.
  6710. PermitCCC := DEF_FTP_PERMIT_CCC;
  6711. end;
  6712. { TIdDataChannel }
  6713. constructor TIdDataChannel.Create(APASV: Boolean; AControlContext: TIdFTPServerContext;
  6714. const ARequirePASVFromSameIP: Boolean; AServer: TIdFTPServer);
  6715. var
  6716. LIO: TIdIOHandlerSocket;
  6717. LDataChannelSvr: TIdSimpleServer;
  6718. LDataChannelCli: TIdTCPClient;
  6719. begin
  6720. inherited Create;
  6721. FNegotiateTLS := False;
  6722. FOKReply := TIdReplyRFC.Create(nil);
  6723. FErrorReply := TIdReplyRFC.Create(nil);
  6724. FReply := TIdReplyRFC.Create(nil);
  6725. FRequirePASVFromSameIP := ARequirePASVFromSameIP;
  6726. FControlContext := AControlContext;
  6727. FServer := AServer;
  6728. // RLebeau: do not set both BoundPortMin/Max and BoundPort at the same time.
  6729. // If they are all non-zero, BoundPort will take priority in TIdSocketHandle.
  6730. // The DefaultDataPort property should not be assigned to zero in order to
  6731. // support Active-mode transfers, but doing so will cause BoundPortMin/Max
  6732. // to be ignored for Passive-mode transfers. So assign them in an either-or
  6733. // manner.
  6734. if APASV then begin
  6735. FDataChannel := TIdSimpleServer.Create(nil);
  6736. LDataChannelSvr := TIdSimpleServer(FDataChannel);
  6737. LDataChannelSvr.BoundIP := FControlContext.Binding.IP;
  6738. if (AServer.PASVBoundPortMin <> 0) and (AServer.PASVBoundPortMax <> 0) then begin
  6739. LDataChannelSvr.BoundPortMin := AServer.PASVBoundPortMin;
  6740. LDataChannelSvr.BoundPortMax := AServer.PASVBoundPortMax;
  6741. end else begin
  6742. LDataChannelSvr.BoundPort := AServer.DefaultDataPort;
  6743. end;
  6744. LDataChannelSvr.IPVersion := FControlContext.Binding.IPVersion;
  6745. LDataChannelSvr.OnBeforeBind := AControlContext.PortOnBeforeBind;
  6746. LDataChannelSvr.OnAfterBind := AControlContext.PortOnAfterBind;
  6747. end else begin
  6748. FDataChannel := TIdTCPClient.Create(nil);
  6749. //the TCPClient for the dataport must be bound to a default port
  6750. LDataChannelCli := TIdTCPClient(FDataChannel);
  6751. LDataChannelCli.BoundIP := FControlContext.Binding.IP;
  6752. LDataChannelCli.BoundPort := AServer.DefaultDataPort;
  6753. LDataChannelCli.IPVersion := FControlContext.Binding.IPVersion;
  6754. end;
  6755. if AControlContext.Server.IOHandler is TIdServerIOHandlerSSLBase then begin
  6756. if APASV then begin
  6757. LIO := TIdServerIOHandlerSSLBase(AServer.IOHandler).MakeFTPSvrPasv;
  6758. end else begin
  6759. LIO := TIdServerIOHandlerSSLBase(AServer.IOHandler).MakeFTPSvrPort;
  6760. end;
  6761. TIdSSLIOHandlerSocketBase(LIO).PassThrough := True;
  6762. // always uses a ssl iohandler, but passthrough is true...
  6763. end else begin
  6764. LIO := FServer.IOHandler.MakeClientIOHandler(nil) as TIdIOHandlerSocket;
  6765. end;
  6766. {$IFDEF USE_OBJECT_ARC}
  6767. // under ARC, the TIdTCPConnection.IOHandler property is a weak reference.
  6768. // MakeFTPSvrPasv(), MakeFTPSvrPort(), and MakeClientIOHandler() return an
  6769. // IOHandler with no Owner assigned, so lets make the TIdTCPConnection become
  6770. // the Owner in order to keep the IOHandler alive when this method exits.
  6771. //
  6772. // TODO: should we assign Ownership unconditionally on all platforms?
  6773. //
  6774. // TODO: add an AOwner parameter to MakeFTPSvrPasv(), MakeFTPSvrPort() and
  6775. // MakeClientIOHandler
  6776. //
  6777. FDataChannel.InsertComponent(LIO);
  6778. {$ENDIF}
  6779. FDataChannel.IOHandler := LIO;
  6780. FDataChannel.ManagedIOHandler := True;
  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. FreeAndNil(FOKReply);
  6797. FreeAndNil(FErrorReply);
  6798. FreeAndNil(FReply);
  6799. if Assigned(FDataChannel) then begin
  6800. FDataChannel.IOHandler := nil;
  6801. end;
  6802. FreeAndNil(FDataChannel);
  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.