IdMappedFTP.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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.13 2004.02.03 5:43:58 PM czhower
  18. Name changes
  19. Rev 1.12 2/2/2004 4:56:26 PM JPMugaas
  20. DotNET with fries and a Coke :-)
  21. Rev 1.11 1/21/2004 3:11:28 PM JPMugaas
  22. InitComponent
  23. Rev 1.10 2003.11.29 10:18:56 AM czhower
  24. Updated for core change to InputBuffer.
  25. Rev 1.9 2003.10.21 9:13:10 PM czhower
  26. Now compiles.
  27. Rev 1.8 9/19/2003 03:30:02 PM JPMugaas
  28. Now should compile again.
  29. Rev 1.7 3/6/2003 5:08:50 PM SGrobety
  30. Updated the read buffer methodes to fit the new core (InputBuffer ->
  31. InputBufferAsString + call to CheckForDataOnSource)
  32. Rev 1.6 4/3/2003 7:56:56 PM BGooijen
  33. Added TODO item.
  34. Rev 1.5 2/24/2003 09:14:32 PM JPMugaas
  35. Rev 1.4 1/17/2003 06:52:42 PM JPMugaas
  36. Now compiles with new framework.
  37. Rev 1.3 1-8-2003 22:20:38 BGooijen
  38. these compile (TIdContext)
  39. Rev 1.2 12/16/2002 06:59:08 PM JPMugaas
  40. MLSD added as a command requiring a data command.
  41. Rev 1.1 12/7/2002 06:43:06 PM JPMugaas
  42. These should now compile except for Socks server. IPVersion has to be a
  43. property someplace for that.
  44. Rev 1.0 11/13/2002 07:56:34 AM JPMugaas
  45. 2001.12.14 - beta preview (but FTPVC work fine ;-)
  46. }
  47. {
  48. Author: Andrew P.Rybin [[email protected]]
  49. Th (rfc959):
  50. 1) EOL = #13#10 [#10 last char]
  51. 2) reply = IntCode (three digit number) ' ' text
  52. 3) PORT h1,h2,h3,h4,p1,p2 -> Client Listen >>'200 Port command successful.'
  53. 4) PASV -> Server Listen >>'227 Entering Passive Mode (%d,%d,%d,%d,%d,%d).'
  54. Err:
  55. 426 RSFTPDataConnClosedAbnormally
  56. }
  57. //BGO: TODO: convert TIdMappedFtpDataThread to a context.
  58. unit IdMappedFTP;
  59. interface
  60. {$i IdCompilerDefines.inc}
  61. uses
  62. Classes,
  63. IdContext, IdAssignedNumbers, IdMappedPortTCP, IdStack, IdYarn,
  64. IdTCPConnection, IdThread, IdGlobal;
  65. type
  66. TIdMappedFtpDataThread = class;
  67. TIdMappedFtpContext = class(TIdMappedPortContext)
  68. protected
  69. FFtpCommand: string;
  70. FFtpParams: string;
  71. FHost, FoutboundHost: string; //local,remote(mapped)
  72. FPort, FoutboundPort: TIdPort;
  73. FDataChannelThread: TIdMappedFtpDataThread;
  74. //
  75. procedure HandleLocalClientData; override;
  76. //
  77. function GetFtpCmdLine: string; //Cmd+' '+Params {Do not Localize}
  78. procedure CreateDataChannelThread;
  79. //procedure FreeDataChannelThread;
  80. function ProcessFtpCommand: Boolean; virtual;
  81. procedure ProcessOutboundDc(const APASV: Boolean); virtual;
  82. procedure ProcessDataCommand; virtual;
  83. public
  84. constructor Create(
  85. AConnection: TIdTCPConnection;
  86. AYarn: TIdYarn;
  87. AList: TIdContextThreadList = nil
  88. ); override;
  89. property FtpCommand: string read FFtpCommand write FFtpCommand;
  90. property FtpParams: string read FFtpParams write FFtpParams;
  91. property FtpCmdLine: string read GetFtpCmdLine;
  92. property Host: string read FHost write FHost;
  93. property OutboundHost: string read FOutboundHost write FOutboundHost;
  94. property Port: TIdPort read FPort write FPort;
  95. property OutboundPort: TIdPort read FOutboundPort write FOutboundPort;
  96. property DataChannelThread: TIdMappedFtpDataThread read FDataChannelThread;
  97. end;
  98. TIdMappedFtpDataThread = class(TIdThread)
  99. protected
  100. FMappedFtpThread: TIdMappedFtpContext;
  101. FConnection: TIdTcpConnection;
  102. FOutboundClient: TIdTCPConnection;
  103. FReadList: TIdSocketList;
  104. FNetData: TIdBytes;
  105. //
  106. procedure BeforeRun; override;
  107. procedure Run; override;
  108. public
  109. constructor Create(AMappedFtpThread: TIdMappedFtpContext); reintroduce;
  110. destructor Destroy; override;
  111. property MappedFtpThread: TIdMappedFtpContext read FMappedFtpThread;
  112. property Connection: TIdTcpConnection read FConnection; //local
  113. property OutboundClient: TIdTCPConnection read FOutboundClient; //remote(mapped)
  114. property NetData: TIdBytes read FNetData write FNetData;
  115. end;
  116. TIdMappedFtpOutboundDcMode = (fdcmClient, fdcmPort, fdcmPasv);
  117. TIdMappedFTP = class(TIdMappedPortTCP)
  118. protected
  119. FOutboundDcMode: TIdMappedFtpOutboundDcMode;
  120. procedure InitComponent; override;
  121. published
  122. property DefaultPort default IdPORT_FTP;
  123. property MappedPort default IdPORT_FTP;
  124. property OutboundDcMode: TIdMappedFtpOutboundDcMode read FOutboundDcMode
  125. write FOutboundDcMode default fdcmClient;
  126. end;
  127. //=============================================================================
  128. implementation
  129. uses
  130. IdGlobalProtocols, IdIOHandlerSocket,
  131. IdResourceStringsProtocols, IdTcpClient, IdSimpleServer, IdStackConsts,
  132. SysUtils;
  133. const
  134. // iLastGetCmd = 2;
  135. saDataCommands: array[0..6] of string = (
  136. {GET}'RETR', 'LIST', 'NLST', {Do not Localize}
  137. {PUT}'STOU', 'APPE', 'STOR', {Do not localize}
  138. 'MLSD'); {Do not Localize}
  139. { TIdMappedFTP }
  140. procedure TIdMappedFTP.InitComponent;
  141. begin
  142. inherited InitComponent;
  143. DefaultPort := IdPORT_FTP;
  144. MappedPort := IdPORT_FTP;
  145. FContextClass := TIdMappedFtpContext;
  146. FOutboundDcMode := fdcmClient;
  147. end;
  148. { TIdMappedFtpContext }
  149. constructor TIdMappedFtpContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
  150. begin
  151. inherited Create(AConnection, AYarn, AList);
  152. FHost := ''; {Do not Localize}
  153. FoutboundHost := ''; {Do not Localize}
  154. FPort := 0; //system choice
  155. FoutboundPort := 0;
  156. end;
  157. procedure TIdMappedFtpContext.HandleLocalClientData;
  158. var
  159. s: String;
  160. begin
  161. repeat
  162. s := Connection.IOHandler.ReadLn; //USeR REQuest
  163. if Length(s) > 0 then
  164. begin
  165. FFtpParams := s;
  166. FFtpCommand := UpperCase(Fetch(FFtpParams, ' ', True)); {Do not Localize}
  167. FNetData := ToBytes(FtpCmdLine, Connection.IOHandler.DefStringEncoding);
  168. TIdMappedFTP(Server).DoLocalClientData(Self); //bServer
  169. if not ProcessFtpCommand then
  170. begin
  171. FOutboundClient.IOHandler.WriteLn(FtpCmdLine); //send USRREQ to FtpServer
  172. ProcessDataCommand;
  173. end;
  174. end;
  175. until Connection.IOHandler.InputBufferIsEmpty;
  176. end;
  177. procedure TIdMappedFtpContext.CreateDataChannelThread;
  178. begin
  179. FDataChannelThread := TIdMappedFtpDataThread.Create(Self);
  180. //FDataChannelThread.OnException := TIdTCPServer(FConnection.Server).OnException;
  181. end;
  182. procedure TIdMappedFtpContext.ProcessDataCommand;
  183. begin
  184. if PosInStrArray(FFtpCommand, saDataCommands) > -1 then begin
  185. FDataChannelThread.Start;
  186. end;
  187. end;
  188. function TIdMappedFtpContext.ProcessFtpCommand: Boolean;
  189. procedure ParsePort;
  190. var
  191. LLo, LHi: Integer;
  192. LParm: string;
  193. LDataChannel: TIdTCPClient;
  194. begin
  195. //1.setup local
  196. LParm := FtpParams;
  197. Host := Fetch(LParm, ',') + '.' + //h1 {Do not Localize}
  198. Fetch(LParm, ',') + '.' + //h2 {Do not Localize}
  199. Fetch(LParm, ',') + '.' + //h3 {Do not Localize}
  200. Fetch(LParm, ','); //h4 {Do not Localize}
  201. LLo := IndyStrToInt(Fetch(LParm, ',')); //p1 {Do not Localize}
  202. LHi := IndyStrToInt(LParm); //p2
  203. Port := (LLo * 256) + LHi;
  204. CreateDataChannelThread;
  205. DataChannelThread.FConnection := TIdTCPClient.Create(nil);
  206. LDataChannel := TIdTCPClient(DataChannelThread.FConnection);
  207. LDataChannel.Host := Host;
  208. LDataChannel.Port := Port;
  209. //2.setup remote (mapped)
  210. ProcessOutboundDc(False);
  211. //3. send ack to client
  212. Connection.IOHandler.WriteLn('200 ' + IndyFormat(RSFTPCmdSuccessful, ['PORT'])); {Do not Localize}
  213. end;
  214. procedure ParsePasv;
  215. var
  216. LParm: string;
  217. LDataChannel: TIdSimpleServer;
  218. begin
  219. //1.setup local
  220. Host := Connection.Socket.Binding.IP;
  221. CreateDataChannelThread;
  222. DataChannelThread.FConnection := TIdSimpleServer.Create(nil);
  223. LDataChannel := TIdSimpleServer(DataChannelThread.FConnection);
  224. LDataChannel.BoundIP := Self.Host;
  225. LDataChannel.BoundPort := Self.Port;
  226. LDataChannel.BeginListen;
  227. Host := LDataChannel.Binding.IP;
  228. Port := LDataChannel.Binding.Port;
  229. LParm := ReplaceAll(Host, '.', ','); {Do not Localize}
  230. LParm := LParm + ',' + IntToStr(Port div 256) + ',' + IntToStr(Port mod 256); {Do not Localize}
  231. //2.setup remote (mapped)
  232. ProcessOutboundDc(True);
  233. //3. send ack to client
  234. Connection.IOHandler.WriteLn('227 ' + IndyFormat(RSFTPPassiveMode, [LParm])); {Do not Localize}
  235. end;
  236. begin
  237. if FFtpCommand = 'PORT' then {Do not Localize}
  238. begin
  239. ParsePort;
  240. Result := True;
  241. end
  242. else if FFtpCommand = 'PASV' then {Do not Localize}
  243. begin
  244. ParsePasv;
  245. Result := True;
  246. end else begin
  247. Result := False; //command NOT processed
  248. end;
  249. end;
  250. procedure TIdMappedFtpContext.ProcessOutboundDc(const APASV: Boolean);
  251. var
  252. Mode: TIdMappedFtpOutboundDcMode;
  253. procedure SendPort;
  254. var
  255. LDataChannel: TIdSimpleServer;
  256. begin
  257. OutboundHost := OutboundClient.Socket.Binding.IP;
  258. DataChannelThread.FOutboundClient := TIdSimpleServer.Create(nil);
  259. LDataChannel := TIdSimpleServer(DataChannelThread.FOutboundClient);
  260. LDataChannel.BoundIP := Self.OutboundHost;
  261. LDataChannel.BoundPort := Self.OutboundPort;
  262. LDataChannel.BeginListen;
  263. OutboundHost := LDataChannel.Binding.IP;
  264. OutboundPort := LDataChannel.Binding.Port;
  265. OutboundClient.SendCmd(
  266. 'PORT ' + ReplaceAll(OutboundHost, '.', ',') + {Do not Localize}
  267. ',' + IntToStr(OutboundPort div 256) + ',' + {Do not Localize}
  268. IntToStr(OutboundPort mod 256), 200);
  269. end;
  270. procedure SendPasv;
  271. var
  272. i, bLeft, bRight: integer;
  273. s: string;
  274. LDataChannel: TIdTCPClient;
  275. begin
  276. OutboundClient.SendCmd('PASV', 227); {Do not Localize}
  277. s := Trim(OutboundClient.LastCmdResult.Text[0]);
  278. // Case 1 (Normal)
  279. // 227 Entering passive mode(100,1,1,1,23,45)
  280. bLeft := IndyPos('(', s); {Do not Localize}
  281. bRight := IndyPos(')', s); {Do not Localize}
  282. if (bLeft = 0) or (bRight = 0) then
  283. begin
  284. // Case 2
  285. // 227 Entering passive mode on 100,1,1,1,23,45
  286. bLeft := RPos(#32, s);
  287. s := Copy(s, bLeft + 1, Length(s) - bLeft);
  288. end
  289. else
  290. begin
  291. s := Copy(s, bLeft + 1, bRight - bLeft - 1);
  292. end;
  293. FOutboundHost := ''; {Do not Localize}
  294. for i := 1 to 4 do
  295. begin
  296. FOutboundHost := FOutboundHost + '.' + Fetch(s, ','); {Do not Localize}
  297. end;
  298. IdDelete(FOutboundHost, 1, 1);
  299. // Determine port
  300. FOutboundPort := IndyStrToInt(Fetch(s, ',')) * 256; {Do not Localize}
  301. FOutboundPort := FOutboundPort + IndyStrToInt(Fetch(s, ',')); {Do not Localize}
  302. DataChannelThread.FOutboundClient := TIdTCPClient.Create(nil);
  303. LDataChannel := TIdTCPClient(DataChannelThread.FOutboundClient);
  304. LDataChannel.Host := OutboundHost;
  305. LDataChannel.Port := OutboundPort;
  306. end;
  307. begin
  308. Mode := TIdMappedFtp(Server).OutboundDcMode;
  309. if Mode = fdcmClient then
  310. begin
  311. if APASV then begin
  312. Mode := fdcmPasv;
  313. end else begin
  314. Mode := fdcmPort;
  315. end;
  316. end;
  317. if Mode = fdcmPasv then begin
  318. //PASV (IfFtp.pas)
  319. SendPasv;
  320. end else begin
  321. //PORT
  322. SendPort;
  323. end;
  324. end;
  325. {TODO: procedure TIdMappedFtpContext.FreeDataChannelThread;
  326. Begin
  327. if Assigned(FDataChannelThread) then begin
  328. //TODO: çäåñü íàäî Disconnect
  329. FDataChannelThread.Terminate;
  330. FDataChannelThread:=NIL;
  331. end;
  332. End;}
  333. function TIdMappedFtpContext.GetFtpCmdLine: string;
  334. begin
  335. if Length(FFtpParams) > 0 then begin
  336. Result := FFtpCommand + ' ' + FFtpParams; {Do not Localize}
  337. end else begin
  338. Result := FFtpCommand;
  339. end;
  340. end;
  341. { TIdMappedFtpDataThread }
  342. procedure TIdMappedFtpDataThread.BeforeRun;
  343. begin
  344. inherited BeforeRun;
  345. //? Is it normal code?
  346. // TODO: check error. Send reply to client, send abort to server
  347. //1.Outbound PASV => connect
  348. if FOutboundClient is TIdTCPClient then
  349. begin
  350. TIdTCPClient(FOutboundClient).Connect;
  351. TIdSimpleServer(FConnection).Listen;
  352. end
  353. //2.Local PORT => Connect
  354. else if FOutboundClient is TIdSimpleServer then
  355. begin
  356. TIdTCPClient(FConnection).Connect;
  357. TIdSimpleServer(FOutboundClient).Listen;
  358. end;
  359. end;
  360. constructor TIdMappedFtpDataThread.Create(AMappedFtpThread: TIdMappedFtpContext);
  361. begin
  362. inherited Create(True);
  363. FMappedFtpThread := AMappedFtpThread; //owner
  364. StopMode := smTerminate;
  365. FreeOnTerminate := True;
  366. FReadList := TIdSocketList.CreateSocketList;
  367. end;
  368. destructor TIdMappedFtpDataThread.Destroy;
  369. begin
  370. FreeAndNil(FOutboundClient);
  371. FreeAndNil(FConnection);
  372. FreeAndNil(FReadList);
  373. inherited Destroy;
  374. end;
  375. procedure TIdMappedFtpDataThread.Run;
  376. var
  377. LConnectionHandle, LOutBoundHandle: TIdStackSocketHandle;
  378. LReadList: TIdSocketList;
  379. begin
  380. try
  381. try
  382. LConnectionHandle := (Connection.IOHandler as TIdIOHandlerSocket).Binding.Handle;
  383. LOutBoundHandle := (FOutboundClient.IOHandler as TIdIOHandlerSocket).Binding.Handle;
  384. FReadList.Clear;
  385. FReadList.Add(LConnectionHandle);
  386. FReadList.Add(LOutBoundHandle);
  387. LReadList := nil;
  388. if FReadList.SelectReadList(LReadList, IdTimeoutInfinite) then
  389. begin
  390. try
  391. if LReadList.ContainsSocket(LConnectionHandle) then
  392. begin
  393. Connection.IOHandler.CheckForDataOnSource(0);
  394. SetLength(FNetData, 0);
  395. Connection.IOHandler.InputBuffer.ExtractToBytes(FNetData);
  396. if Length(FNetData) > 0 then
  397. begin
  398. // TODO: DoLocalClientData(TIdMappedPortThread(AThread));//bServer
  399. FOutboundClient.IOHandler.Write(FNetData);
  400. end;
  401. end;
  402. if LReadList.ContainsSocket(LOutBoundHandle) then
  403. begin
  404. Connection.IOHandler.CheckForDataOnSource(0);
  405. SetLength(FNetData, 0);
  406. FOutboundClient.IOHandler.InputBuffer.ExtractToBytes(FNetData);
  407. if Length(FNetData) > 0 then
  408. begin
  409. // TODO: DoOutboundClientData(TIdMappedPortThread(AThread));
  410. FConnection.IOHandler.Write(FNetData);
  411. end;
  412. end;
  413. finally
  414. LReadList.Free;
  415. end;
  416. end;
  417. finally
  418. if not FOutboundClient.Connected then
  419. begin
  420. // TODO: DoOutboundDisconnect(TIdMappedPortThread(AThread));
  421. FConnection.Disconnect; //disconnect local
  422. Stop;
  423. end;
  424. if not FConnection.Connected then
  425. begin
  426. // TODO: ^^^
  427. FOutboundClient.Disconnect;
  428. Stop;
  429. end;
  430. end;
  431. except
  432. FConnection.Disconnect;
  433. FOutboundClient.Disconnect;
  434. Stop;
  435. end;
  436. end;
  437. end.