{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. } { $Log$ } { Rev 1.9 2/8/05 6:48:30 PM RLebeau Misc. tweaks Rev 1.8 24/10/2004 21:26:14 ANeillans RCPTList can be set Rev 1.7 9/15/2004 5:02:06 PM DSiders Added localization comments. Rev 1.6 31/08/2004 20:21:34 ANeillans Bug fix -- format problem. Rev 1.5 08/08/2004 21:03:10 ANeillans Continuing.... Rev 1.4 02/08/2004 21:14:28 ANeillans Auth working Rev 1.3 01/08/2004 13:02:16 ANeillans Development. Rev 1.2 01/08/2004 09:50:26 ANeillans Continued development. Rev 1.1 7/28/2004 8:26:46 AM JPMugaas Further work on the SMTP Server. Not tested yet. Rev 1.0 7/27/2004 5:14:38 PM JPMugaas Start on TIdSMTPServer rewrite. } unit IdSMTPServer; interface {$i IdCompilerDefines.inc} uses Classes, IdAssignedNumbers, IdCustomTCPServer, //for TIdServerContext IdCmdTCPServer, IdCommandHandlers, IdContext, IdEMailAddress, IdException, IdExplicitTLSClientServerBase, IdReply, IdReplyRFC, IdReplySMTP, IdTCPConnection, IdTCPServer, IdYarn, IdStack, IdGlobal; type EIdSMTPServerError = class(EIdException); EIdSMTPServerNoRcptTo = class(EIdSMTPServerError); TIdMailFromReply = ( mAccept, //accept the mail message mReject, //reject the mail message mSystemFull, //no more space on server mLimitExceeded //exceeded message size limit ); TIdRCPToReply = ( rAddressOk, //address is okay rRelayDenied, //we do not relay for third-parties rInvalid, //invalid address rWillForward, //not local - we will forward rNoForward, //not local - will not forward - please use rTooManyAddresses, //too many addresses rDisabledPerm, //disabled permentantly - not accepting E-Mail rDisabledTemp, //disabled temporarily - not accepting E-Mail rSystemFull, //no more space on server rLimitExceeded //exceeded message size limit ); TIdDataReply = ( dOk, //accept the mail message dMBFull, //Mail box full dSystemFull, //no more space on server dLocalProcessingError, //local processing error dTransactionFailed, //transaction failed dLimitExceeded //exceeded administrative limit ); TIdSPFReply = ( spfNone, //no published records or checkable domain spfNeutral, //domain explicitially stated no assertion spfPass, //authorized spfFail, //not authorized spfSoftFail, //may not be authorized spfTempError, //transient error spfPermError //permanent error ); TIdSMTPServerContext = class; TOnMailFromEvent = procedure(ASender: TIdSMTPServerContext; const AAddress : string; AParams: TStrings; var VAction : TIdMailFromReply) of object; TOnMsgReceive = procedure(ASender: TIdSMTPServerContext; AMsg: TStream; var VAction : TIdDataReply) of object; TOnRcptToEvent = procedure(ASender: TIdSMTPServerContext; const AAddress : string; AParams: TStrings; var VAction : TIdRCPToReply; var VForward : String) of object; TOnReceived = procedure(ASender: TIdSMTPServerContext; var AReceived : String) of object; TOnSMTPEvent = procedure(ASender: TIdSMTPServerContext) of object; TOnSMTPUserLoginEvent = procedure(ASender: TIdSMTPServerContext; const AUsername, APassword: string; var VAuthenticated: Boolean) of object; TOnSPFCheck = procedure(ASender: TIdSMTPServerContext; const AIP, ADomain, AIdentity: String; var VAction: TIdSPFReply) of object; TOnDataStreamEvent = procedure(ASender: TIdSMTPServerContext; var VStream: TStream) of object; TIdSMTPServer = class(TIdExplicitTLSServer) protected //events FOnBeforeMsg : TOnDataStreamEvent; FOnMailFrom : TOnMailFromEvent; FOnMsgReceive : TOnMsgReceive; FOnRcptTo : TOnRcptToEvent; FOnReceived : TOnReceived; FOnReset: TOnSMTPEvent; FOnSPFCheck: TOnSPFCheck; FOnUserLogin : TOnSMTPUserLoginEvent; //misc FServerName : String; FAllowPipelining: Boolean; FMaxMsgSize: Integer; // function CreateGreeting: TIdReply; override; function CreateReplyUnknownCommand: TIdReply; override; // procedure DoAuthLogin(ASender: TIdCommand; const Mechanism, InitialResponse: string); // //command handlers procedure CommandNOOP(ASender: TIdCommand); procedure CommandQUIT(ASender: TIdCommand); procedure CommandEHLO(ASender: TIdCommand); procedure CommandHELO(ASender: TIdCommand); procedure CommandAUTH(ASender: TIdCommand); procedure CommandMAIL(ASender: TIdCommand); procedure CommandRCPT(ASender: TIdCommand); procedure CommandDATA(ASender: TIdCommand); procedure CommandRSET(ASender: TIdCommand); procedure CommandSTARTTLS(ASender: TIdCommand); procedure CommandBDAT(ASender: TIdCommand); { Note that for SMTP, I make a lot of procedures for replies. The reason is that we use precise enhanced status codes. These serve as diangostics and give much more information than the 3 number standard replies. The enhanced codes will sometimes appear in bounce notices. Note: Enhanced status codes should only appear if a client uses EHLO instead of HELO. } //common reply procs procedure AuthFailed(ASender: TIdCommand); procedure CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply : TIdReply = nil); overload; procedure CmdSyntaxError(ASender: TIdCommand); overload; procedure BadSequenceError(ASender: TIdCommand); procedure InvalidSyntax(ASender: TIdCommand); procedure NoHello(ASender: TIdCommand); procedure MustUseTLS(ASender: TIdCommand); //Mail From procedure MailFromAccept(ASender: TIdCommand; const AAddress : String = ''); procedure MailFromReject(ASender: TIdCommand; const AAddress : String = ''); //address replies - RCPT TO procedure AddrValid(ASender: TIdCommand; const AAddress : String = ''); procedure AddrInvalid(ASender: TIdCommand; const AAddress : String = ''); procedure AddrWillForward(ASender: TIdCommand; const AAddress : String = ''; const ATo : String = ''); procedure AddrNotWillForward(ASender: TIdCommand; const AAddress : String = ''; const ATo : String = ''); procedure AddrDisabledPerm(ASender: TIdCommand; const AAddress : String = ''); procedure AddrDisabledTemp(ASender: TIdCommand; const AAddress : String = ''); procedure AddrNoRelaying(ASender: TIdCommand; const AAddress : String = ''); procedure AddrTooManyRecipients(ASender: TIdCommand); //mail submit replies procedure MailSubmitOk(ASender: TIdCommand); procedure MailSubmitLimitExceeded(ASender: TIdCommand); procedure MailSubmitStorageExceededFull(ASender: TIdCommand); procedure MailSubmitTransactionFailed(ASender: TIdCommand); procedure MailSubmitLocalProcessingError(ASender: TIdCommand); procedure MailSubmitSystemFull(ASender: TIdCommand); procedure SetEnhReply(AReply: TIdReply; const ANumericCode: Integer; const AEnhReply, AText: String; const IsEHLO: Boolean); // overrides for SMTP function GetReplyClass: TIdReplyClass; override; function GetRepliesClass: TIdRepliesClass; override; procedure InitComponent; override; procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); override; procedure InitializeCommandHandlers; override; // procedure DoReset(AContext: TIdSMTPServerContext; AIsTLSReset: Boolean = False); procedure MsgBegan(AContext: TIdSMTPServerContext; var VStream: TStream); procedure MsgReceived(ASender: TIdCommand; AMsgData: TStream); procedure SetMaxMsgSize(AValue: Integer); function SPFAuthOk(AContext: TIdSMTPServerContext; AReply: TIdReply; const ACmd, ADomain, AIdentity: String): Boolean; {$IFDEF WORKAROUND_INLINE_CONSTRUCTORS} public constructor Create(AOwner: TComponent); reintroduce; overload; {$ENDIF} published //events property OnBeforeMsg : TOnDataStreamEvent read FOnBeforeMsg write FOnBeforeMsg; property OnMailFrom : TOnMailFromEvent read FOnMailFrom write FOnMailFrom; property OnMsgReceive : TOnMsgReceive read FOnMsgReceive write FOnMsgReceive; property OnRcptTo : TOnRcptToEvent read FOnRcptTo write FOnRcptTo; property OnReceived: TOnReceived read FOnReceived write FOnReceived; property OnReset: TOnSMTPEvent read FOnReset write FOnReset; property OnSPFCheck: TOnSPFCheck read FOnSPFCheck write FOnSPFCheck; property OnUserLogin : TOnSMTPUserLoginEvent read FOnUserLogin write FOnUserLogin; //properties property AllowPipelining : Boolean read FAllowPipelining write FAllowPipelining default False; property DefaultPort default IdPORT_SMTP; property MaxMsgSize: Integer read FMaxMsgSize write SetMaxMsgSize default 0; property ServerName : String read FServerName write FServerName; property UseTLS; end; TIdSMTPState = (idSMTPNone, idSMTPHelo, idSMTPMail, idSMTPRcpt, idSMTPData, idSMTPBDat); TIdSMTPBodyType = (idSMTP7Bit, idSMTP8BitMime, idSMTPBinaryMime); TIdSMTPServerContext = class(TIdServerContext) protected FSMTPState: TIdSMTPState; FFrom: string; FRCPTList: TIdEMailAddressList; FHELO: Boolean; FEHLO: Boolean; FHeloString: String; FUsername: string; FPassword: string; FLoggedIn: Boolean; FMsgSize: Integer; FPipeLining : Boolean; FFinalStage : Boolean; FBDataStream: TStream; FBodyType: TIdSMTPBodyType; function GetUsingTLS: Boolean; function GetCanUseExplicitTLS: Boolean; function GetTLSIsRequired: Boolean; procedure SetPipeLining(const AValue : Boolean); public constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override; destructor Destroy; override; // procedure CheckPipeLine; procedure Reset(AIsTLSReset: Boolean = False); virtual; // property SMTPState: TIdSMTPState read FSMTPState write FSMTPState; property From: String read FFrom write FFrom; property RCPTList: TIdEMailAddressList read FRCPTList; property HELO: Boolean read FHELO write FHELO; property EHLO: Boolean read FEHLO write FEHLO; property HeloString : String read FHeloString write FHeloString; property Username: String read FUsername write FUsername; property Password: String read FPassword write FPassword; property LoggedIn: Boolean read FLoggedIn write FLoggedIn; property MsgSize: Integer read FMsgSize write FMsgSize; property FinalStage: Boolean read FFinalStage write FFinalStage; property UsingTLS: Boolean read GetUsingTLS; property CanUseExplicitTLS: Boolean read GetCanUseExplicitTLS; property TLSIsRequired: Boolean read GetTLSIsRequired; property PipeLining: Boolean read FPipeLining write SetPipeLining; // end; const IdSMTPSvrReceivedString = 'Received: from $hostname[$ipaddress] (helo=$helo) by $svrhostname[$svripaddress] with $protocol ($servername)'; {do not localize} implementation uses IdCoderMIME, IdGlobalProtocols, IdResourceStringsProtocols, IdSSL, SysUtils; { TIdSMTPServer } {$IFDEF WORKAROUND_INLINE_CONSTRUCTORS} constructor TIdSMTPServer.Create(AOwner: TComponent); begin inherited Create(AOwner); end; {$ENDIF} procedure TIdSMTPServer.CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply: TIdReply); var LTmp : String; LReply : TIdReply; begin //First make the first word uppercase LTmp := UpCaseFirstWord(ALine); try if Assigned(AReply) then begin LReply := AReply; end else begin LReply := TIdReplySMTP.CreateWithReplyTexts(nil, ReplyTexts); LReply.Assign(ReplyUnknownCommand); end; SetEnhReply(LReply, 500, '5.0.0', IndyFormat(RSFTPCmdNotRecognized, [LTmp]), {do not localize} TIdSMTPServerContext(AContext).Ehlo); AContext.Connection.IOHandler.Write(LReply.FormattedReply); finally if not Assigned(AReply) then begin FreeAndNil(LReply); end; end; end; procedure TIdSMTPServer.BadSequenceError(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 503, Id_EHR_PR_OTHER_PERM, RSSMTPSvrBadSequence, TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.CmdSyntaxError(ASender: TIdCommand); begin CmdSyntaxError(ASender.Context, ASender.RawLine, FReplyUnknownCommand ); ASender.PerformReply := False; end; function TIdSMTPServer.CreateGreeting: TIdReply; begin Result := TIdReplySMTP.CreateWithReplyTexts(nil, ReplyTexts); TIdReplySMTP(Result).SetEnhReply(220, '' ,RSSMTPSvrWelcome) end; function TIdSMTPServer.CreateReplyUnknownCommand: TIdReply; begin Result := TIdReplySMTP.CreateWithReplyTexts(nil, ReplyTexts); TIdReplySMTP(Result).SetEnhReply(500, Id_EHR_PR_SYNTAX_ERR, 'Syntax Error'); {do not localize} end; procedure TIdSMTPServer.CommandEHLO(ASender: TIdCommand); var LContext : TIdSMTPServerContext; begin LContext := TIdSMTPServerContext(ASender.Context); DoReset(LContext); LContext.EHLO := True; LContext.HeloString := ASender.UnparsedParams; if SPFAuthOk(LContext, ASender.Reply, 'EHLO', DomainName(ASender.UnparsedParams), ASender.UnparsedParams) then {do not localize} begin SetEnhReply(ASender.Reply, 250, '', IndyFormat(RSSMTPSvrHello, [ASender.UnparsedParams]), True); if Assigned(FOnUserLogin) then begin ASender.Reply.Text.Add('AUTH LOGIN'); {Do not Localize} end; ASender.Reply.Text.Add('ENHANCEDSTATUSCODES'); {do not localize} if FAllowPipelining then begin ASender.Reply.Text.Add('PIPELINING'); {do not localize} end; ASender.Reply.Text.Add(IndyFormat('SIZE %d', [FMaxMsgSize])); {do not localize} if LContext.CanUseExplicitTLS and (not LContext.UsingTLS) then begin ASender.Reply.Text.Add('STARTTLS'); {Do not Localize} end; ASender.Reply.Text.Add('CHUNKING'); {do not localize} ASender.Reply.Text.Add('8BITMIME'); {do not localize} ASender.Reply.Text.Add('BINARYMIME'); {do not localize} LContext.SMTPState := idSMTPHelo; end; end; procedure TIdSMTPServer.DoReplyUnknownCommand(AContext: TIdContext; ALine: string); begin CmdSyntaxError(AContext, ALine); end; function TIdSMTPServer.GetRepliesClass: TIdRepliesClass; begin Result := TIdRepliesSMTP; end; function TIdSMTPServer.GetReplyClass: TIdReplyClass; begin Result := TIdReplySMTP; end; procedure TIdSMTPServer.InitComponent; begin inherited InitComponent; FContextClass := TIdSMTPServerContext; HelpReply.Code := ''; //we will handle the help ourselves FRegularProtPort := IdPORT_SMTP; FImplicitTLSProtPort := IdPORT_ssmtp; DefaultPort := IdPORT_SMTP; FServerName := 'Indy SMTP Server'; {do not localize} end; procedure TIdSMTPServer.InitializeCommandHandlers; var LCmd : TIdCommandHandler; begin inherited InitializeCommandHandlers; LCmd := CommandHandlers.Add; LCmd.Command := 'EHLO'; {do not localize} LCmd.OnCommand := CommandEHLO; LCmd.NormalReply.NumericCode := 250; LCmd.ParseParams := True; SetEnhReply(LCmd.ExceptionReply, 451,Id_EHR_PR_OTHER_TEMP, 'Internal Error', False); {do not localize} LCmd := CommandHandlers.Add; LCmd.Command := 'HELO'; {do not localize} LCmd.OnCommand := CommandHELO; LCmd.NormalReply.NumericCode := 250; LCmd.ParseParams := True; SetEnhReply(LCmd.ExceptionReply, 451,Id_EHR_PR_OTHER_TEMP, 'Internal Error', False); {do not localize} LCmd := CommandHandlers.Add; LCmd.Command := 'AUTH'; {do not localize} LCmd.OnCommand := CommandAUTH; SetEnhReply(LCmd.ExceptionReply, 451,Id_EHR_PR_OTHER_TEMP, 'Internal Error', False); {do not localize} LCmd := CommandHandlers.Add; // NOOP LCmd.Command := 'NOOP'; {Do not Localize} SetEnhReply(LCmd.NormalReply ,250,Id_EHR_GENERIC_OK,RSSMTPSvrOk, True); LCmd.OnCommand := CommandNOOP; SetEnhReply(LCmd.ExceptionReply, 451,Id_EHR_PR_OTHER_TEMP, 'Internal Error', False); {do not localize} LCmd := CommandHandlers.Add; // QUIT LCmd.Command := 'QUIT'; {Do not Localize} LCmd.CmdDelimiter := ' '; {Do not Localize} LCmd.Disconnect := True; SetEnhReply(LCmd.NormalReply, 221, Id_EHR_GENERIC_OK, RSSMTPSvrQuit, False); LCmd.OnCommand := CommandQUIT; LCmd := CommandHandlers.Add; // RCPT TO: LCmd.Command := 'RCPT'; {Do not Localize} LCmd.CmdDelimiter := ' '; {Do not Localize} LCmd.OnCommand := CommandRcpt; SetEnhReply(LCmd.NormalReply, 250, Id_EHR_MSG_VALID_DEST, '', False); SetEnhReply(LCmd.ExceptionReply, 550, Id_EHR_MSG_BAD_DEST, '', False); LCmd := CommandHandlers.Add; // MAIL FROM: LCmd.Command := 'MAIL'; {Do not Localize} LCmd.CmdDelimiter := ' '; {Do not Localize} LCmd.OnCommand := CommandMail; SetEnhReply(LCmd.NormalReply, 250, Id_EHR_MSG_OTH_OK, '', False); SetEnhReply(LCmd.ExceptionReply, 451, Id_EHR_MSG_BAD_SENDER_ADDR, '', False); LCmd := CommandHandlers.Add; // DATA LCmd.Command := 'DATA'; {Do not Localize} LCmd.OnCommand := CommandDATA; SetEnhReply(LCmd.NormalReply , 354, '', RSSMTPSvrStartData, False); SetEnhReply(LCmd.ExceptionReply, 451, Id_EHR_PR_OTHER_TEMP, 'Internal Error' , False); {do not localize} LCmd := CommandHandlers.Add; // RSET LCmd.Command := 'RSET'; {Do not Localize} LCmd.NormalReply.SetReply(250, RSSMTPSvrOk); LCmd.OnCommand := CommandRSET; LCmd := CommandHandlers.Add; // STARTTLS LCmd.Command := 'STARTTLS'; {Do not Localize} SetEnhReply(LCmd.NormalReply, 220, Id_EHR_GENERIC_OK, RSSMTPSvrReadyForTLS, False); LCmd.OnCommand := CommandStartTLS; LCmd := CommandHandlers.Add; // BDAT [ LAST] LCmd.Command := 'BDAT'; {Do not Localize} LCmd.OnCommand := CommandBDAT; LCmd.ParseParams := True; SetEnhReply(LCmd.NormalReply, 250, Id_EHR_GENERIC_OK, '', False); SetEnhReply(LCmd.ExceptionReply, 451, Id_EHR_PR_OTHER_TEMP, 'Internal Error' , False); {do not localize} end; procedure TIdSMTPServer.MustUseTLS(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 530, Id_EHR_USE_STARTTLS, RSSMTPSvrReqSTARTTLS, TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.CommandAUTH(ASender: TIdCommand); var LContext: TIdSMTPServerContext; S, LMech: String; begin LContext := TIdSMTPServerContext(ASender.Context); //Note you can not use PIPELINING with AUTH LContext.PipeLining := False; if not LContext.EHLO then begin // Only available with EHLO BadSequenceError(ASender); Exit; end; if LContext.TLSIsRequired then begin MustUseTLS(ASender); Exit; end; if not Assigned(FOnUserLogin) then begin AuthFailed(ASender); Exit; end; if Length(ASender.UnparsedParams) > 0 then begin S := ASender.UnparsedParams; LMech := Fetch(S); DoAuthLogin(ASender, LMech, Trim(S)); end else begin CmdSyntaxError(ASender); end; end; procedure TIdSMTPServer.CommandHELO(ASender: TIdCommand); var LContext : TIdSMTPServerContext; begin LContext := TIdSMTPServerContext(ASender.Context); if LContext.SMTPState <> idSMTPNone then begin BadSequenceError(ASender); Exit; end; DoReset(LContext); LContext.HeloString := ASender.UnparsedParams; LContext.HELO := True; if SPFAuthOk(LContext, ASender.Reply, 'HELO', DomainName(ASender.UnparsedParams), ASender.UnparsedParams) then {do not localize} begin ASender.Reply.SetReply(250, IndyFormat(RSSMTPSvrHello, [ASender.UnparsedParams])); LContext.SMTPState := idSMTPHelo; end; end; procedure TIdSMTPServer.DoAuthLogin(ASender: TIdCommand; const Mechanism, InitialResponse: string); var S, LUsername, LPassword: string; LAuthFailed: Boolean; LAccepted: Boolean; LContext : TIdSMTPServerContext; LEncoder: TIdEncoderMIME; LDecoder: TIdDecoderMIME; begin LContext := TIdSMTPServerContext(ASender.Context); LAuthFailed := True; LContext.PipeLining := False; if TextIsSame(Mechanism, 'LOGIN') then begin {Do not Localize} // LOGIN USING THE LOGIN AUTH - BASE64 ENCODED try LEncoder := TIdEncoderMIME.Create; try LDecoder := TIdDecoderMIME.Create; try if InitialResponse = '' then begin // no [initial-response] parameter specified // Encoding a string literal? S := LEncoder.Encode('Username:'); {Do not Localize} ASender.Reply.SetReply(334, S); {Do not Localize} ASender.SendReply; S := Trim(LContext.Connection.IOHandler.ReadLn); end else if InitialResponse = '=' then begin {Do not Localize} // empty [initial-response] parameter value S := ''; end else begin S := InitialResponse; end; if S <> '' then begin {Do not Localize} LUsername := LDecoder.DecodeString(S); end; // What? Encode this string literal? S := LEncoder.Encode('Password:'); {Do not Localize} ASender.Reply.SetReply(334, S); {Do not Localize} ASender.SendReply; S := Trim(ASender.Context.Connection.IOHandler.ReadLn); if S <> '' then begin LPassword := LDecoder.DecodeString(S); end; LAuthFailed := False; finally FreeAndNil(LDecoder); end; finally FreeAndNil(LEncoder); end; except end; end; // Add other login units here if not LAuthFailed then begin LAccepted := not Assigned(FOnUserLogin); if not LAccepted then begin FOnUserLogin(LContext, LUsername, LPassword, LAccepted); end; LContext.LoggedIn := LAccepted; if LAccepted then begin LContext.Username := LUsername; SetEnhReply(ASender.Reply, 235, Id_EHR_SEC_OTHER_OK, ' welcome ' + Trim(LUsername), LContext.EHLO); {Do not Localize} ASender.SendReply; Exit; end; end; AuthFailed(ASender); end; procedure TIdSMTPServer.SetEnhReply(AReply: TIdReply; const ANumericCode: Integer; const AEnhReply, AText: String; const IsEHLO: Boolean); begin if IsEHLO and (AReply is TIdReplySMTP) then begin TIdReplySMTP(AReply).SetEnhReply(ANumericCode, AEnhReply, AText); end else begin AReply.SetReply(ANumericCode, AText); end; end; procedure TIdSMTPServer.AuthFailed(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 535, Id_EHR_SEC_OTHER_PERM, RSSMTPSvrAuthFailed, TIdSMTPServerContext(ASender.Context).EHLO); ASender.SendReply; end; procedure TIdSMTPServer.AddrInvalid(ASender: TIdCommand; const AAddress : String = ''); begin SetEnhReply(ASender.Reply, 500, Id_EHR_MSG_BAD_DEST, IndyFormat(RSSMTPSvrAddressError, [AAddress]), TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.AddrNotWillForward(ASender: TIdCommand; const AAddress : String = ''; const ATo : String = ''); var LMsg: String; begin if ATo <> '' then begin LMsg := IndyFormat(RSSMTPUserNotLocal, [AAddress, ATo]); end else begin LMsg := IndyFormat(RSSMTPUserNotLocalNoAddr, [AAddress]); end; SetEnhReply(ASender.Reply, 521, Id_EHR_SEC_DEL_NOT_AUTH, LMsg, TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.AddrValid(ASender: TIdCommand; const AAddress : String = ''); begin SetEnhReply(ASender.Reply, 250, Id_EHR_MSG_VALID_DEST, IndyFormat(RSSMTPSvrAddressOk, [AAddress]), TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.AddrNoRelaying(ASender: TIdCommand; const AAddress: String); begin SetEnhReply(ASender.Reply, 550, Id_EHR_SEC_DEL_NOT_AUTH, IndyFormat(RSSMTPSvrNoRelay, [AAddress]), TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.AddrWillForward(ASender: TIdCommand; const AAddress : String = ''; const ATo : String = ''); var LMsg: String; begin if ATo <> '' then begin LMsg := IndyFormat(RSSMTPUserNotLocalFwdAddr, [AAddress, ATo]); end else begin LMsg := IndyFormat(RSSMTPUserNotLocalNoAddr, [AAddress]); end; SetEnhReply(ASender.Reply, 251, Id_EHR_MSG_VALID_DEST, LMsg, TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.AddrTooManyRecipients(ASender: TIdCommand); begin SetEnhReply(ASender.Reply,250,Id_EHR_PR_TOO_MANY_RECIPIENTS_PERM, RSSMTPTooManyRecipients, TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.AddrDisabledPerm(ASender: TIdCommand; const AAddress: String); begin SetEnhReply(ASender.Reply, 550, Id_EHR_MB_DISABLED_PERM, IndyFormat(RSSMTPAccountDisabled, [AAddress]), TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.AddrDisabledTemp(ASender: TIdCommand; const AAddress: String); begin SetEnhReply(ASender.Reply, 550, Id_EHR_MB_DISABLED_TEMP, IndyFormat(RSSMTPAccountDisabled, [AAddress]), TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.MailSubmitLimitExceeded(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 552, Id_EHR_MB_MSG_LEN_LIMIT, RSSMTPMsgLenLimit, TIdSMTPServerContext(ASender.Context).EHLO); ASender.SendReply; end; procedure TIdSMTPServer.MailSubmitLocalProcessingError( ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 451, Id_EHR_MD_OTHER_TRANS, RSSMTPLocalProcessingError, TIdSMTPServerContext(ASender.Context).EHLO); ASender.SendReply; end; procedure TIdSMTPServer.MailSubmitOk(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 250, '', RSSMTPSvrOk, TIdSMTPServerContext(ASender.Context).EHLO); ASender.SendReply; end; procedure TIdSMTPServer.MailSubmitStorageExceededFull(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 552, Id_EHR_MB_FULL, RSSMTPSvrExceededStorageAlloc, TIdSMTPServerContext(ASender.Context).EHLO); ASender.SendReply; end; procedure TIdSMTPServer.MailSubmitSystemFull(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 452, Id_EHR_MD_MAIL_SYSTEM_FULL, RSSMTPSvrInsufficientSysStorage, TIdSMTPServerContext(ASender.Context).EHLO); ASender.SendReply; end; procedure TIdSMTPServer.MailSubmitTransactionFailed(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 554, Id_EHR_MB_OTHER_STATUS_TRANS, RSSMTPSvrTransactionFailed, TIdSMTPServerContext(ASender.Context).EHLO); ASender.SendReply; end; procedure TIdSMTPServer.MailFromAccept(ASender: TIdCommand; const AAddress : String = ''); begin SetEnhReply(ASender.Reply, 250, Id_EHR_MSG_OTH_OK, IndyFormat(RSSMTPSvrAddressOk, [AAddress]), TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.MailFromReject(ASender: TIdCommand; const AAddress : String = ''); begin SetEnhReply(ASender.Reply, 550, Id_EHR_SEC_DEL_NOT_AUTH, IndyFormat(RSSMTPSvrNotPermitted, [AAddress]), TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.NoHello(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 501, Id_EHR_PR_OTHER_PERM, RSSMTPSvrNoHello, TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.CommandMAIL(ASender: TIdCommand); var EMailAddress: TIdEMailAddressItem; LContext : TIdSMTPServerContext; LM : TIdMailFromReply; LParams: TStringList; S: String; LSize: Integer; LBodyType: TIdSMTPBodyType; begin //Note that unlike other protocols, it might not be possible //to completely disable MAIL FROM for people not using SSL //because SMTP is also used from server to server mail transfers. LContext := TIdSMTPServerContext(ASender.Context); if LContext.HELO or LContext.EHLO then begin // Looking for either HELO or EHLO //reset all information LContext.From := ''; {Do not Localize} LContext.RCPTList.Clear; if TextStartsWith(ASender.UnparsedParams, 'FROM:') then begin {Do not Localize} EMailAddress := TIdEMailAddressItem.Create(nil); try S := TrimLeft(Copy(ASender.UnparsedParams, 6, MaxInt)); EMailAddress.Text := Fetch(S); if SPFAuthOk(LContext, ASender.Reply, 'MAIL FROM', EmailAddress.Domain, EmailAddress.Address) then {do not localize} begin LM := mAccept; LParams := TStringList.Create; try SplitDelimitedString(S, LParams, True); // RLebeau: check the message size before accepting the message if LParams.IndexOfName('SIZE') <> -1 then begin LSize := IndyStrToInt(LParams.Values['SIZE']); if (FMaxMsgSize > 0) and (LSize > FMaxMsgSize) then begin MailSubmitLimitExceeded(ASender); Exit; end; end else begin LSize := -1; end; // RLebeau: get the message encoding type and store it for later use if LParams.IndexOfName('BODY') <> -1 then {do not localize} begin case PosInStrArray(LParams.Values['BODY'], ['7BIT', '8BITMIME', 'BINARYMIME'], False) of {do not localize} 0: LBodyType := idSMTP7Bit; 1: LBodyType := idSMTP8BitMime; 2: LBodyType := idSMTPBinaryMime; else InvalidSyntax(ASender); Exit; end; end else begin LBodyType := idSMTP8BitMime; end; // let the user perform custom validations if Assigned(FOnMailFrom) then begin FOnMailFrom(LContext, EMailAddress.Address, LParams, LM); end; finally FreeAndNil(LParams); end; case LM of mAccept : begin MailFromAccept(ASender, EMailAddress.Address); LContext.From := EMailAddress.Address; // RLebeau: store the message size in case the OnRCPT handler // wants to verify the size on a per-recipient basis LContext.MsgSize := LSize; LContext.FBodyType := LBodyType; LContext.SMTPState := idSMTPMail; end; mReject : begin MailFromReject(ASender, EMailAddress.Text); end; mSystemFull: begin MailSubmitSystemFull(ASender); end; mLimitExceeded: begin MailSubmitLimitExceeded(ASender); end; end; end; finally FreeAndNil(EMailAddress); end; end else begin InvalidSyntax(ASender); end; end else begin // No EHLO / HELO was received NoHello(ASender); end; LContext.CheckPipeLine; end; procedure TIdSMTPServer.InvalidSyntax(ASender: TIdCommand); begin SetEnhReply(ASender.Reply, 501, Id_EHR_PR_INVALID_CMD_ARGS, RSPOP3SvrInvalidSyntax, TIdSMTPServerContext(ASender.Context).EHLO); end; procedure TIdSMTPServer.CommandRCPT(ASender: TIdCommand); var EMailAddress: TIdEMailAddressItem; LContext : TIdSMTPServerContext; LAction : TIdRCPToReply; LParams: TStringList; LForward, S : String; begin LForward := ''; LContext := TIdSMTPServerContext(ASender.Context); if not (LContext.SMTPState in [idSMTPMail, idSMTPRcpt]) then begin BadSequenceError(ASender); Exit; end; if LContext.HELO or LContext.EHLO then begin if TextStartsWith(ASender.UnparsedParams, 'TO:') then begin {Do not Localize} LAction := rRelayDenied; //do not change this in the OnRcptTo event unless one of the following //things is TRUE: // //1. The user authenticated to the SMTP server // //2. The user is from an IP address being served by the SMTP server. // Test the IP address for this. // //3. Another SMTP server outside of your network is sending E-Mail to a // user on YOUR system. // //The reason is that you do not want to relay E-Messages for outsiders //if the E-Mail is from outside of your network. Be very CAREFUL. Otherwise, //you have a security hazard that spammers can abuse. EMailAddress := TIdEMailAddressItem.Create(nil); try S := TrimLeft(Copy(ASender.UnparsedParams, 4, MaxInt)); // TODO: remove this Fetch() and let TIdEMailAddressItem parse the // entire text, as it may have embedded spaces in it EMailAddress.Text := Fetch(S); if Assigned(FOnRcptTo) then begin LParams := TStringList.Create; try SplitDelimitedString(S, LParams, True); FOnRcptTo(LContext, EMailAddress.Address, LParams, LAction, LForward); finally FreeAndNil(LParams); end; case LAction of rAddressOk : begin AddrValid(ASender, EMailAddress.Address); LContext.RCPTList.Add.Assign(EMailAddress); LContext.SMTPState := idSMTPRcpt; end; rRelayDenied : begin AddrNoRelaying(ASender, EMailAddress.Address); end; rWillForward : begin AddrWillForward(ASender, EMailAddress.Address, LForward); if LForward <> '' then begin LContext.RCPTList.Add.Text := LForward; end else begin LContext.RCPTList.Add.Assign(EMailAddress); end; LContext.SMTPState := idSMTPRcpt; end; rNoForward : AddrNotWillForward(ASender, EMailAddress.Address, LForward); rTooManyAddresses : AddrTooManyRecipients(ASender); rDisabledPerm : AddrDisabledPerm(ASender, EMailAddress.Address); rDisabledTemp : AddrDisabledTemp(ASender, EMailAddress.Address); rSystemFull : MailSubmitSystemFull(ASender); rLimitExceeded : MailSubmitLimitExceeded(ASender); else AddrInvalid(ASender, EMailAddress.Address); end; end else begin raise EIdSMTPServerNoRcptTo.Create(RSSMTPNoOnRcptTo); end; finally FreeAndNil(EMailAddress); end; end else begin SetEnhReply(ASender.Reply, 501, Id_EHR_PR_SYNTAX_ERR,RSSMTPSvrParmErrRcptTo, LContext.EHLO); end; end else begin // No EHLO / HELO was received NoHello(ASender); end; LContext.CheckPipeLine; end; procedure TIdSMTPServer.CommandSTARTTLS(ASender: TIdCommand); var LContext : TIdSMTPServerContext; begin LContext := TIdSMTPServerContext(ASender.Context); if not LContext.EHLO then begin BadSequenceError(ASender); Exit; end; if not LContext.CanUseExplicitTLS then begin CmdSyntaxError(ASender); LContext.PipeLining := False; Exit; end; if LContext.UsingTLS then begin // we are already using TLS BadSequenceError(ASender); Exit; end; SetEnhReply(ASender.Reply, 220, Id_EHR_GENERIC_OK, RSSMTPSvrReadyForTLS, LContext.EHLO); ASender.SendReply; TIdSSLIOHandlerSocketBase(LContext.Connection.IOHandler).PassThrough := False; DoReset(LContext, True); end; procedure TIdSMTPServer.CommandNOOP(ASender: TIdCommand); begin //we just use the default NOOP and only clear pipelining for synchronization TIdSMTPServerContext(ASender.Context).PipeLining := False; end; procedure TIdSMTPServer.CommandQUIT(ASender: TIdCommand); var LContext: TIdSMTPServerContext; begin //clear pipelining before exit LContext := TIdSMTPServerContext(ASender.Context); LContext.PipeLining := False; DoReset(LContext); ASender.SendReply; end; procedure TIdSMTPServer.CommandRSET(ASender: TIdCommand); begin DoReset(TIdSMTPServerContext(ASender.Context)); end; // RLebeau: if HostByAddress() fails, the received // message gets lost, so trapping any exceptions here function AddrFromHost(const AIP: String): String; begin try Result := GStack.HostByAddress(AIP); except Result := 'unknown'; {do not localize} end; end; procedure TIdSMTPServer.CommandDATA(ASender: TIdCommand); const BodyEncType: array[TIdSMTPBodyType] of IdTextEncodingType = (encASCII, enc8Bit, enc8Bit); var LContext : TIdSMTPServerContext; LStream: TStream; LEncoding: IIdTextEncoding; begin LContext := TIdSMTPServerContext(ASender.Context); if LContext.SMTPState <> idSMTPRcpt then begin BadSequenceError(ASender); Exit; end; if LContext.HELO or LContext.EHLO then begin // BINARYMIME cannot be used with the DATA command if LContext.FBodyType = idSMTPBinaryMime then begin BadSequenceError(ASender); Exit; end; MsgBegan(LContext, LStream); try // RLebeau: TODO - do not even create the stream if the OnMsgReceive // event is not assigned, or at least create a stream that discards // any data received... LEncoding := IndyTextEncoding(BodyEncType[LContext.FBodyType]); SetEnhReply(ASender.Reply, 354, '', RSSMTPSvrStartData, LContext.EHLO); ASender.SendReply; LContext.PipeLining := False; LContext.Connection.IOHandler.Capture(LStream, '.', True, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF}); {Do not Localize} MsgReceived(ASender, LStream); finally FreeAndNil(LStream); DoReset(LContext); end; end else begin // No EHLO / HELO was received NoHello(ASender); end; LContext.PipeLining := False; end; procedure TIdSMTPServer.CommandBDAT(ASender: TIdCommand); var LContext : TIdSMTPServerContext; LSize: TIdStreamSize; LLast: Boolean; begin LContext := TIdSMTPServerContext(ASender.Context); if not (LContext.SMTPState in [idSMTPRcpt, idSMTPBDat]) then begin BadSequenceError(ASender); Exit; end; if LContext.HELO or LContext.EHLO then begin LContext.PipeLining := False; if ASender.Params.Count > 0 then begin LSize := IndyStrToStreamSize(ASender.Params[0], -1); if LSize < 0 then begin CmdSyntaxError(ASender); Exit; end; if ASender.Params.Count > 1 then begin if not TextIsSame(ASender.Params[1], 'LAST') then begin {do not localize} LContext.Connection.IOHandler.Discard(LSize); CmdSyntaxError(ASender); Exit; end; LLast := True; end else begin LLast := False; end; LContext.SMTPState := idSMTPBDat; if not Assigned(LContext.FBDataStream) then begin MsgBegan(LContext, LContext.FBDataStream); end; LContext.Connection.IOHandler.ReadStream(LContext.FBDataStream, LSize, False); if not LLast then begin Exit; // do not turn off pipelining yet end; try MsgReceived(ASender, LContext.FBDataStream); finally DoReset(LContext); end; end else begin CmdSyntaxError(ASender); end; end else begin // No EHLO / HELO was received NoHello(ASender); end; LContext.PipeLining := False; end; procedure TIdSMTPServer.DoReset(AContext: TIdSMTPServerContext; AIsTLSReset: Boolean = False); begin AContext.Reset(AIsTLSReset); if Assigned(FOnReset) then begin FOnReset(AContext); end; end; procedure TIdSMTPServer.SetMaxMsgSize(AValue: Integer); begin FMaxMsgSize := IndyMax(AValue, 0); end; // RLebeau: processing the tokens dynamically now // so that only the tokens that are actually present // will be processed. This helps to avoid unnecessary // lookups for tokens that are not actually used function ReplaceReceivedTokens(AContext: TIdSMTPServerContext; const AReceivedString: String): String; var LTokens: TStringList; i: Integer; //we do it this way so we can take advantage of the StringBuilder in DotNET. ReplaceOld, ReplaceNew: array of string; {$IFNDEF HAS_TStrings_ValueFromIndex} S: String; {$ENDIF} begin LTokens := TStringList.Create; try if Pos('$hostname', AReceivedString) <> 0 then begin {do not localize} LTokens.Add('$hostname=' + AddrFromHost(AContext.Binding.PeerIP)); {do not localize} end; if Pos('$ipaddress', AReceivedString) <> 0 then begin {do not localize} LTokens.Add('$ipaddress=' + AContext.Binding.PeerIP); {do not localize} end; if Pos('$helo', AReceivedString) <> 0 then begin {do not localize} LTokens.Add('$helo=' + AContext.HeloString); {do not localize} end; if Pos('$protocol', AReceivedString) <> 0 then begin {do not localize} LTokens.Add('$protocol=' + iif(AContext.EHLO, 'esmtp', 'smtp')); {do not localize} end; if Pos('$servername', AReceivedString) <> 0 then begin {do not localize} LTokens.Add('$servername=' + TIdSMTPServer(AContext.Server).ServerName); {do not localize} end; if Pos('$svrhostname', AReceivedString) <> 0 then begin {do not localize} LTokens.Add('$svrhostname=' + AddrFromHost(AContext.Binding.IP)); {do not localize} end; if Pos('$svripaddress', AReceivedString) <> 0 then begin {do not localize} LTokens.Add('$svripaddress=' + AContext.Binding.IP); {do not localize} end; if LTokens.Count > 0 then begin SetLength(ReplaceNew, LTokens.Count); SetLength(ReplaceOld, LTokens.Count); for i := 0 to LTokens.Count-1 do begin ReplaceOld[i] := LTokens.Names[i]; {$IFDEF HAS_TStrings_ValueFromIndex} ReplaceNew[i] := LTokens.ValueFromIndex[i]; {$ELSE} S := LTokens.Strings[i]; ReplaceNew[i] := Copy(S, Pos('=', S)+1, MaxInt); {$ENDIF} end; Result := StringsReplace(AReceivedString, ReplaceOld, ReplaceNew); end else begin Result := AReceivedString; end; finally FreeAndNil(LTokens); end; end; procedure TIdSMTPServer.MsgBegan(AContext: TIdSMTPServerContext; var VStream: TStream); var LReceivedString: string; begin VStream := nil; if Assigned(FOnBeforeMsg) then begin FOnBeforeMsg(AContext, VStream); end; if not Assigned(VStream) then begin VStream := TMemoryStream.Create; end; try LReceivedString := IdSMTPSvrReceivedString; if Assigned(FOnReceived) then begin FOnReceived(AContext, LReceivedString); end; if AContext.FinalStage then begin // If at the final delivery stage, add the Return-Path line for the received MAIL FROM line. WriteStringToStream(VStream, 'Received-Path: <' + AContext.From + '>' + EOL); {do not localize} end; if LReceivedString <> '' then begin WriteStringToStream(VStream, ReplaceReceivedTokens(AContext, LReceivedString) + EOL); end; except FreeAndNil(VStream); raise; end; end; procedure TIdSMTPServer.MsgReceived(ASender: TIdCommand; AMsgData: TStream); var LAction: TIdDataReply; begin LAction := dOk; AMsgData.Position := 0; // RLebeau: verify the message size now if (FMaxMsgSize > 0) and (AMsgData.Size > FMaxMsgSize) then begin LAction := dLimitExceeded; end else if Assigned(FOnMsgReceive) then begin FOnMsgReceive(TIdSMTPServerContext(ASender.Context), AMsgData, LAction); end; case LAction of dOk : MailSubmitOk(ASender); //accept the mail message dMBFull : MailSubmitStorageExceededFull(ASender); //Mail box full dSystemFull : MailSubmitSystemFull(ASender); //no more space on server dLocalProcessingError : MailSubmitLocalProcessingError(ASender); //local processing error dTransactionFailed : MailSubmitTransactionFailed(ASender); //transaction failed dLimitExceeded : MailSubmitLimitExceeded(ASender); //exceeded administrative limit end; end; function TIdSMTPServer.SPFAuthOk(AContext: TIdSMTPServerContext; AReply: TIdReply; const ACmd, ADomain, AIdentity: String): Boolean; var LAction: TIdSPFReply; begin Result := False; LAction := spfNeutral; if Assigned(FOnSPFCheck) then begin FOnSPFCheck(AContext, AContext.Binding.PeerIP, ADomain, AIdentity, LAction); end; case LAction of spfNone, spfNeutral, spfPass, spfSoftFail: // let the caller handle the reply as needed Result := True; spfFail: begin SetEnhReply(AReply, 550, '5.7.1', IndyFormat(RSSMTPSvrSPFCheckFailed, [ACmd]), AContext.EHLO); {do not localize} end; spfTempError, spfPermError: begin SetEnhReply(AReply, 451, '4.4.3', IndyFormat(RSSMTPSvrSPFCheckError, [ACmd]), AContext.EHLO); {do not localize} end; end; end; { TIdSMTPServerContext } procedure TIdSMTPServerContext.CheckPipeLine; begin if not Connection.IOHandler.InputBufferIsEmpty then begin PipeLining := True; end; end; constructor TIdSMTPServerContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); begin inherited Create(AConnection, AYarn, AList); SMTPState := idSMTPNone; From := ''; HELO := False; EHLO := False; Username := ''; Password := ''; LoggedIn := False; FRCPTList := TIdEMailAddressList.Create(nil); end; destructor TIdSMTPServerContext.Destroy; begin FreeAndNil(FRCPTList); inherited Destroy; end; function TIdSMTPServerContext.GetUsingTLS: Boolean; begin Result := Connection.IOHandler is TIdSSLIOHandlerSocketBase; if Result then begin Result := not TIdSSLIOHandlerSocketBase(Connection.IOHandler).PassThrough; end; end; function TIdSMTPServerContext.GetCanUseExplicitTLS: Boolean; begin Result := Connection.IOHandler is TIdSSLIOHandlerSocketBase; if Result then begin Result := TIdSMTPServer(Server).UseTLS in ExplicitTLSVals; end; end; function TIdSMTPServerContext.GetTLSIsRequired: Boolean; begin Result := TIdSMTPServer(Server).UseTLS = utUseRequireTLS; if Result then begin Result := not UsingTLS; end; end; procedure TIdSMTPServerContext.Reset(AIsTLSReset: Boolean = False); begin // RLebeau: do not reset the user authentication except for STARTTLS! A // normal reset (RSET, HELO/EHLO after a session is started, and QUIT) // should only abort the current mail transaction and clear its buffers // and state tables, nothing more if (not AIsTLSReset) and (FEHLO or FHELO) then begin FSMTPState := idSMTPHelo; end else begin FSMTPState := idSMTPNone; FEHLO := False; FHELO := False; FHeloString := ''; FUsername := ''; FPassword := ''; FLoggedIn := False; end; FFrom := ''; FRCPTList.Clear; FMsgSize := 0; FBodyType := idSMTP8BitMime; FFinalStage := False; FreeAndNil(FBDataStream); CheckPipeLine; end; procedure TIdSMTPServerContext.SetPipeLining(const AValue: Boolean); begin if AValue and (not PipeLining) then begin Connection.IOHandler.WriteBufferOpen; end else if (not AValue) and PipeLining then begin Connection.IOHandler.WriteBufferClose; end; FPipeLining := AValue; end; end.