Date: Thu, 13 May 1999 12:17:03 -0500
From: mark david mcCreary <mdm@internet-tools.com>

Here is some sample code that might be useful for handling
X-Failed-Recipients headers generated by Exim, with mailing lists.

It will generate one bounce message for each bad address.  The message
will be sent back to the mailing list that generated it.

It consists of some procmail routines to decipher which list generated
the error, with a Perl program to parse and generate the email message
for each email address in the X-Failed-Recipients: header.

You need Procmail 3.13 or better

You need Perl 5.004 plus the Internet::Mail Perl Module.
Or you can call your MTA directly from Perl, if you do not have
Internet::Mail Perl Module.


Step 1) Procmail recipe that gets called with bounces that Exim generates.

#   The mail-list.com front-end for Smartlist Mailing Lists
#
#	Copyright (c) 1999 Internet Tools, Inc.
#
#	This program is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#
#
#   X-Failed Routine
#
#   This code may be useful for deciphering error messages
#   for mailing lists.
#
#	This routine will receive error messages generated by Exim
#   Those messages will have a special header, called
#
#         X-Failed-Recipients:
#
#   Up to 50 email addresses can be listed on each header.
#   There can be multiple X-Failed-Recipients: headers.
#
#   This procmail procedure will determine the list name
#
#   For example, if mailing list message was sent from
#
#       elvis-admin@domain.com
#
#   Then elvis would be the name of the list.
#
#   Once the list name is determined based on the contents of the message,
#   a perl program is called to parse the email addresess.
#
#   In addition, a bounce error message is sent back to the mailing list,
#   so it can be removed from the list.
#
#
#

PATH=.:/home/ftp/.bin:/bin:/usr/bin:/usr/local/bin:/usr/sbin:$PATH
SHELL=/bin/sh

#VERBOSE=yes
#LOGABSTRACT=all
VERBOSE=no
LOGABSTRACT=all
LOGFILE=$HOME/procmailog
COMSAT=no
test=test		# /usr/bin/test
mkdir=mkdir     # /bin/mkdir

DOMAIN=domain.com		# the common domain for all the lists
SUBDOMAIN=`hostname`		# the fully qualified hostname

SUFFIX=-admin

#  detect mail loop
#  save in folder for debugging purposes
#  terminate

#  strip Reply-to and Sender addresses, as many people screw these up
#  Then the formail -rtzx will try to grab that address

:0 fh
| formail -I "Reply-To:" -I "Sender:"

# log requestor email To: From: address

SENDER  = `formail -rtzx To:`
SUBJECT = `formail -zxSubject:`
FROM = `formail -zxFrom:`
TODAY   = `date "+%Y-%m-%d %T"`

#  Figure out the listname being subscribed to
#  Considered to be the base part, followed by -admin@domain-name.com
#
#
#  If address does not end in -on, or otherwise cannot be determined, send to
#  dummy-request for handling
#

BASELIST=dummy

#  locate addresses within <> symbols first

:0
* ^Envelope-to:.*[<]\/[^ ,@]+
{
    STRING = $MATCH
    TERM = $SUFFIX
    INCLUDERC = /home/ftp/.etc/rc.rm_term
    BASELIST = $STRING
}

# otherwise, if no match above, just look for @ symbol to grab email address

:0 E
* ^Envelope-to: \/[^ ,@]+
{
    STRING = $MATCH
    TERM = $SUFFIX
    INCLUDERC = /home/ftp/.etc/rc.rm_term
    BASELIST = $STRING
}

#  Exim generates bounce back messages with X-Failed-Recipients Headers
#  If this email message is one of those, then process by a special
#  perl program, which will generate a bounce message for each failed
#  email address.


:0 h
* ^From:.*Mailer-Daemon@.*.domain.com
* ^X-Failed-Recipients:
| xfailed.pl $BASELIST-admin@[127.0.0.1]




Step 2)  Procmail Sub routine to recurvisely parse email address
         by Philip Guenther.

         Place in /home/ftp or tweak script for where you place it.

