summaryrefslogtreecommitdiff
path: root/Mailman/MailList.py
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/MailList.py')
-rw-r--r--Mailman/MailList.py285
1 files changed, 192 insertions, 93 deletions
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index fb85584d8..0a52567d9 100644
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2003 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2005 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
@@ -38,6 +38,7 @@ from types import *
import email.Iterators
from email.Utils import getaddresses, formataddr, parseaddr
+from email.Header import Header
from Mailman import mm_cfg
from Mailman import Utils
@@ -56,6 +57,7 @@ from Mailman.HTMLFormatter import HTMLFormatter
from Mailman.ListAdmin import ListAdmin
from Mailman.SecurityManager import SecurityManager
from Mailman.TopicMgr import TopicMgr
+from Mailman import Pending
# gui components package
from Mailman import Gui
@@ -64,7 +66,6 @@ from Mailman import Gui
from Mailman import MemberAdaptor
from Mailman.OldStyleMemberships import OldStyleMemberships
from Mailman import Message
-from Mailman import Pending
from Mailman import Site
from Mailman import i18n
from Mailman.Logging.Syslog import syslog
@@ -84,7 +85,7 @@ except NameError:
# Use mixins here just to avoid having any one chunk be too large.
class MailList(HTMLFormatter, Deliverer, ListAdmin,
Archiver, Digester, SecurityManager, Bouncer, GatewayManager,
- Autoresponder, TopicMgr):
+ Autoresponder, TopicMgr, Pending.Pending):
#
# A MailList object's basic Python object model support
@@ -194,8 +195,11 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
def GetOwnerEmail(self):
return self.getListAddress('owner')
- def GetRequestEmail(self):
- return self.getListAddress('request')
+ def GetRequestEmail(self, cookie=''):
+ if mm_cfg.VERP_CONFIRMATIONS and cookie:
+ return self.GetConfirmEmail(cookie)
+ else:
+ return self.getListAddress('request')
def GetConfirmEmail(self, cookie):
return mm_cfg.VERP_CONFIRM_FORMAT % {
@@ -203,6 +207,26 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
'cookie': cookie,
} + '@' + self.host_name
+ def GetConfirmJoinSubject(self, listname, cookie):
+ if mm_cfg.VERP_CONFIRMATIONS and cookie:
+ cset = Utils.GetCharSet(self.preferred_language)
+ subj = Header(
+ _('Your confirmation is required to join the %(listname)s mailing list'),
+ cset, header_name='subject')
+ return subj
+ else:
+ return 'confirm ' + cookie
+
+ def GetConfirmLeaveSubject(self, listname, cookie):
+ if mm_cfg.VERP_CONFIRMATIONS and cookie:
+ cset = Utils.GetCharSet(self.preferred_language)
+ subj = Header(
+ _('Your confirmation is required to leave the %(listname)s mailing list'),
+ cset, header_name='subject')
+ return subj
+ else:
+ return 'confirm ' + cookie
+
def GetListEmail(self):
return self.getListAddress()
@@ -251,7 +275,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
if name:
self._full_path = Site.get_listpath(name)
else:
- self._full_path = None
+ self._full_path = ''
# Only one level of mixin inheritance allowed
for baseclass in self.__class__.__bases__:
if hasattr(baseclass, 'InitTempVars'):
@@ -315,6 +339,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.send_goodbye_msg = mm_cfg.DEFAULT_SEND_GOODBYE_MSG
self.bounce_matching_headers = \
mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS
+ self.header_filter_rules = []
self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST
internalname = self.internal_name()
self.real_name = internalname[0].upper() + internalname[1:]
@@ -334,7 +359,11 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.include_list_post_header = 1
self.filter_mime_types = mm_cfg.DEFAULT_FILTER_MIME_TYPES
self.pass_mime_types = mm_cfg.DEFAULT_PASS_MIME_TYPES
+ self.filter_filename_extensions = \
+ mm_cfg.DEFAULT_FILTER_FILENAME_EXTENSIONS
+ self.pass_filename_extensions = mm_cfg.DEFAULT_PASS_FILENAME_EXTENSIONS
self.filter_content = mm_cfg.DEFAULT_FILTER_CONTENT
+ self.collapse_alternatives = mm_cfg.DEFAULT_COLLAPSE_ALTERNATIVES
self.convert_html_to_plaintext = \
mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT
self.filter_action = mm_cfg.DEFAULT_FILTER_ACTION
@@ -357,6 +386,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.discard_these_nonmembers = []
self.forward_auto_discards = mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS
self.generic_nonmember_action = mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION
+ self.nonmember_rejection_notice = ''
# Ban lists
self.ban_list = []
# BAW: This should really be set in SecurityManager.InitVars()
@@ -365,7 +395,6 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# 2-tuple of the date of the last autoresponse and the number of
# autoresponses sent on that date.
self.hold_and_cmd_autoresponses = {}
-
# Only one level of mixin inheritance allowed
for baseclass in self.__class__.__bases__:
if hasattr(baseclass, 'InitVars'):
@@ -382,6 +411,10 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.encode_ascii_prefixes = 0
else:
self.encode_ascii_prefixes = 2
+ # scrub regular delivery
+ self.scrub_nondigest = mm_cfg.DEFAULT_SCRUB_NONDIGEST
+ # automatic discarding
+ self.max_days_to_hold = mm_cfg.DEFAULT_MAX_DAYS_TO_HOLD
#
@@ -432,7 +465,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
#
# List creation
#
- def Create(self, name, admin, crypted_password, langs=None):
+ def Create(self, name, admin, crypted_password,
+ langs=None, emailhost=None):
if Utils.list_exists(name):
raise Errors.MMListAlreadyExistsError, name
# Validate what will be the list's posting address. If that's
@@ -440,7 +474,9 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# part doesn't really matter, since that better already be valid.
# However, most scripts already catch MMBadEmailError as exceptions on
# the admin's email address, so transform the exception.
- postingaddr = '%s@%s' % (name, mm_cfg.DEFAULT_EMAIL_HOST)
+ if emailhost is None:
+ emailhost = mm_cfg.DEFAULT_EMAIL_HOST
+ postingaddr = '%s@%s' % (name, emailhost)
try:
Utils.ValidateEmail(postingaddr)
except Errors.MMBadEmailError:
@@ -564,7 +600,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
if type(dict) <> DictType:
return None, 'Load() expected to return a dictionary'
except (EOFError, ValueError, TypeError, MemoryError,
- cPickle.PicklingError), e:
+ cPickle.PicklingError, cPickle.UnpicklingError), e:
return None, e
finally:
fp.close()
@@ -572,7 +608,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.__timestamp = mtime
return dict, None
- def Load(self, check_version=1):
+ def Load(self, check_version=True):
if not Utils.list_exists(self.internal_name()):
raise Errors.MMUnknownListError
# We first try to load config.pck, which contains the up-to-date
@@ -605,13 +641,20 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
raise Errors.MMCorruptListDatabaseError, e
# Now, if we didn't end up using the primary database file, we want to
# copy the fallback into the primary so that the logic in Save() will
- # still work. For giggles, we'll copy it to a safety backup.
- if file == plast:
- shutil.copy(file, pfile)
- shutil.copy(file, pfile + '.safety')
- elif file == dlast:
- shutil.copy(file, dfile)
- shutil.copy(file, pfile + '.safety')
+ # still work. For giggles, we'll copy it to a safety backup. Note we
+ # MUST do this with the underlying list lock acquired.
+ if file == plast or file == dlast:
+ syslog('error', 'fixing corrupt config file, using: %s', file)
+ unlock = True
+ try:
+ try:
+ self.__lock.lock()
+ except LockFile.AlreadyLockedError:
+ unlock = False
+ self.__fix_corrupt_pckfile(file, pfile, plast, dfile, dlast)
+ finally:
+ if unlock:
+ self.__lock.unlock()
# Copy the loaded dictionary into the attributes of the current
# mailing list object, then run sanity check on the data.
self.__dict__.update(dict)
@@ -619,6 +662,36 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.CheckVersion(dict)
self.CheckValues()
+ def __fix_corrupt_pckfile(self, file, pfile, plast, dfile, dlast):
+ if file == plast:
+ # Move aside any existing pickle file and delete any existing
+ # safety file. This avoids EPERM errors inside the shutil.copy()
+ # calls if those files exist with different ownership.
+ try:
+ os.rename(pfile, pfile + '.corrupt')
+ except OSError, e:
+ if e.errno <> errno.ENOENT: raise
+ try:
+ os.remove(pfile + '.safety')
+ except OSError, e:
+ if e.errno <> errno.ENOENT: raise
+ shutil.copy(file, pfile)
+ shutil.copy(file, pfile + '.safety')
+ elif file == dlast:
+ # Move aside any existing marshal file and delete any existing
+ # safety file. This avoids EPERM errors inside the shutil.copy()
+ # calls if those files exist with different ownership.
+ try:
+ os.rename(dfile, dfile + '.corrupt')
+ except OSError, e:
+ if e.errno <> errno.ENOENT: raise
+ try:
+ os.remove(dfile + '.safety')
+ except OSError, e:
+ if e.errno <> errno.ENOENT: raise
+ shutil.copy(file, dfile)
+ shutil.copy(file, dfile + '.safety')
+
#
# Sanity checks
@@ -691,14 +764,14 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
"""
invitee = userdesc.address
Utils.ValidateEmail(invitee)
- requestaddr = self.GetRequestEmail()
# Hack alert! Squirrel away a flag that only invitations have, so
# that we can do something slightly different when an invitation
# subscription is confirmed. In those cases, we don't need further
# admin approval, even if the list is so configured. The flag is the
# list name to prevent invitees from cross-subscribing.
userdesc.invitation = self.internal_name()
- cookie = Pending.new(Pending.SUBSCRIPTION, userdesc)
+ cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc)
+ requestaddr = self.getListAddress('request')
confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
cookie)
listname = self.real_name
@@ -712,17 +785,13 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
'cookie' : cookie,
'listowner' : self.GetOwnerEmail(),
}, mlist=self)
- if mm_cfg.VERP_CONFIRMATIONS:
- subj = _(
- 'You have been invited to join the %(listname)s mailing list')
- sender = self.GetConfirmEmail(cookie)
- else:
- # Do it the old fashioned way
- subj = 'confirm ' + cookie
- sender = requestaddr
+ sender = self.GetRequestEmail(cookie)
msg = Message.UserNotification(
- invitee, sender, subj,
- text, lang=self.preferred_language)
+ invitee, sender,
+ text=text, lang=self.preferred_language)
+ subj = self.GetConfirmJoinSubject(listname, cookie)
+ del msg['subject']
+ msg['Subject'] = subj
msg.send(self)
def AddMember(self, userdesc, remote=None):
@@ -807,11 +876,11 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# means the user must confirm; 2 means the admin must approve; 3 means
# the user must confirm and then the admin must approve
if self.subscribe_policy == 0:
- self.ApprovedAddMember(userdesc)
+ self.ApprovedAddMember(userdesc, whence=remote or '')
elif self.subscribe_policy == 1 or self.subscribe_policy == 3:
# User confirmation required. BAW: this should probably just
# accept a userdesc instance.
- cookie = Pending.new(Pending.SUBSCRIPTION, userdesc)
+ cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc)
# Send the user the confirmation mailback
if remote is None:
by = remote = ''
@@ -829,18 +898,18 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
'listaddr' : self.GetListEmail(),
'listname' : realname,
'cookie' : cookie,
- 'requestaddr' : self.GetRequestEmail(),
+ 'requestaddr' : self.getListAddress('request'),
'remote' : remote,
'listadmin' : self.GetOwnerEmail(),
'confirmurl' : confirmurl,
}, lang=lang, mlist=self)
msg = Message.UserNotification(
- recipient, self.GetRequestEmail(),
+ recipient, self.GetRequestEmail(cookie),
text=text, lang=lang)
# BAW: See ChangeMemberAddress() for why we do it this way...
del msg['subject']
- msg['Subject'] = 'confirm ' + cookie
- msg['Reply-To'] = self.GetRequestEmail()
+ msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie)
+ msg['Reply-To'] = self.GetRequestEmail(cookie)
msg.send(self)
who = formataddr((name, email))
syslog('subscribe', '%s: pending %s %s',
@@ -854,7 +923,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
raise Errors.MMNeedApproval, _(
'subscriptions to %(realname)s require moderator approval')
- def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text=''):
+ def ApprovedAddMember(self, userdesc, ack=None, admin_notif=None, text='',
+ whence=''):
"""Add a member right now.
The member's subscription must be approved by what ever policy the
@@ -903,8 +973,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
kind = ' (digest)'
else:
kind = ''
- syslog('subscribe', '%s: new%s %s', self.internal_name(),
- kind, formataddr((email, name)))
+ syslog('subscribe', '%s: new%s %s, %s', self.internal_name(),
+ kind, formataddr((email, name)), whence)
if ack:
self.SendSubscribeAck(email, self.getMemberPassword(email),
digest, text)
@@ -1004,8 +1074,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
if newaddr == self.GetListEmail().lower():
raise Errors.MMBadEmailError
# Pend the subscription change
- cookie = Pending.new(Pending.CHANGE_OF_ADDRESS,
- oldaddr, newaddr, globally)
+ cookie = self.pend_new(Pending.CHANGE_OF_ADDRESS,
+ oldaddr, newaddr, globally)
confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
cookie)
realname = self.real_name
@@ -1016,7 +1086,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
'listaddr' : self.GetListEmail(),
'listname' : realname,
'cookie' : cookie,
- 'requestaddr': self.GetRequestEmail(),
+ 'requestaddr': self.getListAddress('request'),
'remote' : '',
'listadmin' : self.GetOwnerEmail(),
'confirmurl' : confirmurl,
@@ -1029,15 +1099,21 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# Subject: in a separate step, although we have to delete the one
# UserNotification adds.
msg = Message.UserNotification(
- newaddr, self.GetRequestEmail(),
+ newaddr, self.GetRequestEmail(cookie),
text=text, lang=lang)
del msg['subject']
- msg['Subject'] = 'confirm ' + cookie
- msg['Reply-To'] = self.GetRequestEmail()
+ msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie)
+ msg['Reply-To'] = self.GetRequestEmail(cookie)
msg.send(self)
def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally):
- self.changeMemberAddress(oldaddr, newaddr)
+ # It's possible they were a member of this list, but choose to change
+ # their membership globally. In that case, we simply remove the old
+ # address.
+ if self.getMemberCPAddress(oldaddr) == newaddr:
+ self.removeMember(oldaddr)
+ else:
+ self.changeMemberAddress(oldaddr, newaddr)
# If globally is true, then we also include every list for which
# oldaddr is a member.
if not globally:
@@ -1053,7 +1129,11 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
continue
mlist.Lock()
try:
- mlist.changeMemberAddress(oldaddr, newaddr)
+ # Same logic as above, re newaddr is already a member
+ if mlist.getMemberCPAddress(oldaddr) == newaddr:
+ mlist.removeMember(oldaddr)
+ else:
+ mlist.changeMemberAddress(oldaddr, newaddr)
mlist.Save()
finally:
mlist.Unlock()
@@ -1063,15 +1143,16 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# Confirmation processing
#
def ProcessConfirmation(self, cookie, context=None):
- data = Pending.confirm(cookie)
- if data is None:
- raise Errors.MMBadConfirmation, 'data is None'
+ rec = self.pend_confirm(cookie)
+ if rec is None:
+ raise Errors.MMBadConfirmation, 'No cookie record for %s' % cookie
try:
- op = data[0]
- data = data[1:]
+ op = rec[0]
+ data = rec[1:]
except ValueError:
- raise Errors.MMBadConfirmation, 'op-less data %s' % (data,)
+ raise Errors.MMBadConfirmation, 'op-less data %s' % (rec,)
if op == Pending.SUBSCRIPTION:
+ whence = 'via email confirmation'
try:
userdesc = data[0]
# If confirmation comes from the web, context should be a
@@ -1080,6 +1161,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# context is a Message and isn't relevant, so ignore it.
if isinstance(context, UserDesc):
userdesc += context
+ whence = 'via web confirmation'
addr = userdesc.address
fullname = userdesc.fullname
password = userdesc.password
@@ -1104,7 +1186,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
name = self.real_name
raise Errors.MMNeedApproval, _(
'subscriptions to %(name)s require administrator approval')
- self.ApprovedAddMember(userdesc)
+ self.ApprovedAddMember(userdesc, whence=whence)
return op, addr, password, digest, lang
elif op == Pending.UNSUBSCRIPTION:
addr = data[0]
@@ -1125,8 +1207,10 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
approved = None
# Confirmation should be coming from email, where context should
# be the confirming message. If the message does not have an
- # Approved: header, this is a discard, otherwise it's an approval
- # (if the passwords match).
+ # Approved: header, this is a discard. If it has an Approved:
+ # header that does not match the list password, then we'll notify
+ # the list administrator that they used the wrong password.
+ # Otherwise it's an approval.
if isinstance(context, Message.Message):
# See if it's got an Approved: header, either in the headers,
# or in the first text/plain section of the response. For
@@ -1140,7 +1224,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
subpart = None
if subpart:
s = StringIO(subpart.get_payload())
- while 1:
+ while True:
line = s.readline()
if not line:
break
@@ -1153,11 +1237,19 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# then
approved = line[i+1:].strip()
break
- # Okay, does the approved header match the list password?
- if approved and self.Authenticate([mm_cfg.AuthListAdmin,
- mm_cfg.AuthListModerator],
- approved) <> mm_cfg.UnAuthorized:
- action = mm_cfg.APPROVE
+ # Is there an approved header?
+ if approved is not None:
+ # Does it match the list password? Note that we purposefully
+ # do not allow the site password here.
+ if self.Authenticate([mm_cfg.AuthListAdmin,
+ mm_cfg.AuthListModerator],
+ approved) <> mm_cfg.UnAuthorized:
+ action = mm_cfg.APPROVE
+ else:
+ # The password didn't match. Re-pend the message and
+ # inform the list moderators about the problem.
+ self.pend_repend(cookie, rec)
+ raise Errors.MMBadPasswordError
else:
action = mm_cfg.DISCARD
try:
@@ -1171,11 +1263,13 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
member = data[1]
self.setDeliveryStatus(member, MemberAdaptor.ENABLED)
return op, member
+ else:
+ assert 0, 'Bad op: %s' % op
def ConfirmUnsubscription(self, addr, lang=None, remote=None):
if lang is None:
lang = self.getMemberLanguage(addr)
- cookie = Pending.new(Pending.UNSUBSCRIPTION, addr)
+ cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr)
confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
cookie)
realname = self.real_name
@@ -1191,18 +1285,18 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
'listaddr' : self.GetListEmail(),
'listname' : realname,
'cookie' : cookie,
- 'requestaddr' : self.GetRequestEmail(),
+ 'requestaddr' : self.getListAddress('request'),
'remote' : remote,
'listadmin' : self.GetOwnerEmail(),
'confirmurl' : confirmurl,
}, lang=lang, mlist=self)
msg = Message.UserNotification(
- addr, self.GetRequestEmail(),
+ addr, self.GetRequestEmail(cookie),
text=text, lang=lang)
# BAW: See ChangeMemberAddress() for why we do it this way...
del msg['subject']
- msg['Subject'] = 'confirm ' + cookie
- msg['Reply-To'] = self.GetRequestEmail()
+ msg['Subject'] = self.GetConfirmLeaveSubject(realname, cookie)
+ msg['Reply-To'] = self.GetRequestEmail(cookie)
msg.send(self)
@@ -1211,20 +1305,19 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
#
def HasExplicitDest(self, msg):
"""True if list name or any acceptable_alias is included among the
- to or cc addrs."""
- # BAW: fall back to Utils.ParseAddr if the first test fails.
- # this is the list's full address
+ addresses in the recipient headers.
+ """
+ # This is the list's full address.
listfullname = '%s@%s' % (self.internal_name(), self.host_name)
recips = []
- # check all recipient addresses against the list's explicit addresses,
+ # Check all recipient addresses against the list's explicit addresses,
# specifically To: Cc: and Resent-to:
to = []
for header in ('to', 'cc', 'resent-to', 'resent-cc'):
to.extend(getaddresses(msg.get_all(header, [])))
for fullname, addr in to:
- # It's possible that if the header doesn't have a valid
- # (i.e. RFC822) value, we'll get None for the address. So skip
- # it.
+ # It's possible that if the header doesn't have a valid RFC 2822
+ # value, we'll get None for the address. So skip it.
if addr is None:
continue
addr = addr.lower()
@@ -1233,40 +1326,39 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
localpart == self.internal_name() or
# exact match against the complete list address
addr == listfullname):
- return 1
+ return True
recips.append((addr, localpart))
- #
- # helper function used to match a pattern against an address. Do it
+ # Helper function used to match a pattern against an address.
def domatch(pattern, addr):
try:
- if re.match(pattern, addr):
- return 1
+ if re.match(pattern, addr, re.IGNORECASE):
+ return True
except re.error:
# The pattern is a malformed regexp -- try matching safely,
# with all non-alphanumerics backslashed:
- if re.match(re.escape(pattern), addr):
- return 1
- #
+ if re.match(re.escape(pattern), addr, re.IGNORECASE):
+ return True
+ return False
# Here's the current algorithm for matching acceptable_aliases:
#
# 1. If the pattern does not have an `@' in it, we first try matching
# it against just the localpart. This was the behavior prior to
- # 2.0beta3, and is kept for backwards compatibility.
- # (deprecated).
+ # 2.0beta3, and is kept for backwards compatibility. (deprecated).
#
# 2. If that match fails, or the pattern does have an `@' in it, we
# try matching against the entire recip address.
+ aliases = self.acceptable_aliases.splitlines()
for addr, localpart in recips:
- for alias in self.acceptable_aliases.split('\n'):
+ for alias in aliases:
stripped = alias.strip()
if not stripped:
- # ignore blank or empty lines
+ # Ignore blank or empty lines
continue
if '@' not in stripped and domatch(stripped, localpart):
- return 1
+ return True
if domatch(stripped, addr):
- return 1
- return 0
+ return True
+ return False
def parse_matching_header_opt(self):
"""Return a list of triples [(field name, regex, line), ...]."""
@@ -1313,13 +1405,17 @@ bad regexp in bounce_matching_header line: %s
return line
return 0
- def autorespondToSender(self, sender):
+ def autorespondToSender(self, sender, lang=None):
"""Return true if Mailman should auto-respond to this sender.
This is only consulted for messages sent to the -request address, or
for posting hold notifications, and serves only as a safety value for
mail loops with email 'bots.
"""
+ # language setting
+ if lang == None:
+ lang = self.preferred_language
+ i18n.set_language(lang)
# No limit
if mm_cfg.MAX_AUTORESPONSES_PER_DAY == 0:
return 1
@@ -1347,11 +1443,12 @@ bad regexp in bounce_matching_header line: %s
'listname': '%s@%s' % (self.real_name, self.host_name),
'num' : count,
'owneremail': self.GetOwnerEmail(),
- })
+ },
+ lang=lang)
msg = Message.UserNotification(
sender, self.GetOwnerEmail(),
_('Last autoresponse notification for today'),
- text)
+ text, lang=lang)
msg.send(self)
return 0
self.hold_and_cmd_autoresponses[sender] = (today, count+1)
@@ -1370,4 +1467,6 @@ bad regexp in bounce_matching_header line: %s
# preferred language.
if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs:
langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE)
- return langs
+ # When testing, it's possible we've disabled a language, so just
+ # filter things out so we don't get tracebacks.
+ return [lang for lang in langs if mm_cfg.LC_DESCRIPTIONS.has_key(lang)]