[Mailman-Users] Writing a custom handler

Mark Sapiro mark at msapiro.net
Tue Jul 2 00:35:34 CEST 2013

On 7/1/2013 10:24 AM, Chris Nulk wrote:
> Hello user,
> I am writing a custom handler to globally ban email address from sending
> messages sent to Mailman.  I know I can use Mark's add_banned.py script
> to add an address to all lists.  However, if I add an address to be
> banned, as the administrator for all lists, I don't want a list admin to
> remove a pest from their list(s).  I banned an address for being a pest
> to all lists (or a majority of them), therefore, the address stays banned.

Note that the ban_list only prevents the address from subscribing. If
the address was already a member when banned, it can still post.

> Before I put the custom handler in place and screw up my lists, I
> thought I would post it here so others more knowledgeable can review it
> and let me know if it will work, correct it, and/or improve it.
> #!/usr/bin/env python
> #
> """This is a custom handler that will check all the sender addresses of
> a message against a global ban list.  If any of the sender addresses are
> on the global ban list, the message will get logged and discarded.
> """
> import sys
> from Mailman import mm_cfg
> from Mailman import Utils
> from Mailman import Message
> from Mailman import Errors
> from Mailman.i18n import _
> from Mailman.Logging.Syslog import syslog
> from Mailman.MailList import MailList

You don't actually use MailList so there's no need to import it. Also
Utils, Message, Errors and _ are used only by the discard code you
copied. You could instead just

>From Mailman.Handlers.Moderate import do_discard

and use that if it wasn't important to have specific messages. OTOH, see

> def process(mlist, msg, msgdata):
>     # added because it was in Mailman/Handlers/Moderate.py
>     #   I am guessing it has to due in part with an upstream
>     #   pipeline handler marking the message approved and/or
>     #   because the handler can be moved to different parts
>     #   of the pipeline.
>     #   But, I have been wrong before.
>     if msgdata.get('approved') or msgdata.get('fromusenet'):
>         return

There are two things in the above. The test for 'approved' says if this
message was pre-approved by inclusion of an Approved: <list_pw> header
or pseudo-header, or if this message was held for moderation by a prior
handler and approved by the moderator, then skip this handler.

The test for 'fromusenet' says if this post arrived via the
Mail<->Usenet gateway, then skip this handler.

You probably don't want the " or msgdata.get('fromusenet')" part even if
you are gating from usenet.

>     # First, initialize the banlist
>     banlist = []
>     # Read in the global ban list of email addresses
>     #  mm_cfg.GLOBALBANLIST_FILENAME is defined in mm_cfg and should
>     #  be the full path to the file.
>     try:
>         with open(mm_cfg.GLOBALBANLIST_FILENAME) as f:
>             for addr in f:
>                 banlist.append(addr.lower().strip())
>     except IOError:
>         # cannot open the global ban list for whatever reason
>         # log it and continue with the next pipeline handler
>         syslog('error', 'An error occurred opening the global ban list')

I would put something like

    except IOError, e:
               "Can't open %s: %s" % (mm_cfg.GLOBALBANLIST_FILENAME, e)

>         return
>     except:
>         # unspecified error
>         # log it and continue with the next pipeline handler
>         syslog('error', "ERROR: Unknown error: ", sys.exc_info()[0])

and here I would put

               'ERROR: %s: %s' % (sys.exc_info()[0], sys.exc_info()[1])

>         return
>     # Go through possible senders.  Check if any of them are
>     #   on the global ban list
>     for sender in msg.get_senders():
>         if sender.lower() in banlist:
>             break
>     else:
>         # None of the sender addresses were in the global ban
>         #   list so return and continue with the next pipeline
>         #   handler
>         return
>     # A sender was found on the global ban list.  Log it and
>     #   discard the message notifying the list owner
>     if sender:

How can this be False?

>         # Log banned sender to the vette log
>         syslog('vette', '%s is banned by the global ban list', sender)
>         # Perform message discard
>         do_discard_globalban(mlist, msg)

You could just import do_discard from Mailman.Handlers.Moderate and use
that, but you may want the custom messages. If so, you may also want

        do_discard_globalban(mlist, msg, sender)

>     else:
>         assert 0, 'Bad sender in GlobalBan.py'
> # copied almost verbatim from Mailman/Handlers/Moderate.py
> def do_discard_globalban(mlist, msg):
>     sender = msg.get_sender

Actually, that should be

    sender = msg.get_sender()

but as I indicate above it would be better to pass the bad sender in the
call because msg.get_senders() returns a list of sender addresses and
msg.get_sender() returns a single address which may not be the
get_senders() address you found in your blacklist.

>     # forward auto-discards to list owners?
>     if mlist.forward_auto_discards:
>         lang = mlist.preferred_language
>         # is varhelp used anywhere?
>         varhelp = '%s/?VARHELP=privacy/sender/discard_these_nonmembers' % \
>                   mlist.GetScriptURL('admin', absolute=1)

It is the URL to the web admin (Details for discard_these_nonmembers)
page. Someone intended to include it in the auto-discard notice but
either never did or at some point it was dropped without removing the
above. It is currently superfluous.

>         nmsg = Message.UserNotification(mlist.GetOwnerEmail(),
>                                         mlist.GetBouncesEmail(),
>                                         _('Global Ban List Auto-discard
> notification'),
>                                         lang=lang)
>         nmsg.set_type('multipart/mixed')
>         text = MIMEText(Utils.wrap(_("""\
> The sender of the attached message is on the Global Ban list. Therefore,
> the message
> has been automatically discarded.""")),
>                         _charset=Utils.GetCharSet(lang))
>         nmsg.attach(text)
>         nmsg.attach(MIMEMessage(msg))
>         nmsg.send(mlist)
>     # Discard the message
>     raise Errors.DiscardMessage

Mark Sapiro <mark at msapiro.net>        The highway is for gamblers,
San Francisco Bay Area, California    better use your sense - B. Dylan

More information about the Mailman-Users mailing list