Re: [exim] Exim & Mailman

Startseite
Nachricht löschen
Nachricht beantworten
Autor: Ian Eiloart
Datum:  
To: Dave Evans, exim-users
Betreff: Re: [exim] Exim & Mailman


--On 15 November 2007 08:59:10 +0000 Dave Evans
<exim-users-20070913@???> wrote:

> On Thu, Nov 15, 2007 at 10:39:48AM +0300, Odhiambo Washington wrote:
>> On Nov 14, 2007 11:09 PM, Dave Evans <exim-users-20070913@???>
>> wrote:
>> > Speaking of which, does anyone have any good tips to share on how to
>> > write an ACL for incoming Mailman traffic, which (say) rejects
>> > post-DATA for messages which are not from list members? (Obviously
>> > only for listname@domain; not for -owner nor -request, for example).
>>
>> Mailman should do that if properly configured, no?
>> There must be information from the Mailman FAQ (and even tips on the
>> net) on how to properly configure Mailman lists to prevent abuse.
>
> Not AFAIK. Much of mailman's configuration, to the best of my knowledge,
> takes effect after the MTA has already accepted the message.


Actually, all of it. Email addressed to a non-existant list won't be routed
to that list, of course. Nothing else is considered at SMTP time, though.

Where the Mailman config says "reject", it means "generate a bounce
message".

> Having said that, I think I may have just found an answer to my question
> elsewhere, so I'll go and have a play with that.


I've a python script (attached) that attempts to reproduce the logic, but I
think it's not quite accurate. I'm not an experienced python coder, and
I've guessed the logic based on configuration options, not on existant
code. I've never integrated it to Exim.

The script always allows owners and moderators and to post (because we
don't care too much if they get bounce messages), and people listed in
accept_these_nonmembers

It always rejects email from people listed in reject_these_nonmembers

Then it checks whether generic_nonmember_action is set to "2" - the current
value for "reject". NB this isn't future proof, it's hard coded and
shouldn't be. If it is, the sender address is compared with the catenation
of the lists of regular members and digest members.

Everything else is accepted.


--
Ian Eiloart
IT Services, University of Sussex
x3148#! /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 unknown %(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()