[Mailman-Developers] Postfix, Mailman, no aliases file, neat setup?

Barry A. Warsaw barry@digicool.com
Fri, 17 Nov 2000 00:34:43 -0500


>>>>> "JCL" == J C Lawrence <claw@kanga.nu> writes:

    JCL> I'm currently toying about to see if I can't persuade some of
    JCL> the alias re-writing and procmail to play to get the sme
    JCL> hiding level as Exim.  The most obvious attack is via the
    JCL> $luser, but I'm not real happy with that.

>>>>> "AM" == Andrew McNamara <andrewm@connect.com.au> writes:

    AM> Mailman would then receive all unknown local destinations, and
    AM> would have to recognise owner- and -request style addresses
    AM> also. It would also need to return EX_NOUSER (exit 67). Not as
    AM> pretty, but should work okay.

Bling!  You guys just put the puzzle together for me.  What follows is
a one hour hack, but it works well enough that I'm fairly certain it
could be fleshed out into a real solution.

First, add these configuration options to Postfix's main.cf:

luser_relay = mm+$user@wooz.org
recipient_delimiter = +

(changing of course wooz.org to whatever your own domain is).  Now all
unknown recipients will be forwarded to mm+recipient@wooz.org, but
they'll /really/ be sent to the `mm' alias.  Which is defined as:

mm: "|/home/mailman/mail/wrapper auto"

This will send all such email through the `auto' script (given below)
after traveling as normal through the wrapper safety net.  You'll need
this patch to src/mail-wrapper.c:

-------------------- snip snip --------------------
Index: mail-wrapper.c
===================================================================
RCS file: /cvsroot/mailman/mailman/src/mail-wrapper.c,v
retrieving revision 1.16
diff -u -r1.16 mail-wrapper.c
--- mail-wrapper.c	2000/08/02 03:23:25	1.16
+++ mail-wrapper.c	2000/11/17 05:19:41
@@ -36,6 +36,7 @@
 	"post", 
 	"mailcmd",
 	"mailowner",
+	"auto",
 	NULL				     /* Sentinel, don't remove */
 };
-------------------- snip snip --------------------

and this patch to scripts/Makefile.in:

-------------------- snip snip --------------------
Index: Makefile.in
===================================================================
RCS file: /cvsroot/mailman/mailman/scripts/Makefile.in,v
retrieving revision 1.5
diff -u -r1.5 Makefile.in
--- Makefile.in	2000/03/21 06:26:37	1.5
+++ Makefile.in	2000/11/17 05:20:37
@@ -40,7 +40,7 @@
 
 SHELL=		/bin/sh
 
-SCRIPTS=	answer_majordomo_mail mailcmd mailowner post driver
+SCRIPTS=	answer_majordomo_mail mailcmd mailowner post driver auto
 
 # Modes for directories and executables created by the install
 # process.  Default to group-writable directories but
-------------------- snip snip --------------------

The new auto script is the heart of the beast.  Briefly what it does
is look at the recipients list (To:, Cc:, Resent-To:, and Resent-Cc:
headers) to see if it has a valid Mailman list destination.  It
gathers this from all the list objects on the system and should be
virtual host aware.

Next it matches the recipients its found in the message against the
list of possible valid Mailman destinations.  If if finds no matches,
it exits with EX_NOUSER.  If it does find a match, it enqueues the
message in a manner similar to the post, mailcmd, and mailowner
scripts.  qrunner would handle those enqueued messages without ever
knowing the difference.

Here are some gotchas, which I believe can either be worked out or
lived with:

- Say I send a message to bogus@wooz.org.  This message will travel
  through the auto script and I've verified that you get a user
  unknown bounce, but the bounce message says that mm+bogus@wooz.org
  is the bouncing address, not bogus@wooz.org, which is what I'd like
  to see.

- On a system with a lot of lists, auto either should just a
  trampoline into a long running daemon, or it should try to cache
  more information.  Trying to open and read every config.db file for
  every received message is probably way overkill.

- I'm not sure that auto as it's currently written will handle
  cross-posting correctly, since enqueuing the message multiple times
  (say for list1@, list2@, and list3@) will still result in just one
  .db and .msg file for the received message.  Shouldn't be too hard
  to craft a different sha hash for each separately.

- This won't live very nicely with other $luser_relay's if you've got
  them.  It would be nice if Postfix had a slightly more elaborate
  hookable framework here, where I could say: "let auto try to handle
  the address, but if it can't, pass it on to the next hook in the
  sequence."

- The recipient_delimiter bit is probably extraneous.

So anyway, time for sleep.  Have fun!  If this can be fleshed out and
completed, it can be added to 2.1.

-Barry

P.S. auto below requires Python 2.0.  Taste of things to come. :)

-------------------- snip snip --------------------
#! /usr/bin/env python
#
# Copyright (C) 1998,1999,2000 by the Free Software Foundation, 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.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software 
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import sys

import paths
from Mailman import mm_cfg
from Mailman import Utils
from Mailman import MailList
from Mailman import Message
from Mailman.Logging.Utils import LogStdErr
from Mailman.Logging.Syslog import syslog

# Error code if it's really not a Mailman list addr destination
EX_NOUSER = 67

LogStdErr('auto', 'auto')



def fqdn_listname(listname, hostname):
    return ('%s@%s' % (listname, hostname)).lower()



def main():
    syslog('auto', 'Running the auto deliver script')

    # valid destinations
    valids = {}
    lists = {}
    for listname in Utils.list_names():
        mlist = MailList.MailList(listname, lock=0)
        lists[listname] = mlist
        valids[fqdn_listname(listname, mlist.host_name)] = 1

    syslog('auto', 'valids: %s' % valids)

    # Get the message from standard input
    msg = Message.Message(sys.stdin)
    addrs = []
    for header in ('to', 'cc', 'resent-to', 'resent-cc'):
        addrs.extend(msg.getaddrlist(header))

    syslog('auto', 'found these addresses: %s' % addrs)

    # Parse the headers.  Keys are 'post', 'request', 'owner'
    destinations = {}
    for name, addr in addrs:
        localpart, hostname = addr.split('@', 1)
        try:
            listname, subdest = localpart.split('-', 1)
        except ValueError:
            listname = localpart
            subdest = 'post'
        if subdest in ('post', 'request', 'owner', 'admin') and \
               valids.has_key(fqdn_listname(listname, hostname)):
            destinations.setdefault(subdest, []).append(listname)

    if not destinations:
        # Tell Postfix we found no valid list destinations
        syslog('auto', 'No Mailman destination found')
        return EX_NOUSER

    # TBD: This may not work for cross-posted messages
    for subdest, listnames in destinations.items():
        for listname in listnames:
            syslog('auto', 'Enqueuing to %s-%s' % (listname, subdest))
            mlist = lists[listname]
            if subdest == 'post':
                msg.Enqueue(mlist, tolist=1)
            elif subdest == 'request':
                msg.Enqueue(mlist, torequest=1)
            elif subdest in ('owner', 'admin'):
                msg.Enqueue(mlist, toadmin=1)

    return 0



if __name__ == '__main__':
    code = main()
    sys.exit(code)