Update of /cvsroot/mailman/mailman/Mailman
In directory sc8-pr-cvs1:/tmp/cvs-serv24139/Mailman
Modified Files:
Tag: Release_2_1-maint
Defaults.py.in Deliverer.py Errors.py ListAdmin.py LockFile.py
MailList.py MemberAdaptor.py Message.py OldStyleMemberships.py
Pending.py Utils.py i18n.py
Log Message:
Backporting from trunk
Index: Defaults.py.in
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Defaults.py.in,v
retrieving revision 2.112.2.1
retrieving revision 2.112.2.2
diff -u -d -r2.112.2.1 -r2.112.2.2
--- Defaults.py.in 8 Feb 2003 07:13:48 -0000 2.112.2.1
+++ Defaults.py.in 31 Mar 2003 21:49:32 -0000 2.112.2.2
@@ -507,13 +507,13 @@
# Mutually exclusive with SMTP_LOG_REFUSED.
SMTP_LOG_SUCCESS = (
'post',
- 'post to %(listname)s from %(sender)s, size=%(size)d, success')
+ 'post to %(listname)s from %(sender)s, size=%(size)d,
message-id=%(msg_message-id)s, success')
# This will only be printed if there were any addresses which encountered an
# immediate smtp failure. Mutually exclusive with SMTP_LOG_SUCCESS.
SMTP_LOG_REFUSED = (
'post',
- 'post to %(listname)s from %(sender)s, size=%(size)d, %(#refused)d failures')
+ 'post to %(listname)s from %(sender)s, size=%(size)d,
message-id=%(msg_message-id)s, %(#refused)d failures')
# This will be logged for each specific recipient failure. Additional %()s
# keys are:
@@ -1032,6 +1032,9 @@
# will be dequeued and those recipients will never receive the message.
DELIVERY_RETRY_PERIOD = days(5)
+# How long should we wait before we retry a temporary delivery failure?
+DELIVERY_RETRY_WAIT = hours(1)
+
#####
@@ -1061,6 +1064,25 @@
# lock by the incoming qrunner process. If the lock acquisition times out,
# the message will be re-queued for later delivery.
LIST_LOCK_TIMEOUT = seconds(10)
+
+# Set this to true to turn on lock debugging messages for the pending requests
+# database, which will be written to logs/locks. If you think you're having
+# lock problems, or just want to tune the locks for your system, turn on lock
+# debugging.
+PENDINGDB_LOCK_DEBUGGING = 0
+
+# This variable specifies how long an attempt will be made to acquire a
+# pendingdb lock by the incoming qrunner process. If the lock acquisition
+# times out, the message will be re-queued for later delivery.
+PENDINGDB_LOCK_TIMEOUT = seconds(30)
+
+# The pendingdb is shared among all lists, and handles all list
+# (un)subscriptions, admin approvals and otherwise held messages, so it is
+# potentially locked a lot more often than single lists. Mailman deals with
+# this by re-trying any attempts to alter the pendingdb that failed because of
+# locking errors. This variable indicates how many attempt should be made
+# before abandoning all hope.
+PENDINGDB_LOCK_ATTEMPTS = 10
Index: Deliverer.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Deliverer.py,v
retrieving revision 2.22
retrieving revision 2.22.2.1
diff -u -d -r2.22 -r2.22.2.1
--- Deliverer.py 31 Dec 2002 03:13:51 -0000 2.22
+++ Deliverer.py 31 Mar 2003 21:49:35 -0000 2.22.2.1
@@ -1,4 +1,4 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2003 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
@@ -24,9 +24,17 @@
from Mailman import Errors
from Mailman import Utils
from Mailman import Message
-from Mailman.i18n import _
+from Mailman import i18n
from Mailman.Logging.Syslog import syslog
+_ = i18n._
+
+try:
+ True, False
+except NameError:
+ True = 1
+ False = 0
+
class Deliverer:
@@ -54,9 +62,10 @@
'welcome' : welcome,
'umbrella' : umbrella,
'emailaddr' : self.GetListEmail(),
- 'listinfo_url': self.GetScriptURL('listinfo', absolute=1),
- 'optionsurl' : self.GetOptionsURL(name, absolute=1),
+ 'listinfo_url': self.GetScriptURL('listinfo', absolute=True),
+ 'optionsurl' : self.GetOptionsURL(name, absolute=True),
'password' : password,
+ 'user' : self.getMemberCPAddress(name),
}, lang=pluser, mlist=self)
if digest:
digmode = _(' (Digest mode)')
@@ -109,7 +118,7 @@
'listname' : self.real_name,
'fqdn_lname' : self.GetListEmail(),
'password' : self.getMemberPassword(user),
- 'options_url': self.GetOptionsURL(user, absolute=1),
+ 'options_url': self.GetOptionsURL(user, absolute=True),
'requestaddr': requestaddr,
'owneraddr' : self.GetOwnerEmail(),
}, lang=self.getMemberLanguage(user), mlist=self)
@@ -118,7 +127,7 @@
msg['X-No-Archive'] = 'yes'
msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES)
- def ForwardMessage(self, msg, text=None, subject=None, tomoderators=1):
+ def ForwardMessage(self, msg, text=None, subject=None, tomoderators=True):
# Wrap the message as an attachment
if text is None:
text = _('No reason given')
@@ -134,3 +143,41 @@
notice.attach(text)
notice.attach(attachment)
notice.send(self)
+
+ def SendHostileSubscriptionNotice(self, listname, address):
+ # Some one was invited to one list but tried to confirm to a different
+ # list. We inform both list owners of the bogosity, but be careful
+ # not to reveal too much information.
+ selfname = self.internal_name()
+ syslog('mischief', '%s was invited to %s but confirmed to %s',
+ address, listname, selfname)
+ # First send a notice to the attacked list
+ msg = Message.OwnerNotification(
+ self,
+ _('Hostile subscription attempt detected'),
+ Utils.wrap(_("""%(address)s was invited to a different mailing
+list, but in a deliberate malicious attempt they tried to confirm the
+invitation to your list. We just thought you'd like to know. No further
+action by you is required.""")))
+ msg.send(self)
+ # Now send a notice to the invitee list
+ try:
+ # Avoid import loops
+ from Mailman.MailList import MailList
+ mlist = MailList(listname, lock=False)
+ except Errors.MMListError:
+ # Oh well
+ return
+ otrans = i18n.get_translation()
+ i18n.set_language(mlist.preferred_language)
+ try:
+ msg = Message.OwnerNotification(
+ mlist,
+ _('Hostile subscription attempt detected'),
+ Utils.wrap(_("""You invited %(address)s to your list, but in a
+deliberate malicious attempt, they tried to confirm the invitation to a
+different list. We just thought you'd like to know. No further action by you
+is required.""")))
+ msg.send(mlist)
+ finally:
+ i18n.set_translation(otrans)
Index: Errors.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Errors.py,v
retrieving revision 2.10
retrieving revision 2.10.2.1
diff -u -d -r2.10 -r2.10.2.1
--- Errors.py 2 Dec 2002 13:59:45 -0000 2.10
+++ Errors.py 31 Mar 2003 21:49:35 -0000 2.10.2.1
@@ -1,17 +1,17 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2003 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
+# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
@@ -145,3 +145,10 @@
def notice(self):
return self.__notice
+
+
+# Additional exceptions
+class HostileSubscriptionError(MailmanError):
+ """A cross-subscription attempt was made."""
+ # This exception gets raised when an invitee attempts to use the
+ # invitation to cross-subscribe to some other mailing list.
Index: ListAdmin.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/ListAdmin.py,v
retrieving revision 2.41.2.1
retrieving revision 2.41.2.2
diff -u -d -r2.41.2.1 -r2.41.2.2
--- ListAdmin.py 8 Feb 2003 07:13:48 -0000 2.41.2.1
+++ ListAdmin.py 31 Mar 2003 21:49:35 -0000 2.41.2.2
@@ -205,7 +205,9 @@
self.__opendb()
# get the next unique id
id = self.__request_id()
- assert not self.__db.has_key(id)
+ while self.__db.has_key(id):
+ # Shouldn't happen unless the db has gone odd, but let's cope.
+ id = self.__request_id()
# get the message sender
sender = msg.get_sender()
# calculate the file name for the message text and write it to disk
Index: LockFile.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/LockFile.py,v
retrieving revision 2.6
retrieving revision 2.6.2.1
diff -u -d -r2.6 -r2.6.2.1
--- LockFile.py 19 Oct 2002 20:58:14 -0000 2.6
+++ LockFile.py 31 Mar 2003 21:49:36 -0000 2.6.2.1
@@ -1,17 +1,17 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2003 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
+# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Portable, NFS-safe file locking with timeouts.
@@ -271,14 +271,15 @@
elif e.errno <> errno.EEXIST:
# Something very bizarre happened. Clean up our state and
# pass the error on up.
- self.__writelog('unexpected link error: %s' % e)
+ self.__writelog('unexpected link error: %s' % e,
+ important=1)
os.unlink(self.__tmpfname)
raise
elif self.__linkcount() <> 2:
# Somebody's messin' with us! Log this, and try again
# later. TBD: should we raise an exception?
self.__writelog('unexpected linkcount: %d' %
- self.__linkcount())
+ self.__linkcount(), important=1)
elif self.__read() == self.__tmpfname:
# It was us that already had the link.
self.__writelog('already locked')
@@ -296,7 +297,8 @@
if time.time() > self.__releasetime() + CLOCK_SLOP:
# Yes, so break the lock.
self.__break()
- self.__writelog('lifetime has expired, breaking')
+ self.__writelog('lifetime has expired, breaking',
+ important=1)
# Okay, someone else has the lock, our claim hasn't timed out yet,
# and the expected lock lifetime hasn't expired yet. So let's
# wait a while for the owner of the lock to give it up.
@@ -402,8 +404,8 @@
# Private interface
#
- def __writelog(self, msg):
- if self.__withlogging:
+ def __writelog(self, msg, important=0):
+ if self.__withlogging or important:
logf = _get_logfile()
logf.write('%s %s\n' % (self.__logprefix, msg))
traceback.print_stack(file=logf)
@@ -560,7 +562,7 @@
except KeyboardInterrupt:
pass
os._exit(0)
-
+
def _reap(kids):
if not kids:
Index: MailList.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/MailList.py,v
retrieving revision 2.100.2.1
retrieving revision 2.100.2.2
diff -u -d -r2.100.2.1 -r2.100.2.2
--- MailList.py 2 Jan 2003 05:28:48 -0000 2.100.2.1
+++ MailList.py 31 Mar 2003 21:49:36 -0000 2.100.2.2
@@ -66,11 +66,19 @@
from Mailman import Message
from Mailman import Pending
from Mailman import Site
-from Mailman.i18n import _
+from Mailman import i18n
from Mailman.Logging.Syslog import syslog
+_ = i18n._
+
EMPTYSTRING = ''
+try:
+ True, False
+except NameError:
+ True = 1
+ False = 0
+
# Use mixins here just to avoid having any one chunk be too large.
@@ -93,25 +101,27 @@
self.InitTempVars(name)
# Default membership adaptor class
self._memberadaptor = OldStyleMemberships(self)
- if name:
- if lock:
- # This will load the database.
- self.Lock()
- else:
- self.Load()
- # This extension mechanism allows list-specific overrides of any
- # method (well, except __init__(), InitTempVars(), and InitVars()
- # I think).
- filename = os.path.join(self.fullpath(), 'extend.py')
- dict = {}
- try:
- execfile(filename, dict)
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- else:
- func = dict.get('extend')
- if func:
- func(self)
+ # This extension mechanism allows list-specific overrides of any
+ # method (well, except __init__(), InitTempVars(), and InitVars()
+ # I think). Note that fullpath() will return None when we're creating
+ # the list, which will only happen when name is None.
+ if name is None:
+ return
+ filename = os.path.join(self.fullpath(), 'extend.py')
+ dict = {}
+ try:
+ execfile(filename, dict)
+ except IOError, e:
+ if e.errno <> errno.ENOENT: raise
+ else:
+ func = dict.get('extend')
+ if func:
+ func(self)
+ if lock:
+ # This will load the database.
+ self.Lock()
+ else:
+ self.Load()
def __getattr__(self, name):
# Because we're using delegation, we want to be sure that attribute
@@ -677,8 +687,9 @@
# 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
- userdesc.invitation = 1
+ # 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)
confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
cookie)
@@ -890,17 +901,25 @@
self.SendSubscribeAck(email, self.getMemberPassword(email),
digest, text)
if admin_notif:
- realname = self.real_name
- subject = _('%(realname)s subscription notification')
+ lang = self.preferred_language
+ otrans = i18n.get_translation()
+ i18n.set_language(lang)
+ try:
+ realname = self.real_name
+ subject = _('%(realname)s subscription notification')
+ finally:
+ i18n.set_translation(otrans)
+ if isinstance(name, UnicodeType):
+ name = name.encode(Utils.GetCharSet(lang), 'replace')
text = Utils.maketext(
"adminsubscribeack.txt",
- {"listname" : self.real_name,
+ {"listname" : realname,
"member" : formataddr((name, email)),
}, mlist=self)
msg = Message.OwnerNotification(self, subject, text)
msg.send(self)
- def DeleteMember(self, name, whence=None, admin_notif=0, userack=1):
+ def DeleteMember(self, name, whence=None, admin_notif=None, userack=True):
realname, email = parseaddr(name)
if self.unsubscribe_policy == 0:
self.ApprovedDeleteMember(name, whence, admin_notif, userack)
@@ -1059,11 +1078,18 @@
except ValueError:
raise Errors.MMBadConfirmation, 'bad subscr data %s' % (data,)
# Hack alert! Was this a confirmation of an invitation?
- invitation = getattr(userdesc, 'invitation', 0)
+ invitation = getattr(userdesc, 'invitation', False)
# We check for both 2 (approval required) and 3 (confirm +
# approval) because the policy could have been changed in the
# middle of the confirmation dance.
- if not invitation and self.subscribe_policy in (2, 3):
+ if invitation:
+ if invitation <> self.internal_name():
+ # Not cool. The invitee was trying to subscribe to a
+ # different list than they were invited to. Alert both
+ # list administrators.
+ self.SendHostileSubscriptionNotice(invitation, addr)
+ raise Errors.HostileSubscriptionError
+ elif self.subscribe_policy in (2, 3):
self.HoldSubscription(addr, fullname, password, digest, lang)
name = self.real_name
raise Errors.MMNeedApproval, _(
Index: MemberAdaptor.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/MemberAdaptor.py,v
retrieving revision 2.9
retrieving revision 2.9.2.1
diff -u -d -r2.9 -r2.9.2.1
--- MemberAdaptor.py 17 Sep 2002 21:38:42 -0000 2.9
+++ MemberAdaptor.py 31 Mar 2003 21:49:38 -0000 2.9.2.1
@@ -1,4 +1,4 @@
-# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2003 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
@@ -42,8 +42,8 @@
backend's responsibility. Adaptors are allowed to support parts of the
writeable interface.
-For any writeable method not supported, a NotImplemented exception should be
-raised.
+For any writeable method not supported, a NotImplementedError exception should
+be raised.
"""
# Delivery statuses
@@ -61,15 +61,15 @@
#
def getMembers(self):
"""Get the LCE for all the members of the mailing list."""
- raise NotImplemented
+ raise NotImplementedError
def getRegularMemberKeys(self):
"""Get the LCE for all regular delivery members (i.e. non-digest)."""
- raise NotImplemented
+ raise NotImplementedError
def getDigestMemberKeys(self):
"""Get the LCE for all digest delivery members."""
- raise NotImplemented
+ raise NotImplementedError
def isMember(self, member):
"""Return 1 if member KEY/LCE is a valid member, otherwise 0."""
@@ -79,14 +79,14 @@
If member does not refer to a valid member, raise NotAMemberError.
"""
- raise NotImplemented
+ raise NotImplementedError
def getMemberCPAddress(self, member):
"""Return the CPE for the member KEY/LCE.
If member does not refer to a valid member, raise NotAMemberError.
"""
- raise NotImplemented
+ raise NotImplementedError
def getMemberCPAddresses(self, members):
"""Return a sequence of CPEs for the given sequence of members.
@@ -96,7 +96,7 @@
in the returned sequence will be None (i.e. NotAMemberError is never
raised).
"""
- raise NotImplemented
+ raise NotImplementedError
def authenticateMember(self, member, response):
"""Authenticate the member KEY/LCE with the given response.
@@ -106,14 +106,14 @@
password, but it will be used to craft a session cookie, so it should
be persistent for the life of the session.
- If the authentication failed return 0. If member did not refer to a
- valid member, raise NotAMemberError.
+ If the authentication failed return False. If member did not refer to
+ a valid member, raise NotAMemberError.
Normally, the response will be the password typed into a web form or
given in an email command, but it needn't be. It is up to the adaptor
to compare the typed response to the user's authentication token.
"""
- raise NotImplemented
+ raise NotImplementedError
def getMemberPassword(self, member):
"""Return the member's password.
@@ -121,7 +121,7 @@
If the member KEY/LCE is not a member of the list, raise
NotAMemberError.
"""
- raise NotImplemented
+ raise NotImplementedError
def getMemberLanguage(self, member):
"""Return the preferred language for the member KEY/LCE.
@@ -132,7 +132,7 @@
If member does not refer to a valid member, the list's default
language is returned instead of raising a NotAMemberError error.
"""
- raise NotImplemented
+ raise NotImplementedError
def getMemberOption(self, member, flag):
"""Return the boolean state of the member option for member KEY/LCE.
@@ -141,7 +141,7 @@
If member does not refer to a valid member, raise NotAMemberError.
"""
- raise NotImplemented
+ raise NotImplementedError
def getMemberName(self, member):
"""Return the full name of the member KEY/LCE.
@@ -151,14 +151,14 @@
characters in the name. NotAMemberError is raised if member does not
refer to a valid member.
"""
- raise NotImplemented
+ raise NotImplementedError
def getMemberTopics(self, member):
"""Return the list of topics this member is interested in.
The return value is a list of strings which name the topics.
"""
- raise NotImplemented
+ raise NotImplementedError
def getDeliveryStatus(self, member):
"""Return the delivery status of this member.
@@ -176,7 +176,7 @@
If member is not a member of the list, raise NotAMemberError.
"""
- raise NotImplemented
+ raise NotImplementedError
def getDeliveryStatusChangeTime(self, member):
"""Return the time of the last disabled delivery status change.
@@ -185,7 +185,7 @@
be zero. If member is not a member of the list, raise
NotAMemberError.
"""
- raise NotImplemented
+ raise NotImplementedError
def getDeliveryStatusMembers(self,
status=(UNKNOWN, BYUSER, BYADMIN, BYBOUNCE)):
@@ -195,7 +195,7 @@
of ENABLED, UNKNOWN, BYUSER, BYADMIN, or BYBOUNCE. The members whose
delivery status is in this sequence are returned.
"""
- raise NotImplemented
+ raise NotImplementedError
def getBouncingMembers(self):
"""Return the list of members who have outstanding bounce information.
@@ -204,7 +204,7 @@
getDeliveryStatusMembers() since getBouncingMembers() will return
member who have bounced but not yet reached the disable threshold.
"""
- raise NotImplemented
+ raise NotImplementedError
def getBounceInfo(self, member):
"""Return the member's bounce information.
@@ -217,7 +217,7 @@
If member is not a member of the list, raise NotAMemberError.
"""
- raise NotImplemented
+ raise NotImplementedError
#
@@ -245,14 +245,14 @@
Raise AlreadyAMemberError it the member is already subscribed to the
list. Raises ValueError if **kws contains an invalid option.
"""
- raise NotImplemented
+ raise NotImplementedError
def removeMember(self, memberkey):
"""Unsubscribes the member from the mailing list.
Raise NotAMemberError if member is not subscribed to the list.
"""
- raise NotImplemented
+ raise NotImplementedError
def changeMemberAddress(self, memberkey, newaddress, nodelete=0):
"""Change the address for the member KEY.
@@ -267,7 +267,7 @@
If nodelete flag is true, then the old membership is not removed.
"""
- raise NotImplemented
+ raise NotImplementedError
def setMemberPassword(self, member, password):
"""Set the password for member LCE/KEY.
@@ -275,9 +275,8 @@
If member does not refer to a valid member, raise NotAMemberError.
Also raise BadPasswordError if the password is illegal (e.g. too
short or easily guessed via a dictionary attack).
-
"""
- raise NotImplemented
+ raise NotImplementedError
def setMemberLanguage(self, member, language):
"""Set the language for the member LCE/KEY.
@@ -286,7 +285,7 @@
Also raise BadLanguageError if the language is invalid (e.g. the list
is not configured to support the given language).
"""
- raise NotImplemented
+ raise NotImplementedError
def setMemberOption(self, member, flag, value):
"""Set the option for the given member to value.
@@ -298,7 +297,7 @@
Also raise BadOptionError if the flag does not refer to a valid
option.
"""
- raise NotImplemented
+ raise NotImplementedError
def setMemberName(self, member, realname):
"""Set the member's full name.
@@ -307,7 +306,7 @@
be a Unicode string if there are non-ASCII characters in the name.
NotAMemberError is raised if member does not refer to a valid member.
"""
- raise NotImplemented
+ raise NotImplementedError
def setMemberTopics(self, member, topics):
"""Add list of topics to member's interest.
@@ -316,7 +315,7 @@
NotAMemberError is raised if member does not refer to a valid member.
topics must be a sequence of strings.
"""
- raise NotImplemented
+ raise NotImplementedError
def setDeliveryStatus(self, member, status):
"""Set the delivery status of the member's address.
@@ -337,7 +336,7 @@
ENABLED, then the change time information will be deleted. This value
is retrievable via getDeliveryStatusChangeTime().
"""
- raise NotImplemented
+ raise NotImplementedError
def setBounceInfo(self, member, info):
"""Set the member's bounce information.
@@ -347,4 +346,4 @@
Bounce info is opaque to the MemberAdaptor. It is set by this method
and returned by getBounceInfo() without modification.
"""
- raise NotImplemented
+ raise NotImplementedError
Index: Message.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Message.py,v
retrieving revision 2.25
retrieving revision 2.25.2.1
diff -u -d -r2.25 -r2.25.2.1
--- Message.py 23 Oct 2002 13:38:59 -0000 2.25
+++ Message.py 31 Mar 2003 21:49:38 -0000 2.25.2.1
@@ -1,17 +1,17 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2003 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
+# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Standard Mailman message object.
@@ -20,6 +20,7 @@
which is more convenient for use inside Mailman.
"""
+import re
import email
import email.Message
import email.Utils
@@ -33,7 +34,8 @@
COMMASPACE = ', '
-VERSION = tuple([int(s) for s in email.__version__.split('.')])
+mo = re.match(r'([\d.]+)', email.__version__)
+VERSION = tuple([int(s) for s in mo.group().split('.')])
@@ -200,7 +202,8 @@
self.set_payload(text, charset)
if subject is None:
subject = '(no subject)'
- self['Subject'] = Header(subject, charset, header_name='Subject')
+ self['Subject'] = Header(subject, charset, header_name='Subject',
+ errors='replace')
self['From'] = sender
if isinstance(recip, ListType):
self['To'] = COMMASPACE.join(recip)
Index: OldStyleMemberships.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/OldStyleMemberships.py,v
retrieving revision 2.13.2.1
retrieving revision 2.13.2.2
diff -u -d -r2.13.2.1 -r2.13.2.2
--- OldStyleMemberships.py 2 Jan 2003 05:34:36 -0000 2.13.2.1
+++ OldStyleMemberships.py 31 Mar 2003 21:49:38 -0000 2.13.2.2
@@ -113,8 +113,11 @@
raise Errors.NotAMemberError, member
def getMemberLanguage(self, member):
- return self.__mlist.language.get(member.lower(),
- self.__mlist.preferred_language)
+ lang = self.__mlist.language.get(
+ member.lower(), self.__mlist.preferred_language)
+ if lang in self.__mlist.GetAvailableLanguages():
+ return lang
+ return self.__mlist.preferred_language
def getMemberOption(self, member, flag):
self.__assertIsMember(member)
@@ -168,7 +171,7 @@
def addNewMember(self, member, **kws):
assert self.__mlist.Locked()
# Make sure this address isn't already a member
- if self.__mlist.isMember(member):
+ if self.isMember(member):
raise Errors.MMAlreadyAMember, member
# Parse the keywords
digest = 0
@@ -335,8 +338,8 @@
self.__assertIsMember(member)
member = member.lower()
if status == MemberAdaptor.ENABLED:
+ # Enable by resetting their bounce info.
self.setBounceInfo(member, None)
- # Otherwise, nothing to do
else:
self.__mlist.delivery_status[member] = (status, time.time())
Index: Pending.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Pending.py,v
retrieving revision 2.9
retrieving revision 2.9.2.1
diff -u -d -r2.9 -r2.9.2.1
--- Pending.py 14 Mar 2002 04:54:54 -0000 2.9
+++ Pending.py 31 Mar 2003 21:49:39 -0000 2.9.2.1
@@ -1,17 +1,17 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2003 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
+# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
""" Track pending confirmation of subscriptions.
@@ -56,29 +56,48 @@
# It's a programming error if this assertion fails! We do it this way so
# the assert test won't fail if the sequence is empty.
assert content[:1] in _ALLKEYS
- # Acquire the pending database lock, letting TimeOutError percolate up.
- lock = LockFile.LockFile(LOCKFILE)
- lock.lock(timeout=30)
+
+ # Get a lock handle now, but only lock inside the loop.
+ lock = LockFile.LockFile(LOCKFILE,
+ withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
+ # We try the main loop several times. If we get a lock error somewhere
+ # (for instance because someone broke the lock) we simply try again.
+ retries = mm_cfg.PENDINGDB_LOCK_ATTEMPTS
try:
- # Load the current database
- db = _load()
- # Calculate a unique cookie
- while 1:
- n = random.random()
- now = time.time()
- hashfood = str(now) + str(n) + str(content)
- cookie = sha.new(hashfood).hexdigest()
- if not db.has_key(cookie):
- break
- # Store the content, plus the time in the future when this entry will
- # be evicted from the database, due to staleness.
- db[cookie] = content
- evictions = db.setdefault('evictions', {})
- evictions[cookie] = now + mm_cfg.PENDING_REQUEST_LIFE
- _save(db)
- return cookie
+ while retries:
+ retries -= 1
+ if not lock.locked():
+ try:
+ lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
+ except LockFile.TimeOutError:
+ continue
+ # Load the current database
+ db = _load()
+ # Calculate a unique cookie
+ while 1:
+ n = random.random()
+ now = time.time()
+ hashfood = str(now) + str(n) + str(content)
+ cookie = sha.new(hashfood).hexdigest()
+ if not db.has_key(cookie):
+ break
+ # Store the content, plus the time in the future when this entry
+ # will be evicted from the database, due to staleness.
+ db[cookie] = content
+ evictions = db.setdefault('evictions', {})
+ evictions[cookie] = now + mm_cfg.PENDING_REQUEST_LIFE
+ try:
+ _save(db, lock)
+ except LockFile.NotLockedError:
+ continue
+ return cookie
+ else:
+ # We failed to get the lock or keep it long enough to save the
+ # data!
+ raise LockFile.TimeOutError
finally:
- lock.unlock()
+ if lock.locked():
+ lock.unlock()
@@ -88,30 +107,53 @@
If optional expunge is true (the default), the record is also removed from
the database.
"""
- # Acquire the pending database lock, letting TimeOutError percolate up.
- # BAW: we perhaps shouldn't acquire the lock if expunge==0.
- lock = LockFile.LockFile(LOCKFILE)
- lock.lock(timeout=30)
- try:
- # Load the database
+ if not expunge:
db = _load()
missing = []
content = db.get(cookie, missing)
if content is missing:
return None
- # Remove the entry from the database
- if expunge:
+ return content
+
+ # Get a lock handle now, but only lock inside the loop.
+ lock = LockFile.LockFile(LOCKFILE,
+ withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
+ # We try the main loop several times. If we get a lock error somewhere
+ # (for instance because someone broke the lock) we simply try again.
+ retries = mm_cfg.PENDINGDB_LOCK_ATTEMPTS
+ try:
+ while retries:
+ retries -= 1
+ if not lock.locked():
+ try:
+ lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
+ except LockFile.TimeOutError:
+ continue
+ # Load the database
+ db = _load()
+ missing = []
+ content = db.get(cookie, missing)
+ if content is missing:
+ return None
del db[cookie]
del db['evictions'][cookie]
- _save(db)
- return content
+ try:
+ _save(db, lock)
+ except LockFile.NotLockedError:
+ continue
+ return content
+ else:
+ # We failed to get the lock and keep it long enough to save the
+ # data!
+ raise LockFile.TimeOutError
finally:
- lock.unlock()
+ if lock.locked():
+ lock.unlock()
def _load():
- # The list's lock must be acquired.
+ # The list's lock must be acquired if you wish to alter data and save.
#
# First try to load the pickle file
fp = None
@@ -134,8 +176,10 @@
fp.close()
-def _save(db):
- # Lock must be acquired.
+def _save(db, lock):
+ # Lock must be acquired before loading the data that is now being saved.
+ if not lock.locked():
+ raise LockFile.NotLockedError
evictions = db['evictions']
now = time.time()
for cookie, data in db.items():
@@ -154,13 +198,17 @@
omask = os.umask(007)
# Always save this as a pickle (safely), and after that succeeds, blow
# away any old marshal file.
- tmpfile = PCKFILE + '.tmp'
+ tmpfile = '%s.tmp.%d.%d' % (PCKFILE, os.getpid(), now)
fp = None
try:
fp = open(tmpfile, 'w')
cPickle.dump(db, fp)
fp.close()
fp = None
+ if not lock.locked():
+ # Our lock was broken?
+ os.remove(tmpfile)
+ raise LockFile.NotLockedError
os.rename(tmpfile, PCKFILE)
if os.path.exists(DBFILE):
os.remove(DBFILE)
@@ -173,8 +221,9 @@
def _update(olddb):
# Update an old pending_subscriptions.db database to the new format
- lock = LockFile.LockFile(LOCKFILE)
- lock.lock(timeout=30)
+ lock = LockFile.LockFile(LOCKFILE,
+ withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
+ lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
try:
# We don't need this entry anymore
if olddb.has_key('lastculltime'):
@@ -199,6 +248,7 @@
# request was made. The new format keeps it as the time the
# request should be evicted.
evictions[cookie] = data[-1] + mm_cfg.PENDING_REQUEST_LIFE
- _save(db)
+ _save(db, lock)
finally:
- lock.unlock()
+ if lock.locked():
+ lock.unlock()
Index: Utils.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Utils.py,v
retrieving revision 2.45.2.1
retrieving revision 2.45.2.2
diff -u -d -r2.45.2.1 -r2.45.2.2
--- Utils.py 8 Feb 2003 07:13:49 -0000 2.45.2.1
+++ Utils.py 31 Mar 2003 21:49:39 -0000 2.45.2.2
@@ -192,7 +192,7 @@
# TBD: what other characters should be disallowed?
-_badchars = re.compile('[][()<>|;^,/]')
+_badchars = re.compile(r'[][()<>|;^,/\200-\377]')
def ValidateEmail(s):
"""Verify that the an email address isn't grossly evil."""
Index: i18n.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/i18n.py,v
retrieving revision 2.10
retrieving revision 2.10.2.1
diff -u -d -r2.10 -r2.10.2.1
--- i18n.py 9 Dec 2002 04:26:11 -0000 2.10
+++ i18n.py 31 Mar 2003 21:49:39 -0000 2.10.2.1
@@ -1,4 +1,4 @@
-# Copyright (C) 2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 2000-2003 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
@@ -17,7 +17,7 @@
import sys
import time
import gettext
-from types import StringType
+from types import StringType, UnicodeType
from Mailman import mm_cfg
from Mailman.SafeDict import SafeDict
@@ -74,8 +74,19 @@
# missing key in the dictionary.
dict = SafeDict(frame.f_globals.copy())
dict.update(frame.f_locals)
- # Translate the string, then interpolate into it.
- return _translation.gettext(s) % dict
+ # Translating the string returns an encoded 8-bit string. Rather than
+ # turn that into a Unicode, we turn any Unicodes in the dictionary values
+ # into encoded 8-bit strings. BAW: Returning a Unicode here broke too
+ # much other stuff and _() has many tentacles. Eventually I think we want
+ # to use Unicode everywhere.
+ tns = _translation.gettext(s)
+ charset = _translation.charset()
+ if not charset:
+ charset = 'us-ascii'
+ for k, v in dict.items():
+ if isinstance(v, UnicodeType):
+ dict[k] = v.encode(charset, 'replace')
+ return tns % dict
_______________________________________________
Mailman-checkins mailing list
[EMAIL PROTECTED]
http://mail.python.org/mailman/listinfo/mailman-checkins