diff -Naur mailman-2.1.5.orig/Mailman/Defaults.py.in mailman-2.1.5/Mailman/Defaults.py.in
--- mailman-2.1.5.orig/Mailman/Defaults.py.in	Sat Apr 24 21:30:03 2004
+++ mailman-2.1.5/Mailman/Defaults.py.in	Wed Jun  2 21:13:34 2004
@@ -447,6 +447,7 @@
 # global pipeline by defining a `pipeline' attribute.
 GLOBAL_PIPELINE = [
     # These are the modules that do tasks common to all delivery paths.
+    'SpamAssassin',
     'SpamDetect',
     'Approve',
     'Replybot',
@@ -1171,6 +1172,17 @@
            'plain'   : DisableMime,
            'nodupes' : DontReceiveDuplicates
            }
+
+## Default SpamAssassin variables
+# global variables
+SPAMASSASSIN_SPAMD_HOST = 'localhost'
+SPAMASSASSIN_SPAMD_PORT = 783
+SPAMASSASSIN_PROTOCOL_VERSION = "SPAMC/1.3"
+# list variables
+DEFAULT_SPAMASSASSIN_DISCARD_SCORE = 10
+DEFAULT_SPAMASSASSIN_HOLD_SCORE = 5
+DEFAULT_SPAMASSASSIN_MEMBER_BONUS = 2
+DEFAULT_SPAMASSASSIN_ENABLE = 0
 
 # Authentication contexts.
 #
diff -Naur mailman-2.1.5.orig/Mailman/Gui/Privacy.py mailman-2.1.5/Mailman/Gui/Privacy.py
--- mailman-2.1.5.orig/Mailman/Gui/Privacy.py	Sun Nov 30 20:34:55 2003
+++ mailman-2.1.5/Mailman/Gui/Privacy.py	Wed Jun  2 21:25:33 2004
@@ -42,6 +42,7 @@
                     ('sender',      _('Sender&nbsp;filters')),
                     ('recipient',   _('Recipient&nbsp;filters')),
                     ('spam',        _('Spam&nbsp;filters')),
+                    ('sa',          _('SpamAssassin&nbsp;filters')),
                     ]
         return None
 