#
#   Copyright (c) 1997 Philip Guenther
#
#
#  3/05/97		Philip Guenther
#
# Remove a terminating string $TERM from $STRING, returning it in
# STRING.  $TERM *must* start with a '-', as that's what this
# routine splits up STRING on.  "found" is a temporary variable that
# must be empty on entry.  The script will clear it on exit, so things
# should be fine as long as you don't use it yourself.

# Append the next 'word' in STRING to $found

#  10/15/97  mdm   make result lower case for file name matchups
#


:0
* STRING ?? $ ^^$\found\/${found+-}[^-]*
{ found = "$found$MATCH" }
:0 E
{
    # This cannot happen unless found was set on entry!
    LOG = "*** WARNING ***
variable 'found' was set to $found on entry to $_
one of the uses must be renamed for rm_term.rc to work!
"
    # Help me, help me!
    :0:
    $DEFAULT
}

# Are we done?
:0
* ! STRING ?? $ ^^$\found$TERM^^
{
    # Nope
    INCLUDERC = $_
}
:0 E
{
    # Return the match, and clear our temporary.
    STRING = `echo $found | tr 'A-Z' 'a-z'`
    found
}




Step 3) Sample Perl Code to parse X-Failed-Recipients: headers, and
        send email message containing bounce error code.

#!/usr/bin/perl -w
#
#   The mail-list.com front-end for Smartlist Mailing Lists
#
#    Copyright (c) 1999 Internet Tools, Inc.
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#
#  When Exim is unable to deliver a message, it generates a bounceback
#  with X-Failed_Recipients header(s).
#
#  Each bad address is listed in these header(s).
#
#  This program will generate bounce messages, and send them to the
#  appropriate list-admin address.
#
#  This program is invoked by a Procmail recipe, with the list-request
#  address in the first and only argument.
#
#  This program uses the Perl Module  Mail::Internet to send each message.
#
#
#

($list_address) = @ARGV;

use Mail::Internet;

chop(my $Date = `date "+%Y-%m-%d %T"`);

my $bounce_body = '     550 ';  #  550 is SMTP error code for user unknown
open(LOG,">>/tmp/xfailed.log") || die(" Could not open xfailed.log $!");

$ENV{'SMTPHOSTS'} = 'localhost';

my $mesg = new Mail::Internet \*STDIN;

# look at mail headers, and grab the data

my $from = $mesg->head->get('From'); chop($from);

# can be more than 1 X-Failed_Recipients header, so put into array

my @xfail = $mesg->head->get('X-Failed-Recipients');

$xfail = "@xfail";        # stick array of headers into scalar
chop($xfail);             # remove final newline

# turn any newlines from middle of multiple X-Failed-Recipients into commas

$xfail =~ s/\n/,/g;

my @tokens = split(/,\s/, $xfail);        # put email address, minus commas into array

my $email_addr;
foreach $email_addr (@tokens) {

    unless ($email_addr =~ m/\@/) {
        next;
    }

    unless ($list_address =~ m/\@/) {
        next;
    }

    # make the body of the message a simple bounce message that smartlist can handle

    my $message_body = $bounce_body . "<" . $email_addr . ">" . "\n";

    my $new_mesg = new Mail::Internet(
        [ ],
        'Body' => [$message_body]
        );

    # this is who the mail is directed to via SMTP;

    $ENV{MAILADDRESS} = $from;

    # these are the addresses placed in the header block of the message.

    $new_mesg->head()->add('From', $from);
    $new_mesg->head()->add('To', $list_address);
    $new_mesg->head()->add('Subject', 'bounce from xfailed');

#    $new_mesg->print_header(\*LOG);
#    $new_mesg->print_body(\*LOG);

    print LOG  "$Date\t$list_address\t$email_addr\n";

    my @recips =  $new_mesg->smtpsend;

    unless (@recips > 0) {
               print LOG "Failed to deliver ($from,$list_address,$email_addr) \n";
        next;
    }

}

exit;
