Re: [exim] RHS expansion a la mailertable

Top Page
Delete this message
Reply to this message
Author: Phil Pennock
Date:  
To: Victor Sudakov
CC: exim-users
Subject: Re: [exim] RHS expansion a la mailertable
On 2006-02-15 at 23:31 +0600, Victor Sudakov wrote:
> Let's take the following router definition:


and a little bit of time for me to notice the email amongst the
exim-users backlog ... sorry for the delay.

> fido:
>  transport = ifmail
>  driver = manualroute
>  route_list = \
>     \N^(.*)\.f149\.n5005\.z2\.fidonet\.org $1.f149.n5005.z2.fidonet.org ; \
>     \N^(.*)\.f49\.n5005\.z2\.fidonet\.org $1.f49.n5005.z2.fidonet.org ; \
>     \N^.*\.fidonet\.org f14.n5005.z2.fidonet.org

>
>
> How can I replace route_list with route_data here and achieve the same
> functionality ? I want to move these routing rules into a separate file.


I'd write a Perl sub-routine to do the routing. Embedded scripting
interpreters let you do pretty much as you want. See "12. EMBEDDED
PERL" in the Spec.

You can avoid using Perl unnecessarily by making sure that the keys
conform to a supported format, such as wildlsearch; this way, Perl is
only called when it absolutely will be needed.

Given:
% cat mail-routes
example.org        example.tld
foo.example.org        example.tld
*.example.org        *.example.tld


And demo_perl.pl:
----------------------------8< cut here >8------------------------------
# demo_perl.pl

use IO::File;

# Parameters: search term, pattern
#  pattern is literal string or *.literal
sub match_glob
{
    return 0 if length $_[1] > length $_[0];
    return 1 if lc($_[0]) eq lc($_[1]);
    return 0 unless $_[1] =~ /^\*\./;
    (my $t = lc $_[1]) =~ s/^\*//;
    my $tlen = length $t;
    return 1 if substr(lc $_[0], -$tlen) eq $t;
    return 0;
}


sub funky_mail_table
{
    my ($file, $search) = @_;
    my ($key, $value);


    my $fh = new IO::File $file, '<'
        or die "read-open($file) failed: $!";
    while (<$fh>) {
        chomp;
        s/#.*//; s/^\s+//; s/\s+\z//;
        next unless /./;
        if (/^ (\S+) (?:\s+(?::\s*)?|:\s*) (.+) \z/x) {
            my ($k, $v) = ($1, $2);
            next unless match_glob($search, $k);
            ($key, $value) = ($k, $v);
            last;
        } else {
# do what you want to do about malformed lines.
# If you warn, they end up in Exim's mainlog
        }
    }
    $fh->close();


    return undef unless defined $value; # exim expansion fails
    return $value if lc($key) eq lc($search);


    $key =~ s/^\*//;
    $value =~ s/^\*//;
    (my $result = $search) =~ s/\Q$key\E\z/$value/;
    return $result;
}


1;
----------------------------8< cut here >8------------------------------

Then:

perl_startup = do '/etc/exim/demo_perl.pl'

# ...

funky_mail_routes:
driver = redirect
domains = wildlsearch;CONFIGDIR/mail-routes
data = ${quote_local_part:$local_part}@${quote:${perl{funky_mail_table}{CONFIGDIR/mail-routes}{$domain}}}

should work.

Optimisations for high load involve using perl_at_start and a disk-based
cache, together with some checking of the file modification time to
catch updates.


Separately, I almost had it in pure Exim with the following, which
doesn't work; it's included purely for mind-warping value and in the
hope that someone knowledgeable might comment upon why $1 isn't
available.

The main problem is that the matching key value from the lookup is not
AFAIK available in a variable afterwards; you have $domain_data but not
corresponding $domain_key variable. This is reasonable since it takes a
very twisted set-up to need to know that.

So, encode the static portion of the original domain in the value:

% cat routetable 
*.example.org           %example.org%.target.tld
*.foo.example.net       %foo.example.org%.target2.tld
*.bar.example.net       %bar.example.org%.target3.tld
example.net             target4.tld


and then apply horrid substitutions and length calculations on the
result of a wildlsearch:

mailertable:
  driver = redirect
  domains = wildlsearch;/home/pdp/tmp/routetable
  data = ${quote_local_part:$local_part}@${if eq{${substr_0_1:$domain_data}}{%} \
        {${sg{$domain_data}{\N^%([^%]+)%(.+)$\N}{${substr{0}{${eval10:${strlen:$domain}+1-${strlen:\$1}}}{$domain}}\$2}}}\
        {$domain_data}}


But \$1 becomes the literal string "$1" and so it only subtracts 2 and
everything goes horribly wrong. (And yes, a final quote wrapper around
the domain is missing).

Use Perl. Or ${dlfunc{...}} if C is your preferred poison.
--
I am keeping international relations on a peaceable footing.
You are biding your time before acting.
He is coddling tyrants.
-- Roger BW on topic of verb conjugation