{ $HDR$} {**********************************************************************} { Unit archived using Team Coherence } { Team Coherence is Copyright 2002 by Quality Software Components } { } { For further information / comments, visit our WEB site at } { http://www.TeamCoherence.com } {**********************************************************************} {} { $Log: 15243: FTPServer_console1.dpr { { Rev 1.20 7/3/2004 3:15:50 AM JPMugaas { Checked in so everyone else can work on stuff while I'm away. } { Rev 1.19 3/15/2003 12:34:36 AM BGooijen Updated for new API } { { Rev 1.18 2/22/2003 04:56:36 AM JPMugaas { Updated for new API. } { { Rev 1.16 2/14/2003 12:10:46 PM JPMugaas { Fixed a potential security flaw where a CDUP could move up to a real { directory where the server was not intended to access. This occured with { MS-DOS emulation. } { { Rev 1.15 2/9/2003 02:59:54 PM JPMugaas { Enabled both implicit and explicit TLS support } { { Rev 1.14 2/8/2003 10:39:38 AM JPMugaas { Now fakes an inode value with Unix style and the -i switch. { Recursive dirs no longer permitted with the standard "myuser" account to { demonstrate policy with that. } { { Rev 1.13 2/8/2003 04:52:20 AM JPMugaas { FTP Server dir routines now always provide the . and .. sybmols even for { non-recursive dir listings. This is now selectively omitted with the { IdFTPList.ExportList function in an appropriate manner depending upon the { ListFormat property and if the "-A" switch is present in Unix. } { { Rev 1.12 2/5/2003 08:47:12 AM JPMugaas { Modified to support some Unix switches when in emulating Unix. More should { be supported as more is added. { The .. and . are now in the dir list only in Unix mode and only if the -A { switch was not passed. } { { Rev 1.11 2/4/2003 05:35:00 PM JPMugaas { Adjusted for new parameters. The FTP Server now also can do a recursive { listing. } { { Rev 1.10 2/3/2003 11:10:06 AM JPMugaas { Started port to Linux. Note that it still does not yet work. I also added { code for setting a "block count" so we can get a nice "total x" line for ls { -l emulation. } { { Rev 1.9 1/31/2003 01:20:04 PM JPMugaas { Now properly compiles. } { { Rev 1.8 1/31/2003 06:39:58 AM JPMugaas { Now only uses an arbitrary base directory instead of a specific drive when { refering to directories. This should prevent unintended read-write access to { a system or potential trouble (such as uploading a trojan horse). { Added an "administrative" account with the weakest security settings to { demonstrate selective security privilleges. { Disable STAT for normal users so that they can't use it to "fingerprint" a { system. Stat is still enabled on administrative account. { No longer identifies itself as the Indy Demo in the SYST command. We changed { the behavior of the SYST command and it's best to make the SYST description { as generic as possible anyway. } { { Rev 1.7 1/30/2003 02:49:40 AM JPMugaas { Updated exception handling fixes. } { { Rev 1.6 1/28/2003 04:08:54 PM JPMugaas { Updated test program for exceptions. } { { Rev 1.5 1/27/2003 05:06:30 AM JPMugaas { Added a Status information event. } { { Rev 1.4 1/27/2003 02:23:54 AM JPMugaas { Removed old commented code for an "XCRC" command since it now supported { differently (according to how IdFTP and CuteFTP Pro use it). { Commented out some code permissions and ownership since those can now use { coded defaults in the TIdFTPListItem object if none are provided. } { { Rev 1.2 1/25/2003 01:56:38 AM JPMugaas { Refinements for checksum commands. Checksum commands will now fail for dirs { instead of the connection being closed. } { Rev 1.1 1/23/2003 7:37:14 PM BGooijen fixed IdFTPServer1GetFileSize when the specified file doesn't exists, flashfxp uses this to check if the file already exists on the server. } { { Rev 1.0 1/21/2003 12:25:12 PM JPMugaas { Server Test for IdFTPServer and core. } program FTPServer_console; (* Sample of the usage of the TIdFtpServer component. Also shows how to use Indy in console apps Created by: Bas Gooijen (bas_gooijen@yahoo.com) Disclaimer: Use it at your own risk, it could contain bugs. Copyright: Freeware for all use *) {$APPTYPE console} {.$DEFINE LOGGING} { Note that the logging code can not work in a console application because the main thread does not run with a standard windows handle. Oh, well. } {.$DEFINE USESSL} uses Classes, {$IFDEF LOGGING} IdSync, {$ENDIF} {$IFDEF WIN32} windows, {$ENDIF} {$IFDEF LINUX} libc, {$ENDIF} sysutils, {$IFDEF WIN32} IdCompressorZLibEx, {$ENDIF} IdContext, IdExplicitTLSClientServerBase, IdFileSystemWin32, IdFTPCommon, IdFTPList, IdFTPListOutput, IdFTPServer, IdFTPServerContextBase, IdGlobal, IdGlobalProtocols, IdSSLOpenSSL, idtcpserver, IdSocketHandle, IdHashCRC, IdIOHandlerSocket, IdReply, IdReplyRFC, IdReplyFTP, IdStack; //for resolving the peer's IP address into a name type TFTPServer = class private { Private declarations } {$IFDEF WIN32} FCompressor : TIdCompressorZLibEx; {$ENDIF} FIdFTPServer: tIdFTPServer; {$IFDEF USESSL} FIdExplicit : TIdServerIOHandlerSSLOpenSSL; FIdImplicit : TIdServerIOHandlerSSLOpenSSL; FIdFTPSvrImplicit : TIdFTPServer; procedure FIdSSLPassGetPassword(var Password: String); {$ENDIF} {$IFDEF LOGGING} procedure FIdFTPServerOnBeforeCmd(ASender: TIdTCPServer; const AData: string; AContext: TIdContext); {$ENDIF} function FixUpBanner(const ABanner : String; AThread: TIdFTPServerContext) : String; procedure FIdFTPServerOnGreeting(ASender: TIdFTPServerContext; AGreeting : TIdReply); procedure FIdFTPServerOnQuit(ASender: TIdFTPServerContext; AGreeting : TIdReply); procedure FIdFTPServerOnLoginSuccessfulBanner(ASender: TIdFTPServerContext; AGreeting : TIdReply); procedure FIdFTPServerOnLoginFailureBanner(ASender: TIdFTPServerContext; AGreeting : TIdReply); procedure FIdFTPServerUserLogin( ASender: TIdFTPServerContext; const AUsername, APassword: string; var AAuthenticated: Boolean ); procedure FIdFTPServerListDirectory(ASender: TIdFTPServerContext; const APath: string; ADirectoryListing: TIdFTPListOutput; const ACmd : String; const ASwitches : String); procedure FIdFTPServerRenameFile( ASender: TIdFTPServerContext; const ARenameFromFile, ARenameToFile: string ) ; procedure FIdFTPServerRetrieveFile( ASender: TIdFTPServerContext; const AFilename: string; var VStream: TStream ); procedure FIdFTPServerStoreFile( ASender: TIdFTPServerContext; const AFilename: string; AAppend: Boolean; var VStream: TStream ); procedure FIdFTPServerRemoveDirectory( ASender: TIdFTPServerContext; var VDirectory: string ) ; procedure FIdFTPServerMakeDirectory( ASender: TIdFTPServerContext; var VDirectory: string ) ; procedure FIdFTPServerGetFileSize( ASender: TIdFTPServerContext; const AFilename: string; var VFileSize: Int64 ); procedure FIdFTPServerSetFileDate(ASender: TIdFTPServerContext; const AFileName : String; var AFileTime : TDateTime); procedure FIdFTPServerDeleteFile( ASender: TIdFTPServerContext; const APathname: string ) ; procedure FIdFTPServerChangeDirectory( ASender: TIdFTPServerContext; var VDirectory: string ) ; // procedure FIdFTPServerCommandXCRC( ASender: TIdCommand ) ; procedure FIdFTPServerDisConnect( AThread: TIdContext ) ; procedure FIdFTPServerCombine(ASender: TIdFTPServerContext; const ATargetFileName: string; AParts : TStrings) ; procedure FIdFTPServerCRC(ASender: TIdFTPServerContext; const AFileName : String; var AIOStream : TStream); procedure FIdFTPServerStat(ASender: TIdFTPServerContext; AStatusInfo : TStrings); function PathSep : String; protected procedure RecurseFilesList(const APath, AHomeDir : String; ADir : TIdFTPListOutput); function UndoTranslatePath(const APathName, HomeDir : String) : String; function TransLatePath( const APathname, homeDir: string ) : string; public constructor Create; reintroduce; destructor Destroy; override; end; type EPermissionDenied = class(Exception) Constructor CreateMsg; end; {$I IdCompilerDefines.inc} {$IFDEF LOGGING} TLogMsgSync = class(TIdSync) protected FMsgLog : String; // procedure DoSynchronize; override; public class procedure LogMsg(AContext : TIdContext;const APeerIP : String; const AData : String); end; {$ENDIF} function MakePathStr(const APath : String): String; begin {$IFDEF VCL6ORABOVE} Result := SysUtils.IncludeTrailingPathDelimiter(APath); {$ELSE} Result := IncludeTrailingBackSlash(APath); {$ENDIF} end; {$IFDEF USESSL} function GetSSLCertPath : String; begin Result := MakePathStr(ExtractFilePath(ParamStr(0))+'cert'); end; {$ENDIF} function GetBasePath : String; begin Result := MakePathStr(ExtractFilePath(ParamStr(0))+'home'); end; {$IFNDEF VCL6ORABOVE} {$IFDEF WIN32} //This is necessary for D4 and D5 because adding FileCtrl will trigger the //VCL library being linked in which adds unneeded bloat. //Obtained from: // http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=utf-8&threadm=3addbfb1.249423802%40forums.borland.com&rnum=8&prev=/groups%3Fq%3DDirectoryExists%2BDelphi%26hl%3Den%26lr%3D%26ie%3DUTF-8%26oe%3Dutf-8%26selm%3D3addbfb1.249423802%2540forums.borland.com%26rnum%3D8 function DirectoryExists(const Name: string): Boolean; var Code: Integer; begin Code := GetFileAttributes(PChar(Name)); Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0); end; {$ENDIF} {$ENDIF} procedure MakeSureRealBasePathExists; begin if (FileExists(GetBasePath)=False) and (DirectoryExists(GetBasePath)=False) then begin MkDir(GetBasePath); end; end; function HashStr(const AString : String): Cardinal; var value: Cardinal; IdHashCRC32: TIdHashCRC32; begin IdHashCRC32 := TIdHashCRC32.create; try Result := IdHashCRC32.HashValue(AString); finally IdHashCRC32.free; end; end; procedure AddlistItem( aDirectoryListing: TIdFTPListOutput; Filename: string; ItemType: TIdDirItemType; size: int64; AModTime, ACreateTime, AAccessTime : tdatetime ) ; var listitem: TIdFTPListOutputItem; function CalcBlocks(const ASize : Integer): Integer; begin //we give an estimated block count so we can export a total //line in the list to simulate /bin/ls -l. //Note that the total line is the number of blocks the files use. //In Linux, the block size for list is 1024 while in FreeBSD, it is 512 //In addition, for each directory whose contents are displayed, the // total number of 512-byte blocks used by the files in the directory is // displayed on a line by itself immediately before the information for the // files in the directory. //URL: http://www.freebsd.org/cgi/man.cgi?query=ls&apropos=0&sektion=0&manpath=FreeBSD+4.7-RELEASE&format=html //and for linux, consulted info ls page and verified myself using the ls -ls command. Result := ASize div 512; if (ASize mod 512) > 0 then begin Inc(Result); end; end; begin listitem := aDirectoryListing.Add; listitem.ItemType := ItemType; if listitem.ItemType = ditDirectory then begin listitem.NumberBlocks := 1; end else begin listitem.NumberBlocks := CalcBlocks(Size); end; listitem.FileName := Filename; if aDirectoryListing.DirFormat = doUnix then begin //CygWin simply hashes the complete filename and then returns the hash value as the inode //On Linux, you get the inode value from the "stat" function listitem.Inode := HashStr(FileName); end; listitem.Size := size; listitem.ModifiedDateGMT := AModTime; listitem.CreationDateGMT := ACreateTime; listitem.LastAccessDateGMT := AAccessTime; if listitem.ItemType = ditDirectory then begin // listitem.MLISTPermissions := 'cdeflmp'; listitem.MLISTPermissions := 'el'; end else begin // listitem.MLISTPermissions := 'adfrw'; listitem.MLISTPermissions := 'r'; end; end; function RemoveTrailingPathDel(const AData : String) : String; begin Result := AData; if Result <> '' then begin if (Result[Length(Result)] = '/') or (Result[Length(Result)] = '\') then begin System.Delete(Result,Length(Result),1); end; end; end; {$IFDEF USESSL} procedure TFTPServer.FIdSSLPassGetPassword(var Password: String); begin Password := 'aaaa'; end; {$ENDIF} procedure TFTPServer.RecurseFilesList(const APath, AHomeDir : String; ADir : TIdFTPListOutput); procedure AddFolder(APath, AHomeDir: string); var F: TExtSrchRec; LDir : String; const findTypes=faArchive+faHidden+faReadOnly+faAnyFile+faDirectory; begin LDir:= MakePathStr(APath); if ExFindFirst(TransLatePath(LDir+'*.*',AHomeDir), findTypes, F)=0 then try repeat if (F.attr and faDirectory=faDirectory) then begin AddListItem(ADir,LDir+f.Name,ditDirectory,0, f.ModifiedTimeGMT, f.AccessedTimeGMT, f.AccessedTimeGMT); if (F.Name<>'.') and (F.Name<>'..') then begin AddFolder(LDir+F.Name,AHomeDir); end; end else begin AddListItem(ADir,LDir+f.Name,ditFile, f.size, f.ModifiedTimeGMT, f.AccessedTimeGMT, f.AccessedTimeGMT); end; until ExFindNext(F)<>0; finally ExFindClose(F) end; end; begin AddFolder(APath,AHomeDir); end; constructor TFTPServer.Create; begin //we create a base dir if none exists so that the server will only use something //relative to that base directory instead of relative to a particular drive //Using something relative to a drive can have too many undesirable consequences //such as someone imbedding a trojan or getting access to something they shouldn't //have access to. MakeSureRealBasePathExists; FIdFTPServer := tIdFTPServer.create( nil ) ; FIdFTPServer.MLSDFacts := [mlsdPerms, mlsdUnixModes, mlsdFileCreationTime,mlsdFileLastAccessTime]; {$IFDEF WIN32} FCompressor := TIdCompressorZLibEx.Create(nil); FIdFTPServer.Compressor := FCompressor; {$ENDIF} {$IFDEF LOGGING} FIdFTPServer.OnBeforeCommandHandler := FIdFTPServerOnBeforeCmd; {$ENDIF} {$IFDEF WIN32} FIdFTPServer.DefaultPort := 21; FIdFTPServer.DefaultDataPort := 20; {$ENDIF} {$IFDEF LINUX} //note that we use a different port because the standard FTP ports //are in the reserved range and only root can use those. FIdFTPServer.DefaultPort := 2100; FIdFTPServer.DefaultDataPort := 2000; {$ENDIF} {$IFDEF USESSL} FIdExplicit := TIdServerIOHandlerSSLOpenSSL.Create(nil); FIdExplicit.SSLOptions.RootCertFile := GetSSLCertPath + 'CAcert.crt'; FIdExplicit.SSLOptions.CertFile := GetSSLCertPath + 'WSScert.pem'; FIdExplicit.SSLOptions.KeyFile := GetSSLCertPath + 'WSSkey.pem'; FIdExplicit.OnGetPassword := FIdSSLPassGetPassword; FIdExplicit.SSLOptions.Method :=sslvSSLv23 ; FIdExplicit.SSLOptions.Mode:= sslmUnassigned; FIdFTPServer.IOHandler := FIdExplicit; FIdFTPServer.UseTLS := utUseExplicitTLS; FIdImplicit := TIdServerIOHandlerSSLOpenSSL.Create(nil); FIdImplicit.SSLOptions.RootCertFile := GetSSLCertPath + 'CAcert.crt'; FIdImplicit.SSLOptions.CertFile := GetSSLCertPath + 'WSScert.pem'; FIdImplicit.SSLOptions.KeyFile := GetSSLCertPath + 'WSSkey.pem'; FIdImplicit.OnGetPassword := FIdSSLPassGetPassword; FIdImplicit.SSLOptions.Method :=sslvSSLv23 ; FIdImplicit.SSLOptions.Mode:= sslmUnassigned; {$IFDEF WIN32} FIdImplicit.Compressor := FCompressor; {$ENDIF} {$ENDIF} FIdFTPServer.AllowAnonymousLogin := False; FIdFTPServer.OnGreeting := FIdFTPServerOnGreeting; FIdFTPServer.OnQuitBanner := FIdFTPServerOnQuit; FIdFTPServer.OnLoginSuccessBanner := FIdFTPServerOnLoginSuccessfulBanner; FIdFTPServer.OnLoginFailureBanner := FIdFTPServerOnLoginFailureBanner; FIdFTPServer.DirFormat := ftpdfUnix; FIdFTPServer.OnChangeDirectory := FIdFTPServerChangeDirectory; FIdFTPServer.OnChangeDirectory := FIdFTPServerChangeDirectory; FIdFTPServer.OnGetFileSize := FIdFTPServerGetFileSize; FIdFTPServer.OnListDirectory := FIdFTPServerListDirectory; FIdFTPServer.OnUserLogin := FIdFTPServerUserLogin; FIdFTPServer.OnRenameFile := FIdFTPServerRenameFile; FIdFTPServer.OnDeleteFile := FIdFTPServerDeleteFile; FIdFTPServer.OnRetrieveFile := FIdFTPServerRetrieveFile; FIdFTPServer.OnStoreFile := FIdFTPServerStoreFile; FIdFTPServer.OnMakeDirectory := FIdFTPServerMakeDirectory; FIdFTPServer.OnRemoveDirectory := FIdFTPServerRemoveDirectory; FIdFTPServer.Greeting.NumericCode := 220; FIdFTPServer.OnDisconnect := FIdFTPServerDisConnect; FIdFTPServer.OnSetModifiedTime := FIdFTPServerSetFileDate; FIdFTPServer.OnCombineFiles := FIdFTPServerCombine; FIdFTPServer.OnCRCFile := FIdFTPServerCRC; FIdFTPServer.OnStat := FIdFTPServerStat; //It is probably a good idea to disable this for some users //because the STAT command could be used to help "fingerprint" the system FIdFTPServer.FTPSecurityOptions.DisableSTATCommand := True; FIdFTPServer.FTPSecurityOptions.DisableSYSTCommand := True; {$IFDEF USESSL} FIdFTPSvrImplicit := TIdFTPServer.Create(nil); FIdFTPSvrImplicit.IOHandler := FIdImplicit; FIdFTPSvrImplicit.UseTLS := utUseImplicitTLS; FIdFTPSvrImplicit.AllowAnonymousLogin := False; FIdFTPSvrImplicit.OnGreeting := FIdFTPServerOnGreeting; FIdFTPSvrImplicit.OnQuitBanner := FIdFTPServerOnQuit; FIdFTPSvrImplicit.OnLoginSuccessBanner := FIdFTPServerOnLoginSuccessfulBanner; FIdFTPSvrImplicit.OnLoginFailureBanner := FIdFTPServerOnLoginFailureBanner; FIdFTPSvrImplicit.DirFormat := FIdFTPServer.DirFormat; FIdFTPSvrImplicit.OnChangeDirectory := FIdFTPServerChangeDirectory; FIdFTPSvrImplicit.OnChangeDirectory := FIdFTPServerChangeDirectory; FIdFTPSvrImplicit.OnGetFileSize := FIdFTPServerGetFileSize; FIdFTPSvrImplicit.OnListDirectory := FIdFTPServerListDirectory; FIdFTPSvrImplicit.OnUserLogin := FIdFTPServerUserLogin; FIdFTPSvrImplicit.OnRenameFile := FIdFTPServerRenameFile; FIdFTPSvrImplicit.OnDeleteFile := FIdFTPServerDeleteFile; FIdFTPSvrImplicit.OnRetrieveFile := FIdFTPServerRetrieveFile; FIdFTPSvrImplicit.OnStoreFile := FIdFTPServerStoreFile; FIdFTPSvrImplicit.OnMakeDirectory := FIdFTPServerMakeDirectory; FIdFTPSvrImplicit.OnRemoveDirectory := FIdFTPServerRemoveDirectory; FIdFTPSvrImplicit.Greeting.NumericCode := 220; FIdFTPSvrImplicit.OnDisconnect := FIdFTPServerDisConnect; FIdFTPSvrImplicit.OnSetModifiedTime := FIdFTPServerSetFileDate; FIdFTPSvrImplicit.OnCombineFiles := FIdFTPServerCombine; FIdFTPSvrImplicit.OnCRCFile := FIdFTPServerCRC; FIdFTPSvrImplicit.OnStat := FIdFTPServerStat; //It is probably a good idea to disable this for some users //because the STAT command could be used to help "fingerprint" the system FIdFTPSvrImplicit.FTPSecurityOptions.DisableSTATCommand := True; FIdFTPSvrImplicit.FTPSecurityOptions.DisableSYSTCommand := True; {$IFDEF LINUX} //note that we use a different port because the standard FTP ports //are in the reserved range and only root can use those. FIdFTPSvrImplicit.DefaultPort := 9100; FIdFTPSvrImplicit.DefaultDataPort := 9000; {$ENDIF} {$ENDIF} FIdFTPServer.Active := true; {$IFDEF USESSL} FIdFTPSvrImplicit.Active := True; {$ENDIF} end; function IsFileName( const AFileName : String): Boolean; var LFRec : TSearchRec; begin Result := False; if SysUtils.FindFirst(AFileName,faAnyFile,LFRec) = 0 then begin if LFRec.Attr and faDirectory = 0 then begin Result := True; end; SysUtils.FindClose(LFRec); end; end; function CalculateCRC( const path: string ) : string; var f: tfilestream; value: Cardinal; IdHashCRC32: TIdHashCRC32; begin IdHashCRC32 := nil; f := nil; try IdHashCRC32 := TIdHashCRC32.create; f := TFileStream.create( path, fmOpenRead or fmShareDenyWrite ) ; value := IdHashCRC32.HashValue( f ) ; result := inttohex( value, 8 ) ; finally f.free; IdHashCRC32.free; end; end; destructor TFTPServer.Destroy; begin FIdFTPServer.free; {$IFDEF USESSL} FreeAndNil( FIdExplicit ); FreeAndNil(FIdFTPSvrImplicit); FreeAndNil(FIdFTPSvrImplicit); {$ENDIF} {$IFDEF WIN32} FreeAndNil(FCompressor ); {$ENDIF} inherited destroy; end; function StartsWith( const str, substr: string ) : boolean; begin result := copy( str, 1, length( substr ) ) = substr; end; function BackSlashToSlash( const str: string ) : string; var a: Cardinal; begin result := str; for a := 1 to length( result ) do if result[a] = '\' then result[a] := '/'; end; function SlashToBackSlash( const str: string ) : string; var a: Cardinal; begin result := str; for a := 1 to length( result ) do if result[a] = '/' then result[a] := '\'; end; {$IFDEF LOGGING} procedure TFTPServer.FIdFTPServerOnBeforeCmd(ASender: TIdTCPServer; const AData: string; AContext: TIdContext); begin TLogMsgSync.LogMsg(AContext,TIdIOHandlerSocket(AContext.Connection.IOHandler).Binding.PeerIP,AData); end; {$ENDIF} function TFTPServer.UndoTranslatePath(const APathName, HomeDir : String) : String; begin Result := APathName; if Pos(SlashToBackSlash( homeDir ),APathName) = 0 then begin System.Delete(Result,1,Length(SlashToBackSlash( homeDir ))); end; end; function TFTPServer.TransLatePath( const APathname, homeDir: string ) : string; var tmppath: string; begin try result := SlashToBackSlash( homeDir ) ; tmppath := SlashToBackSlash( APathname ) ; if homedir = '/' then begin result := tmppath; Result := GetBasePath+Result; exit; end; if length( APathname ) = 0 then exit; if result[length( result ) ] = '\' then result := copy( result, 1, length( result ) - 1 ) ; if tmppath[1] <> '\' then result := result + '\'; result := result + tmppath; finally while (Copy(Result,1,2)='\\') or (Copy(Result,1,2)='//') do begin System.Delete(Result,1,1); end; end; Result := GetBasePath+Result; end; function GetSizeOfFile( const APathname: string ) : int64; begin result := FileSizeByName( APathname ) ; end; function GetNewDirectory( old, action: string ) : string; var a: integer; LAct : String; begin LAct := IndyGetFilePath(Action); //if just a dot, do nothing // if (action = './') or (action = '.\') then if LAct='.' then begin Result := Old; Exit; end; // if (action = '../') or (action = '..\') then if (LAct='..') then begin if (old = '/') or (old='\') then begin result := old; exit; end; a := length( old ) - 1; while ( old[a] <> '\' ) and ( old[a] <> '/' ) do dec( a ) ; result := copy( old, 1, a ) ; exit; end; if ( action[1] = '/' ) or ( action[1] = '\' ) then result := action else result := old + action; end; procedure TFTPServer.FIdFTPServerUserLogin( ASender: TIdFTPServerContext; const AUsername, APassword: string; var AAuthenticated: Boolean ) ; begin AAuthenticated := ( AUsername = 'myuser' ) and ( APassword = 'mypass' ) ; if not AAuthenticated then begin AAuthenticated := ( AUsername = 'admin' ) and ( APassword = 'myadminpass' ) ; if AAuthenticated then begin //for an administrative account, you might want the weakest security settings //while for anonymous FTP, you probably want something fairly strong ASender.UserSecurity.RequirePASVFromSameIP := False; ASender.UserSecurity.RequirePORTFromSameIP := False; ASender.UserSecurity.NoReservedRangePORT := False; ASender.UserSecurity.BlockAllPORTTransfers := False; ASender.UserSecurity.DisableSYSTCommand := False; ASender.UserSecurity.DisableSTATCommand := False; end; end; if not AAuthenticated then exit; if FIdFTPServer.DirFormat <> ftpdfDOS then begin ASender.UserSecurity.DisableSYSTCommand := False; ASender.HomeDir := '/'; asender.currentdir := '/'; end else begin ASender.HomeDir := '\'; asender.currentdir := '\'; end; end; procedure TFTPServer.FIdFTPServerListDirectory( ASender: TIdFTPServerContext; const APath: string; ADirectoryListing: TIdFTPListOutput; const ACmd : String; const ASwitches : String); var f: TExtSrchRec; a: integer; function DeletRSwitch(const AString : String):String; var i : Integer; begin Result := ''; for i := 1 to Length(AString) do begin if AString[i]<>'R' then begin Result := Result + AString[i]; end; end; end; begin if ASender.Username = 'myuser' then begin //It's probably best to prevent normal users from doing recursive dirs //because that can eat up more bandwidth and CPU cycles than a nromal DIR //list would. It's probably best only for mirroring software using a designated //account and for administrators of the system. ADirectoryListing.Switches := DeletRSwitch(ASwitches); end else begin ADirectoryListing.Switches := ASwitches; end; // ADirectoryListing.DirectoryName := apath; // if FileExists(TranslatePath(APath,ASender.HomeDir))=False then // begin // raise EPermissionDenied.CreateMsg; // end; //in MLST, we are asking for information about a particular item, not //asking for the complete contents of the item if (ACmd = 'MLST') then begin if RemoveTrailingPathDel (TransLatePath( apath, ASender.HomeDir )) = '' then begin AddlistItem( ADirectoryListing, '/', ditDirectory, 0, 0, 0,0); exit; end else begin a := ExFindFirst( RemoveTrailingPathDel (TransLatePath( apath, ASender.HomeDir )) , faAnyFile, f ) ; end; end else begin a := ExFindFirst( TransLatePath( apath, ASender.HomeDir ) + '*.*', faAnyFile, f ) ; end; if (FIdFTPServer.DirFormat <> ftpdfEPLF) and (Pos('R',ASwitches)>0) then begin ExFindClose( f ) ; RecurseFilesList(RemoveTrailingPathDel (apath), ASender.HomeDir,ADirectoryListing); Exit; end; while ( a = 0 ) do begin if (ADirectoryListing.DirFormat = doEPLF ) and ((f.Name = '.') or (f.Name = '..')) then begin AddlistItem( ADirectoryListing, ASender.HomeDir, ditDirectory, f.size, 0,0,0 ); end else begin if ( f.Attr and faDirectory > 0 ) then begin //procedure AddlistItem( aDirectoryListing: TIdFTPListOutput; Filename: string; ItemType: TIdDirItemType; size: int64; AModTime, ACreateTime, AAccessTime : tdatetime ) ; AddlistItem( ADirectoryListing, f.Name, ditDirectory, f.size, f.ModifiedTimeGMT, f.CreateTimeGMT, f.AccessedTimeGMT); end else begin AddlistItem( ADirectoryListing, f.Name, ditFile, f.size, f.ModifiedTimeGMT, f.CreateTimeGMT, f.AccessedTimeGMT); end; end; a := ExFindNext( f ) ; end; ExFindClose( f ) ; end; procedure TFTPServer.FIdFTPServerRenameFile( ASender: TIdFTPServerContext; const ARenameFromFile, ARenameToFile: string ) ; begin {$IFDEF WIN32} if not MoveFile( pchar( TransLatePath( ARenameFromFile, ASender.HomeDir ) ) , pchar( TransLatePath( ARenameToFile, ASender.HomeDir ) ) ) then begin RaiseLastWin32Error; end; {$ELSE} {$ENDIF} end; procedure TFTPServer.FIdFTPServerRetrieveFile( ASender: TIdFTPServerContext; const AFilename: string; var VStream: TStream ) ; begin VStream := TFileStream.create( translatepath( AFilename, ASender.HomeDir ) , fmopenread or fmShareDenyWrite ) ; end; procedure TFTPServer.FIdFTPServerStoreFile( ASender: TIdFTPServerContext; const AFilename: string; AAppend: Boolean; var VStream: TStream ) ; begin if FileExists( translatepath( AFilename, ASender.HomeDir ) ) and AAppend then begin VStream := TFileStream.create( translatepath( AFilename, ASender.HomeDir ) , fmOpenWrite or fmShareExclusive ) ; VStream.Seek( 0, soFromEnd ) ; end else VStream := TFileStream.create( translatepath( AFilename, ASender.HomeDir ) , fmCreate or fmShareExclusive ) ; end; procedure TFTPServer.FIdFTPServerRemoveDirectory( ASender: TIdFTPServerContext; var VDirectory: string ) ; begin RmDir( TransLatePath( VDirectory, ASender.HomeDir ) ) ; end; procedure TFTPServer.FIdFTPServerMakeDirectory( ASender: TIdFTPServerContext; var VDirectory: string ) ; begin MkDir( TransLatePath( VDirectory, ASender.HomeDir ) ) ; end; procedure TFTPServer.FIdFTPServerGetFileSize( ASender: TIdFTPServerContext; const AFilename: string; var VFileSize: Int64 ) ; begin try VFileSize := GetSizeOfFile( TransLatePath( AFilename, ASender.HomeDir ) ) ; except VFileSize := -1; end; end; procedure TFTPServer.FIdFTPServerDeleteFile( ASender: TIdFTPServerContext; const APathname: string ) ; begin if DeleteFile( pchar( TransLatePath( ASender.CurrentDir + '/' + APathname, ASender.HomeDir ) ) )=False then begin raise EPermissionDenied.CreateMsg; end; end; procedure TFTPServer.FIdFTPServerChangeDirectory( ASender: TIdFTPServerContext; var VDirectory: string ) ; begin if DirectoryExists(TransLatePath(VDirectory, ASender.HomeDir)) then begin VDirectory := GetNewDirectory( ASender.CurrentDir, VDirectory ) ; end else begin raise EPermissionDenied.CreateMsg; end; end; procedure TFTPServer.FIdFTPServerDisConnect( AThread: TIdContext ) ; begin // nothing much here end; procedure TFTPServer.FIdFTPServerCombine(ASender: TIdFTPServerContext; const ATargetFileName: string; AParts: TStrings); var i : Integer; LSource, LDest : TStream; begin if FileExists(TransLatePath( ASender.CurrentDir + '/' + ATargetFileName, ASender.HomeDir )) then begin LDest := TFileStream.Create(TransLatePath( ASender.CurrentDir + '/' + ATargetFileName, ASender.HomeDir ) ,fmOpenReadWrite or fmShareExclusive); LDest.Seek(0,soFromEnd); end else begin LDest := TFileStream.Create(TransLatePath( ASender.CurrentDir + '/' + ATargetFileName, ASender.HomeDir ) ,fmCreate); end; try for i := 0 to AParts.Count -1 do begin LSource := TFileStream.Create( TransLatePath( ASender.CurrentDir + '/' + AParts[i], ASender.HomeDir ) , fmopenread or fmShareDenyWrite); LDest.CopyFrom(LSource,0); FreeAndNil(LSource); end; //Do this separately in case there was a failure to find a source file part for i := 0 to AParts.Count-1 do begin FIdFTPServerDeleteFile(ASender,AParts[i]); end; finally FreeAndNil(LSource); FreeAndNil(LDest); end; end; procedure TFTPServer.FIdFTPServerCRC(ASender: TIdFTPServerContext; const AFileName: String; var AIOStream: TStream); var LFileName : String; begin LFileName := TransLatePath( ASender.CurrentDir + '/' + AFileName, ASender.HomeDir); AIOStream := TFileStream.create( LFileName, fmopenread or fmShareDenyWrite ) ; end; function DayPeriodGreeting : String; var LHour, LMin, LSec, LMSec : Word; //Note that we do not use Night for a greeting. //"Good Night" is used as a farewell, not a greeting begin DecodeTime(Time,LHour, LMin,LSec,LMSec); if LHour < 12 then begin Result := 'Morning'; end else begin if LHour < 18 then begin Result := 'Afternoon'; end else begin Result := 'Evening'; end; end; end; function DayPeriodFairwell : String; var LHour, LMin, LSec, LMSec : Word; //Note that we do not use Night for a greeting. //"Good Night" is used as a farewell, not a greeting begin DecodeTime(Time,LHour, LMin,LSec,LMSec); if LHour < 19 then begin Result := 'Day'; end else begin Result := 'Night'; end; end; procedure TFTPServer.FIdFTPServerStat(ASender: TIdFTPServerContext; AStatusInfo : TStrings); var Line : String; begin AStatusInfo.Add('Connected to '+ GStack.HostByAddress(TIdIOHandlerSocket(ASender.Connection.IOHandler).Binding.PeerIP) ); Line := 'Logged in as '; if (ASender.UserType = utAnonymousUser) then begin Line := Line + ASender.Password; end else begin Line := Line + ASender.Username; end; AStatusInfo.Add(Line); Line := 'TYPE: '; case ASender.DataType of ftASCII : Line := Line + 'ASCII'; ftBinary : Line := Line + 'BINARY'; end; Line := Line + ', FORM: Nonprint; STRUcture: '; case ASender.DataStruct of dsFile : Line := Line + 'File'; dsRecord : Line := Line + 'Record'; dsPage : Line := Line + 'Page'; end; // Line := Line + '; transfer MODE: '; // case ASender.DataMode of // dmBlock : Line := Line + 'BLOCK'; // dmCompressed : Line := Line + 'COMPRESSED'; // dmStream : Line := Line + 'STREAM'; // end; AStatusInfo.Add(Line); if Assigned(ASender.DataChannel) then begin AStatusInfo.Add('Data Connection Active'); end else begin AStatusInfo.Add('No Data Connection'); end; end; function TFTPServer.FixUpBanner(const ABanner : String; AThread: TIdFTPServerContext) : String; var LPeerHostName : String; LMyHostName : String; begin LPeerHostName := GStack.HostByAddress(TIdIOHandlerSocket(AThread.Connection.IOHandler).Binding.PeerIP); LMyHostName := GStack.HostByAddress(TIdIOHandlerSocket(AThread.Connection.IOHandler).Binding.IP); Result := StringReplace(ABanner,'%DAYGREETINGWORD%',DayPeriodGreeting,[rfReplaceAll]); Result := StringReplace(Result,'%DAYFAREWELLWORD%',DayPeriodFairwell,[rfReplaceAll]); Result := StringReplace(Result,'%PEERNAME%',LPeerHostName,[rfReplaceAll]); Result := StringReplace(Result,'%MYNAME%',LMyHostName,[rfReplaceAll]); Result := StringReplace(Result,'%USERNAME%',AThread.Username,[rfReplaceAll]); end; procedure TFTPServer.FIdFTPServerOnGreeting(ASender: TIdFTPServerContext; AGreeting : TIdReply); begin AGreeting.Text.Clear; AGreeting.Text.Add('Good %DAYGREETINGWORD%, user at %PEERNAME%.'); AGreeting.Text.Add(''); AGreeting.Text.Add('Welcome to the Internet Direct (Indy) demo running on %MYNAME%.'); AGreeting.Text.Add(''); AGreeting.Text.Add('Server at %MYNAME% ready.'); AGreeting.Text.Text := FixUpBanner(AGreeting.Text.Text,ASender); { AGreeting.NumericCode := 421; AGreeting.Text.Clear; AGreeting.Text.Add('FTP Service has been disabled to prevent system abuse'); AGreeting.Text.Add(''); AGreeting.Text.Add('You may now only use our web site at http://www.oursite.com'); AGreeting.Text.Add('to download our great software.'); AGreeting.Text.Add(''); AGreeting.Text.Add('Have a nice day.'); } end; procedure TFTPServer.FIdFTPServerOnQuit(ASender: TIdFTPServerContext; AGreeting : TIdReply); begin if ASender.Authenticated then begin AGreeting.Text.Clear; AGreeting.Text.Add('Good %DAYFAREWELLWORD%, %USERNAME%!!!'); AGreeting.Text.Text := FixUpBanner(AGreeting.Text.Text,ASender); end; end; procedure TFTPServer.FIdFTPServerOnLoginSuccessfulBanner(ASender: TIdFTPServerContext; AGreeting : TIdReply); begin AGreeting.Text.Clear; AGreeting.Text.Add('User %USERNAME% logged in.'); AGreeting.Text.Text := FixUpBanner(AGreeting.Text.Text,ASender); end; procedure TFTPServer.FIdFTPServerOnLoginFailureBanner(ASender: TIdFTPServerContext; AGreeting : TIdReply); begin AGreeting.Text.Clear; AGreeting.Text.Add('Login failed.'); AGreeting.Text.Add('Visit from %PEERNAME% has been logged.'); AGreeting.Text.Add(''); AGreeting.Text.Add('Good %DAYFAREWELLWORD%!!!'); AGreeting.Text.Text := FixUpBanner(AGreeting.Text.Text,ASender); end; procedure TFTPServer.FIdFTPServerSetFileDate(ASender: TIdFTPServerContext; const AFileName : String; var AFileTime : TDateTime); begin if GMTSetFileModifyDate(AFileName,AFileTime)<>0 then begin //in case we failed to set the date AFileTime := GMTGetFileModifyDate(AFileName); end; end; { EPermissionDenied } constructor EPermissionDenied.CreateMsg; begin inherited Create('Permission Denied'); end; function TFTPServer.PathSep: String; begin if FIdFTPServer.DirFormat = ftpdfUnix then begin Result := PATH_FILENAME_SEP_DOS; end else begin Result := PATH_FILENAME_SEP_UNIX; end; end; {$IFDEF LOGGING} { TLogMsgSync } procedure TLogMsgSync.DoSynchronize; begin WriteLn(FMsgLog); end; class procedure TLogMsgSync.LogMsg(AContext : TIdContext; const APeerIP, AData: String); begin with Create do begin FMsgLog := APeerIP + ': '+AData; Synchronize; // Notify; end; end; {$ENDIF} begin with TFTPServer.Create do try writeln( 'Running, press [enter] to terminate' ) ; readln; finally free; end; end.