{ $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 1/4/2005 5:27:20 PM JPMugaas Removed Windows unit from uses clause. Rev 1.8 04/01/2005 15:34:18 ANeillans Renamed InternalSend to RelayInternalSend, as it was conflicting with InternalSend in IdSMTPBase, and no e-mails were ever being sent. Some formatting and spelling corrections Rev 1.7 10/26/2004 10:55:34 PM JPMugaas Updated refs. Rev 1.6 2004.03.06 1:31:52 PM czhower To match Disconnect changes to core. Rev 1.5 2004.02.07 4:57:20 PM czhower Fixed warning. Rev 1.4 2004.02.03 5:45:48 PM czhower Name changes Rev 1.3 1/21/2004 4:03:26 PM JPMugaas InitComponent Rev 1.2 11/4/2003 10:22:56 PM DSiders Use IdException for exceptions moved to the unit. Rev 1.1 2003.10.18 9:42:14 PM czhower Boatload of bug fixes to command handlers. Rev 1.0 6/15/2003 03:27:18 PM JPMugaas Renamed IdDirect SMTP to IdSMTPRelay. Rev 1.15 6/15/2003 01:09:30 PM JPMugaas Now uses new base class in TIdSMTPBase. I removed the old original unoptimized code that made a connection for each recipient for better maintenance and because I doubt anyone would use that anyway. Rev 1.14 6/5/2003 04:54:16 AM JPMugaas Reworkings and minor changes for new Reply exception framework. Rev 1.13 5/26/2003 12:21:50 PM JPMugaas Rev 1.12 5/25/2003 03:54:12 AM JPMugaas Rev 1.11 5/25/2003 01:41:08 AM JPMugaas Sended changed to Sent. TryImplicitTLS now will only be true if UseSSL<>NoSSL. UseEHLO=False will now disable TLS usage and TLS usage being set causes UseEHLO to be true. StatusItem collection Items now have default values for Sent and ReplyCode. Rev 1.10 5/25/2003 12:40:34 AM JPMugaas Published events from TIdExplicitTLSClient to be consistant with other components in Indy and for any custom handling of certain TLS negotiatiation problems. Rev 1.9 5/25/2003 12:17:16 AM JPMugaas Now can support SSL (either requiring SSL or using it if available). This is in addition, it can optionally support ImplicitTLS (note that I do not recommend this because that is depreciated -and the port for it was deallocated and this does incur a performance penalty with an additional connect attempt - and possibly a timeout in some firewall situations). Rev 1.8 5/23/2003 7:48:12 PM BGooijen TIdSMTPRelayStatusItem.FEnhancedCode object was not created Rev 1.7 5/23/2003 05:02:42 AM JPMugaas Rev 1.6 5/23/2003 04:52:22 AM JPMugaas Work started on TIdSMTPRelay to support enhanced error codes. Rev 1.5 5/18/2003 02:31:50 PM JPMugaas Reworked some things so IdSMTP and IdDirectSMTP can share code including stuff for pipelining. Rev 1.4 4/28/2003 03:36:22 PM JPMugaas Revered back to the version I checked in earlier because of my API changes to the DNS classes. Rev 1.2 4/28/2003 07:00:02 AM JPMugaas Should now compile. Rev 1.1 12/6/2002 02:35:22 PM JPMugaas Now compiles with Indy 10. Rev 1.0 11/14/2002 02:17:28 PM JPMugaas } unit IdSMTPRelay; { Original Author: Maximiliano Di Rienzo dirienzo@infovia.com.ar Variation of the TIdSMTP that connects directly with the recipient's SMTP server and delivers the message, it doesn't use a relay SMTP server to deliver the message. Hides the procs Connect and Disconnect (protected now) because these are called internally to connect to the dynamically resolved host based on the MX server of the recipient's domain. The procs related to the auth schema have been removed because they aren't needed. Introduces the property property StatusList:TIdSMTPRelayStatusList; it's a collection containing the status of each recipient's address after the Send method is called and the event property OnDirectSMTPStatus:TIdSMTPRelayStatus; to keep track of the status as the sending is in progress. } interface {$i IdCompilerDefines.inc} uses Classes, IdAssignedNumbers, IdException, IdExceptionCore, IdEMailAddress, IdGlobal, IdHeaderList, IdDNSResolver, IdMessage, IdMessageClient, IdBaseComponent, IdSMTPBase, IdReplySMTP, SysUtils; const DEF_OneConnectionPerDomain = True; type TIdSMTPRelayStatusAction = (dmResolveMS, dmConnecting, dmConnected, dmSending, dmWorkBegin, dmWorkEndOK, dmWorkEndWithException); TIdSMTPRelayStatus = procedure(Sender: TObject; AEMailAddress: TIdEmailAddressItem; Action: TIdSMTPRelayStatusAction) of Object; EIdDirectSMTPCannotAssignHost = class(EIdException); EIdDirectSMTPCannotResolveMX = class(EIdException); TIdSSLSupport = (NoSSL, SupportSSL, RequireSSL); const DEF_SSL_SUPPORT = NoSSL; DEF_TRY_IMPLICITTLS = False; DEF_REPLY_CODE = 0; DEF_SENT = False; type TIdSMTPRelayStatusItem = class(TCollectionItem) protected FSent: Boolean; FExceptionMessage: String; FEmailAddress: String; FReplyCode : Integer; FEnhancedCode : TIdSMTPEnhancedCode; procedure SetEnhancedCode(const Value: TIdSMTPEnhancedCode); public constructor Create(Collection: TCollection); override; destructor Destroy; override; published property EmailAddress: String read FEmailAddress write FEmailAddress; property ExceptionMessage: String read FExceptionMessage write FExceptionMessage; property Sent: Boolean read FSent write FSent default DEF_SENT; property ReplyCode : Integer read FReplyCode write FReplyCode default DEF_REPLY_CODE; property EnhancedCode : TIdSMTPEnhancedCode read FEnhancedCode write SetEnhancedCode; end; TIdSMTPRelayStatusList = class(TOwnedCollection) protected function GetItems(Index: Integer): TIdSMTPRelayStatusItem; procedure SetItems(Index: Integer;const Value: TIdSMTPRelayStatusItem); public function Add : TIdSMTPRelayStatusItem; property Items[Index: Integer]: TIdSMTPRelayStatusItem read GetItems write SetItems; default; end; TIdSMTPRelay = class; TIdSSLSupportOptions = class(TIdBaseComponent) protected FSSLSupport : TIdSSLSupport; FTryImplicitTLS : Boolean; FOwner : TIdSMTPRelay; procedure SetSSLSupport(const Value: TIdSSLSupport); procedure SetTryImplicitTLS(const Value: Boolean); public constructor Create(AOwner : TIdSMTPRelay); procedure Assign(Source: TPersistent); override; published property SSLSupport : TIdSSLSupport read FSSLSupport write SetSSLSupport default DEF_SSL_SUPPORT; property TryImplicitTLS : Boolean read FTryImplicitTLS write SetTryImplicitTLS default DEF_TRY_IMPLICITTLS; end; TIdSMTPRelay = class(TIdSMTPBase) protected FMXServerList: TStrings; FStatusList: TIdSMTPRelayStatusList; FDNSServer: String; FOnDirectSMTPStatus: TIdSMTPRelayStatus; FSSLOptions : TIdSSLSupportOptions; FRelaySender: String; procedure Connect(AEMailAddress : TIdEMailAddressItem); reintroduce; procedure ResolveMXServers(AAddress:String); procedure SetDNSServer(const Value: String); procedure SetOnStatus(const Value: TIdSMTPRelayStatus); procedure SetUseEhlo(const AValue : Boolean); override; procedure SetHost(const Value: String); override; function GetSupportsTLS : boolean; override; procedure ProcessException(AException: Exception; AEMailAddress : TIdEMailAddressItem); procedure SetSSLOptions(const Value: TIdSSLSupportOptions); procedure SetRelaySender(const Value: String); // procedure InitComponent; override; // // holger: .NET compatibility change property Port; public procedure Assign(Source: TPersistent); override; destructor Destroy; override; procedure DisconnectNotifyPeer; override; procedure Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList); override; published property DNSServer: String read FDNSServer write SetDNSServer; property RelaySender: String read FRelaySender write SetRelaySender; property StatusList: TIdSMTPRelayStatusList read FStatusList; property SSLOptions: TIdSSLSupportOptions read FSSLOptions write SetSSLOptions; property OnDirectSMTPStatus: TIdSMTPRelayStatus read FOnDirectSMTPStatus write SetOnStatus; property OnTLSHandShakeFailed; property OnTLSNotAvailable; property OnTLSNegCmdFailed; end; implementation uses IdGlobalProtocols, IdStack, IdCoderMIME, IdDNSCommon, IdResourceStringsProtocols, IdExplicitTLSClientServerBase, IdSSL, IdStackConsts, IdTCPClient, IdTCPConnection; { TIdSMTPRelay } procedure TIdSMTPRelay.Assign(Source: TPersistent); begin if Source is TIdSMTPRelay then begin MailAgent := TIdSMTPRelay(Source).MailAgent; Port := TIdSMTPRelay(Source).Port; DNSServer := TIdSMTPRelay(Source).DNSServer; end else begin inherited Assign(Source); end; end; procedure TIdSMTPRelay.Connect(AEMailAddress : TIdEMailAddressItem); var LCanImplicitTLS: Boolean; begin LCanImplicitTLS := Self.FSSLOptions.TryImplicitTLS; if LCanImplicitTLS then begin try UseTLS := utUseImplicitTLS; inherited Connect; except on E: EIdSocketError do begin // If 10061 - connection refused - retry without ImplicitTLS // If 10060 - connection timed out - retry without ImplicitTLS if (E.LastError = Id_WSAECONNREFUSED) or (E.LastError = Id_WSAETIMEDOUT) then begin LCanImplicitTLS := False; end else begin raise; end; end; end; end; if not LCanImplicitTLS then begin case Self.FSSLOptions.FSSLSupport of SupportSSL : FUseTLS := utUseExplicitTLS; RequireSSL : FUseTLS := utUseRequireTLS; else FUseTLS := utNoTLSSupport; end; inherited Connect; end; try GetResponse([220]); SendGreeting; StartTLS; except on E : Exception do begin ProcessException(E,AEMailAddress); Disconnect; raise; end; end; end; procedure TIdSMTPRelay.InitComponent; begin inherited InitComponent; FSSLOptions := TIdSSLSupportOptions.Create(Self); FMXServerList := TStringList.Create; FStatusList := TIdSMTPRelayStatusList.Create(Self, TIdSMTPRelayStatusItem); end; destructor TIdSMTPRelay.Destroy; begin FreeAndNil(FSSLOptions); FreeAndNil(FMXServerList); FreeAndNil(FStatusList); inherited Destroy; end; procedure TIdSMTPRelay.DisconnectNotifyPeer; begin inherited DisconnectNotifyPeer; SendCmd('QUIT', 221); {Do not Localize} end; function TIdSMTPRelay.GetSupportsTLS: boolean; begin Result := ( FCapabilities.IndexOf('STARTTLS') > -1 ); //do not localize end; procedure TIdSMTPRelay.ProcessException(AException: Exception; AEMailAddress : TIdEMailAddressItem); var LE: EIdSMTPReplyError; LStatus: TIdSMTPRelayStatusItem; begin LStatus := FStatusList.Add; LStatus.EmailAddress := AEmailAddress.Address; LStatus.Sent := False; LStatus.ExceptionMessage := AException.Message; if AException is EIdSMTPReplyError then begin LE := AException as EIdSMTPReplyError; LStatus.ReplyCode := LE.ErrorCode; LStatus.EnhancedCode.ReplyAsStr := LE.EnhancedCode.ReplyAsStr; end; if Assigned(FOnDirectSMTPStatus) then begin FOnDirectSMTPStatus(Self, AEmailAddress, dmWorkEndWithException); end; end; procedure TIdSMTPRelay.ResolveMXServers(AAddress: String); var IdDNSResolver1: TIdDNSResolver; DnsResource : TResultRecord; LMx: TMxRecord; LDomain:String; i: Integer; iPref: Word; begin { Get the list of MX Servers for a given domain into FMXServerList } i := Pos('@', AAddress); if i = 0 then begin raise EIdDirectSMTPCannotResolveMX.CreateFmt(RSDirSMTPInvalidEMailAddress, [AAddress]); end; LDomain := Copy(AAddress, i+1, MaxInt); IdDNSResolver1 := TIdDNSResolver.Create(Self); try FMXServerList.Clear; IdDNSResolver1.AllowRecursiveQueries := True; if Assigned(IOHandler) and (IOHandler.ReadTimeOut > 0) then begin //thirty seconds - maximum amount of time allowed for DNS query IdDNSResolver1.WaitingTime := IOHandler.ReadTimeout; end else begin IdDNSResolver1.WaitingTime := 30000; end; IdDNSResolver1.QueryType := [qtMX]; IdDNSResolver1.Host := DNSServer; IdDNSResolver1.Resolve(LDomain); if IdDNSResolver1.QueryResult.Count > 0 then begin iPref := High(Word); for i := 0 to IdDNSResolver1.QueryResult.Count - 1 do begin DnsResource := IdDNSResolver1.QueryResult[i]; if (DnsResource is TMXRecord) then begin LMx := TMXRecord(DnsResource); // lower preference values at top of the list // could use AddObject and CustomSort, or TIdBubbleSortStringList // currently inserts lower values at top if LMx.Preference < iPref then begin iPref := LMx.Preference; FMXServerList.Insert(0, LMx.ExchangeServer); end else begin FMXServerList.Add(LMx.ExchangeServer); end; end; end; end; if FMXServerList.Count = 0 then begin raise EIdDirectSMTPCannotResolveMX.CreateFmt(RSDirSMTPNoMXRecordsForDomain, [LDomain]); end; finally FreeAndNil(IdDNSResolver1); end; end; procedure TIdSMTPRelay.Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList); var LAllEntries, LCurDomEntries: TIdEMailAddressList; SDomains: TStrings; LFrom: String; i: Integer; procedure RelayInternalSend(const ALMsg: TIdMessage; const AFrom: String; const AEmailAddresses: TIdEMailAddressList); var ServerIndex:Integer; EMailSent: Boolean; LStatusItem: TIdSMTPRelayStatusItem; begin if AEmailAddresses.Count = 0 then begin Exit; end; EMailSent := False; if Assigned(FOnDirectSMTPStatus) then begin FOnDirectSMTPStatus(Self, AEmailAddresses[0], dmWorkBegin); end; try try if Assigned(FOnDirectSMTPStatus) then begin FOnDirectSMTPStatus(Self, AEmailAddresses[0], dmResolveMS); end; ResolveMXServers(AEMailAddresses[0].Address); ServerIndex := 0; while (ServerIndex <= FMXServerList.Count - 1) and (not EMailSent) do begin FHost := FMXServerList[ServerIndex]; try if Connected then begin Disconnect; end; if Assigned(FOnDirectSMTPStatus) then begin FOnDirectSMTPStatus(Self, AEmailAddresses[0], dmConnecting); end; Connect(AEmailAddresses[0]); if Assigned(FOnDirectSMTPStatus) then begin FOnDirectSMTPStatus(Self, AEmailAddresses[0], dmConnected); end; if Assigned(FOnDirectSMTPStatus) then begin FOnDirectSMTPStatus(Self, AEmailAddresses[0], dmSending); end; if Trim(MailAgent) <> '' then begin ALMsg.Headers.Values[XMAILER_HEADER] := Trim(MailAgent); end; InternalSend(ALMsg, AFrom, AEmailAddresses); EMailSent := True; LStatusItem := FStatusList.Add; LStatusItem.EmailAddress := AEmailAddresses[0].Address; LStatusItem.Sent := True; if Assigned(FOnDirectSMTPStatus) then begin FOnDirectSMTPStatus(Self, AEmailAddresses[0], dmWorkEndOK); end; except // Sit on the error, and move on to the next server. Inc(ServerIndex); end; end; if (not Connected) and (not EMailSent) then // If we were unable to connect to all the servers, throw exception begin raise EIdTCPConnectionError.CreateFmt(RSDirSMTPCantConnectToSMTPSvr, [AEmailAddresses[0].Address]); end; except on E : Exception do begin ProcessException(E, AEmailAddresses[0]); end; end; finally Disconnect; end; end; begin if Trim(FRelaySender) <> '' then begin LFrom := FRelaySender; end else begin LFrom := AMsg.From.Address; end; if Assigned(ARecipients) then begin LAllEntries := ARecipients; end else begin LAllEntries := TIdEMailAddressList.Create(nil); end; try if not Assigned(ARecipients) then begin LAllEntries.AddItems(AMsg.Recipients); LAllEntries.AddItems(AMsg.CCList); LAllEntries.AddItems(AMsg.BccList); end; SDomains := TStringList.Create; try LAllEntries.GetDomains(SDomains); LCurDomEntries := TIdEMailAddressList.Create(nil); try for i := 0 to SDomains.Count -1 do begin LAllEntries.AddressesByDomain(LCurDomEntries, SDomains[i]); RelayInternalSend(AMsg, LFrom, LCurDomEntries); end; finally FreeAndNil(LCurDomEntries); end; finally FreeAndNil(SDomains); end; finally if not Assigned(ARecipients) then begin FreeAndNil(LAllEntries); end; end; end; procedure TIdSMTPRelay.SetDNSServer(const Value: String); begin FDNSServer := Value; end; procedure TIdSMTPRelay.SetHost(const Value: String); begin raise EIdDirectSMTPCannotAssignHost.Create(RSDirSMTPCantAssignHost); end; procedure TIdSMTPRelay.SetOnStatus(const Value: TIdSMTPRelayStatus); begin FOnDirectSMTPStatus := Value; end; procedure TIdSMTPRelay.SetSSLOptions(const Value: TIdSSLSupportOptions); begin FSSLOptions.Assign(Value); end; procedure TIdSMTPRelay.SetUseEhlo(const AValue: Boolean); begin inherited; FSSLOptions.FSSLSupport := noSSL; end; procedure TIdSMTPRelay.SetRelaySender(const Value: String); begin FRelaySender := Value; end; { TIdSMTPRelayStatusList } function TIdSMTPRelayStatusList.Add: TIdSMTPRelayStatusItem; begin Result := TIdSMTPRelayStatusItem(inherited Add); end; function TIdSMTPRelayStatusList.GetItems(Index: Integer): TIdSMTPRelayStatusItem; begin Result := TIdSMTPRelayStatusItem(inherited Items[Index]); end; procedure TIdSMTPRelayStatusList.SetItems(Index: Integer; const Value: TIdSMTPRelayStatusItem); begin Items[Index].Assign(Value); end; { TIdSMTPRelayStatusItem } constructor TIdSMTPRelayStatusItem.Create(Collection: TCollection); begin inherited Create(Collection); FEnhancedCode := TIdSMTPEnhancedCode.Create; FSent := DEF_SENT; FReplyCode := DEF_REPLY_CODE; end; destructor TIdSMTPRelayStatusItem.Destroy; begin FreeAndNil(FEnhancedCode); inherited Destroy; end; procedure TIdSMTPRelayStatusItem.SetEnhancedCode( const Value: TIdSMTPEnhancedCode); begin FEnhancedCode.ReplyAsStr := Value.ReplyAsStr; end; { TIdSSLSupportOptions } procedure TIdSSLSupportOptions.Assign(Source: TPersistent); var LS: TIdSSLSupportOptions; begin if (Source is TIdSSLSupportOptions) then begin LS := TIdSSLSupportOptions(Source); SSLSupport := LS.FSSLSupport; TryImplicitTLS := LS.TryImplicitTLS; end else begin inherited Assign(Source); end; end; constructor TIdSSLSupportOptions.Create(AOwner: TIdSMTPRelay); begin inherited Create; FOwner := AOwner; FSSLSupport := DEF_SSL_SUPPORT; FTryImplicitTLS := DEF_TRY_IMPLICITTLS; end; procedure TIdSSLSupportOptions.SetSSLSupport(const Value: TIdSSLSupport); begin if (Value <> noSSL) and (not IsLoading) then begin FOwner.CheckIfCanUseTLS; end; if (Value <> noSSL) and (not FOwner.UseEhlo) then begin FOwner.FUseEHLO := True; end; if (Value = noSSL) then begin FTryImplicitTLS := False; end; FSSLSupport := Value; end; procedure TIdSSLSupportOptions.SetTryImplicitTLS(const Value: Boolean); begin if Value and (not IsLoading) then begin FOwner.CheckIfCanUseTLS; end; if Value and (Self.FSSLSupport=NoSSL) then begin SSLSupport := SupportSSL; end; FTryImplicitTLS := Value; end; end.