[Mailman-Users] Writing a custom handler

Chris Nulk cnulk at scu.edu
Wed Jul 3 00:09:51 CEST 2013

On 7/2/2013 2:37 PM, Mark Sapiro wrote:
> If you raise some exception other than the Mailman.Errors exceptions
> DiscardMessage, HoldMessage or RejectMessage, it will be the same as if
> you didn't catch the exception, i.e., the exception will be logged in
> the 'error' log with a traceback and the message will be shunted.
> I can see that you don't really want that because if your ban file is
> unreadable or you can't stat it for some reason, you'd be shunting every
> message for a reason that has really nothing to do with processing the
> message. So, I think you do want to catch the exception, log it and
> return as that is the only way to continue processing messages.
> Just watch your 'error' log as you'll get an entry per message if
> something is wrong.

Thank you for the help.   Hopefully a last question.  In the 
do_discard_globalban function, how can I send the discard message to 
mailman list?  Or should I?  As it stands now, each list owner/admin 
would be getting the message for their list.  I am thinking that as the 
site administrator and maintainer of the global ban list, I should also 
get the message.

In the updated code, I did change the populating of the banlist in the 
Read_GlobalBan_File function.  Now, it strips and lowercases the 
addresses before it checks if the address is in the banlist. Before, it 
checked then populated a stripped, lowercase version. This could have 
resulted in duplicates if the address in the banlist but the address 
checked had a different case profile.


------------------------  Updated Global Ban list Custom Handler 

#!/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
import os

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

# Global variables
#   Initialize the banlist
banlist = []

#   Keep the Global Ban lists modification time
#     set to -1 to indicate ban file hasn't been read
ban_mtime = -1

# Define ban_file
#   mm_cfg.GLOBALBANLIST_FILENAME is defined in mm_cfg and should
#   be the full path to the file.

def process(mlist, msg, msgdata):
     # Upstream pipeline handler marked message approved -
     #   respect the decision or message has 'Approved: <pwd>' header
     if msgdata.get('approved'):

     # ban_file gets its value from mm_cfg.GLOBALBANLIST_FILENAME. If
     #   mm_cfg.GLOBALBANLIST_FILENAME is not defined, then neither is
     #   ban_file, so simply return.
     if not ban_file:

     # Read in the global ban list of email addresses
     if Ban_File_Changed(ban_file, ban_mtime):
         # Global Ban list has changed (or ban_mtime = -1),
         #   read in the changes
         if not Read_GlobalBan_File(ban_file):
             # Problems reading the GlobalBan list

     # Check if banlist has anything, if not, no need to go further
     if not banlist:

     # 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:
         # None of the sender addresses were in the global ban
         #   list so return and continue with the next pipeline
         #   handler

     # A sender was found on the global ban list.  Log it and
     #   discard the message notifying the list owner
     if sender:
         # 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, sender)
         assert 0, 'Bad sender in GlobalBan.py'

# Stat the ban file to get the modification time and compare it to the
#   last time the file was changed.  If a changed occured, update
#   ban_mtime to current change time
def Ban_File_Changed(ban_file, ban_mtime):
         statinfo = os.stat(ban_file)
     except IOError, e:
         # cannot stat the global ban list for whatever reason
         # log it and continue with the next pipeline handler
                "Can't stat %s: %s" % (ban_file, e)
         return False
         # unspecified error
         # log it and continue with the next pipeline handler
                'ERROR: %s: %s' % (sys.exc_info()[0], sys.exc_info()[1])
         return False

     # if ban_mtime = -1, statinfo.st_mtime should always be greater, this
     #   is a special case for when the code is first loaded and run
     if statinfo.st_mtime > ban_mtime:
         # ban_file has changed or it's the special case
         ban_mtime = statinfo.st_mtime
         return True

     # no change in ban file
     return False

# Read the Global Ban file and populate the banlist.
def Read_GlobalBan_File(ban_file):
         with open(ban_file) as f:
             for addr in f:
                 # strip and lowercase addr
                 addr = addr.lower().strip()
                 # if addr is not in banlist, add it - to avoid 
duplicates, should not add blank addresses
                 if (addr) and (addr not in banlist):
     except IOError, e:
         # cannot open the global ban list for whatever reason
         # log it and continue with the next pipeline handler
                "Can't open %s: %s" % (ban_file, e)
         return False
         # unspecified error
         # log it and continue with the next pipeline handler
                'ERROR: %s: %s' % (sys.exc_info()[0], sys.exc_info()[1])
         return False

     # success
     return True

# copied almost verbatim from Mailman/Handlers/Moderate.py
def do_discard_globalban(mlist, msg, sender):
     # forward auto-discards to list owners?
     if mlist.forward_auto_discards:
         lang = mlist.preferred_language
         nmsg = Message.UserNotification(mlist.GetOwnerEmail(),
                                         _('Global Ban List Auto-discard 
         text = MIMEText(Utils.wrap(_("""\
The sender - %(sender)s - of the attached message is on the Global Ban 
list.  Therefore, the message
has been automatically discarded.""")),
     # Discard the message
     raise Errors.DiscardMessage

More information about the Mailman-Users mailing list