debian/rules
   
Thu, 15 Jul 2004

Making your Backup MX do recipient verification with Exim...

Well, in response to Kevin and Pascal's posts regarding backup MX's, I decided to actually do something about mine...

My backup MX does backup for my domain, as well as for a few friends and acquaintances. One such acquaintance happens to get one hell of a lot of spam traffic to his domain, which is directed at the backup MX... Me. He also happens to have recipient verification turned on. So, I end up with a gazillion messages in the queue on my backup MX.

So, I found a solution FOR EXIM *3* which gives the backup MX the ability to do recipient verification, without needing some static list of recipients available for a domain.

For those that want to do the same, here's the instructions.

To start with, you need an exim with the embedded perl interpreter, which isn't available with woody's exim. Source build time. Commands:

   apt-get build-dep exim
   apt-get install libperl-dev
   apt-get source exim
   cd exim-3.35

Edit src/EDITME, and uncomment the line: EXIM_PERL=perl.o

   dpkg-buildpackage -us -uc -rfakeroot
   cd ..
   sudo dpkg -i exim_3.35-1woody3_i386.deb

Right, now we have an exim package installed with the embedded perl interpreter enabled.

Second up, we need to make a few changes to exim.conf, so that we can do verification on domains we relay for. Here's a list:

  1. Move all domains that we relay for, from the relaydomains directive to the localdomains directive [0].
  2. Add "domains = !<relay domains>" to all existing directors
  3. Add the following to the main configuration section:

    # Lets get perl going
    perl_at_start = true
    perl_startup = do '/etc/exim/relay_verify.pl'
    
  4. Add the following to the directors section:

    # This is to verify local parts of relayed domains
    relay_verify:
      domains = <relay domains>
      driver = smartuser
      verify_only = true
      new_address = ${perl{relay_verify}}
    
    
    relay_deliver:
      domains = <relay domains>
      driver = smartuser
      transport = remote_smtp_relay
    
  5. Add the following to the transports section:

    remote_smtp_relay:
      driver = smtp
      hosts = ${perl{relay_getmx}}
    

That's exim done. Now we just need to create the perl script that'll do the job for us. From above, create /etc/exim/relay_verify.pl with the following:

  #!/usr/bin/perl

  use strict;
  use Net::SMTP;

  sub relay_verify
  {
     # Get the local part and domain of the address we're relaying to.
     my $local_part = Exim::expand_string('$local_part');
     my $domain = Exim::expand_string('$domain');

     # Get our hostname for the HELO string below...
     my $hostname = Exim::expand_string('$primary_hostname');

     # Get the mx entries from exim, for the domain we're relaying for
     my $primarymx = relay_getmx();

     my $code = 451;
     my $msg = "Unknown error. Try again later.";

     if (defined $primarymx) {
        my $smtp = Net::SMTP->new($primarymx,
                                  Hello => $hostname,
                                  Timeout => 10,
                                  Debug => 0);

        if (defined $smtp) {
           $smtp->mail('');
           $smtp->to("$local_part\@$domain");

           $code = $smtp->code();
           $msg = $smtp->message();

           $smtp->quit;
        } else {
           # Cut our losses, if the primary MX isn't responding,
           # Accept anyway.
           $code = 250;
        }
     }

     if ($code >= 500) {
        return ":fail: $msg";
     } elsif ($code >= 400) {
        return ":defer: $msg";
     } else {
        return "$local_part\@$domain";
     }
  }

  sub relay_getmx
  {
     # Get the mx entries from exim, for the domain we're relaying for
     my $mxs = Exim::expand_string('${lookup dnsdb{mx=$domain}{$value}fail}');

     my $lowest = -1;
     my $primarymx;

     # Get the lowest valued MX entry.
     for my $entry (split("\n", $mxs)) {
        my ($pref, $host) = split(" ", $entry);

        if (($lowest == -1) or ($pref < $lowest)) {
           $lowest = $pref;
           $primarymx = $host;
        }
     }

     return $primarymx;
  }

The Code should be fairly self explanatory - in effect, if we can get a reply from the Primary MX for a domain we're relaying for to either fail or defer an arbitrary address, we pass that on to the system contacting the backup MX. If we get no reply in 10 seconds, we just accept the mail, since that's what a backup MX is supposed to do.

Setting the Debug option to 1 in the arguments to the Net::SMTP constructor, and using exim's -bh option, you can do some testing to see that this is working. (e.g.: exim -bh <some remote IP> )

[0]: I use files to list the domains my mailservers use, so I have things like:

  local_domains = /etc/exim/local_domains : /etc/exim/virtual_domains
  relay_domains = /etc/exim/relay_domains

[04:52] [/Hacking] [permanent link]