diff -r -u -P --exclude=.DS_Store mailman-2.1.3/Mailman/Cgi/private.py mailman-2.1.3-antispam/Mailman/Cgi/private.py --- mailman-2.1.3/Mailman/Cgi/private.py Sat Feb 8 07:13:50 2003 +++ mailman-2.1.3-antispam/Mailman/Cgi/private.py Fri Nov 28 14:15:09 2003 @@ -109,35 +109,36 @@ username = cgidata.getvalue('username', '') password = cgidata.getvalue('password', '') - is_auth = 0 - realname = mlist.real_name - message = '' - - if not mlist.WebAuthenticate((mm_cfg.AuthUser, - mm_cfg.AuthListModerator, - mm_cfg.AuthListAdmin, - mm_cfg.AuthSiteAdmin), - password, username): - if cgidata.has_key('submit'): - # This is a re-authorization attempt - message = Bold(FontSize('+1', _('Authorization failed.'))).Format() - # Output the password form - charset = Utils.GetCharSet(mlist.preferred_language) - print 'Content-type: text/html; charset=' + charset + '\n\n' - while path and path[0] == '/': - path=path[1:] # Remove leading /'s - print Utils.maketext( - 'private.html', - {'action' : mlist.GetScriptURL('private', absolute=1), - 'realname': mlist.real_name, - 'message' : message, - }, mlist=mlist) - return - - lang = mlist.getMemberLanguage(username) - i18n.set_language(lang) - doc.set_language(lang) - + if mlist.archive_private: + is_auth = 0 + realname = mlist.real_name + message = '' + + if not mlist.WebAuthenticate((mm_cfg.AuthUser, + mm_cfg.AuthListModerator, + mm_cfg.AuthListAdmin, + mm_cfg.AuthSiteAdmin), + password, username): + if cgidata.has_key('submit'): + # This is a re-authorization attempt + message = Bold(FontSize('+1', _('Authorization failed.'))).Format() + # Output the password form + charset = Utils.GetCharSet(mlist.preferred_language) + print 'Content-type: text/html; charset=' + charset + '\n\n' + while path and path[0] == '/': + path=path[1:] # Remove leading /'s + print Utils.maketext( + 'private.html', + {'action' : mlist.GetScriptURL('private', absolute=1), + 'realname': mlist.real_name, + 'message' : message, + }, mlist=mlist) + return + + lang = mlist.getMemberLanguage(username) + i18n.set_language(lang) + doc.set_language(lang) + # Authorization confirmed... output the desired file try: ctype, enc = guess_type(path, strict=0) @@ -160,5 +161,8 @@ syslog('error', 'Private archive file not found: %s', true_filename) else: print 'Content-type: %s\n' % ctype - sys.stdout.write(f.read()) + if ctype in ('text/html', 'text/plain') and mm_cfg.ANTISPAM_MODE_ALL: + sys.stdout.write(mm_cfg.deny_harvest(f.read())) + else: + sys.stdout.write(f.read()) f.close() diff -r -u -P --exclude=.DS_Store mailman-2.1.3/Mailman/Defaults.py.in mailman-2.1.3-antispam/Mailman/Defaults.py.in --- mailman-2.1.3/Mailman/Defaults.py.in Mon Sep 22 05:26:14 2003 +++ mailman-2.1.3-antispam/Mailman/Defaults.py.in Fri Nov 28 15:34:49 2003 @@ -1105,6 +1105,81 @@ ##### +# Anti-spam email address harvesting prevention measures. +# +# These measures are to limit the ability of spam generators to acquire +# email address from archived material in Mailman's list archives. +# Implementation is via a dynamic search and replace for email +# addresses, appearing in files of MIME type text/html or text/plain, as +# those files are requested. The underlying archive file content as +# generated by the archiving software remains unchanged. +# +# The implementation requires that archive files are all delivered by a +# modified private.py CGI script which only requires user authentication +# if the list whose archive material is being requested is set up as a private +# list. In order to get public archives served by private.py a RewriteRule +# like this: +# +# RewriteRule ^/pipermail/(.*) /mailman/private/$1 [PT] +# +# needs to be used in the Apache httpd.conf to transparently redirect +# public arechive file requests. +# +# When email addresses are found, the domain part of the addressed is replaced +# with a string of 'x' characters. If the local part of the address appears to +# have been VERP'ed then the VERP information is similarly obscured. This is +# a fairly brutal set of irreversible modifications to any email addresses in +# the returned text and will break any mailto: links in the text. +# +# Th eamil address regex looks for either an '@' character or its HTML escaped +# version '%40' as the local-part/domain separator. You should set +# ARCHIVER_OBSCURES_EMAILADDRS = 0 and run bin/arch to rebuild existing archives +# to prevent that feature interfering with the operation of these harvesting +# prevention measures. +# +# If you decide to change the regexes then copy all of this stuff into +# mm_cfg.py and make the changes there. +# +##### + +import re + +# Enable email address harvesting prevention if this is true +ANTISPAM_MODE_ALL = Yes + +# Pattern to discover mail addresses in order to hide them from spammers +# but only relevant if ARCHIVER_OBSCURES_ALLADDRS is true +# This is default spammode setting used by MHonArc if your are interested +ANTISPAM_REGEX = r'(?P[\!\%\w\.\-+=/]+?)(?P@|%40)(?P[\w\-]+\.[\w\.\-]+)' +ANTISPAM_PAT = re.compile(ANTISPAM_REGEX, re.M) + +# Possible VERP regexes and associated extraction variables +# which must be defined. If you a brave enough to change any of them in +# mm_cfg.py then you should copy all of this stuff over to mm_cfg.py as well. +# Add to this tuple if more VERP patterns are defined. +POSSIBLE_VERP_REGEX_LIST = (('VERP_REGEXP', 'bounces', 'mailbox'), + ('VERP_CONFIRM_REGEXP', 'addr', 'cookie')) +VERP_REGEX_PATS = [] +for _regex_pat_name, _alias_part, _verp_part in POSSIBLE_VERP_REGEX_LIST: + VERP_REGEX_PATS.append((re.compile(_regex_pat_name), _alias_part, + _verp_part)) + +def _antispam_mods(matchobj): + _hidden = 'x' * len(matchobj.group('domain')) + _local_part = matchobj.group('localpart') + for _regex_pat, _alias_part, _verp_part in VERP_REGEX_PATS: + _verp_match_obj = _regex_pat.search(matchobj.group('localpart') + \ + '@' + matchobj.group('domain')) + if _verp_match_obj: + _local_part = _verp_match_obj.group(_alias_part) + \ + 'x' * (len(_verp_match_obj.group(_verp_part)) + 1) + break + return _local_part + matchobj.group('what') + _hidden + +def deny_harvest(text): + return re.sub(ANTISPAM_PAT, _antispam_mods, text) + +##### # Nothing below here is user configurable. Most of these values are in this # file for internal system convenience. Don't change any of them or override # any of them in your mm_cfg.py file!