Add Debian directory
[mspang/vmailman.git] / debian / contrib / SpamAssassin.py
1 # Copyright (C) 2002-2003 by James Henstridge <james@daa.com.au>
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
7
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software 
15 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, US
16
17 """Perform spam detection with SpamAssassin.
18
19 Messages are passed to a spamd (SpamAssassin) daemon for spam checking.
20 Depending on the score returned, messages may be rejected or held for
21 moderation.
22 """
23
24 import string
25 import spamd
26
27 from Mailman import mm_cfg
28 from Mailman import Errors
29 from Mailman.Logging.Syslog import syslog
30 from Mailman.Handlers import Hold
31 from Mailman.Handlers.Moderate import matches_p
32
33 SPAMD_HOST    = getattr(mm_cfg, 'SPAMASSASSIN_HOST', '')
34 DISCARD_SCORE = getattr(mm_cfg, 'SPAMASSASSIN_DISCARD_SCORE', 10)
35 HOLD_SCORE    = getattr(mm_cfg, 'SPAMASSASSIN_HOLD_SCORE', 5)
36 MEMBER_BONUS  = getattr(mm_cfg, 'SPAMASSASSIN_MEMBER_BONUS', 2)
37
38 class SpamAssassinDiscard(Errors.DiscardMessage):
39     '''The message was scored above the discard threshold'''
40     reason = 'SpamAssassin identified this message as spam'
41     rejection = 'Your message has been discarded as spam'
42
43 class SpamAssassinHold(Errors.HoldMessage):
44     '''The message was scored above the hold threshold'''
45     def __init__(self, score=-1, symbols=''):
46         Errors.HoldMessage.__init__(self)
47         self.reason = 'SpamAssassin identified this message as possible ' \
48                       'spam (score %g)' % score
49         self.rejection = 'Your message was held for moderation because ' \
50                          'SpamAssassin gave the message a score of %g ' \
51                          'for the following reasons:\n\n%s' % \
52                          (score, symbols)
53
54 def check_message(mlist, message):
55     '''Check a message against a SpamAssassin spamd process.
56     Returns a tuple of the form (score, symbols)'''
57     try:
58         connection = spamd.SpamdConnection(SPAMD_HOST)
59         # identify as the mailing list, to allow storing per-list
60         # AWL and bayes databases.
61         connection.addheader('User', mlist.internal_name())
62         res = connection.check(spamd.SYMBOLS, message)
63
64         score = connection.getspamstatus()[1]
65         symbols = connection.response_message.replace(',', ', ')
66
67         return score, symbols
68     except spamd.error, ex:
69         syslog('error', 'spamd: %s' % str(ex))
70         return -1, ''
71
72 def process(mlist, msg, msgdata):
73     if msgdata.get('approved'):
74         return
75     
76     score, symbols = check_message(mlist, str(msg))
77
78     if MEMBER_BONUS != 0:
79         for sender in msg.get_senders():
80             if mlist.isMember(sender) or \
81                    matches_p(sender, mlist.accept_these_nonmembers):
82                 score -= MEMBER_BONUS
83                 break
84
85     if score > DISCARD_SCORE:
86         listname = mlist.real_name
87         sender = msg.get_sender()
88         syslog('vette', '%s post from %s discarded: '
89                         'SpamAssassin score was %g (discard threshold is %g)'
90                           % (listname, sender, score, DISCARD_SCORE))
91         raise SpamAssassinDiscard
92     elif score > HOLD_SCORE:
93         Hold.hold_for_approval(mlist, msg, msgdata,
94                                SpamAssassinHold(score, symbols))