[Mailman-Developers] Fwd: suggested improvement for Mailman's bounce processing

Ian Eiloart iane at sussex.ac.uk
Tue Aug 15 20:37:01 CEST 2006



--On 14 August 2006 14:06:06 -0400 Barry Warsaw <barry at python.org> wrote:

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On Aug 14, 2006, at 9:49 AM, Ian Eiloart wrote:
>
>> One thing that would make integration easier, would be a script
>> bin/may_post (or something), which takes a list name (ideally
>> qualified
>> with domain) and sender address, and returns true if the sender
>> address is
>> allowed to post, and false otherwise.
>
> Why don't you code something up and submit it here? :)
>
> - -Barry

I started to write that I've no python coding experience. Well, about 3 
lines because php can't do "utf-something or other". Then I thought, well 
it's about time I got some.

I had hacked up a shell script using the existing Mailman scripts, but that 
was far too inefficient. Instead I've hacked up the attached. It started 
life as list_config, but hopefully I've not left much trace of that. The 
second issue below ***MUST*** be resolved before using this script with an 
MTA.

The attached script takes these arguments:
-o --outputfile FILE_PATH can be used to specify logging of denies. use '-' 
to log to stdout
-v --verbose causes logging of all results, allows as well as denies.
-h --help prints help
-s --sender EMAIL_ADDRESS is required

The script applies these tests, printing 'allow' or 'deny' to std out on 
the first match.
allow list owners
allow list moderators
allow members of accept_these_nonmembers
deny members of reject_these_nonmembers
if generic_nonmember_action is 'reject':
    allow members to post
    deny non-members
allow by default

These issues are outstanding:
----------------------------
On allow, I say "return 1" on deny I say "return 0". I'm not sure whether 
that's correct. Actually, I think I want the script to succeed every time, 
so it can't be.

I've not figured out how to do a pattern match so accept_these_nonmembers 
and reject_these_nonmembers are only tested for exact string matches. This 
*****needs to be fixed***** for accept_these_nonmembers, otherwise some 
won't be permitted to post.

It'd be nice to log to syslog, but the MTA could take care of that.

It might be nice to say 'hold' or 'discard' where appropriate. It's often 
sensible to reject rather than discard a message, for example.

The list's nonmember_rejection_notice isn't used here. It could be returned 
instead of 'deny' for the MTA to construct a rejection string with.

I've hard-coded '2' as the 'reject' key to generic_nonmember_action, which 
is sinful.
-- 
Ian Eiloart
IT Services, University of Sussex
-------------- next part --------------
#! /local/bin/python
#
# Copyright (C) 1998-2003 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.

"""Find out whether list will reject a message from sender.

Usage: check_sender.py [options] -s sender listname

Options:

    --outputfile filename
    -o filename
        optionally log denys to list file.

    --sender sender
    -s sender
        check whether the sender is allowed to post to the list.

    --verbose
    -v
        log allows as well as denys.

    --help
    -h
        Print this help message and exit.

The option -s is required.

"""

import sys
import re
import time
import getopt
from types import TupleType

import paths
from Mailman import mm_cfg
from Mailman import MailList
from Mailman import Utils
from Mailman import Errors
from Mailman.i18n import _

NL = '\n'



def usage(code, msg=''):
    if code:
        fd = sys.stderr
    else:
        fd = sys.stdout
    print >> fd, _(__doc__)
    if msg:
        print >> fd, msg
    sys.exit(code)



def do_check(listname, sender, outfile, verbose):
    closep = 0
    try:
        if outfile == '-':
            outfp = sys.stdout
        else:
            outfp = open(outfile, 'a')
            closep = 1
        # Open the specified list unlocked, since we're only reading it.
        try:
            mlist = MailList.MailList(listname, lock=0)
        except Errors.MMListError:
            usage(1, _('No such list: %(listname)s'))
        # get all the list config info.  all this stuff is accessible via
the
        # web interface
        when = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())

        # always allow the owner to post
        if sender in mlist.owner :
            if verbose:
                print >> outfp, _('''%(when)s %(listname)s owner %(sender)s
allowed''')
            print >> sys.stdout, 'allow'
            return 1
        # always allow moderators to post
        if sender in mlist.moderator :
            if verbose:
                print >> outfp, _('''%(when)s %(listname)s moderator
%(sender)s allowed''')
            print >> sys.stdout, 'allow'
            return 1
        # always allow accept_these_nonmembers to post
        if sender in mlist.accept_these_nonmembers :
            if verbose:
                print >> outfp, _('''%(when)s %(listname)s
accept_these_nonmembers %(sender)s allowed''')
            print >> sys.stdout, 'allow'
            return 1
        # deny reject_these_nonmembers
        if sender in mlist.reject_these_nonmembers :
            print >> outfp, _('''%(when)s %(listname)s
reject_these_nonmembers %(sender)s allowed''')
            print >> sys.stdout, 'deny'
            return 0
        # 2 is the key for 'reject', but there's probably a global we
should use
        if  mlist.generic_nonmember_action == 2:
            # regular members
            rmembers = mlist.getRegularMemberKeys()
            # digest members
            dmembers = mlist.getDigestMemberKeys()
            allmembers = rmembers + dmembers
            # should lowercase sender
            if sender.lower() in allmembers:
                if verbose:
                    print >> outfp, _('''%(when)s %(listname)s member
%(sender)s allowed''')
                print >> sys.stdout, 'allow'
                return 1
            else:
                print >> outfp, _('''%(when)s %(listname)s member
%(sender)s denied''')
                print >> sys.stdout, 'deny'
                return 0
        print >> outfp, _('''%(when)s %(listname)s unknow %(sender)s
allowed by default''')
        print >> sys.stdout, 'allow'
        return 1
    finally:
        if closep:
            outfp.close()






def main():
    try:
        opts, args = getopt.getopt(
            sys.argv[1:], 's:o:vh',
            ['list=', 'sender=', 'verbose', 'help'])
    except getopt.error, msg:
        usage(1, msg)

    # defaults
    infile = None
    outfile = None
    list = None
    sender = None
    verbose = 0
    for opt, arg in opts:
        if opt in ('-h', '--help'):
            usage(0)
        elif opt in ('-l', '--list'):
            list = arg
        elif opt in ('-s', '--sender'):
            sender = arg
        elif opt in ('-o', '--outputfile'):
            outfile = arg
        elif opt in ('-v', '--verbose'):
            verbose = 1

    # sanity check
    if sender is None:
        usage(1, _('--sender is required'))

    # get the list name
    if len(args) <> 1:
        usage(1, _('List name is required'))
    listname = args[0].lower().strip()

    do_check(listname, sender, outfile, verbose)



if __name__ == '__main__':
    main()


More information about the Mailman-Developers mailing list