restemplate/indy/Protocols/IdSMTPRelay.pas

677 lines
20 KiB
Plaintext

{
$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.