"""Do more detailed spam detection.
This module hard codes site wide spam detection. By hacking the
KNOWN_SPAMMERS variable, you can set up more regular expression matches
against message headers. If spam is detected the message is discarded
immediately.
TBD: This needs to be made more configurable and robust.
"""
import re
from cStringIO import StringIO
from email.Generator import Generator
from Mailman import mm_cfg
from Mailman import Errors
from Mailman import i18n
from Mailman.Handlers.Hold import hold_for_approval
try:
True, False
except NameError:
True = 1
False = 0
def _(s):
return s
class SpamDetected(Errors.DiscardMessage):
"""The message contains known spam"""
class HeaderMatchHold(Errors.HoldMessage):
reason = _('The message headers matched a filter rule')
_ = i18n._
class Tee:
def __init__(self, outfp_a, outfp_b):
self._outfp_a = outfp_a
self._outfp_b = outfp_b
def write(self, s):
self._outfp_a.write(s)
self._outfp_b.write(s)
class HeaderGenerator(Generator):
def __init__(self, outfp, mangle_from_=True, maxheaderlen=78):
Generator.__init__(self, outfp, mangle_from_, maxheaderlen)
self._headertxt = ''
def _write_headers(self, msg):
sfp = StringIO()
oldfp = self._fp
self._fp = Tee(oldfp, sfp)
try:
Generator._write_headers(self, msg)
finally:
self._fp = oldfp
self._headertxt = sfp.getvalue()
def header_text(self):
return self._headertxt
def process(mlist, msg, msgdata):
if msgdata.get('approved'):
return
for header, regex in mm_cfg.KNOWN_SPAMMERS:
cre = re.compile(regex, re.IGNORECASE)
for value in msg.get_all(header, []):
mo = cre.search(value)
if mo:
raise SpamDetected
headers = ''
for p in msg.walk():
g = HeaderGenerator(StringIO())
g.flatten(p)
headers += g.header_text()
headers = re.sub('\n+', '\n', headers)
headers = re.sub('\n\s', ' ', headers)
for patterns, action, empty in mlist.header_filter_rules:
if action == mm_cfg.DEFER:
continue
for pattern in patterns.splitlines():
if pattern.startswith('#'):
continue
if not pattern.strip():
continue
if re.search(pattern, headers, re.IGNORECASE|re.MULTILINE):
if action == mm_cfg.DISCARD:
raise Errors.DiscardMessage
if action == mm_cfg.REJECT:
if msgdata.get('toowner'):
raise Errors.DiscardMessage
raise Errors.RejectMessage(
_('Message rejected by filter rule match'))
if action == mm_cfg.HOLD:
if msgdata.get('toowner'):
return
hold_for_approval(mlist, msg, msgdata, HeaderMatchHold)
if action == mm_cfg.ACCEPT:
return