Saturday, May 30, 2009

Mailman - Python powered mailing list manager

This post can be entertained by people who are list administrators wanting to add Custom Handlers to their mailing lists or audience to learn what Mailman is all about.

First I would like to congratulate all the people at GNU for churning out such useful *and* free softwares( read Free Software Foundation ). Hats off to Stallman for the phenomenon called GNU and FSF!

So what is Mailman all about, it's a mailing list manager... something very similar to a google group. When you start a google group you get an account where you may make changes, moderate and do stuff... but you have a limit to customization! The more common and professional way to manage mailing lists is to use Mailman ( or other mailling list managers )! My version of setting up the server was a complicated one though... it included the following steps:

1. Deploying a Mail Transfer Agent Locally, i used Postfix, other choices- Exim, Sendmail
2. Installing mailman on the same server, there are lot of permission issues.... be really aware of them :D
3. Testing the server and the mailman installation, if everything is right do next!
4. Customizing the installation with Filters you would like to have, Mailman has an easy interface to write plugins in Python
5. Getting a Global Domain so that servers like google, yahoo can connect to me on port 25( SMTP )
6. Initializing lists and populating members through the Web Interface( the coolest part ;) )

I'll take up step 4 for you guys.... rest steps require more of installing capability and negotiating it out with your ISP to grant you a DNS entry :D ( our side ISP's are cool enough ;) ).

The architecture of Mailman has a feature called pipeline, if you lookout for the file Defaults.py in mailman/Mailman/Defaults.py somewhere in /usr/lib or /var/lib or /usr/local/lib. There is a setting variable called GLOBAL_PIPELINE. This is a generic setting of the process a Mail has to go through. Though there can be exceptions for lists or mails directly to the Admin. You just need to place your own Handler in some suitable place. No, no dont edit the Defaults file, do this rather:

in mm_cfg.py add:
GLOBAL_PIPELINE.insert(GLOBAL_PIPELINE.index('Hold'), 'MyContentHandler')

This adds a handler called MyContentHandler.py BEFORE hold. The corresponding file should be pasted in Mailman/Handlers/ folder.

[Remember whenver you make a change to the MyContentHandler.py file reload mailman, the workaround could be to compile it locally... do as you wish!]

I'll post the handler I wrote... :

#Author: Sanket Agarwal
#Email: sketagarwal@gmail.com

"""Determines whether the content of the message that has been passed to mailman conttains any 'restricted' words!"""

#All imports taken from Hold.py
#TODO:Eliminate the non necessary ones
import email
import Hold
from email.MIMEText import MIMEText
from email.MIMEMessage import MIMEMessage
import email.Utils
from types import ClassType
import re

from Mailman import mm_cfg
from Mailman import Utils
from Mailman import Errors
from Mailman import Message
from Mailman import i18n
from Mailman import Pending
from Mailman.Logging.Syslog import syslog

# First, play footsie with _ so that the following are marked as translated,
# but aren't actually translated until we need the text later on.
def _(s):
return s

#The custom error class
class ForbiddenContent(Errors.HoldMessage):
"""This class defines our custom error for filtered content"""
reason = _('The content posted was caught in the filters!')
rejection = _('This message cannot be accepted.')

# And reset the translator
_ = i18n._

#The malicious content filter

def is_illegal(thread_text):
""" This function Returns a true if it finds a piece of illegal/illicit content in the message text, the whole thread has to be parsed unfortunately, as the sender can easily modify the message in case we neglect the history of the thread """

find_match = re.match('\w*bad_word\w*',str(thread_text))
if find_match == None:
return True #Content is cool enough
else:
return False #Restricted content found


def process(mlist, msg, msgdata):
#Experimentation period!
thread_text = email.message.Message.get_payload(msg)[0]
if( is_illegal(thread_text) ):
raise Errors.RejectMessage, "Your messaage was perhaps marked as containing some restriced content... such strikes might ban you from the list! \n -Sanket \n List Admin"
Hold.hold_for_approval(mlist,msg,msgdata,ForbiddenContent)
else:
pass


Pretty cool huh ?

You can have a pigmented version at my website.... http://maillist-cse.iitkgp.ernet.in/kgp/cab/4/

So referring to the pigmented version lemme take you through the code:

Ln: 1-2 , Thank you guys :D
Ln: 6-22: Pretty much from the Hold.py, I had to hold the messages that were caught... so that's why Hold.py
Ln: 40-58: This is the logic which checks if the message text contains bad content... using regular expressions is the best bet! Have a look at the python re docs for details.
Ln: 30-33: Extends the Errors.HoldMessage, a hold of message means that the message will go for moderation!
Ln: 50: A must have method, every handler has it... so should you!
Ln: 52: Extracts the text part of the message, look for Pyhton email.message Docs for details
Ln: 54: Here you go! The message shall go to the list-owner for further approval!

I liked this plugin thing because it makes all the more sense to make my filters only for Mailman or for that reason, the script might be restricted to a particular mailing list( I'll give the link... keep reading ). U might not have the server write privelages to properly setup a SpamAssasin or some other mail filter, but when you've got the Power of Python and Source of Mailman. Go here for a more technically sound version... but you wont find the code newhere else ;)

http://wiki.list.org/pages/viewpage.action?pageId=4030615


"""May the source be with you"""

No comments:

Post a Comment