procmail
[Top] [All Lists]

Re: [Q] Managing forwardees

1996-11-06 01:31:40
    > e.g. filename "list.txt" contains the following.
    > 
    >   A001
    >   B1(_at_)domain(_dot_)com
    >   A002
    >   B2(_at_)domain(_dot_)com
    >   ...etc.
    > 
    > Now the receipe.
    > 
    >   LIST=`egrep '^[^@ ]+$' list.txt`
    >   USERS=`echo $LIST | sed 's/  /|/g'`
    > 
    >   :0
    >   * $^TO_\/($USERS)
    >   { USER=$MATCH
    >     FORWARDEE=`grep -A 1 -wi $USER list.txt | tail -1`
    >     :0
    >     ! $FORWARDEE
    >   }
    > 
    >   # This doesn't allow for multiple receipients.
    > 
    > Any comments would be appreciated.

One problem with USER=$MATCH is that the text in the $MATCH may
contain text from a comment string:

    To: "King of A001 Sauce" joe(_at_)foo(_dot_)com

will match A001 in the comment, which is not the address.

Another problem is when there are multiple addresses on the To: or Cc:
lines; I presume that you want to forward to *all* of their forwarding
addresses?  

When matching with a regexp with multiple addresses, you discover if at
least one of them matches, and you get it in $MATCH, but you do not
discover if *more* than one of them matches, and you can't get them
easily.

To solve the problem of using a table of addresses for multiple address
forwarding is difficult when procmail doesn't have any explicit looping
structures.  So, you must either do "loop unrolling" or recursion.

The recursion is dangerous because a little mistake in the recipe can
cause a terrible system overload.

So, that leaves loop unrolling. 

There are two ways to use a table with an "unrolled loop": you either
convert the table into the proper procmail recipes, or you write the
table *as* procmail recipes.  I'll demonstrate both.

First, as someone else pointed out, making the records in this little
database be line-oriented, using whitespace to separate the USER from
the ADDRESS, will make the loop unrolling conversion easier.  Here's
what your table might look like:

    A001        B1(_at_)domain(_dot_)com
    A002        B2(_at_)domain(_dot_)com
    A003        B3(_at_)domain(_dot_)com

and so on.

We'll be using the following template:

    :0c
    * ^TO_$USER([^a-zA-Z0-9._-]|$)
    ! $ADDR

where $USER and $ADDR will be substituted with the corresponding
pairs from the address table.  For each record in the address table,
we will generate a copy of the above recipe with the variables
substituted. 

So, here's the shell command to create a procmail recipe out of this 
table (named $ADDRTBL):

    awk '{printf ":0c\n* ^TO_%s([^a-zA-Z0-9._-]|$)\n! %s\n",$1,$2}' $ADDRTBL

Here's the resulting output (based on the little table above):

    > awk '{printf ":0c\n* ^TO_%s([^a-zA-Z0-9._-]|$)\n! %s\n",$1,$2}' < tmp-tbl
    :0c
    * ^TO_A001([^a-zA-Z0-9._-]|$)
    ! B1(_at_)domain(_dot_)com
    :0c
    * ^TO_A002([^a-zA-Z0-9._-]|$)
    ! B2(_at_)domain(_dot_)com
    :0c
    * ^TO_A003([^a-zA-Z0-9._-]|$)
    ! B3(_at_)domain(_dot_)com

Now, if the $ADDRTBL can be dynamically, or frequently updated, you may wish
to create the procmail recipe file dynamically for each mail.  However, since
this is a little time-consuming, it is probably better to have it already 
prepared when mail arrives.

Here's how you could do this dynamically in your procmailrc:

    ADDRTBL=address.tbl
    FORWARDS=tmp.forwards.rc    # our temporary forwards.rc file
    LOCKFILE=$FORWARDS.lock     # lock the file we're creating
    X=`cat /dev/null >$FORWARDS`# ensure an empty file
    X=`awk '{printf ":0c\n* ^TO_%s([^a-zA-Z0-9._-]|$)\n! %s\n",$1,$2}' \
       <$ADDRTBL >$FORWARDS`
    :0                          # did the file get created?
    * ? test -s $FORWARDS
    { INCLUDERC=$FORWARDS }     # run the mail through the filter
    LOCKFILE                    # all done with the forwarding filter
    #  ... whatever other filtering you need to do

If you don't want to create the $FORWARD file on the fly, the recipe
looks like this:

    FORWARDS=forwards.rc
    :0:$FORWARDS.lock
    * ? test -s $FORWARDS
    { INCLUDERC=$FORWARDS }

In this case, a local lockfile will work.  

In addition, if you like, you can add a "c" flag to the recipe above,
and the forwarding filtering will take place in a child procmail
process, while the parent process continues on.


The other way, of having the address table exist as real procmail
conditions, is to use "subroutine" procmail rc files, as I've 
already described in a previous email (check the procmail archives).  
But, I'll briefly show an example here:

    U=A001      A=A1(_at_)domain(_dot_)com              INCLUDERC=fwdcheck.rc
    U=A002      A=A2(_at_)domain(_dot_)com              INCLUDERC=fwdcheck.rc
    U=A003      A=A3(_at_)domain(_dot_)com              INCLUDERC=fwdcheck.rc
    ...

The recipe file "fwdcheck.rc" might look like this:

    # Check $U against ^TO_, and if matching, forward the
    # mail to $A.
    :0
    * U ?? .
    * A ?? .
    * $^TO_$U([^a-zA-Z0-9._-]|\$)
    ! $A

This recipe says, if "U" is defined, and "A" is defined, and if the
value of U is an address matched by the ^TO_ macro, then forward the
mail to $A.

It might be interesting to perform some timing comparisons to see
whether the table to recipe file conversion is slower or faster than
the "subroutine" procmailrc file method.

___________________________________________________________
Alan Stebbens <aks(_at_)sgi(_dot_)com>      http://reality.sgi.com/aks

<Prev in Thread] Current Thread [Next in Thread>