Package: mailman Version: 2.1.13-2 Severity: wishlist Tags: sid * Add forum archiver patch that allow multiple archivers * Session : - Modify SecurityManager.py to make it extendable - Add OldSecurityManager.py as the default security manager * MailList.py : modified - Search extend.py first in /var/lib/lists - Make it possible to extend SecurityManager.py * Add dist-packages (python library directory) to ./misc/paths.py.in
-- System Information: Debian Release: squeeze/sid APT prefers oldstable APT policy: (500, 'oldstable'), (500, 'unstable'), (500, 'testing'), (500, 'stable'), (1, 'experimental') Architecture: i386 (i686) Kernel: Linux 2.6.32-trunk-686 (SMP w/2 CPU cores) Locale: LANG=fr_FR.UTF-8, LC_CTYPE=fr_FR.UTF-8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/bash Versions of packages mailman depends on: ii adduser 3.112 add and remove users and groups ii apache2 2.2.15-2 Apache HTTP Server metapackage ii apache2-mpm-prefork [httpd] 2.2.15-2 Apache HTTP Server - traditional n ii cron 3.0pl1-107 process scheduling daemon ii debconf [debconf-2.0] 1.5.30 Debian configuration management sy ii exim4 4.71-3 metapackage to ease Exim MTA (v4) ii exim4-daemon-light [mail-tran 4.71-3 lightweight Exim MTA (v4) daemon ii libc6 2.10.2-6 Embedded GNU C Library: Shared lib ii logrotate 3.7.8-5 Log rotation utility ii lsb-base 3.2-23 Linux Standard Base 3.2 init scrip ii pwgen 2.06-1+b1 Automatic Password generation ii python 2.5.4-9 An interactive high-level object-o ii python-support 1.0.7 automated rebuilding support for P ii ucf 3.0025 Update Configuration File: preserv mailman recommends no packages. Versions of packages mailman suggests: pn listadmin <none> (no description available) ii lynx 2.8.8dev.2-1 Text-mode WWW Browser (transitiona ii spamassassin 3.3.1-1 Perl-based spam filter using text
Index: mailman-2.1.13/Mailman/MailList.py =================================================================== --- mailman-2.1.13.orig/Mailman/MailList.py 2009-12-22 19:00:43.000000000 +0100 +++ mailman-2.1.13/Mailman/MailList.py 2010-03-29 11:29:07.712414092 +0200 @@ -1,3 +1,4 @@ + # Copyright (C) 1998-2008 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or @@ -57,6 +58,7 @@ from Mailman.HTMLFormatter import HTMLFormatter from Mailman.ListAdmin import ListAdmin from Mailman.SecurityManager import SecurityManager +from Mailman.OldSecurityManager import OldSecurityManager from Mailman.TopicMgr import TopicMgr from Mailman import Pending @@ -85,7 +87,7 @@ # Use mixins here just to avoid having any one chunk be too large. class MailList(HTMLFormatter, Deliverer, ListAdmin, - Archiver, Digester, SecurityManager, Bouncer, GatewayManager, + Archiver, Digester, Bouncer, GatewayManager, Autoresponder, TopicMgr, Pending.Pending): # @@ -103,14 +105,20 @@ self.InitTempVars(name) # Default membership adaptor class self._memberadaptor = OldStyleMemberships(self) + # Default security manager class + self._securitymanager = OldSecurityManager(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 = {} + defaultfile = os.path.join(self.fullpath(), 'extend.py') + if os.path.exists(defaultfile): + filename = defaultfile + else: + filename = os.path.join(self.fullpath().strip(self.internal_name()), 'extend.py') + dict = {} try: execfile(filename, dict) except IOError, e: @@ -120,9 +128,13 @@ else: syslog('error', 'IOError reading list extension: %s', e) else: - func = dict.get('extend') + func = dict.get('extendMemberAdaptor') if func: func(self) + func = dict.get('extendSecurityManager') + if func: + func(self) + if lock: # This will load the database. self.Lock() @@ -136,14 +148,18 @@ # order. try: return getattr(self._memberadaptor, name) + getattr(self._securitymanager, name) except AttributeError: - for guicomponent in self._gui: - try: - return getattr(guicomponent, name) - except AttributeError: - pass - else: - raise AttributeError, name + try: + return getattr(self._securitymanager, name) + except AttributeError: + for guicomponent in self._gui: + try: + return getattr(guicomponent, name) + except AttributeError: + pass + else: + raise AttributeError, name def __repr__(self): if self.Locked(): Index: mailman-2.1.13/Mailman/OldSecurityManager.py =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ mailman-2.1.13/Mailman/OldSecurityManager.py 2010-03-29 11:30:00.088409004 +0200 @@ -0,0 +1,364 @@ +# Copyright (C) 1998-2008 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + + +"""Handle passwords and sanitize approved messages.""" + +# There are current 5 roles defined in Mailman, as codified in Defaults.py: +# user, list-creator, list-moderator, list-admin, site-admin. +# +# Here's how we do cookie based authentication. +# +# Each role (see above) has an associated password, which is currently the +# only way to authenticate a role (in the future, we'll authenticate a +# user and assign users to roles). +# +# Each cookie has the following ingredients: the authorization context's +# secret (i.e. the password, and a timestamp. We generate an SHA1 hex +# digest of these ingredients, which we call the `mac'. We then marshal +# up a tuple of the timestamp and the mac, hexlify that and return that as +# a cookie keyed off the authcontext. Note that authenticating the user +# also requires the user's email address to be included in the cookie. +# +# The verification process is done in CheckCookie() below. It extracts +# the cookie, unhexlifies and unmarshals the tuple, extracting the +# timestamp. Using this, and the shared secret, the mac is calculated, +# and it must match the mac passed in the cookie. If so, they're golden, +# otherwise, access is denied. +# +# It is still possible for an adversary to attempt to brute force crack +# the password if they obtain the cookie, since they can extract the +# timestamp and create macs based on password guesses. They never get a +# cleartext version of the password though, so security rests on the +# difficulty and expense of retrying the cgi dialog for each attempt. It +# also relies on the security of SHA1. + +import os +import re +import time +import Cookie +import marshal +import binascii +import urllib +from types import StringType, TupleType +from urlparse import urlparse + +try: + import crypt +except ImportError: + crypt = None + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Errors +from Mailman.Logging.Syslog import syslog +from Mailman.Utils import md5_new, sha_new +from Mailman import SecurityManager + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +class OldSecurityManager(SecurityManager.SecurityManager): + def __init__(self,mlist): + self.__mlist = mlist + # We used to set self.password here, from a crypted_password argument, + # but that's been removed when we generalized the mixin architecture. + # self.password is really a SecurityManager attribute, but it's set in + # MailList.InitVars(). + self.mod_password = None + # Non configurable + self.__mlist.passwords = {} + + def AuthContextInfo(self, authcontext, user=None): + # authcontext may be one of AuthUser, AuthListModerator, + # AuthListAdmin, AuthSiteAdmin. Not supported is the AuthCreator + # context. + # + # user is ignored unless authcontext is AuthUser + # + # Return the authcontext's secret and cookie key. If the authcontext + # doesn't exist, return the tuple (None, None). If authcontext is + # AuthUser, but the user isn't a member of this mailing list, a + # NotAMemberError will be raised. If the user's secret is None, raise + # a MMBadUserError. + key = self.__mlist.internal_name() + '+' + if authcontext == mm_cfg.AuthUser: + if user is None: + # A bad system error + raise TypeError, 'No user supplied for AuthUser context' + secret = self.__mlist.getMemberPassword(user) + userdata = urllib.quote(Utils.ObscureEmail(user), safe='') + key += 'user+%s' % userdata + elif authcontext == mm_cfg.AuthListModerator: + secret = self.mod_password + key += 'moderator' + elif authcontext == mm_cfg.AuthListAdmin: + secret = self.__mlist.password + key += 'admin' + # BAW: AuthCreator + elif authcontext == mm_cfg.AuthSiteAdmin: + sitepass = Utils.get_global_password() + if mm_cfg.ALLOW_SITE_ADMIN_COOKIES and sitepass: + secret = sitepass + key = 'site' + else: + # BAW: this should probably hand out a site password based + # cookie, but that makes me a bit nervous, so just treat site + # admin as a list admin since there is currently no site + # admin-only functionality. + secret = self.__mlist.password + key += 'admin' + else: + return None, None + return key, secret + + def Authenticate(self, authcontexts, response, user=None): + # Given a list of authentication contexts, check to see if the + # response matches one of the passwords. authcontexts must be a + # sequence, and if it contains the context AuthUser, then the user + # argument must not be None. + # + # Return the authcontext from the argument sequence that matches the + # response, or UnAuthorized. + if not response: + # Don't authenticate null passwords + return mm_cfg.UnAuthorized + for ac in authcontexts: + if ac == mm_cfg.AuthCreator: + ok = Utils.check_global_password(response, siteadmin=0) + if ok: + return mm_cfg.AuthCreator + elif ac == mm_cfg.AuthSiteAdmin: + ok = Utils.check_global_password(response) + if ok: + return mm_cfg.AuthSiteAdmin + elif ac == mm_cfg.AuthListAdmin: + def cryptmatchp(response, secret): + try: + salt = secret[:2] + if crypt and crypt.crypt(response, salt) == secret: + return True + return False + except TypeError: + # BAW: Hard to say why we can get a TypeError here. + # SF bug report #585776 says crypt.crypt() can raise + # this if salt contains null bytes, although I don't + # know how that can happen (perhaps if a MM2.0 list + # with USE_CRYPT = 0 has been updated? Doubtful. + return False + # The password for the list admin and list moderator are not + # kept as plain text, but instead as an sha hexdigest. The + # response being passed in is plain text, so we need to + # digestify it first. Note however, that for backwards + # compatibility reasons, we'll also check the admin response + # against the crypted and md5'd passwords, and if they match, + # we'll auto-migrate the passwords to sha. + key, secret = self.AuthContextInfo(ac) + if secret is None: + continue + sharesponse = sha_new(response).hexdigest() + upgrade = ok = False + if sharesponse == secret: + ok = True + elif md5_new(response).digest() == secret: + ok = upgrade = True + elif cryptmatchp(response, secret): + ok = upgrade = True + if upgrade: + save_and_unlock = False + if not self.__mlist.Locked(): + self.__mlist.Lock() + save_and_unlock = True + try: + self._mlist.password = sharesponse + if save_and_unlock: + self.__mlist.Save() + finally: + if save_and_unlock: + self.Unlock() + if ok: + return ac + elif ac == mm_cfg.AuthListModerator: + # The list moderator password must be sha'd + key, secret = self.AuthContextInfo(ac) + if secret and sha_new(response).hexdigest() == secret: + return ac + elif ac == mm_cfg.AuthUser: + if user is not None: + try: + if self.__mlist.authenticateMember(user, response): + return ac + except Errors.NotAMemberError: + pass + else: + # What is this context??? + syslog('error', 'Bad authcontext: %s', ac) + raise ValueError, 'Bad authcontext: %s' % ac + return mm_cfg.UnAuthorized + + def WebAuthenticate(self, authcontexts, response, user=None): + # Given a list of authentication contexts, check to see if the cookie + # contains a matching authorization, falling back to checking whether + # the response matches one of the passwords. authcontexts must be a + # sequence, and if it contains the context AuthUser, then the user + # argument should not be None. + # + # Returns a flag indicating whether authentication succeeded or not. + for ac in authcontexts: + ok = self.CheckCookie(ac,user) + if ok: + return True + # Check passwords + ac = self.Authenticate(authcontexts, response, user) + if ac: + print self.MakeCookie(ac, user) + return True + return False + + def MakeCookie(self, authcontext, user=None): + key, secret = self.AuthContextInfo(authcontext, user) + if key is None or secret is None or not isinstance(secret, StringType): + raise ValueError + # Timestamp + issued = int(time.time()) + # Get a digest of the secret, plus other information. + mac = sha_new(secret + `issued`).hexdigest() + # Create the cookie object. + c = Cookie.SimpleCookie() + c[key] = binascii.hexlify(marshal.dumps((issued, mac))) + # The path to all Mailman stuff, minus the scheme and host, + # i.e. usually the string `/mailman' + path = urlparse(self.__mlist.web_page_url)[2] + c[key]['path'] = path + # We use session cookies, so don't set `expires' or `max-age' keys. + # Set the RFC 2109 required header. + c[key]['version'] = 1 + return c + + def ZapCookie(self, authcontext, user=None): + # We can throw away the secret. + key, secret = self.AuthContextInfo(authcontext, user) + # Logout of the session by zapping the cookie. For safety both set + # max-age=0 (as per RFC2109) and set the cookie data to the empty + # string. + c = Cookie.SimpleCookie() + c[key] = '' + # The path to all Mailman stuff, minus the scheme and host, + # i.e. usually the string `/mailman' + path = urlparse(self.__mlist.web_page_url)[2] + c[key]['path'] = path + c[key]['max-age'] = 0 + # Don't set expires=0 here otherwise it'll force a persistent cookie + c[key]['version'] = 1 + return c + + def CheckCookie(self, authcontext, user=None): + # Two results can occur: we return 1 meaning the cookie authentication + # succeeded for the authorization context, we return 0 meaning the + # authentication failed. + # + # Dig out the cookie data, which better be passed on this cgi + # environment variable. If there's no cookie data, we reject the + # authentication. + cookiedata = os.environ.get('HTTP_COOKIE') + if not cookiedata: + return False + # We can't use the Cookie module here because it isn't liberal in what + # it accepts. Feed it a MM2.0 cookie along with a MM2.1 cookie and + # you get a CookieError. :(. All we care about is accessing the + # cookie data via getitem, so we'll use our own parser, which returns + # a dictionary. + c = parsecookie(cookiedata) + # If the user was not supplied, but the authcontext is AuthUser, we + # can try to glean the user address from the cookie key. There may be + # more than one matching key (if the user has multiple accounts + # subscribed to this list), but any are okay. + if authcontext == mm_cfg.AuthUser: + if user: + usernames = [user] + else: + usernames = [] + prefix = self.__mlist.internal_name() + '+user+' + for k in c.keys(): + if k.startswith(prefix): + usernames.append(k[len(prefix):]) + # If any check out, we're golden. Note: `@'s are no longer legal + # values in cookie keys. + for user in [Utils.UnobscureEmail(urllib.unquote(u)) + for u in usernames]: + ok = self.__checkone(c, authcontext, user) + if ok: + return True + return False + else: + return self.__checkone(c, authcontext, user) + + def __checkone(self, c, authcontext, user): + # Do the guts of the cookie check, for one authcontext/user + # combination. + try: + key, secret = self.AuthContextInfo(authcontext, user) + except Errors.NotAMemberError: + return False + if not c.has_key(key) or not isinstance(secret, StringType): + return False + # Undo the encoding we performed in MakeCookie() above. BAW: I + # believe this is safe from exploit because marshal can't be forced to + # load recursive data structures, and it can't be forced to execute + # any unexpected code. The worst that can happen is that either the + # client will have provided us bogus data, in which case we'll get one + # of the caught exceptions, or marshal format will have changed, in + # which case, the cookie decoding will fail. In either case, we'll + # simply request reauthorization, resulting in a new cookie being + # returned to the client. + try: + data = marshal.loads(binascii.unhexlify(c[key])) + issued, received_mac = data + except (EOFError, ValueError, TypeError, KeyError): + return False + # Make sure the issued timestamp makes sense + now = time.time() + if now < issued: + return False + # Calculate what the mac ought to be based on the cookie's timestamp + # and the shared secret. + mac = sha_new(secret + `issued`).hexdigest() + if mac <> received_mac: + return False + # Authenticated! + return True + + + +splitter = re.compile(';\s*') + +def parsecookie(s): + c = {} + for line in s.splitlines(): + for p in splitter.split(line): + try: + k, v = p.split('=', 1) + except ValueError: + pass + else: + c[k] = v + return c Index: mailman-2.1.13/Mailman/SecurityManager.py =================================================================== --- mailman-2.1.13.orig/Mailman/SecurityManager.py 2009-12-22 19:00:43.000000000 +0100 +++ mailman-2.1.13/Mailman/SecurityManager.py 2010-03-29 11:29:07.716415355 +0200 @@ -15,348 +15,52 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. +"""This is an interface to list-specific security information. -"""Handle passwords and sanitize approved messages.""" +This class should not be instantiated directly, but instead, it should be +subclassed for specific adaptation to session manager. The default +MM2.0.x style adaptor is in OldSecurityManager.py. Through the extendSM.py +mechanism, you can instantiate different session approval. For instance +accept another cookie than the one created by mailman (in the case of +mailman integration in another web pplication). +""" -# There are current 5 roles defined in Mailman, as codified in Defaults.py: -# user, list-creator, list-moderator, list-admin, site-admin. -# -# Here's how we do cookie based authentication. -# -# Each role (see above) has an associated password, which is currently the -# only way to authenticate a role (in the future, we'll authenticate a -# user and assign users to roles). -# -# Each cookie has the following ingredients: the authorization context's -# secret (i.e. the password, and a timestamp. We generate an SHA1 hex -# digest of these ingredients, which we call the `mac'. We then marshal -# up a tuple of the timestamp and the mac, hexlify that and return that as -# a cookie keyed off the authcontext. Note that authenticating the user -# also requires the user's email address to be included in the cookie. -# -# The verification process is done in CheckCookie() below. It extracts -# the cookie, unhexlifies and unmarshals the tuple, extracting the -# timestamp. Using this, and the shared secret, the mac is calculated, -# and it must match the mac passed in the cookie. If so, they're golden, -# otherwise, access is denied. -# -# It is still possible for an adversary to attempt to brute force crack -# the password if they obtain the cookie, since they can extract the -# timestamp and create macs based on password guesses. They never get a -# cleartext version of the password though, so security rests on the -# difficulty and expense of retrying the cgi dialog for each attempt. It -# also relies on the security of SHA1. - -import os -import re -import time -import Cookie -import marshal -import binascii -import urllib -from types import StringType, TupleType -from urlparse import urlparse - -try: - import crypt -except ImportError: - crypt = None - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import Errors -from Mailman.Logging.Syslog import syslog -from Mailman.Utils import md5_new, sha_new - -try: - True, False -except NameError: - True = 1 - False = 0 - class SecurityManager: def InitVars(self): - # We used to set self.password here, from a crypted_password argument, - # but that's been removed when we generalized the mixin architecture. - # self.password is really a SecurityManager attribute, but it's set in - # MailList.InitVars(). - self.mod_password = None - # Non configurable - self.passwords = {} + """Initialize the list password""" + raise NotImplementedError def AuthContextInfo(self, authcontext, user=None): - # authcontext may be one of AuthUser, AuthListModerator, - # AuthListAdmin, AuthSiteAdmin. Not supported is the AuthCreator - # context. - # - # user is ignored unless authcontext is AuthUser - # - # Return the authcontext's secret and cookie key. If the authcontext - # doesn't exist, return the tuple (None, None). If authcontext is - # AuthUser, but the user isn't a member of this mailing list, a - # NotAMemberError will be raised. If the user's secret is None, raise - # a MMBadUserError. - key = self.internal_name() + '+' - if authcontext == mm_cfg.AuthUser: - if user is None: - # A bad system error - raise TypeError, 'No user supplied for AuthUser context' - secret = self.getMemberPassword(user) - userdata = urllib.quote(Utils.ObscureEmail(user), safe='') - key += 'user+%s' % userdata - elif authcontext == mm_cfg.AuthListModerator: - secret = self.mod_password - key += 'moderator' - elif authcontext == mm_cfg.AuthListAdmin: - secret = self.password - key += 'admin' - # BAW: AuthCreator - elif authcontext == mm_cfg.AuthSiteAdmin: - sitepass = Utils.get_global_password() - if mm_cfg.ALLOW_SITE_ADMIN_COOKIES and sitepass: - secret = sitepass - key = 'site' - else: - # BAW: this should probably hand out a site password based - # cookie, but that makes me a bit nervous, so just treat site - # admin as a list admin since there is currently no site - # admin-only functionality. - secret = self.password - key += 'admin' - else: - return None, None - return key, secret + """Return the authcontext's secret and cookie key""" + raise NotImplementedError def Authenticate(self, authcontexts, response, user=None): - # Given a list of authentication contexts, check to see if the - # response matches one of the passwords. authcontexts must be a - # sequence, and if it contains the context AuthUser, then the user - # argument must not be None. - # - # Return the authcontext from the argument sequence that matches the - # response, or UnAuthorized. - if not response: - # Don't authenticate null passwords - return mm_cfg.UnAuthorized - for ac in authcontexts: - if ac == mm_cfg.AuthCreator: - ok = Utils.check_global_password(response, siteadmin=0) - if ok: - return mm_cfg.AuthCreator - elif ac == mm_cfg.AuthSiteAdmin: - ok = Utils.check_global_password(response) - if ok: - return mm_cfg.AuthSiteAdmin - elif ac == mm_cfg.AuthListAdmin: - def cryptmatchp(response, secret): - try: - salt = secret[:2] - if crypt and crypt.crypt(response, salt) == secret: - return True - return False - except TypeError: - # BAW: Hard to say why we can get a TypeError here. - # SF bug report #585776 says crypt.crypt() can raise - # this if salt contains null bytes, although I don't - # know how that can happen (perhaps if a MM2.0 list - # with USE_CRYPT = 0 has been updated? Doubtful. - return False - # The password for the list admin and list moderator are not - # kept as plain text, but instead as an sha hexdigest. The - # response being passed in is plain text, so we need to - # digestify it first. Note however, that for backwards - # compatibility reasons, we'll also check the admin response - # against the crypted and md5'd passwords, and if they match, - # we'll auto-migrate the passwords to sha. - key, secret = self.AuthContextInfo(ac) - if secret is None: - continue - sharesponse = sha_new(response).hexdigest() - upgrade = ok = False - if sharesponse == secret: - ok = True - elif md5_new(response).digest() == secret: - ok = upgrade = True - elif cryptmatchp(response, secret): - ok = upgrade = True - if upgrade: - save_and_unlock = False - if not self.Locked(): - self.Lock() - save_and_unlock = True - try: - self.password = sharesponse - if save_and_unlock: - self.Save() - finally: - if save_and_unlock: - self.Unlock() - if ok: - return ac - elif ac == mm_cfg.AuthListModerator: - # The list moderator password must be sha'd - key, secret = self.AuthContextInfo(ac) - if secret and sha_new(response).hexdigest() == secret: - return ac - elif ac == mm_cfg.AuthUser: - if user is not None: - try: - if self.authenticateMember(user, response): - return ac - except Errors.NotAMemberError: - pass - else: - # What is this context??? - syslog('error', 'Bad authcontext: %s', ac) - raise ValueError, 'Bad authcontext: %s' % ac - return mm_cfg.UnAuthorized + """Given a list of authentication contexts, check to see if the + response matches one of the passwords.""" + raise NotImplementedError def WebAuthenticate(self, authcontexts, response, user=None): - # Given a list of authentication contexts, check to see if the cookie - # contains a matching authorization, falling back to checking whether - # the response matches one of the passwords. authcontexts must be a - # sequence, and if it contains the context AuthUser, then the user - # argument should not be None. - # - # Returns a flag indicating whether authentication succeeded or not. - for ac in authcontexts: - ok = self.CheckCookie(ac, user) - if ok: - return True - # Check passwords - ac = self.Authenticate(authcontexts, response, user) - if ac: - print self.MakeCookie(ac, user) - return True - return False + """Given a list of authentication contexts, check to see if the cookie + contains a matching authorization, falling back to checking whether + the response matches one of the passwords""" + raise NotImplementedError def MakeCookie(self, authcontext, user=None): - key, secret = self.AuthContextInfo(authcontext, user) - if key is None or secret is None or not isinstance(secret, StringType): - raise ValueError - # Timestamp - issued = int(time.time()) - # Get a digest of the secret, plus other information. - mac = sha_new(secret + `issued`).hexdigest() - # Create the cookie object. - c = Cookie.SimpleCookie() - c[key] = binascii.hexlify(marshal.dumps((issued, mac))) - # The path to all Mailman stuff, minus the scheme and host, - # i.e. usually the string `/mailman' - path = urlparse(self.web_page_url)[2] - c[key]['path'] = path - # We use session cookies, so don't set `expires' or `max-age' keys. - # Set the RFC 2109 required header. - c[key]['version'] = 1 - return c + raise NotImplementedError def ZapCookie(self, authcontext, user=None): - # We can throw away the secret. - key, secret = self.AuthContextInfo(authcontext, user) - # Logout of the session by zapping the cookie. For safety both set - # max-age=0 (as per RFC2109) and set the cookie data to the empty - # string. - c = Cookie.SimpleCookie() - c[key] = '' - # The path to all Mailman stuff, minus the scheme and host, - # i.e. usually the string `/mailman' - path = urlparse(self.web_page_url)[2] - c[key]['path'] = path - c[key]['max-age'] = 0 - # Don't set expires=0 here otherwise it'll force a persistent cookie - c[key]['version'] = 1 - return c + raise NotImplementedError def CheckCookie(self, authcontext, user=None): - # Two results can occur: we return 1 meaning the cookie authentication - # succeeded for the authorization context, we return 0 meaning the - # authentication failed. - # - # Dig out the cookie data, which better be passed on this cgi - # environment variable. If there's no cookie data, we reject the - # authentication. - cookiedata = os.environ.get('HTTP_COOKIE') - if not cookiedata: - return False - # We can't use the Cookie module here because it isn't liberal in what - # it accepts. Feed it a MM2.0 cookie along with a MM2.1 cookie and - # you get a CookieError. :(. All we care about is accessing the - # cookie data via getitem, so we'll use our own parser, which returns - # a dictionary. - c = parsecookie(cookiedata) - # If the user was not supplied, but the authcontext is AuthUser, we - # can try to glean the user address from the cookie key. There may be - # more than one matching key (if the user has multiple accounts - # subscribed to this list), but any are okay. - if authcontext == mm_cfg.AuthUser: - if user: - usernames = [user] - else: - usernames = [] - prefix = self.internal_name() + '+user+' - for k in c.keys(): - if k.startswith(prefix): - usernames.append(k[len(prefix):]) - # If any check out, we're golden. Note: `@'s are no longer legal - # values in cookie keys. - for user in [Utils.UnobscureEmail(urllib.unquote(u)) - for u in usernames]: - ok = self.__checkone(c, authcontext, user) - if ok: - return True - return False - else: - return self.__checkone(c, authcontext, user) + """Two results can occur: we return 1 meaning the cookie authentication + succeeded for the authorization context, we return 0 meaning the + authentication failed.""" + raise NotImplementedError def __checkone(self, c, authcontext, user): - # Do the guts of the cookie check, for one authcontext/user - # combination. - try: - key, secret = self.AuthContextInfo(authcontext, user) - except Errors.NotAMemberError: - return False - if not c.has_key(key) or not isinstance(secret, StringType): - return False - # Undo the encoding we performed in MakeCookie() above. BAW: I - # believe this is safe from exploit because marshal can't be forced to - # load recursive data structures, and it can't be forced to execute - # any unexpected code. The worst that can happen is that either the - # client will have provided us bogus data, in which case we'll get one - # of the caught exceptions, or marshal format will have changed, in - # which case, the cookie decoding will fail. In either case, we'll - # simply request reauthorization, resulting in a new cookie being - # returned to the client. - try: - data = marshal.loads(binascii.unhexlify(c[key])) - issued, received_mac = data - except (EOFError, ValueError, TypeError, KeyError): - return False - # Make sure the issued timestamp makes sense - now = time.time() - if now < issued: - return False - # Calculate what the mac ought to be based on the cookie's timestamp - # and the shared secret. - mac = sha_new(secret + `issued`).hexdigest() - if mac <> received_mac: - return False - # Authenticated! - return True - - - -splitter = re.compile(';\s*') + raise NotImplementedError def parsecookie(s): - c = {} - for line in s.splitlines(): - for p in splitter.split(line): - try: - k, v = p.split('=', 1) - except ValueError: - pass - else: - c[k] = v - return c + raise NotImplementedError Index: mailman-2.1.13/misc/paths.py.in =================================================================== --- mailman-2.1.13.orig/misc/paths.py.in 2010-03-29 11:28:30.776910462 +0200 +++ mailman-2.1.13/misc/paths.py.in 2010-03-29 11:29:07.716415355 +0200 @@ -62,6 +62,11 @@ 'site-packages') sys.path.append(sitedir) +# Include Python's dist-packages directory. +distdir = os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], + 'dist-packages') +sys.path.append(distdir) + # In a normal interactive Python environment, the japanese.pth and korean.pth # files would be imported automatically. But because we inhibit the importing
Index: mailman-2.1.13/Mailman/Archiver/Archiver.py =================================================================== --- mailman-2.1.13.orig/Mailman/Archiver/Archiver.py 2010-03-29 10:07:10.704421186 +0200 +++ mailman-2.1.13/Mailman/Archiver/Archiver.py 2010-03-29 10:07:10.828413364 +0200 @@ -200,20 +200,25 @@ # Archive to mbox only. return txt = str(msg) - # should we use the internal or external archiver? + + # keep using the internal archiver + f = StringIO(txt) + import HyperArch + h = HyperArch.HyperArchive(self) + h.processUnixMailbox(f) + h.close() + f.close() + + # now, use the external archiver private_p = self.archive_private if mm_cfg.PUBLIC_EXTERNAL_ARCHIVER and not private_p: self.ExternalArchive(mm_cfg.PUBLIC_EXTERNAL_ARCHIVER, txt) elif mm_cfg.PRIVATE_EXTERNAL_ARCHIVER and private_p: self.ExternalArchive(mm_cfg.PRIVATE_EXTERNAL_ARCHIVER, txt) - else: - # use the internal archiver - f = StringIO(txt) - import HyperArch - h = HyperArch.HyperArchive(self) - h.processUnixMailbox(f) - h.close() - f.close() + + # Mark Sapiro: "The above patch looks fine to me for what you want to do." + # http://mail.python.org/pipermail/mailman-users/2008-January/059900.html + # # called from MailList.MailList.Save()
diff -ruN -x 80_coclico.patch -x 81_forum_archiver.patch mailman-2.1.13.orig/debian/changelog mailman-2.1.13//debian/changelog --- mailman-2.1.13.orig/debian/changelog 2010-04-13 16:22:45.000000000 +0200 +++ mailman-2.1.13//debian/changelog 2010-04-13 16:23:10.000000000 +0200 @@ -1,3 +1,22 @@ +mailman (1:2.1.13-2coclico4) unstable; urgency=low + + * Add python dependancy for postgres and mysql + + -- Christian Bayle <ba...@debian.org> Tue, 30 Mar 2010 11:06:21 +0200 + +mailman (1:2.1.13-2coclico1) karmic; urgency=low + + * Add forum archiver patch that allow multiple archivers + * Session : + - Modify SecurityManager.py to make it extendable + - Add OldSecurityManager.py as the default security manager + * MailList.py : modified + - Search extend.py first in /var/lib/lists + - Make it possible to extend SecurityManager.py + * Add dist-packages (python library directory) to ./misc/paths.py.in + + -- Mélanie Le Bail <melanie.leb...@orange-ftgroup.com> Mon, 29 Mar 2010 11:30:40 +0200 + mailman (1:2.1.13-2) unstable; urgency=low * postfix-to-mailman.py: check for list existence before stripping off diff -ruN -x 80_coclico.patch -x 81_forum_archiver.patch mailman-2.1.13.orig/debian/control mailman-2.1.13//debian/control --- mailman-2.1.13.orig/debian/control 2010-04-13 16:22:45.000000000 +0200 +++ mailman-2.1.13//debian/control 2010-04-13 16:23:10.000000000 +0200 @@ -16,7 +16,7 @@ Architecture: any Pre-Depends: debconf | debconf-2.0 Depends: ${shlibs:Depends}, ${python:Depends}, ${misc:Depends}, - logrotate, cron, ucf, pwgen, lsb-base, + logrotate, cron, ucf, pwgen, lsb-base, python-psycopg2, python-mysqldb, exim4 | mail-transport-agent, apache2 | httpd Suggests: spamassassin, lynx, listadmin XB-Python-Version: ${python:Versions} diff -ruN -x 80_coclico.patch -x 81_forum_archiver.patch mailman-2.1.13.orig/debian/patches/series mailman-2.1.13//debian/patches/series --- mailman-2.1.13.orig/debian/patches/series 2010-04-13 16:22:45.000000000 +0200 +++ mailman-2.1.13//debian/patches/series 2010-04-13 16:23:10.000000000 +0200 @@ -18,3 +18,5 @@ 74_admin_non-ascii_emails.patch 79_archiver_slash.patch 99_js_templates.patch +80_coclico.patch +81_forum_archiver.patch