@@ -405,12 +406,39 @@
              bracketing it.""")),
           ]
 
+        sa_rtn = [
+           _("""This section allows you to configure
+           <a href="http://spamassassin.org/">SpamAssassin</a> filters.
+           """),
+
+           _("SpamAssassin filters"),
+
+            ('sa_enable', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+            _('Enable SpamAssassin.'),
+            _("""Enables SpamAssassin or not.  Note that this depends on
+            SpamAssassin being enabled on the site.""")),
+           ('sa_hold_score', mm_cfg.Number, 5, 0,
+            _('SpamAssassin score required to be held for moderation.'),
+            _("""Hold score: The SpamAssassin score that a message post to
+            the list must achieve before it is held for moderation.""")),
+           ('sa_discard_score', mm_cfg.Number, 5, 0,
+            _('SpamAssassin score required to be discarded.'),
+            _("""Discard score: The SpamAssassin score that a message post to
+            the list must achieve before it is discarded altogether.""")),
+           ('sa_member_bonus', mm_cfg.Number, 5, 0,
+            _('Bonus score subtracted for members.'),
+            _("""Member bonus: The bonus provided to a member of the list,
+            to increase the likelihood that their message will be posted.""")),
+          ]
+
         if subcat == 'sender':
             return sender_rtn
         elif subcat == 'recipient':
             return recip_rtn
         elif subcat == 'spam':
             return spam_rtn
+        elif subcat == 'sa':
+            return sa_rtn
         else:
             return subscribing_rtn
 
diff -Naur mailman-2.1.5.orig/Mailman/Handlers/SpamAssassin.py mailman-2.1.5/Mailman/Handlers/SpamAssassin.py
--- mailman-2.1.5.orig/Mailman/Handlers/SpamAssassin.py	Wed Dec 31 19:00:00 1969
+++ mailman-2.1.5/Mailman/Handlers/SpamAssassin.py	Wed Jun  2 21:17:52 2004
@@ -0,0 +1,140 @@
+# Copyright (C) 2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software 
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Perform spam detection using SpamAssassin.
+
+SpamAssassin:   http://www.spamassassin.org/
+
+Messages are passed to the spamd (SpamAssassin) daemon for analysis and
+scoring.  Depending on the score, messages may be rejected or held for
+moderation.
+"""
+
+import re
+import socket
+import string
+
+from Mailman import mm_cfg
+from Mailman import Errors
+from Mailman.Handlers import Hold
+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
+
+
+class SpamAssassinDiscard(Errors.DiscardMessage):
+    """The message scored above the discard threshold"""
+
+    reason = _('SpamAssassin identified this message as spam')
+    rejection = _('Your message was identified as spam.')
+
+
+class SpamAssassinHold(Errors.HoldMessage):
+    """The message scored above the hold threshold"""
+
+    reason = _('SpamAssassin identified this message as spam')
+    rejection = _('Your message was identified as spam.')
+
+
+def process(mlist, msg, msgdata):
+    if msgdata.get('approved'):
+        return
+    # check that this list wants spamassassin checking in the first place
+    if mlist.sa_enable == 0:
+        return
+    # Pass the flattened string representation of the message to spamd.
+    score, symbols = check_message(str(msg))
+    # If the member bonus has been enabled, check if the sender is a member.
+    if mlist.sa_member_bonus:
+        # Look for the sender(s) in the membership roster.
+        for sender in msg.get_senders():
+            if mlist.isMember(sender):
+                break
+        else:
+            sender = None    
+        # If the sender is a member, apply the bonus to the score.
+        if sender:
+            score = score - mlist.sa_member_bonus
+    # Should this message be discarded?
+    if score > mlist.sa_discard_score:
+        listname = mlist.real_name
+        sender = msg.get_sender()
+        syslog('vette', '%s post from %s discarded: '
+                        'SpamAssassin score was %s (discard threshold is %s)'
+                          % (listname, sender, score, mlist.sa_discard_score))
+        raise SpamAssassinDiscard
+    # If not, should it be held for approval?
+    elif score > mlist.sa_hold_score:
+        listname = mlist.real_name
+        sender = msg.get_sender()
+        syslog('vette', '%s post from %s held: '
+                        'SpamAssassin score was %s (hold threshold is %s)'
+                          % (listname, sender, score, mlist.sa_hold_score))
+        Hold.hold_for_approval(mlist, msg, msgdata,
+                               SpamAssassinHold(score, symbols))
+
+# Compile the regular expressions for interpreting spamd's results.
+resp_re = re.compile(r'^SPAMD/([\d.]+)\s+(-?\d+)\s+(.*)')
+spam_re = re.compile(r'^Spam:\s*(True|False)\s*;\s*(-?[\d.]+)\s*/\s*(-?[\d.]+)')
+
+
+def check_message(message):
+    score = -1
+    symbols = ''
+
+    try:
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.connect((mm_cfg.SPAMASSASSIN_SPAMD_HOST,
+		      mm_cfg.SPAMASSASSIN_SPAMD_PORT))
+        # Build the spamd request header.
+        header = 'SYMBOLS %s\r\nUser: %s\r\nContent-length: %s\r\n\r\n' % \
+                 (mm_cfg.SPAMASSASSIN_PROTOCOL_VERSION, mm_cfg.MAILMAN_USER,
+		  len(message))
+        # Send the header and message to spamd for analysis.
+        sock.send(header)
+        sock.send(message)
+        sock.shutdown(1)
+        # Create a file description from the socket.
+        fd = sock.makefile('r')
+        # Get the result code from spamd.
+        line = fd.readline()
+        match = resp_re.match(line)
+        if not match:
+            syslog('error', 'failed to get result code from spamd')
+            # Return the default score and symbols.
+            return score, symbols
+        if not match.group(2) == '0':
+            syslog('error', 'spamd returned non-zero code: %s' % match.group(3))
+            # Return the default score and symbols.
+            return score, symbols
+        # Get the message's score.
+        line = fd.readline()
+        while line and not line == '\r\n':
+            match = spam_re.match(line)
+            if match: score = float(match.group(2))
+            line = fd.readline()
+        # Read in the symbols representing the spam report.
+        symbols = fd.read()
+        symbols = string.replace(symbols, '\r\n', '\n')
+    except socket.error:
+        pass
+    except IOError:
+        pass
+    # Return the score and symbols from spamd.
+    return score, symbols
diff -Naur mailman-2.1.5.orig/Mailman/MailList.py mailman-2.1.5/Mailman/MailList.py
--- mailman-2.1.5.orig/Mailman/MailList.py	Thu Mar  4 09:10:28 2004
+++ mailman-2.1.5/Mailman/MailList.py	Wed Jun  2 21:19:17 2004
@@ -383,6 +383,12 @@
         else:
             self.encode_ascii_prefixes = 2
 
+        # SpamAssassin variables
+        self.sa_enable = mm_cfg.DEFAULT_SPAMASSASSIN_ENABLE
+        self.sa_hold_score = mm_cfg.DEFAULT_SPAMASSASSIN_HOLD_SCORE
+        self.sa_discard_score = mm_cfg.DEFAULT_SPAMASSASSIN_DISCARD_SCORE
+        self.sa_member_bonus = mm_cfg.DEFAULT_SPAMASSASSIN_MEMBER_BONUS
+
 
     #
     # Web API support via administrative categories
diff -Naur mailman-2.1.5.orig/Mailman/versions.py mailman-2.1.5/Mailman/versions.py
--- mailman-2.1.5.orig/Mailman/versions.py	Sun Nov 30 19:58:44 2003
+++ mailman-2.1.5/Mailman/versions.py	Wed Jun  2 21:20:44 2004
@@ -394,6 +394,12 @@
     add_only_if_missing('encode_ascii_prefixes', encode)
     add_only_if_missing('news_moderation', 0)
     add_only_if_missing('header_filter_rules', [])
+    add_only_if_missing('sa_enable', mm_cfg.DEFAULT_SPAMASSASSIN_ENABLE)
+    add_only_if_missing('sa_hold_score', mm_cfg.DEFAULT_SPAMASSASSIN_HOLD_SCORE)
+    add_only_if_missing('sa_discard_score',
+                        mm_cfg.DEFAULT_SPAMASSASSIN_DISCARD_SCORE)
+    add_only_if_missing('sa_member_bonus',
+                        mm_cfg.DEFAULT_SPAMASSASSIN_MEMBER_BONUS)
 
 
 
