...getting onto the soapbox: SMTP with DNS

Giganews Newsgroups
Subject: ...getting onto the soapbox: SMTP with DNS
Posted by:  Pierre Roux (pier…@infofx.co.za)
Date: Tue, 4 May 2004

This message doesn't contain any real problems, for discussion and opinions
only.
- DNS Lookup and SMTP Sending Code at bottom

Part of what I'm playing with at the moment is DNS delivery of messages
using TIdDNSResolver and TIdSMTP Objects in a list of threads (inside a
service).

First Attempt:
Everything is fine as long as all the messages are delivered and the queue
is cleaned.  One problem, if one message didn't deliver, everybody got the
message again, and again, and again, until that server came up again... was
'great' for spotting when somebody's mailserver went down or when you
misspelled an e-mail address.

Second Attempt:
Put an 'delivery list' with every message.  When the list is empty, remove
the message from the queue (delete from directory together with delivery
list).  Nothing fancy, normal text file for the 'delivery list'.

Methodology (Second Attempt Only - Attempt One sucks):
Create a list of users from the Headers of the TIdMessage.  Order and
duplicates doesn't matter.

Receive Messages with TIdSMTPServer, drop them into a directory (Queue) as
separate files (No anti-spam/anti-relay checking yet, on To-Do list).

A Timer triggers each SMTP delivery sequence.
When the timer is triggered:
- Step through list of threads:
  If Thread Busy, leave it alone
  If Thread Not Busy, hand it a message, a delivery list and a list of DNS
Servers
          Let the thread resume (I start and kill a predetermined number of
threads at start-up and closing only)

- The Thread Loop:
  - Do a DNS Lookup until an MX record is found for the domain, or until you
run out of DNS servers (Code at bottom).
  - Messages gets delivered to the mail server as shown by the MX record in
the DNS lookup, and becomes that servers problem (Code at bottom).
  - On success, remove the address from the delivery list. (If the address
is there twice, deliver twice)
  - If a message fails, increment the Failure TTL that lives inside the
delivery list.  Three (or X) failures, drop a message into SMTP queue
directory and tell the sender what's up. (Log SMTP Failures)
  - On completion, suspend the thread

Unsolved funnies:
- On some mail servers the message would consistently keep on downloading
until actually deleted from the server.  Seems to be related to the UID of
the message, but not tested enough.
- MX Lookup returns the MX hostname as well as the MX IP Address.  So for
two (MX) servers, I'd have four answers from the DNS Resolver, but step
through them all at the moment.
- SMTP Sending Threads won't die correctly yet, I'm probably just
forgetting something, non-issue at the moment

Stuff:
- Is this how 'REAL' SMTP Servers also do it?  Seems very awkward.
- I found the RFC's more confusing that sorting spaghetti.  Anybody have
'RFC for dummies'-type guide?  ...now I have real respect for the Indy Team.
- I've not been able to put any sort of real load onto it yet, but it seems
like the software can actually handle a 'bit' of pressure.
      - Four messages delivering together in four outgoing threads, 2MB
each.
      - Sixteen incoming messages, 2MB each.
- I have NEVER had so many exception handling blocks in ANY piece of code
before...
- Components very easy to use as objects, just remember clean-up.
- This is 'research' and pretty much academical, please critisize.
- I have not checked been paranoid about being thread-safe, but no issues
so far

The bit of code that makes most happen:

Return Value:
- Boolean: Tells you if the message was sent correctly

GetMXRecords has a TStringList value that contains the list of MailServers
as found in the List of DNSServers, both passed as 'var'

function TSMTPSend.SendEMail(sHostName: String; var DNSServers:
TStringList): Boolean;
var
  bMsgSend      : Boolean;
  sDomainPart    : String;
  iMXCount      : Integer;
begin
  sDomainPart := Copy(sHostName, Pos('@', sHostName)+1,Length(sHostName));

  if GetMXRecords(sDomainPart, DNSServers, MailServers) then
    begin
    iMXCount  := 0;
    bMsgSend  := False;
    Result    := False;

    While (iMXCount < MailServers.Count) and not bMsgSend do
        begin
            // Remember to strip the 'sorting' from the returned DNS Server
        SMTPClient.Host  := Copy(MailServers.Strings[iMXCount], Pos(',',
MailServers.Strings[iMXCount])+1,Length(MailServers.Strings[iMXCount]));
        try
        SMTPClient.Connect;
        SMTPClient.Send(EMail);
        SMTPClient.Disconnect;

        bMsgSend := True;

        except

        bMsgSend := False;
        Inc(iMXCount);

        end;

        end;
    end
  else
    begin
    //DNS Failed
    Result := False;

    end;

end;

function TSMTPSend.GetMXRecords(sHostName : String; var DNSServers,
Response: TStringList): Boolean;
var
  DNS      :  TIdDNSResolver;

  iDNSCount,
  iCount  :  Integer;
begin
  DNS      := TIdDNSResolver.Create(nil);

  iDNSCount      := 0;
  Result        := False;

  While (iDNSCount < DNSServers.Count) and not Result do
    begin
    DNS.Host            := DNSServers.Strings[iDNSCount];
    DNS.QueryRecords    := [qtMX];
    DNS.ReceiveTimeout  := 2000;

    DNS.QueryResult.Clear;

    try
    DNS.Resolve(sHostName);

    if DNS.QueryResult.Count > 0 then
        begin
        for iCount := 0 to DNS.QueryResult.Count - 1 do
          Response.Add
(RightStr('00000'+IntToStr(TMXRecord(DNS.QueryResult.Items[iCount]).Preferen
ce), 5) +  ',' + TMXRecord(DNS.QueryResult.Items[iCount]).ExchangeServer);

        Result := True;
        end;

    except

    Inc(iDNSCount);  // Go hunt for next DNS server, this one probably runs
Windows

    end;

    end;

  Response.Sorted := True;

  FreeAndNil(DNS);
end;

Replies