[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.

Thanks,
Chris

------------------------  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.
ban_file = mm_cfg.GLOBALBANLIST_FILENAME

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

     # 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:
         return

     # 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
             return

     # Check if banlist has anything, if not, no need to go further
     if not banlist:
         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:
         # 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)
     else:
         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):
     try:
         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
         syslog('error',
                "Can't stat %s: %s" % (ban_file, e)
                )
         return False
     except:
         # unspecified error
         # log it and continue with the next pipeline handler
         syslog('error',
                '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):
     try:
         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):
                     banlist.append(addr)
     except IOError, e:
         # cannot open the global ban list for whatever reason
         # log it and continue with the next pipeline handler
         syslog('error',
                "Can't open %s: %s" % (ban_file, e)
                )
         return False
     except:
         # unspecified error
         # log it and continue with the next pipeline handler
         syslog('error',
                '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(),
                                         mlist.GetBouncesEmail(),
                                         _('Global Ban List Auto-discard 
notification'),
                                         lang=lang)
         nmsg.set_type('multipart/mixed')
         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.""")),
                         _charset=Utils.GetCharSet(lang))
         nmsg.attach(text)
         nmsg.attach(MIMEMessage(msg))
         nmsg.send(mlist)
     # Discard the message
     raise Errors.DiscardMessage



More information about the Mailman-Users mailing list