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

Reply via email to