{ $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.47 1/7/05 3:29:34 PM RLebeau Fix for AV in Notification() Rev 1.46 11/28/04 2:31:38 PM RLebeau Updated Authenticate() to create the TIdEncoderMIME instance before sending the 'AUTH LOGIN' command. Rev 1.45 11/27/2004 8:58:14 PM JPMugaas Compile errors. Rev 1.44 11/27/04 3:21:30 AM RLebeau Fixed bug in ownership of SASLMechanisms property. Recoded Authenticate() to use a "case of" statement instead. Rev 1.43 10/26/2004 10:55:34 PM JPMugaas Updated refs. Rev 1.42 6/11/2004 9:38:40 AM DSiders Added "Do not Localize" comments. Rev 1.41 2004.03.06 1:31:52 PM czhower To match Disconnect changes to core. Rev 1.40 2/25/2004 5:41:28 AM JPMugaas Authentication bug fixed. Rev 1.39 2004.02.03 5:44:20 PM czhower Name changes Rev 1.38 1/31/2004 3:12:56 AM JPMugaas Removed dependancy on Math unit. It isn't needed and is problematic in some versions of Dlephi which don't include it. Rev 1.37 26/01/2004 01:51:38 CCostelloe Changed implementation of supressing BCC List generation Rev 1.36 25/01/2004 21:16:16 CCostelloe Added support for SuppressBCCListInHeader Rev 1.35 1/25/2004 3:11:44 PM JPMugaas SASL Interface reworked to make it easier for developers to use. SSL and SASL reenabled components. Rev 1.34 2004.01.22 10:29:56 PM czhower Now supports default login mechanism with just username and pw. Rev 1.33 1/21/2004 4:03:22 PM JPMugaas InitComponent Rev 1.32 12/28/2003 4:47:02 PM BGooijen Removed ChangeReplyClass Rev 1.31 22/12/2003 00:46:16 CCostelloe .NET fixes Rev 1.30 24/10/2003 20:53:02 CCostelloe Bug fix of LRecipients.EMailAddresses in Send. Rev 1.29 2003.10.17 6:15:16 PM czhower Bug fix with quit. Rev 1.28 10/17/2003 1:01:04 AM DSiders Added localization comments. Rev 1.27 2003.10.14 1:28:04 PM czhower DotNet Rev 1.26 10/11/2003 7:14:36 PM BGooijen Changed IdCompilerDefines.inc path Rev 1.25 10/10/2003 10:45:10 PM BGooijen DotNet Rev 1.24 2003.10.02 9:27:52 PM czhower DotNet Excludes Rev 1.23 6/15/2003 03:28:30 PM JPMugaas Minor class change. Rev 1.22 6/15/2003 01:13:40 PM JPMugaas Now uses new base class. Rev 1.21 6/5/2003 04:54:08 AM JPMugaas Reworkings and minor changes for new Reply exception framework. Rev 1.20 6/4/2003 04:10:40 PM JPMugaas Removed hacked GetInternelResponse. Updated to use Kudzu's new string reply code. Rev 1.19 5/26/2003 12:24:04 PM JPMugaas Rev 1.18 5/25/2003 03:54:48 AM JPMugaas Rev 1.17 5/25/2003 12:13:22 AM JPMugaas SMTP StartTLS code moved into IdSMTPCommon for sharing with TIdDirectSMTP. StartTLS is now called in Authenticate to prevent unintentional unencrypted password transmission (e.g. AUTH LOGIN being called before STARTTLS). Rev 1.16 5/23/2003 04:52:26 AM JPMugaas Work started on TIdDirectSMTP to support enhanced error codes. Rev 1.15 5/22/2003 05:26:16 PM JPMugaas RFC 2034 Rev 1.14 5/18/2003 02:31:42 PM JPMugaas Reworked some things so IdSMTP and IdDirectSMTP can share code including stuff for pipelining. Rev 1.13 5/15/2003 11:09:46 AM JPMugaas "RFC 2197 SMTP Service Extension for Command Pipelining" now supported. It should increase efficiency in TIdSMTP. Rev 1.12 5/13/2003 07:35:06 AM JPMugaas Made UseEHLO a requirement for explicit TLS because explicit TLS using EHLO to determine if the server supports explicit TLS. Setting UseEHLO will the UseTLS property be the default (no encryption) and setting UseTLS to an explicit TLS setting will cause the UseEHLO property to be true. Rev 1.11 5/13/2003 07:03:48 AM JPMugaas Ciaran Costelloe reported a bug in the Assign method. Username and Password were still being assigned even though the SMTP component does not publish or use them. I have updated the SMTP assign method with the new properties and removed the references to Password and Username. Rev 1.10 5/10/2003 10:10:40 PM JPMugaas Bug fixes. Rev 1.9 5/8/2003 08:44:22 PM JPMugaas Moved some SASL authentication code down to an anscestor for reuse. WIll clean up soon. Rev 1.8 5/8/2003 03:18:30 PM JPMugaas Flattened ou the SASL authentication API, made a custom descendant of SASL enabled TIdMessageClient classes. Rev 1.7 5/8/2003 11:28:14 AM JPMugaas Moved feature negoation properties down to the ExplicitTLSClient level as feature negotiation goes hand in hand with explicit TLS support. Rev 1.6 5/8/2003 02:18:18 AM JPMugaas Fixed an AV in IdPOP3 with SASL list on forms. Made exceptions for SASL mechanisms missing more consistant, made IdPOP3 support feature feature negotiation, and consolidated some duplicate code. Rev 1.5 4/5/2003 02:06:32 PM JPMugaas TLS handshake itself can now be handled. Rev 1.4 3/27/2003 05:46:50 AM JPMugaas Updated framework with an event if the TLS negotiation command fails. Cleaned up some duplicate code in the clients. Rev 1.3 3/26/2003 04:19:34 PM JPMugaas Cleaned-up some code and illiminated some duplicate things. Rev 1.2 3/13/2003 09:49:32 AM JPMugaas Now uses an abstract SSL base class instead of OpenSSL so 3rd-party vendors can plug-in their products. Rev 1.1 12/15/2002 05:50:18 PM JPMugaas SMTP and IMAP4 compile. IdPOP3, IdFTP, IMAP4, and IdSMTP now restored in IdRegister. Rev 1.0 11/13/2002 08:00:48 AM JPMugaas } unit IdSMTP; interface {$i IdCompilerDefines.inc} uses Classes, IdAssignedNumbers, IdEMailAddress, IdException, IdExplicitTLSClientServerBase, IdHeaderList, IdMessage, IdMessageClient, IdSASL, IdSASLCollection, IdSMTPBase, IdBaseComponent, IdGlobal, SysUtils; type TIdSMTPAuthenticationType = (satNone, satDefault, satSASL); const DEF_SMTP_AUTH = satDefault; type //FSASLMechanisms TIdSMTP = class(TIdSMTPBase) protected FAuthType: TIdSMTPAuthenticationType; // This is just an internal flag we use to determine if we already authenticated to the server. FDidAuthenticate: Boolean; FValidateAuthLoginCapability: Boolean; // FSASLMechanisms : TIdSASLList; FSASLMechanisms : TIdSASLEntries; // procedure SetAuthType(const AValue: TIdSMTPAuthenticationType); procedure SetUseEhlo(const AValue: Boolean); override; procedure SetUseTLS(AValue: TIdUseTLS); override; procedure SetSASLMechanisms(AValue: TIdSASLEntries); procedure InitComponent; override; procedure InternalSend(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList); override; procedure Notification(AComponent: TComponent; Operation: TOperation); override; // // holger: .NET compatibility change, OnConnected being reintroduced property OnConnected; public destructor Destroy; override; procedure Assign(Source: TPersistent); override; function Authenticate: Boolean; virtual; procedure Connect; override; procedure Disconnect(ANotifyPeer: Boolean); override; procedure DisconnectNotifyPeer; override; class procedure QuickSend(const AHost, ASubject, ATo, AFrom, AText: string); overload; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use ContentType overload of QuickSend()'{$ENDIF};{$ENDIF} class procedure QuickSend(const AHost, ASubject, ATo, AFrom, AText, AContentType, ACharset, AContentTransferEncoding: string); overload; procedure Expand(AUserName : String; AResults : TStrings); virtual; function Verify(AUserName : String) : String; virtual; // property DidAuthenticate: Boolean read FDidAuthenticate; published property AuthType: TIdSMTPAuthenticationType read FAuthType write FAuthType default DEF_SMTP_AUTH; property Host; property Password; property Port default IdPORT_SMTP; // property SASLMechanisms: TIdSASLList read FSASLMechanisms write FSASLMechanisms; property SASLMechanisms : TIdSASLEntries read FSASLMechanisms write SetSASLMechanisms; property UseTLS; property Username; property ValidateAuthLoginCapability: Boolean read FValidateAuthLoginCapability write FValidateAuthLoginCapability default True; // property OnTLSNotAvailable; end; implementation uses IdCoderMIME, IdGlobalProtocols, IdReplySMTP, IdSSL, IdResourceStringsProtocols, IdTCPConnection; { TIdSMTP } procedure TIdSMTP.Assign(Source: TPersistent); var LS: TIdSMTP; begin if Source is TIdSMTP then begin LS := Source as TIdSMTP; AuthType := LS.AuthType; HeloName := LS.HeloName; SASLMechanisms := LS.SASLMechanisms; UseEhlo := LS.UseEhlo; UseTLS := LS.UseTLS; Host := LS.Host; MailAgent := LS.MailAgent; Port := LS.Port; Username := LS.Username; Password := LS.Password; Pipeline := LS.Pipeline; end else begin inherited Assign(Source); end; end; function TIdSMTP.Authenticate : Boolean; var s : TStrings; LEncoder: TIdEncoderMIME; begin if FDidAuthenticate then begin Result := True; Exit; end; //This will look strange but we have logic in that method to make //sure that the STARTTLS command is used appropriately. //Note we put this in Authenticate only to ensure that TLS negotiation //is done before a password is sent over a network unencrypted. StartTLS; //note that we pass the reply numbers as strings so the SASL stuff can work //with IMAP4 and POP3 where non-numeric strings are used for reply codes case FAuthType of satNone: begin //do nothing FDidAuthenticate := True; end; satDefault: begin { RLebeau: TODO - implement the following code in the future instead of the code below. This way, TIdSASLLogin can be utilized here. SASLMechanisms.LoginSASL('AUTH', FHost, IdGSKSSN_smtp, 'LOGIN', ['235'], ['334'], Self, Capabilities); FDidAuthenticate := True; Or better, if the SASLMechanisms is empty, put some default entries in it, including TIdSASLLogin, and then reset the AuthType to satSASL. Maybe even do that in SetAuthType/Loaded() instead. That way, everything goes through SASLMechanisms only... } if Username <> '' then begin if FValidateAuthLoginCapability then begin s := TStringList.Create; try SASLMechanisms.ParseCapaReplyToList(Capabilities, s); //many servers today do not use username/password authentication if s.IndexOf('LOGIN') = -1 then begin Result := False; Exit; end; finally FreeAndNil(s); end; end; LEncoder := TIdEncoderMIME.Create(nil); try SendCmd('AUTH LOGIN', 334); if SendCmd(LEncoder.Encode(Username), [235, 334]) = 334 then begin SendCmd(LEncoder.Encode(Password), 235); end; finally LEncoder.Free; end; FDidAuthenticate := True; end; end; satSASL: begin SASLMechanisms.LoginSASL('AUTH', FHost, IdGSKSSN_smtp, ['235'], ['334'], Self, Capabilities); {do not localize} FDidAuthenticate := True; end; end; Result := FDidAuthenticate; end; procedure TIdSMTP.Connect; begin FDidAuthenticate := False; inherited Connect; try GetResponse(220); SendGreeting; except Disconnect(False); raise; end; end; procedure TIdSMTP.InitComponent; begin inherited InitComponent; FSASLMechanisms := TIdSASLEntries.Create(Self); FAuthType := DEF_SMTP_AUTH; FValidateAuthLoginCapability := True; end; procedure TIdSMTP.DisconnectNotifyPeer; begin inherited DisconnectNotifyPeer; SendCmd('QUIT', 221); {Do not Localize} end; procedure TIdSMTP.Expand(AUserName: String; AResults: TStrings); begin SendCMD('EXPN ' + AUserName, [250, 251]); {Do not Localize} end; procedure InternalQuickSend(const AHost, ASubject, ATo, AFrom, AText, AContentType, ACharset, AContentTransferEncoding: String); {$IFDEF USE_INLINE}inline;{$ENDIF} var LSMTP: TIdSMTP; LMsg: TIdMessage; begin LSMTP := TIdSMTP.Create(nil); try LMsg := TIdMessage.Create(nil); try LMsg.Subject := ASubject; LMsg.Recipients.EMailAddresses := ATo; LMsg.From.Text := AFrom; LMsg.Body.Text := AText; LMsg.ContentType := AContentType; LMsg.CharSet := ACharset; LMsg.ContentTransferEncoding := AContentTransferEncoding; LSMTP.Host := AHost; LSMTP.Connect; try; LSMTP.Send(LMsg); finally LSMTP.Disconnect; end; finally FreeAndNil(LMsg); end; finally FreeAndNil(LSMTP); end; end; {$I IdDeprecatedImplBugOff.inc} class procedure TIdSMTP.QuickSend(const AHost, ASubject, ATo, AFrom, AText: String); {$I IdDeprecatedImplBugOn.inc} begin InternalQuickSend(AHost, ASubject, ATo, AFrom, AText, '', '', ''); end; {$I IdDeprecatedImplBugOff.inc} class procedure TIdSMTP.QuickSend(const AHost, ASubject, ATo, AFrom, AText, AContentType, ACharset, AContentTransferEncoding: String); {$I IdDeprecatedImplBugOn.inc} begin InternalQuickSend(AHost, ASubject, ATo, AFrom, AText, AContentType, ACharset, AContentTransferEncoding); end; procedure TIdSMTP.InternalSend(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList); begin //Authenticate now calls StartTLS //so that you do not send login information before TLS negotiation (big oops security wise). //It also should see if authentication should be done according to your settings. Authenticate; AMsg.ExtraHeaders.Values[XMAILER_HEADER] := MailAgent; inherited InternalSend(AMsg, AFrom, ARecipients); end; procedure TIdSMTP.SetAuthType(const AValue: TIdSMTPAuthenticationType); Begin FAuthType := AValue; if AValue = satSASL then begin FUseEhlo := True; end; end; procedure TIdSMTP.SetUseEhlo(const AValue: Boolean); Begin FUseEhlo := AValue; if not AValue then begin FAuthType := satDefault; if FUseTLS in ExplicitTLSVals then begin FUseTLS := DEF_USETLS; FPipeLine := False; end; end; End; function TIdSMTP.Verify(AUserName: string): string; begin SendCMD('VRFY ' + AUserName, [250, 251]); {Do not Localize} Result := LastCmdResult.Text[0]; end; procedure TIdSMTP.Notification(AComponent: TComponent; Operation: TOperation); begin if (Operation = opRemove) and (FSASLMechanisms <> nil) then begin FSASLMechanisms.RemoveByComp(AComponent); end; inherited Notification(AComponent, Operation); end; procedure TIdSMTP.SetUseTLS(AValue: TIdUseTLS); begin inherited SetUseTLS(AValue); if FUseTLS in ExplicitTLSVals then begin UseEhlo := True; end; end; procedure TIdSMTP.SetSASLMechanisms(AValue: TIdSASLEntries); begin FSASLMechanisms.Assign(AValue); end; destructor TIdSMTP.Destroy; begin FreeAndNil(FSASLMechanisms); inherited Destroy; end; procedure TIdSMTP.Disconnect(ANotifyPeer: Boolean); begin try inherited Disconnect(ANotifyPeer); finally FDidAuthenticate := False; end; end; end.