[Mailman-Users] Writing a custom handler

Chris Nulk cnulk at scu.edu
Tue Jul 2 22:49:35 CEST 2013


On 7/2/2013 12:17 PM, Mark Sapiro wrote:
> On 07/02/2013 11:38 AM, Chris Nulk wrote:
>>
>> I did forget about some of my other questions.  I plan on writing
>> another custom handler for a list-specific issue.  Where would I look if
>> I wanted to intercept messages related to subscribing, unsubscribing,
>> and options processing?
>
> These are messages to the -request, -join, -subscribe, -leave and
> -unsubscribe addresses and are all processed by CommandRunner. Only
> IncomingRunner processes a pipeline of handlers. There is no comparable
> processing or custom handler option for CommandRunner. You would have to
> modify Mailman/Queue/CommandRunner.py itself.

Hmmm.  I have a few lists where unsubscribes are not allowed (not my 
decision).  On one of them, a persistent member keeps trying to 
unsubscribe.  I wanted to setup an automated response and discard mechanism.

>
>
>> How would I tell the difference between a
>> subscribe message and an unsubscribe message? Also, is there a
>> difference if the person does the subscribing or unsubscribing via the
>> web?  Can I trap those?
>
> The web functions are handled by modules in Mailman/Cgi. They don't
> involve qrunners at all.

I have made enough mods to Mailman.  Don't want make any more if I don't 
have to so I will just keep discarding the unsubscribe requests.

>> Below is the latest update incorporating your suggestions.
>
> See some inline comments.
>
>
> def process(mlist, msg, msgdata):
>      # Upstream pipeline handler marked message approved -
> Or message contained an Approved: <password> header.

Yep.

>
>>      #   respect the decision
>>      if msgdata.get('approved'):
>>          return
>>
>>      # ban_file gets its value from mm_cfg.GLOBALBANLIST_FILENAME. If
>>      #   mm_cfg.GLOBALBANLIST_FILENAME is not defined 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_mlist):
>
> ban_mtime, not ban_mlist.

Yeah, I fixed it after I sent the message.

>
>
>>          # Global Ban list has changed (or ban_mlist = -1),
>
> ban_mtime.

Same here.

>
>
>>          #   read in the changes
>>          rc = Read_GlobalBan_File(ban_file)
>>          if not rc:
>>              # Problems reading the GlobalBan list
>>              return
>
> Or just
>          if not Read_GlobalBan_File(ban_file):
>              return

I actually had more code here.  As I reduced the code, I tightened the 
code.  Missed this one.

>> # 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
>
> Do you really want to do this? The Mailman philosophy is all caught and
> handled exceptions should be explicit. An unanticipated exception will
> be caught at the top qrunner level which will log the error with
> traceback in the 'error' log and shunt the message rather than
> continuing processing with a message that triggered an unanticipated
> exception.

Instead of returning, should I raise an exception?  For the IOError or 
any error, whether or not the file can be read or perform a stat isn't a 
big deal.  I would rather log the error, clear the exception and keep 
going.  If there is a problem in this function, returning False just 
means the ban_file hasn't changed so I will still be running sender 
addresses against a potentially older banlist.
>> 
>> # 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:
>>                  # if addr is not in banlist, add it - to avoid duplicates
>>                  if addr not in banlist:
>>                      banlist.append(addr.lower().strip())
>>      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
>
> See comment above about generic exceptions.

The same applies here also.  If there is a problem, the banlist doesn't 
get updated.

I would rather not have the code produce a traceback for a file system 
error.  If for some reason, the file became unreadable, then the error 
would kill Mailman repeatedly since this custom handler is going into 
the global pipeline.

Can I get rid of the exception handling while still logging those errors?

Thanks,
Chris



More information about the Mailman-Users mailing list