IdFTPServer.pas 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556
  1. { $HDR$}
  2. {**********************************************************************}
  3. { Unit archived using Team Coherence }
  4. { Team Coherence is Copyright 2002 by Quality Software Components }
  5. { }
  6. { For further information / comments, visit our WEB site at }
  7. { http://www.TeamCoherence.com }
  8. {**********************************************************************}
  9. {}
  10. { $Log: 10167: IdFTPServer.pas
  11. {
  12. { Rev 1.6 7/13/04 7:03:30 PM RLebeau
  13. { Readded DataPort property to TIdFTPServerThread and made read-only
  14. }
  15. {
  16. { Rev 1.5 7/13/04 5:42:06 PM RLebeau
  17. { Various changes to hook up the DefaultDataPort property correctly
  18. }
  19. {
  20. { Rev 1.4 2/17/04 4:40:50 PM RLebeau
  21. { OnPASV event added for people needing to change the IP address or port value
  22. { in the PASV command. This should only be done if you have a compelling
  23. { reason to do it.
  24. }
  25. {
  26. Rev 1.3 1/23/2003 9:09:18 PM BGooijen
  27. Changed ABOR to fix the command while uploading
  28. }
  29. {
  30. { Rev 1.2 1-9-2003 11:44:42 BGooijen
  31. { Added ABOR command with telnet escape characters
  32. { Fixed hanging of ABOR command
  33. { STOR and STOU now use REST-position
  34. }
  35. {
  36. { Rev 1.1 12/10/2002 07:43:04 AM JPMugaas
  37. { Merged fix for a problem were resume cause the entire file to be sent instead
  38. { of the part requrested.
  39. }
  40. {
  41. { Rev 1.0 2002.11.12 10:39:06 PM czhower
  42. }
  43. unit IdFTPServer;
  44. {
  45. Original Author: Sergio Perry
  46. Date: 04/21/2001
  47. Fixes and modifications: Doychin Bondzhev
  48. Date: 08/10/2001
  49. Further Extensive changes by Chad Z. Hower (Kudzu)
  50. TODO:
  51. - Change events to use DoXXXX
  52. }
  53. interface
  54. uses
  55. Classes,
  56. SysUtils, IdAssignedNumbers,
  57. IdException, IdFTPList, IdTCPServer, IdTCPConnection, IdUserAccounts,
  58. IdFTPCommon, IdThread, IdRFCReply;
  59. type
  60. TIdFTPUserType = (utNone, utAnonymousUser, utNormalUser);
  61. TIdFTPSystems = (ftpsOther, ftpsDOS, ftpsUNIX, ftpsVAX);
  62. TIdFTPOperation = (ftpRetr, ftpStor);
  63. const
  64. Id_DEF_AllowAnon = False;
  65. Id_DEF_PassStrictCheck = True;
  66. Id_DEF_SystemType = ftpsDOS;
  67. type
  68. TIdFTPServerThread = class;
  69. TOnUserLoginEvent = procedure(ASender: TIdFTPServerThread; const AUsername, APassword: string;
  70. var AAuthenticated: Boolean) of object;
  71. TOnAfterUserLoginEvent = procedure(ASender: TIdFTPServerThread) of object;
  72. TOnDirectoryEvent = procedure(ASender: TIdFTPServerThread; var VDirectory: string) of object;
  73. TOnGetFileSizeEvent = procedure(ASender: TIdFTPServerThread; const AFilename: string;
  74. var VFileSize: Int64) of object;
  75. TOnListDirectoryEvent = procedure(ASender: TIdFTPServerThread; const APath: string;
  76. ADirectoryListing: TIdFTPListItems) of object;
  77. TOnFileEvent = procedure(ASender: TIdFTPServerThread; const APathName: string) of object;
  78. TOnRenameFileEvent = procedure(ASender: TIdFTPServerThread; const ARenameFromFile,ARenameToFile: string) of object;
  79. TOnRetrieveFileEvent = procedure(ASender: TIdFTPServerThread; const AFileName: string;
  80. var VStream: TStream) of object;
  81. TOnStoreFileEvent = procedure(ASender: TIdFTPServerThread; const AFileName: string;
  82. AAppend: Boolean; var VStream: TStream) of object;
  83. //This is for PASV support - do not change the values unless you
  84. //have an extremely compelling reason to do so. This even is ONLY for those compelling case.
  85. TOnPASVEvent = procedure(ASender: TIdFTPServerThread; var VIP : String; var VPort : Word) of object;
  86. EIdFTPServerException = class(EIdException);
  87. EIdFTPServerNoOnListDirectory = class(EIdFTPServerException);
  88. TIdDataChannelThread = class(TIdThread)
  89. protected
  90. FControlChannel: TIdTCPServerConnection;
  91. FDataChannel: TIdTCPConnection;
  92. FErrorReply: TIdRFCReply;
  93. FFtpOperation: TIdFTPOperation;
  94. FOKReply: TIdRFCReply;
  95. //
  96. procedure Run; override;
  97. procedure SetErrorReply(const AValue: TIdRFCReply);
  98. procedure SetOKReply(const AValue: TIdRFCReply);
  99. public
  100. constructor Create(APASV: Boolean; AControlConnection: TIdTCPServerConnection); reintroduce;
  101. destructor Destroy; override;
  102. procedure StartThread(AOperation: TIdFTPOperation);
  103. procedure SetupDataChannel(const AIP: string; APort: Integer);
  104. //
  105. property OKReply: TIdRFCReply read FOKReply write SetOKReply;
  106. property ErrorReply: TIdRFCReply read FErrorReply write SetErrorReply;
  107. end;
  108. TIdFTPServerThread = class(TIdPeerThread)
  109. protected
  110. FUserType: TIdFTPUserType;
  111. FAuthenticated: Boolean;
  112. FALLOSize: Integer;
  113. FCurrentDir: string;
  114. FDataType: TIdFTPTransferType;
  115. FDataMode: TIdFTPTransferMode;
  116. FDataPort: Integer;
  117. FDataStruct: TIdFTPDataStructure;
  118. FDataChannelThread: TIdDataChannelThread;
  119. FHomeDir: string;
  120. FUsername: string;
  121. FPassword: string;
  122. FPASV: Boolean;
  123. FRESTPos: Integer;
  124. FRNFR: string;
  125. //
  126. procedure CreateDataChannel(APASV: Boolean = False);
  127. function IsAuthenticated(ASender: TIdCommand): Boolean;
  128. procedure KillDataChannel;
  129. procedure TerminateAndFreeDataChannel;
  130. procedure ReInitialize;
  131. public
  132. constructor Create(ACreateSuspended: Boolean = True); override;
  133. destructor Destroy; override;
  134. //
  135. property Authenticated: Boolean read FAuthenticated write FAuthenticated;
  136. property ALLOSize: Integer read FALLOSize write FALLOSize;
  137. property CurrentDir: string read FCurrentDir write FCurrentDir;
  138. property DataChannelThread: TIdDataChannelThread read FDataChannelThread
  139. write FDataChannelThread;
  140. property DataType: TIdFTPTransferType read FDataType write FDataType;
  141. property DataMode: TIdFTPTransferMode read FDataMode write FDataMode;
  142. property DataPort: Integer read FDataPort;
  143. property DataStruct: TIdFTPDataStructure read FDataStruct write FDataStruct;
  144. property HomeDir: string read FHomeDir write FHomeDir;
  145. property Password: string read FPassword write FPassword;
  146. property PASV: Boolean read FPASV write FPASV;
  147. property RESTPos: Integer read FRESTPos write FRESTPos;
  148. property Username: string read FUsername write FUsername;
  149. property UserType: TIdFTPUserType read FUserType write FUserType;
  150. end;
  151. TIdFTPServer = class;
  152. TIdOnGetCustomListFormat = procedure(ASender: TIdFTPServer; AItem: TIdFTPListItem;
  153. var VText: string) of object;
  154. { FTP Server }
  155. TIdFTPServer = class(TIdTCPServer)
  156. protected
  157. FAnonymousAccounts: TstringList;
  158. FAllowAnonymousLogin: Boolean;
  159. FAnonymousPassStrictCheck: Boolean;
  160. FCmdHandlerList: TIdCommandHandler;
  161. FCmdHandlerNlst: TIdCommandHandler;
  162. FEmulateSystem: TIdFTPSystems;
  163. FHelpReply: Tstrings;
  164. FSystemType: string;
  165. FDefaultDataPort : Integer;
  166. FUserAccounts: TIdUserManager;
  167. FOnAfterUserLogin: TOnAfterUserLoginEvent;
  168. FOnGetCustomListFormat: TIdOnGetCustomListFormat;
  169. FOnUserLogin: TOnUserLoginEvent;
  170. FOnChangeDirectory: TOnDirectoryEvent;
  171. FOnGetFileSize: TOnGetFileSizeEvent;
  172. FOnListDirectory: TOnListDirectoryEvent;
  173. FOnRenameFile: TOnRenameFileEvent;
  174. FOnDeleteFile: TOnFileEvent;
  175. FOnRetrieveFile: TOnRetrieveFileEvent;
  176. FOnStoreFile: TOnStoreFileEvent;
  177. FOnMakeDirectory: TOnDirectoryEvent;
  178. FOnRemoveDirectory: TOnDirectoryEvent;
  179. FOnPASV : TOnPASVEvent;
  180. //Command replies
  181. procedure CommandUSER(ASender: TIdCommand);
  182. procedure CommandPASS(ASender: TIdCommand);
  183. procedure CommandCWD(ASender: TIdCommand);
  184. procedure CommandCDUP(ASender: TIdCommand);
  185. procedure CommandREIN(ASender: TIdCommand);
  186. procedure CommandPORT(ASender: TIdCommand);
  187. procedure CommandPASV(ASender: TIdCommand);
  188. procedure CommandTYPE(ASender: TIdCommand);
  189. procedure CommandSTRU(ASender: TIdCommand);
  190. procedure CommandMODE(ASender: TIdCommand);
  191. procedure CommandRETR(ASender: TIdCommand);
  192. procedure CommandSSAP(ASender: TIdCommand);
  193. procedure CommandALLO(ASender: TIdCommand);
  194. procedure CommandREST(ASender: TIdCommand);
  195. procedure CommandRNFR(ASender: TIdCommand);
  196. procedure CommandRNTO(ASender: TIdCommand);
  197. procedure CommandABOR(ASender: TIdCommand);
  198. procedure CommandDELE(ASender: TIdCommand);
  199. procedure CommandRMD(ASender: TIdCommand);
  200. procedure CommandMKD(ASender: TIdCommand);
  201. procedure CommandPWD(ASender: TIdCommand);
  202. procedure CommandLIST(ASender: TIdCommand);
  203. procedure CommandSITE(ASender: TIdCommand);
  204. procedure CommandSYST(ASender: TIdCommand);
  205. procedure CommandSTAT(ASender: TIdCommand);
  206. procedure CommandSIZE(ASender: TIdCommand);
  207. procedure CommandFEAT(ASender: TIdCommand);
  208. procedure CommandOPTS(ASender: TIdCommand);
  209. //
  210. procedure DoChangeDirectory(AThread: TIdFTPServerThread; var VDirectory: string);
  211. procedure DoMakeDirectory(AThread: TIdFTPServerThread; var VDirectory: string);
  212. procedure DoRemoveDirectory(AThread: TIdFTPServerThread; var VDirectory: string);
  213. procedure DoGetCustomListFormat(AItem: TIdFTPListItem; var VText: string);
  214. procedure DoOnPASV(AThread: TIdFTPServerThread; var VIP: String; var VPort: Word);
  215. procedure InitializeCommandHandlers; override;
  216. procedure ListDirectory(ASender: TIdFTPServerThread; ADirectory: string;
  217. var ADirContents: TstringList; ADetails: Boolean);
  218. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  219. procedure SetAnonymousAccounts(const AValue: TstringList);
  220. procedure SetHelpReply(const AValue: Tstrings);
  221. procedure SetUserAccounts(const AValue: TIdUserManager);
  222. procedure SetEmulateSystem(const AValue: TIdFTPSystems);
  223. procedure ThreadException(AThread: TIdThread; AException: Exception);
  224. public
  225. constructor Create(AOwner: TComponent); override;
  226. destructor Destroy; override;
  227. published
  228. property AllowAnonymousLogin: Boolean read FAllowAnonymousLogin write FAllowAnonymousLogin default Id_DEF_AllowAnon;
  229. property AnonymousAccounts: TStringList read FAnonymousAccounts write SetAnonymousAccounts;
  230. property AnonymousPassStrictCheck: Boolean read FAnonymousPassStrictCheck
  231. write FAnonymousPassStrictCheck default Id_DEF_PassStrictCheck;
  232. property DefaultDataPort : Integer read FDefaultDataPort write FDefaultDataPort default IdPORT_FTP_DATA;
  233. property EmulateSystem: TIdFTPSystems read FEmulateSystem write SetEmulateSystem default Id_DEF_SystemType;
  234. property HelpReply: Tstrings read FHelpReply write SetHelpReply;
  235. property UserAccounts: TIdUserManager read FUserAccounts write SetUserAccounts;
  236. property SystemType: string read FSystemType write FSystemType;
  237. property OnAfterUserLogin: TOnAfterUserLoginEvent read FOnAfterUserLogin
  238. write FOnAfterUserLogin;
  239. property OnChangeDirectory: TOnDirectoryEvent read FOnChangeDirectory write FOnChangeDirectory;
  240. property OnGetCustomListFormat: TIdOnGetCustomListFormat read FOnGetCustomListFormat
  241. write FOnGetCustomListFormat;
  242. property OnGetFileSize: TOnGetFileSizeEvent read FOnGetFileSize write FOnGetFileSize;
  243. property OnUserLogin: TOnUserLoginEvent read FOnUserLogin write FOnUserLogin;
  244. property OnListDirectory: TOnListDirectoryEvent read FOnListDirectory write FOnListDirectory;
  245. property OnRenameFile: TOnRenameFileEvent read FOnRenameFile write FOnRenameFile;
  246. property OnDeleteFile: TOnFileEvent read FOnDeleteFile write FOnDeleteFile;
  247. property OnRetrieveFile: TOnRetrieveFileEvent read FOnRetrieveFile write FOnRetrieveFile;
  248. property OnStoreFile: TOnStoreFileEvent read FOnStoreFile write FOnStoreFile;
  249. property OnMakeDirectory: TOnDirectoryEvent read FOnMakeDirectory write FOnMakeDirectory;
  250. property OnRemoveDirectory: TOnDirectoryEvent read FOnRemoveDirectory write FOnRemoveDirectory;
  251. {
  252. READ THIS!!!
  253. Do not change values in the OnPASV event unless you have a compelling reason to do so.
  254. }
  255. property OnPASV : TOnPASVEvent read FOnPASV write FOnPASV;
  256. end;
  257. implementation
  258. uses
  259. IdGlobal,
  260. IdIOHandlerSocket,
  261. IdResourcestrings,
  262. IdSimpleServer,
  263. IdSocketHandle,
  264. Idstrings,
  265. IdTCPClient,
  266. IdEMailAddress;
  267. function TranslatePath(const ACurrentDir, AParam: String; const ASystem: TIdFTPSystems): String;
  268. begin
  269. if ASystem = ftpsDOS then begin
  270. Result := ProcessPath(ACurrentDir, AParam, '\'); {Do not Localize}
  271. end else begin
  272. Result := ProcessPath(ACurrentDir, AParam);
  273. end;
  274. end;
  275. { TIdDataChannelThread }
  276. constructor TIdDataChannelThread.Create(APASV: Boolean; AControlConnection: TIdTCPServerConnection);
  277. begin
  278. inherited Create;
  279. StopMode := smSuspend;
  280. FOKReply := TIdRFCReply.Create(nil);
  281. FErrorReply := TIdRFCReply.Create(nil);
  282. FControlChannel := AControlConnection;
  283. if APASV then begin
  284. FDataChannel := TIdSimpleServer.Create(nil);
  285. TIdSimpleServer(FDataChannel).BoundIP := TIdIOHandlerSocket(FControlChannel.IOHandler).Binding.IP;
  286. end else begin
  287. FDataChannel := TIdTCPClient.Create(nil);
  288. TIdTCPClient(FDataChannel).BoundPort := TIdFTPServer(FControlChannel.Server).DefaultDataPort; //Default dataport
  289. end;
  290. end;
  291. destructor TIdDataChannelThread.Destroy;
  292. begin
  293. FreeAndNil(FOKReply);
  294. FreeAndNil(FErrorReply);
  295. FreeAndNil(FDataChannel);
  296. inherited Destroy;
  297. end;
  298. procedure TIdDataChannelThread.StartThread(AOperation: TIdFTPOperation);
  299. begin
  300. FFtpOperation := AOperation; try
  301. if FDataChannel is TIdSimpleServer then begin
  302. TIdSimpleServer(FDataChannel).Listen;
  303. end else if FDataChannel is TIdTCPClient then begin
  304. TIdTCPClient(FDataChannel).Connect;
  305. end;
  306. except
  307. FControlChannel.WriteRFCReply(FErrorReply); //426
  308. raise;
  309. end;
  310. inherited Start;
  311. end;
  312. procedure TIdDataChannelThread.Run;
  313. var
  314. LStrStream: TMemoryStream; //is faster than StringStream
  315. begin
  316. try
  317. try
  318. try
  319. try
  320. if Data is TStream then begin
  321. case FFtpOperation of
  322. ftpRetr: FDataChannel.WriteStream(TStream(Data),False);
  323. ftpStor: FDataChannel.ReadStream(TStream(Data), -1, True);
  324. end;
  325. end else begin
  326. case FFtpOperation of
  327. ftpRetr: FDataChannel.Writestrings(Data as Tstrings);
  328. ftpStor:
  329. begin
  330. LStrStream := TMemoryStream.Create;
  331. try
  332. FDataChannel.ReadStream(LStrStream, -1, True);
  333. SplitLines(LStrStream.Memory, LStrStream.Size,TStrings(Data));
  334. finally
  335. FreeAndNil(LStrStream);
  336. end;
  337. end;//ftpStor
  338. end;//case
  339. end;
  340. finally
  341. FreeAndNIL(FData);
  342. end;
  343. finally
  344. FDataChannel.Disconnect;
  345. end;
  346. FControlChannel.WriteRFCReply(FOKReply); //226
  347. except
  348. FControlChannel.WriteRFCReply(FErrorReply); //426
  349. end;
  350. finally Stop; end;
  351. end;
  352. procedure TIdDataChannelThread.SetupDataChannel(const AIP: string; APort: Integer);
  353. begin
  354. if FDataChannel is TIdSimpleServer then begin
  355. with TIdSimpleServer(FDataChannel) do begin
  356. BoundIP := AIP;
  357. BoundPort := APort;
  358. end;
  359. end else begin
  360. with TIdTCPClient(FDataChannel) do begin
  361. Host := AIP;
  362. Port := APort;
  363. end;
  364. end;
  365. end;
  366. procedure TIdDataChannelThread.SetErrorReply(const AValue: TIdRFCReply);
  367. begin
  368. FErrorReply.Assign(AValue);
  369. end;
  370. procedure TIdDataChannelThread.SetOKReply(const AValue: TIdRFCReply);
  371. begin
  372. FOKReply.Assign(AValue);
  373. end;
  374. { TIdFTPClient }
  375. constructor TIdFTPServerThread.Create(ACreateSuspended: Boolean = True);
  376. begin
  377. inherited Create(ACreateSuspended);
  378. ReInitialize;
  379. end;
  380. procedure TIdFTPServerThread.TerminateAndFreeDataChannel;
  381. Begin
  382. if Assigned(FDataChannelThread) then begin
  383. FDataChannelThread.Terminate; //set Terminated flag
  384. FDataChannelThread.Start; //can be stopped
  385. FreeAndNIL(FDataChannelThread);
  386. end;
  387. End;//
  388. destructor TIdFTPServerThread.Destroy;
  389. begin
  390. TerminateAndFreeDataChannel;
  391. inherited Destroy;
  392. end;
  393. procedure TIdFTPServerThread.CreateDataChannel(APASV: Boolean = False);
  394. begin
  395. {APR 020423. We must cache it, but in future:
  396. if assigned(FDataChannelThread) and not APASV then begin
  397. exit; // we already have one.
  398. end;}
  399. TerminateAndFreeDataChannel; //let the old one terminate
  400. FDataChannelThread := TIdDataChannelThread.Create(APASV, Connection);
  401. FDataChannelThread.OnException := TIdFTPServer(Connection.Server).ThreadException;
  402. //APR 020423 FDataChannelThread.FreeOnTerminate := True;
  403. end;
  404. procedure TIdFTPServerThread.KillDataChannel;
  405. begin
  406. with FDataChannelThread do try
  407. if not Stopped then begin
  408. FDataChannel.DisconnectSocket;
  409. StopMode:=smTerminate; // otherwise the waitfor on the next line waits forever.
  410. WaitFor;
  411. end;
  412. except
  413. { absorb }
  414. end;
  415. end;
  416. procedure TIdFTPServerThread.ReInitialize;
  417. begin
  418. UserType := utNone;
  419. FAuthenticated := False;
  420. FALLOSize := 0;
  421. FCurrentDir := '/'; {Do not Localize}
  422. FDataType := ftASCII;
  423. FDataMode := dmStream;
  424. FDataPort := 0;
  425. FDataStruct := dsFile;
  426. FHomeDir := ''; {Do not Localize}
  427. FUsername := ''; {Do not Localize}
  428. FPassword := ''; {Do not Localize}
  429. FPASV := False;
  430. FRESTPos := 0;
  431. FRNFR := ''; {Do not Localize}
  432. end;
  433. function TIdFTPServerThread.IsAuthenticated(ASender: TIdCommand): Boolean;
  434. begin
  435. if not FAuthenticated then begin
  436. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  437. end
  438. else begin
  439. if Assigned(FDataChannelThread) then begin
  440. if not FDataChannelThread.Stopped and
  441. not AnsiSameText(ASender.CommandHandler.Command, 'ABOR') and {Do not Localize}
  442. not AnsiSameText(ASender.CommandHandler.Command, #$FF#$F4#$FF#$FF'ABOR') // ABOR with telnet escape {Do not Localize}
  443. then begin
  444. Result := False;
  445. Exit;
  446. end;
  447. end;
  448. end;
  449. Result := FAuthenticated;
  450. end;
  451. { TIdFTPServer }
  452. constructor TIdFTPServer.Create(AOwner: TComponent);
  453. begin
  454. inherited Create(AOwner);
  455. FAnonymousAccounts := TstringList.Create;
  456. // By default these user names will be treated as anonymous.
  457. with FAnonymousAccounts do begin
  458. Add('anonymous'); { do not localize }
  459. Add('ftp'); { do not localize }
  460. Add('guest'); { do not localize }
  461. end;
  462. FAllowAnonymousLogin := Id_DEF_AllowAnon;
  463. FAnonymousPassStrictCheck := Id_DEF_PassStrictCheck;
  464. DefaultPort := IDPORT_FTP;
  465. DefaultDataPort := IdPORT_FTP_DATA;
  466. FEmulateSystem := Id_DEF_SystemType;
  467. Greeting.NumericCode := 220;
  468. Greeting.Text.Text := RSFTPDefaultGreeting;
  469. FHelpReply := TstringList.Create;
  470. ThreadClass := TIdFTPServerThread;
  471. ReplyUnknownCommand.NumericCode := 500;
  472. ReplyUnknownCommand.Text.Text := RSFTPCmdSyntaxError;
  473. FUserAccounts := nil;
  474. FSystemType := Id_OS_Win32; {Do not Localize}
  475. end;
  476. procedure TIdFTPServer.InitializeCommandHandlers;
  477. begin
  478. inherited;
  479. //ACCESS CONTROL COMMANDS
  480. //USER <SP> <username> <CRLF>
  481. with CommandHandlers.Add do begin
  482. Command := 'USER'; {Do not Localize}
  483. OnCommand := CommandUSER;
  484. end;
  485. //PASS <SP> <password> <CRLF>
  486. with CommandHandlers.Add do begin
  487. Command := 'PASS'; {Do not Localize}
  488. OnCommand := CommandPASS;
  489. end;
  490. //ACCT <SP> <account-information> <CRLF>
  491. with CommandHandlers.Add do begin
  492. Command := 'ACCT'; {Do not Localize}
  493. ReplyNormal.NumericCode := 202;
  494. ReplyNormal.Text.Text := Format(RSFTPCmdNotImplemented, ['ACCT']); {Do not Localize}
  495. end;
  496. //CWD <SP> <pathname> <CRLF>
  497. with CommandHandlers.Add do begin
  498. Command := 'CWD'; {Do not Localize}
  499. OnCommand := CommandCWD;
  500. ReplyExceptionCode := 550;
  501. end;
  502. //CDUP <CRLF>
  503. with CommandHandlers.Add do begin
  504. Command := 'CDUP'; {Do not Localize}
  505. OnCommand := CommandCDUP;
  506. ReplyExceptionCode := 550;
  507. end;
  508. //SMNT <SP> <pathname> <CRLF>
  509. with CommandHandlers.Add do begin
  510. Command := 'SMNT'; {Do not Localize}
  511. ReplyNormal.NumericCode := 250;
  512. ReplyNormal.Text.Text := RSFTPFileActionCompleted;
  513. end;
  514. //QUIT <CRLF>
  515. with CommandHandlers.Add do begin
  516. Command := 'QUIT'; {Do not Localize}
  517. Disconnect := True;
  518. ReplyNormal.NumericCode := 221;
  519. ReplyNormal.Text.Text := 'Goodbye.'; {Do not Localize}
  520. end;
  521. //REIN <CRLF>
  522. with CommandHandlers.Add do begin
  523. Command := 'REIN'; {Do not Localize}
  524. OnCommand := CommandREIN;
  525. end;
  526. //PORT <SP> <host-port> <CRLF>
  527. with CommandHandlers.Add do begin
  528. Command := 'PORT'; {Do not Localize}
  529. OnCommand := CommandPORT;
  530. end;
  531. //PASV <CRLF>
  532. with CommandHandlers.Add do begin
  533. Command := 'PASV'; {Do not Localize}
  534. OnCommand := CommandPASV;
  535. end;
  536. //TYPE <SP> <type-code> <CRLF>
  537. with CommandHandlers.Add do begin
  538. Command := 'TYPE'; {Do not Localize}
  539. OnCommand := CommandTYPE;
  540. end;
  541. //STRU <SP> <structure-code> <CRLF>
  542. with CommandHandlers.Add do begin
  543. Command := 'STRU'; {Do not Localize}
  544. OnCommand := CommandSTRU;
  545. end;
  546. //MODE <SP> <mode-code> <CRLF>
  547. with CommandHandlers.Add do begin
  548. Command := 'MODE'; {Do not Localize}
  549. OnCommand := CommandMODE;
  550. end;
  551. //FTP SERVICE COMMANDS
  552. //RETR <SP> <pathname> <CRLF>
  553. with CommandHandlers.Add do begin
  554. Command := 'RETR'; {Do not Localize}
  555. OnCommand := CommandRETR;
  556. ReplyExceptionCode := 550;
  557. end;
  558. //STOR <SP> <pathname> <CRLF>
  559. with CommandHandlers.Add do begin
  560. Command := 'STOR'; {Do not Localize}
  561. OnCommand := CommandSSAP;
  562. ReplyExceptionCode := 550;
  563. end;
  564. //STOU <CRLF>
  565. with CommandHandlers.Add do begin
  566. Command := 'STOU'; {Do not Localize}
  567. OnCommand := CommandSSAP;
  568. ReplyExceptionCode := 550;
  569. end;
  570. //APPE <SP> <pathname> <CRLF>
  571. with CommandHandlers.Add do begin
  572. Command := 'APPE'; {Do not Localize}
  573. OnCommand := CommandSSAP;
  574. ReplyExceptionCode := 550;
  575. end;
  576. //ALLO <SP> <decimal-integer>
  577. // [<SP> R <SP> <decimal-integer>] <CRLF>
  578. with CommandHandlers.Add do begin
  579. Command := 'ALLO'; {Do not Localize}
  580. OnCommand := CommandALLO;
  581. end;
  582. //REST <SP> <marker> <CRLF>
  583. with CommandHandlers.Add do begin
  584. Command := 'REST'; {Do not Localize}
  585. OnCommand := CommandREST;
  586. end;
  587. //RNFR <SP> <pathname> <CRLF>
  588. with CommandHandlers.Add do begin
  589. Command := 'RNFR'; {Do not Localize}
  590. OnCommand := CommandRNFR;
  591. end;
  592. //RNTO <SP> <pathname> <CRLF>
  593. with CommandHandlers.Add do begin
  594. Command := 'RNTO'; {Do not Localize}
  595. OnCommand := CommandRNTO;
  596. end;
  597. //ABOR <CRLF>
  598. with CommandHandlers.Add do begin
  599. Command := 'ABOR'; {Do not Localize}
  600. OnCommand := CommandABOR;
  601. end;
  602. //ABOR <CRLF>
  603. with CommandHandlers.Add do begin // ABOR with telnet escape
  604. Command := #$FF#$F4#$FF#$FF'ABOR'; {Do not Localize}
  605. OnCommand := CommandABOR;
  606. end;
  607. //DELE <SP> <pathname> <CRLF>
  608. with CommandHandlers.Add do begin
  609. Command := 'DELE'; {Do not Localize}
  610. OnCommand := CommandDELE;
  611. end;
  612. //RMD <SP> <pathname> <CRLF>
  613. with CommandHandlers.Add do begin
  614. Command := 'RMD'; {Do not Localize}
  615. OnCommand := CommandRMD;
  616. end;
  617. //MKD <SP> <pathname> <CRLF>
  618. with CommandHandlers.Add do begin
  619. Command := 'MKD'; {Do not Localize}
  620. OnCommand := CommandMKD;
  621. end;
  622. //PWD <CRLF>
  623. with CommandHandlers.Add do begin
  624. Command := 'PWD'; {Do not Localize}
  625. OnCommand := CommandPWD;
  626. end;
  627. //LIST [<SP> <pathname>] <CRLF>
  628. FCmdHandlerList := CommandHandlers.Add;
  629. with FCmdHandlerList do begin
  630. Command := 'LIST'; {Do not Localize}
  631. OnCommand := CommandLIST;
  632. end;
  633. //NLST [<SP> <pathname>] <CRLF>
  634. FCmdHandlerNlst := CommandHandlers.Add;
  635. with FCmdHandlerNlst do begin
  636. Command := 'NLST'; {Do not Localize}
  637. OnCommand := CommandLIST;
  638. end;
  639. //SITE <SP> <string> <CRLF>
  640. with CommandHandlers.Add do begin
  641. Command := 'SITE'; {Do not Localize}
  642. OnCommand := CommandSITE;
  643. end;
  644. //SYST <CRLF>
  645. with CommandHandlers.Add do begin
  646. Command := 'SYST'; {Do not Localize}
  647. OnCommand := CommandSYST;
  648. end;
  649. //STAT [<SP> <pathname>] <CRLF>
  650. with CommandHandlers.Add do begin
  651. Command := 'STAT'; {Do not Localize}
  652. OnCommand := CommandSTAT;
  653. end;
  654. //HELP [<SP> <string>] <CRLF>
  655. with CommandHandlers.Add do begin
  656. Command := 'HELP'; {Do not Localize}
  657. ReplyNormal.NumericCode := 214;
  658. //
  659. if Length(FHelpReply.Text) <> 0 then
  660. ReplyNormal.Text := FHelpReply
  661. else
  662. ReplyNormal.Text.Text := 'HELP Command'; {Do not Localize}
  663. end;
  664. //NOOP <CRLF>
  665. with CommandHandlers.Add do begin
  666. Command := 'NOOP'; {Do not Localize}
  667. ReplyNormal.NumericCode := 200;
  668. ReplyNormal.Text.Text := Format(RSFTPCmdSuccessful, ['NOOP']); {Do not Localize}
  669. end;
  670. with CommandHandlers.Add do begin
  671. Command := 'XMKD'; {Do not Localize}
  672. OnCommand := CommandMKD;
  673. end;
  674. with CommandHandlers.Add do begin
  675. Command := 'XRMD'; {Do not Localize}
  676. OnCommand := CommandRMD;
  677. end;
  678. with CommandHandlers.Add do begin
  679. Command := 'XPWD'; {Do not Localize}
  680. OnCommand := CommandPWD;
  681. end;
  682. with CommandHandlers.Add do begin
  683. Command := 'XCUP'; {Do not Localize}
  684. OnCommand := CommandCDUP;
  685. end;
  686. with CommandHandlers.Add do begin
  687. Command := 'FEAT'; {Do not Localize}
  688. OnCommand := CommandFEAT;
  689. end;
  690. //TODO: OPTS - what is this for? Cannot find in RFC 959
  691. with CommandHandlers.Add do begin
  692. Command := 'OPTS'; {Do not Localize}
  693. OnCommand := CommandOPTS;
  694. end;
  695. //SIZE [<FILE>] CRLF
  696. with CommandHandlers.Add do begin
  697. Command := 'SIZE'; {Do not Localize}
  698. OnCommand := CommandSIZE;
  699. end;
  700. end;
  701. destructor TIdFTPServer.Destroy;
  702. begin
  703. FreeAndNil(FAnonymousAccounts);
  704. FreeAndNil(FHelpReply);
  705. inherited Destroy;
  706. end;
  707. procedure TIdFTPServer.ListDirectory(ASender: TIdFTPServerThread; ADirectory: string;
  708. var ADirContents: TstringList; ADetails: Boolean);
  709. var
  710. i: Integer;
  711. LDirectoryList: TIdFTPListItems;
  712. LPathSep: string;
  713. begin
  714. if Assigned(FOnListDirectory) then begin
  715. LDirectoryList := TIdFTPListItems.Create; try
  716. LPathSep := '/'; {Do not Localize}
  717. // Emulated System
  718. case FEmulateSystem of
  719. ftpsOther: begin
  720. if Assigned(OnGetCustomListFormat) then begin
  721. LDirectoryList.ListFormat := flfCustom;
  722. LDirectoryList.OnGetCustomListFormat := DoGetCustomListFormat;
  723. end else begin
  724. LDirectoryList.ListFormat := flfNone;
  725. end;
  726. end;
  727. ftpsDOS: begin
  728. LDirectoryList.ListFormat := flfDos;
  729. LPathSep := '\'; {Do not Localize}
  730. end;
  731. ftpsUNIX: begin
  732. LDirectoryList.ListFormat := flfUnix;
  733. end;
  734. ftpsVAX: begin
  735. LDirectoryList.ListFormat := flfVax;
  736. end;
  737. end;
  738. if Copy(ADirectory, Length(LPathSep), 1) <> LPathSep then begin
  739. ADirectory := ADirectory + LPathSep;
  740. end;
  741. // Event
  742. FOnListDirectory(ASender, ADirectory, LDirectoryList);
  743. for i := 0 to LDirectoryList.Count - 1 do begin
  744. if ADetails then begin
  745. ADirContents.Add(LDirectoryList.Items[i].Text);
  746. end else begin
  747. ADirContents.Add(LDirectoryList.Items[i].Filename);
  748. end;
  749. end;
  750. finally FreeAndNil(LDirectoryList); end;
  751. end else begin
  752. raise EIdFTPServerNoOnListDirectory.Create(RSFTPNoOnDirEvent); {Do not Localize}
  753. end;
  754. end;
  755. procedure TIdFTPServer.SetHelpReply(const AValue: Tstrings);
  756. begin
  757. FHelpReply.Assign(AValue);
  758. end;
  759. procedure TIdFTPServer.SetUserAccounts(const AValue: TIdUserManager);
  760. begin
  761. FUserAccounts := AValue;
  762. if Assigned(FUserAccounts) then
  763. begin
  764. FUserAccounts.FreeNotification(Self);
  765. end;
  766. end;
  767. procedure TIdFTPServer.Notification(AComponent: TComponent; Operation: TOperation);
  768. begin
  769. inherited Notification(AComponent, Operation);
  770. if (Operation = opRemove) and (AComponent = FUserAccounts) then
  771. FUserAccounts := nil;
  772. end;
  773. procedure TIdFTPServer.SetAnonymousAccounts(const AValue: TstringList);
  774. begin
  775. if Assigned(AValue) then
  776. begin
  777. FAnonymousAccounts.Assign(AValue);
  778. end;
  779. end;
  780. procedure TIdFTPServer.SetEmulateSystem(const AValue: TIdFTPSystems);
  781. begin
  782. if AnsiSameText(FSystemType, 'Windows 9x/NT.') or AnsiSameText(FSystemType, 'UNIX type: L8.') then {Do not Localize}
  783. begin
  784. case AValue of
  785. ftpsDOS: FSystemType := 'Windows 9x/NT.'; {Do not Localize}
  786. ftpsUNIX,
  787. ftpsVAX: FSystemType := 'UNIX type: L8.'; {Do not Localize}
  788. end;
  789. end;
  790. FEmulateSystem := AValue;
  791. end;
  792. procedure TIdFTPServer.ThreadException(AThread: TIdThread;
  793. AException: Exception);
  794. begin
  795. ShowException(AException, nil);
  796. end;
  797. //Command Replies/Handling
  798. procedure TIdFTPServer.CommandUSER(ASender: TIdCommand);
  799. begin
  800. with TIdFTPServerThread(ASender.Thread) do begin
  801. if (FAnonymousAccounts.IndexOf(Lowercase(ASender.UnparsedParams)) >= 0)
  802. and (AllowAnonymousLogin) then begin
  803. UserType := utAnonymousUser;
  804. FUsername := ASender.UnparsedParams;
  805. ASender.Reply.SetReply(331, RSFTPAnonymousUserOkay);
  806. end else begin
  807. UserType := utNormalUser;
  808. if Length(ASender.UnparsedParams) > 0 then begin
  809. FUsername := ASender.UnparsedParams;
  810. ASender.Reply.SetReply(331, RSFTPUserOkay);
  811. end else begin
  812. ASender.Reply.SetReply(332, RSFTPNeedAccountForLogin);
  813. end;
  814. end;
  815. end;
  816. end;
  817. procedure TIdFTPServer.CommandPASS(ASender: TIdCommand);
  818. var
  819. LValidated: Boolean;
  820. begin
  821. with TIdFTPServerThread(ASender.Thread) do begin
  822. case FUserType of
  823. utAnonymousUser:
  824. begin
  825. LValidated := Length(ASender.UnparsedParams) > 0;
  826. if FAnonymousPassStrictCheck and LValidated then begin
  827. LValidated := False;
  828. if FindFirstOf('@.', ASender.UnparsedParams) > 0 then begin {Do not Localize}
  829. LValidated := True;
  830. end;
  831. end;
  832. if LValidated then begin
  833. FAuthenticated := True;
  834. FPassword := ASender.UnparsedParams;
  835. ASender.Reply.SetReply(230, RSFTPAnonymousUserLogged);
  836. end else begin
  837. FUserType := utNone;
  838. FAuthenticated := False;
  839. FPassword := ''; {Do not Localize}
  840. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  841. end;
  842. end;//utAnonymousUser
  843. utNormalUser:
  844. begin
  845. if Assigned(FUserAccounts) then begin
  846. FAuthenticated := FUserAccounts.AuthenticateUser(FUsername, ASender.UnparsedParams);
  847. if FAuthenticated then begin
  848. FPassword := ASender.UnparsedParams;
  849. ASender.Reply.SetReply(230, RSFTPUserLogged);
  850. end else begin
  851. FPassword := ''; {Do not Localize}
  852. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  853. end;
  854. end
  855. else if Assigned(FOnUserLogin) then begin
  856. LValidated := False;
  857. FOnUserLogin(TIdFTPServerThread(ASender.Thread), FUsername, ASender.UnparsedParams, LValidated);
  858. FAuthenticated := LValidated;
  859. if LValidated then begin
  860. FPassword := ASender.UnparsedParams;
  861. ASender.Reply.SetReply(230, RSFTPUserLogged);
  862. end else begin
  863. FPassword := ''; {Do not Localize}
  864. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn);
  865. end;
  866. end
  867. //APR 020423
  868. else begin
  869. ASender.Reply.SetReply(530, RSFTPUserNotLoggedIn); // user manager not found
  870. end;
  871. end;//utNormalUser
  872. else
  873. ASender.Reply.SetReply(503, RSFTPNeedLoginWithUser);
  874. end;//case
  875. end;//with
  876. //After login
  877. if TIdFTPServerThread(ASender.Thread).FAuthenticated and Assigned(FOnAfterUserLogin) then begin
  878. FOnAfterUserLogin(TIdFTPServerThread(ASender.Thread));
  879. end;
  880. end;
  881. procedure TIdFTPServer.CommandCWD(ASender: TIdCommand);
  882. var
  883. s: string;
  884. begin
  885. with TIdFTPServerThread(ASender.Thread) do begin
  886. if IsAuthenticated(ASender) then begin
  887. if Assigned(OnChangeDirectory) then begin
  888. s := TranslatePath(FCurrentDir, ASender.UnparsedParams, FEmulateSystem);
  889. DoChangeDirectory(TIdFTPServerThread(ASender.Thread), s);
  890. ASender.Reply.SetReply(250, Format(RSFTPCmdSuccessful, ['CWD'])); {Do not Localize}
  891. FCurrentDir := s;
  892. end else begin
  893. ASender.Reply.SetReply(550, Format(RSFTPCmdNotImplemented, ['CWD'])); {Do not Localize}
  894. end;
  895. end;
  896. end;
  897. end;
  898. procedure TIdFTPServer.CommandCDUP(ASender: TIdCommand);
  899. var
  900. s: string;
  901. begin
  902. with TIdFTPServerThread(ASender.Thread) do begin
  903. if IsAuthenticated(ASender) then begin
  904. case FEmulateSystem of
  905. ftpsDOS: s := '..\'; {Do not Localize}
  906. ftpsOther, ftpsUNIX, ftpsVAX: s := '../'; {Do not Localize}
  907. end;
  908. if Assigned(FOnChangeDirectory) then begin
  909. DoChangeDirectory(TIdFTPServerThread(ASender.Thread), s);
  910. FCurrentDir := s;
  911. ASender.Reply.SetReply(212, Format(RSFTPCurrentDirectoryIs, [FCurrentDir]));
  912. end else begin
  913. ASender.Reply.SetReply(550, Format(RSFTPCmdNotImplemented, ['CWD'])); {Do not Localize}
  914. end;
  915. end;
  916. end;
  917. end;
  918. procedure TIdFTPServer.CommandREIN(ASender: TIdCommand);
  919. begin
  920. with TIdFTPServerThread(ASender.Thread) do
  921. begin
  922. if IsAuthenticated(ASender) then
  923. begin
  924. ReInitialize;
  925. ASender.Reply.SetReply(220, RSFTPServiceOpen);
  926. end;
  927. end;
  928. end;
  929. procedure TIdFTPServer.CommandPORT(ASender: TIdCommand);
  930. var
  931. LLo, LHi: Integer;
  932. LParm, IP: string;
  933. begin
  934. with TIdFTPServerThread(ASender.Thread) do begin
  935. if IsAuthenticated(ASender) then begin
  936. FPASV := False;
  937. LParm := ASender.UnparsedParams;
  938. IP := ''; {Do not Localize}
  939. { h1 }
  940. IP := IP + Fetch(LParm, ',') + '.'; {Do not Localize}
  941. { h2 }
  942. IP := IP + Fetch(LParm, ',') + '.'; {Do not Localize}
  943. { h3 }
  944. IP := IP + Fetch(LParm, ',') + '.'; {Do not Localize}
  945. { h4 }
  946. IP := IP + Fetch(LParm, ','); {Do not Localize}
  947. { p1 }
  948. LLo := StrToInt(Fetch(LParm, ',')); {Do not Localize}
  949. { p2 }
  950. LHi := StrToInt(LParm);
  951. FDataPort := (LLo * 256) + LHi;
  952. CreateDataChannel(False);
  953. FDataChannelThread.SetupDataChannel(IP, FDataPort);
  954. ASender.Reply.SetReply(200, Format(RSFTPCmdSuccessful, ['PORT'])); {Do not Localize}
  955. end;
  956. end;
  957. end;
  958. procedure TIdFTPServer.CommandPASV(ASender: TIdCommand);
  959. var
  960. LParam: string;
  961. LBPort: Word;
  962. LThread: TIdFTPServerThread;
  963. begin
  964. LThread := TIdFTPServerThread(ASender.Thread);
  965. with LThread do begin
  966. if IsAuthenticated(ASender) then begin
  967. LParam := TIdIOHandlerSocket(Connection.IOHandler).Binding.IP;
  968. LBPort := FDefaultDataPort;
  969. DoOnPASV(LThread, LParam, LBPort);
  970. CreateDataChannel(True);
  971. FDataChannelThread.SetupDataChannel(LParam, LBPort);
  972. with TIdSimpleServer(FDataChannelThread.FDataChannel) do begin
  973. BeginListen;
  974. LParam := BoundIP;
  975. LBPort := Binding.Port;
  976. end;
  977. FDataPort := LBPort;
  978. FPASV := True;
  979. LParam := StringReplace(LParam, '.', ',', [rfReplaceAll]) + {Do not Localize}
  980. ',' + IntToStr(LBPort div 256) + ',' + IntToStr(LBPort mod 256); {Do not Localize}
  981. ASender.Reply.SetReply(227, Format(RSFTPPassiveMode, [LParam]));
  982. end;
  983. end;
  984. end;
  985. procedure TIdFTPServer.CommandTYPE(ASender: TIdCommand);
  986. var
  987. LType: Char;
  988. begin
  989. with TIdFTPServerThread(ASender.Thread) do
  990. begin
  991. if IsAuthenticated(ASender) then
  992. begin
  993. if Length(ASender.UnparsedParams) = 1 then
  994. begin
  995. //Default data type is ASCII
  996. LType := Uppercase(ASender.UnparsedParams)[1];
  997. case LType of
  998. 'A': FDataType := ftASCII; {Do not Localize}
  999. 'I': FDataType := ftBinary; {Do not Localize}
  1000. end;
  1001. if FDataType in [ftASCII, ftBinary] then
  1002. begin
  1003. ASender.Reply.SetReply(200, Format(RSFTPTYPEChanged, [LType]));
  1004. end;
  1005. end;
  1006. end;
  1007. end;
  1008. end;
  1009. procedure TIdFTPServer.CommandSTRU(ASender: TIdCommand);
  1010. var
  1011. LDataStruct: Char;
  1012. begin
  1013. with TIdFTPServerThread(ASender.Thread) do
  1014. begin
  1015. if IsAuthenticated(ASender) then
  1016. begin
  1017. if Length(ASender.UnparsedParams) = 1 then
  1018. begin
  1019. //Default structure is file
  1020. LDataStruct := Uppercase(ASender.UnparsedParams)[1];
  1021. case LDataStruct of
  1022. 'F': FDataStruct := dsFile; {Do not Localize}
  1023. 'R': FDataStruct := dsRecord; {Do not Localize}
  1024. 'P': FDataStruct := dsPage; {Do not Localize}
  1025. end;
  1026. if FDataStruct in [dsFile, dsRecord, dsPage] then
  1027. begin
  1028. ASender.Reply.SetReply(200, Format(RSFTPSTRUChanged, [LDataStruct]));
  1029. end;
  1030. end;
  1031. end;
  1032. end;
  1033. end;
  1034. procedure TIdFTPServer.CommandMODE(ASender: TIdCommand);
  1035. var
  1036. LMode: Char;
  1037. begin
  1038. with TIdFTPServerThread(ASender.Thread) do
  1039. begin
  1040. if IsAuthenticated(ASender) then
  1041. begin
  1042. if Length(ASender.UnparsedParams) = 1 then
  1043. begin
  1044. //Default data mode is stream
  1045. LMode := Uppercase(ASender.UnparsedParams)[1];
  1046. case LMode of
  1047. 'B': FDataMode := dmBlock; {Do not Localize}
  1048. 'C': FDataMode := dmCompressed; {Do not Localize}
  1049. 'S': FDataMode := dmStream; {Do not Localize}
  1050. end;
  1051. if FDataMode in [dmBlock, dmCompressed, dmStream] then
  1052. begin
  1053. ASender.Reply.SetReply(200, Format(RSFTPMODEChanged, [LMode]));
  1054. end;
  1055. end;
  1056. end;
  1057. end;
  1058. end;
  1059. procedure TIdFTPServer.CommandRETR(ASender: TIdCommand);
  1060. var
  1061. s: string;
  1062. LStream: TStream;
  1063. begin
  1064. with TIdFTPServerThread(ASender.Thread) do begin
  1065. if IsAuthenticated(ASender) then begin
  1066. if Assigned(FOnRetrieveFile) then begin
  1067. s := TranslatePath(FCurrentDir, ASender.UnparsedParams, FEmulateSystem);
  1068. LStream := nil;
  1069. FOnRetrieveFile(TIdFTPServerThread(ASender.Thread), s, LStream);
  1070. if Assigned(LStream) then begin
  1071. LStream.Position := FRESTPos;
  1072. FRESTPos := 0;
  1073. FDataChannelThread.Data := LStream;
  1074. FDataChannelThread.OKReply.SetReply(226, RSFTPDataConnClosed);
  1075. FDataChannelThread.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  1076. ASender.Reply.SetReply(150, RSFTPDataConnToOpen);
  1077. ASender.SendReply;
  1078. FDataChannelThread.StartThread(ftpRetr);
  1079. end else begin
  1080. ASender.Reply.SetReply(550, RSFTPFileActionAborted);
  1081. end;
  1082. end else begin
  1083. ASender.Reply.SetReply(550, Format(RSFTPCmdNotImplemented, ['RETR'])); {Do not Localize}
  1084. end;
  1085. end;
  1086. end;
  1087. end;
  1088. procedure TIdFTPServer.CommandSSAP(ASender: TIdCommand);
  1089. var
  1090. LStream: TStream;
  1091. LTmp1: string;
  1092. LAppend: Boolean;
  1093. Reply: TIdRFCReply;
  1094. begin
  1095. with TIdFTPServerThread(ASender.Thread) do begin
  1096. if IsAuthenticated(ASender) then begin
  1097. if AnsiSameText(ASender.CommandHandler.Command, 'STOU') then begin {Do not Localize}
  1098. //TODO: Find a better method of finding unique names
  1099. RandSeed := 9944;
  1100. Randomize;
  1101. LTmp1 := 'Tmp' + IntToStr(Random(192)); {Do not Localize}
  1102. end else begin
  1103. LTmp1 := ASender.UnparsedParams;
  1104. end;
  1105. //
  1106. if Assigned(FOnStoreFile) then begin
  1107. LTmp1 := TranslatePath(FCurrentDir, LTmp1, FEmulateSystem);
  1108. LAppend := AnsiSameText(ASender.CommandHandler.Command, 'APPE'); {Do not Localize}
  1109. LStream := nil;
  1110. FOnStoreFile(TIdFTPServerThread(ASender.Thread), LTmp1, LAppend, LStream);
  1111. if Assigned(LStream) then begin
  1112. //Issued previously by ALLO cmd
  1113. if FALLOSize > 0 then begin
  1114. LStream.Size := FALLOSize;
  1115. end;
  1116. if LAppend then begin
  1117. LStream.Position := LStream.Size;
  1118. end else begin
  1119. LStream.Position := FRESTPos;
  1120. FRESTPos:=0;
  1121. //was: LStream.Position := 0;
  1122. end;
  1123. { Data transfer }
  1124. try
  1125. Reply := TIdRFCReply.Create(nil);
  1126. {
  1127. FDataChannelThread.Data := LStream;
  1128. Reply.SetReply(226, RSFTPDataConnClosed);
  1129. FDataChannelThread.OKReply := Reply;
  1130. Reply.SetReply(426, RSFTPDataConnClosedAbnormally);
  1131. FDataChannelThread.ErrorReply := Reply;
  1132. ASender.Reply.SetReply(150, RSFTPDataConnToOpen);
  1133. ASender.SendReply; }
  1134. FDataChannelThread.Data := LStream;
  1135. FDataChannelThread.OKReply.SetReply(226, RSFTPDataConnClosed);
  1136. FDataChannelThread.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  1137. ASender.Reply.SetReply(150, RSFTPDataConnToOpen);
  1138. ASender.SendReply;
  1139. FDataChannelThread.StartThread(ftpStor);
  1140. finally FreeAndNil(Reply); end;
  1141. end else begin
  1142. ASender.Reply.SetReply(550, RSFTPFileActionAborted);
  1143. end;
  1144. end else begin
  1145. ASender.Reply.SetReply(550, Format(RSFTPCmdNotImplemented, [ASender.CommandHandler.Command]));
  1146. end;
  1147. end;
  1148. end;
  1149. end;
  1150. procedure TIdFTPServer.CommandALLO(ASender: TIdCommand);
  1151. var
  1152. s: string;
  1153. begin
  1154. with TIdFTPServerThread(ASender.Thread) do
  1155. begin
  1156. if IsAuthenticated(ASender) then
  1157. begin
  1158. s := Uppercase(ASender.UnparsedParams);
  1159. case s[1] of
  1160. 'R': {Do not Localize}
  1161. begin
  1162. if s[2] = #32 then begin
  1163. FALLOSize := StrToIntDef(Copy(s, 2, Length(s) - 2), 0);
  1164. end;
  1165. end;
  1166. else
  1167. FALLOSize := StrToIntDef(ASender.UnparsedParams, 0);
  1168. end;
  1169. ASender.Reply.SetReply(200, Format(RSFTPCmdSuccessful, ['ALLO'])); {Do not Localize}
  1170. end;
  1171. end;
  1172. end;
  1173. procedure TIdFTPServer.CommandREST(ASender: TIdCommand);
  1174. begin
  1175. with TIdFTPServerThread(ASender.Thread) do
  1176. begin
  1177. if IsAuthenticated(ASender) then
  1178. begin
  1179. FRESTPos := StrToIntDef(ASender.UnparsedParams, 0);
  1180. ASender.Reply.SetReply(350, RSFTPFileActionPending);
  1181. end;
  1182. end;
  1183. end;
  1184. procedure TIdFTPServer.CommandRNFR(ASender: TIdCommand);
  1185. var
  1186. s: string;
  1187. begin
  1188. with TIdFTPServerThread(ASender.Thread) do
  1189. begin
  1190. if IsAuthenticated(ASender) then
  1191. begin
  1192. s := ASender.UnparsedParams;
  1193. if Assigned(FOnRenameFile) then
  1194. begin
  1195. ASender.Reply.SetReply(350, RSFTPFileActionPending);
  1196. FRNFR := s;
  1197. end
  1198. else
  1199. begin
  1200. ASender.Reply.SetReply(350, RSFTPFileActionPending);
  1201. end;
  1202. end;
  1203. end;
  1204. end;
  1205. procedure TIdFTPServer.CommandRNTO(ASender: TIdCommand);
  1206. var
  1207. s: string;
  1208. begin
  1209. with TIdFTPServerThread(ASender.Thread) do
  1210. begin
  1211. if IsAuthenticated(ASender) then
  1212. begin
  1213. s := ASender.UnparsedParams;
  1214. if Assigned(FOnRenameFile) then
  1215. begin
  1216. try
  1217. FOnRenameFile(TIdFTPServerThread(ASender.Thread), FRNFR, s);
  1218. ASender.Reply.NumericCode := 250;
  1219. except
  1220. ASender.Reply.NumericCode := 550;
  1221. raise;
  1222. end;
  1223. end
  1224. else
  1225. begin
  1226. ASender.Reply.SetReply(550, RSFTPFileActionNotTaken);
  1227. end;
  1228. end;
  1229. end;
  1230. end;
  1231. procedure TIdFTPServer.CommandABOR(ASender: TIdCommand);
  1232. begin
  1233. with TIdFTPServerThread(ASender.Thread) do begin
  1234. if IsAuthenticated(ASender) then begin
  1235. if not FDataChannelThread.Stopped then begin
  1236. FDataChannelThread.OkReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  1237. FDataChannelThread.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  1238. KillDataChannel;
  1239. ASender.Reply.SetReply(226, RSFTPDataConnClosed);
  1240. end else begin
  1241. ASender.Reply.SetReply(226, Format(RSFTPCmdSuccessful, ['ABOR'])); {Do not Localize}
  1242. end;
  1243. end;
  1244. end;
  1245. end;
  1246. procedure TIdFTPServer.CommandDELE(ASender: TIdCommand);
  1247. (*
  1248. DELE <SP> <pathname> <CRLF>
  1249. 250 Requested file action okay, completed.
  1250. 450 Requested file action not taken. - File is busy
  1251. 550 Requested action not taken. - File unavailable, no access permitted, etc
  1252. 500 Syntax error, command unrecognized.
  1253. 501 Syntax error in parameters or arguments.
  1254. 502 Command not implemented.
  1255. 421 Service not available, closing control connection. - During server shutdown, etc
  1256. 530 Not logged in.
  1257. *)
  1258. //TODO: Need to set replies when not authenticated and set replynormal to 250
  1259. // do for all procs, list valid replies in comments. Or maybe default is 550
  1260. begin
  1261. with TIdFTPServerThread(ASender.Thread) do begin
  1262. if IsAuthenticated(ASender) then begin
  1263. if Assigned(FOnDeleteFile) then begin
  1264. FOnDeleteFile(TIdFTPServerThread(ASender.Thread), ASender.UnparsedParams);
  1265. ASender.Reply.SetReply(250, RSFTPFileActionCompleted);
  1266. end else begin
  1267. ASender.Reply.SetReply(550, RSFTPFileActionNotTaken);
  1268. end;
  1269. end;
  1270. end;
  1271. end;
  1272. procedure TIdFTPServer.CommandRMD(ASender: TIdCommand);
  1273. var
  1274. s: string;
  1275. begin
  1276. with TIdFTPServerThread(ASender.Thread) do begin
  1277. if IsAuthenticated(ASender) then begin
  1278. if Assigned(FOnRemoveDirectory) then begin
  1279. s := TranslatePath(FCurrentDir, ASender.UnparsedParams, FEmulateSystem);
  1280. DoRemoveDirectory(TIdFTPServerThread(ASender.Thread), s);
  1281. ASender.Reply.SetReply(250, RSFTPFileActionCompleted);
  1282. end else begin
  1283. ASender.Reply.SetReply(550, Format(RSFTPCmdNotImplemented, ['RMD'])); {Do not Localize}
  1284. end;
  1285. end;
  1286. end;
  1287. end;
  1288. procedure TIdFTPServer.CommandMKD(ASender: TIdCommand);
  1289. var
  1290. s: string;
  1291. begin
  1292. with TIdFTPServerThread(ASender.Thread) do begin
  1293. if IsAuthenticated(ASender) then begin
  1294. if Assigned(FOnMakeDirectory) then begin
  1295. s := TranslatePath(FCurrentDir, ASender.UnparsedParams, FEmulateSystem);
  1296. FOnMakeDirectory(TIdFTPServerThread(ASender.Thread), s);
  1297. ASender.Reply.SetReply(257, Format(RSFTPDirFileCreated, [s])); {Do not Localize}
  1298. end else begin
  1299. ASender.Reply.SetReply(550, Format(RSFTPCmdNotImplemented, ['MKD'])); {Do not Localize}
  1300. end;
  1301. end;
  1302. end;
  1303. end;
  1304. procedure TIdFTPServer.CommandPWD(ASender: TIdCommand);
  1305. begin
  1306. with TIdFTPServerThread(ASender.Thread) do
  1307. begin
  1308. if IsAuthenticated(ASender) then
  1309. begin
  1310. ASender.Reply.SetReply(257, Format(RSFTPCurrentDirectoryIs, [FCurrentDir]));
  1311. end;
  1312. end;
  1313. end;
  1314. procedure TIdFTPServer.CommandLIST(ASender: TIdCommand);
  1315. var
  1316. s: String;
  1317. LStream: TstringList;
  1318. begin
  1319. with TIdFTPServerThread(ASender.Thread) do begin
  1320. if IsAuthenticated(ASender) then begin
  1321. s := TranslatePath(FCurrentDir, ASender.UnparsedParams, FEmulateSystem);
  1322. LStream := TStringList.Create;
  1323. try
  1324. ListDirectory(TIdFTPServerThread(ASender.Thread), s, LStream, ASender.CommandHandler = FCmdHandlerList);
  1325. finally
  1326. FDataChannelThread.Data := LStream;
  1327. FDataChannelThread.OKReply.SetReply(226, RSFTPDataConnClosed);
  1328. FDataChannelThread.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
  1329. ASender.Reply.SetReply(125, RSFTPDataConnToOpen);
  1330. ASender.SendReply;
  1331. FDataChannelThread.StartThread(ftpRetr);
  1332. end;
  1333. end;
  1334. end;
  1335. end;
  1336. procedure TIdFTPServer.CommandSITE(ASender: TIdCommand);
  1337. var
  1338. s: string;
  1339. begin
  1340. with TIdFTPServerThread(ASender.Thread) do
  1341. begin
  1342. if IsAuthenticated(ASender) then
  1343. begin
  1344. s := Uppercase(ASender.UnparsedParams);
  1345. if AnsiSameText(s, 'HELP') then {Do not Localize}
  1346. begin
  1347. ASender.Reply.SetReply(214, RSFTPSITECmdsSupported);
  1348. end
  1349. else
  1350. begin
  1351. case FEmulateSystem of
  1352. ftpsDOS: ASender.Reply.SetReply(214, Format(RSFTPDirectorySTRU, ['MS-DOS'])); {Do not Localize}
  1353. ftpsUNIX: ASender.Reply.SetReply(214, Format(RSFTPDirectorySTRU, ['UNIX'])); {Do not Localize}
  1354. ftpsVAX: ASender.Reply.SetReply(214, Format(RSFTPDirectorySTRU, ['VAX/VMS'])); {Do not Localize}
  1355. end;
  1356. end;
  1357. end;
  1358. end;
  1359. end;
  1360. procedure TIdFTPServer.CommandSYST(ASender: TIdCommand);
  1361. begin
  1362. with TIdFTPServerThread(ASender.Thread) do begin
  1363. if IsAuthenticated(ASender) then begin
  1364. ASender.Reply.SetReply(215, FSystemType);
  1365. end;
  1366. end;
  1367. end;
  1368. procedure TIdFTPServer.CommandSTAT(ASender: TIdCommand);
  1369. var
  1370. s: string;
  1371. LStream: TstringList;
  1372. begin
  1373. with TIdFTPServerThread(ASender.Thread) do begin
  1374. if IsAuthenticated(ASender) then begin
  1375. if not FDataChannelThread.Stopped then begin //was .Suspended
  1376. ASender.Reply.SetReply(211, RSFTPOpenDataConn);
  1377. end;
  1378. //else act as LIST command without a data channel
  1379. ASender.Reply.SetReply(211, RSFTPDataConnToOpen);
  1380. ASender.SendReply;
  1381. s := TranslatePath(FCurrentDir, ASender.UnparsedParams, FEmulateSystem);
  1382. LStream := TStringList.Create;
  1383. try
  1384. ListDirectory(TIdFTPServerThread(ASender.Thread), s, LStream, True);
  1385. finally
  1386. Connection.WriteStrings(LStream);
  1387. FreeAndNil(LStream);
  1388. end;
  1389. ASender.Reply.SetReply(211, RSFTPCmdEndOfStat);
  1390. end;
  1391. end;
  1392. end;
  1393. procedure TIdFTPServer.CommandFEAT(ASender: TIdCommand);
  1394. begin
  1395. with TIdFTPServerThread(ASender.Thread) do begin
  1396. begin
  1397. ASender.Reply.SetReply(502,RSFTPCmdSyntaxError);
  1398. end;
  1399. end;
  1400. end;
  1401. procedure TIdFTPServer.CommandOPTS(ASender: TIdCommand);
  1402. var
  1403. s: string;
  1404. begin
  1405. with TIdFTPServerThread(ASender.Thread) do begin
  1406. if IsAuthenticated(ASender) then begin
  1407. //TODO: Actually call event
  1408. s := ASender.UnparsedParams;
  1409. ASender.Reply.SetReply(202, Format(RSFTPCmdNotImplemented, ['OPTS'])); {Do not Localize}
  1410. end;
  1411. end;
  1412. end;
  1413. procedure TIdFTPServer.CommandSIZE(ASender: TIdCommand);
  1414. var
  1415. s: string;
  1416. LSize: Int64;
  1417. begin
  1418. with TIdFTPServerThread(ASender.Thread) do begin
  1419. if IsAuthenticated(ASender) then
  1420. begin
  1421. if Assigned(FOnGetFileSize) then
  1422. begin
  1423. s := TranslatePath(FCurrentDir, ASender.UnparsedParams, FEmulateSystem);
  1424. try
  1425. LSize := -1;
  1426. FOnGetFileSize(TIdFTPServerThread(ASender.Thread), s, LSize);
  1427. if LSize > -1 then begin
  1428. ASender.Reply.SetReply(213, IntToStr(LSize));
  1429. end else begin
  1430. ASender.Reply.SetReply(550, RSFTPFileActionAborted);
  1431. end;
  1432. except
  1433. ASender.Reply.NumericCode := 550;
  1434. raise;
  1435. end;
  1436. end else begin
  1437. ASender.Reply.SetReply(550, Format(RSFTPCmdNotImplemented, ['SIZE'])); {Do not Localize}
  1438. end;
  1439. end;
  1440. end;
  1441. end;
  1442. procedure TIdFTPServer.DoGetCustomListFormat(AItem: TIdFTPListItem; var VText: string);
  1443. begin
  1444. if Assigned(OnGetCustomListFormat) then begin
  1445. OnGetCustomListFormat(Self, AItem, VText);
  1446. end;
  1447. end;
  1448. procedure TIdFTPServer.DoChangeDirectory(AThread: TIdFTPServerThread; var VDirectory: string);
  1449. begin
  1450. if Assigned(FOnChangeDirectory) then begin
  1451. FOnChangeDirectory(AThread, VDirectory);
  1452. end;
  1453. end;
  1454. procedure TIdFTPServer.DoRemoveDirectory(AThread: TIdFTPServerThread; var VDirectory: string);
  1455. begin
  1456. if Assigned(FOnRemoveDirectory) then begin
  1457. FOnRemoveDirectory(AThread, VDirectory);
  1458. end;
  1459. end;
  1460. procedure TIdFTPServer.DoMakeDirectory(AThread: TIdFTPServerThread; var VDirectory: string);
  1461. begin
  1462. if Assigned(FOnMakeDirectory) then begin
  1463. FOnMakeDirectory(AThread, VDirectory);
  1464. end;
  1465. end;
  1466. procedure TIdFTPServer.DoOnPASV(AThread: TIdFTPServerThread; var VIP: String; var VPort: Word);
  1467. begin
  1468. if Assigned(FOnPASV) then begin
  1469. FOnPASV(AThread, VIP, VPort);
  1470. end;
  1471. end;
  1472. end.