summaryrefslogtreecommitdiff
path: root/src/mailman/attic/SecurityManager.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/attic/SecurityManager.py')
-rw-r--r--src/mailman/attic/SecurityManager.py306
1 files changed, 0 insertions, 306 deletions
diff --git a/src/mailman/attic/SecurityManager.py b/src/mailman/attic/SecurityManager.py
deleted file mode 100644
index 8d4a30592..000000000
--- a/src/mailman/attic/SecurityManager.py
+++ /dev/null
@@ -1,306 +0,0 @@
-# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""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 sha
-import time
-import urllib
-import Cookie
-import logging
-import marshal
-import binascii
-
-from urlparse import urlparse
-
-from Mailman import Defaults
-from Mailman import Errors
-from Mailman import Utils
-from Mailman import passwords
-from Mailman.configuration import config
-
-log = logging.getLogger('mailman.error')
-dlog = logging.getLogger('mailman.debug')
-
-SLASH = '/'
-
-
-
-class SecurityManager:
- 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 = urllib.quote(self.fqdn_listname) + '+'
- if authcontext == Defaults.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 == Defaults.AuthListModerator:
- secret = self.mod_password
- key += 'moderator'
- elif authcontext == Defaults.AuthListAdmin:
- secret = self.password
- key += 'admin'
- # BAW: AuthCreator
- elif authcontext == Defaults.AuthSiteAdmin:
- sitepass = Utils.get_global_password()
- if config.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
-
- 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.
- for ac in authcontexts:
- if ac == Defaults.AuthCreator:
- ok = Utils.check_global_password(response, siteadmin=False)
- if ok:
- return Defaults.AuthCreator
- elif ac == Defaults.AuthSiteAdmin:
- ok = Utils.check_global_password(response)
- if ok:
- return Defaults.AuthSiteAdmin
- elif ac == Defaults.AuthListAdmin:
- # 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.
- key, secret = self.AuthContextInfo(ac)
- if secret is None:
- continue
- if passwords.check_response(secret, response):
- return ac
- elif ac == Defaults.AuthListModerator:
- # The list moderator password must be sha'd
- key, secret = self.AuthContextInfo(ac)
- if secret and passwords.check_response(secret, response):
- return ac
- elif ac == Defaults.AuthUser:
- if user is not None:
- try:
- if self.authenticateMember(user, response):
- return ac
- except Errors.NotAMemberError:
- pass
- else:
- # What is this context???
- log.error('Bad authcontext: %s', ac)
- raise ValueError('Bad authcontext: %s' % ac)
- return Defaults.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 _cookie_path(self):
- script_name = os.environ.get('SCRIPT_NAME', '')
- return SLASH.join(script_name.split(SLASH)[:-1]) + SLASH
-
- def MakeCookie(self, authcontext, user=None):
- key, secret = self.AuthContextInfo(authcontext, user)
- if key is None or secret is None or not isinstance(secret, basestring):
- raise ValueError
- # Timestamp
- issued = int(time.time())
- # Get a digest of the secret, plus other information.
- mac = sha.new(secret + repr(issued)).hexdigest()
- # Create the cookie object.
- c = Cookie.SimpleCookie()
- c[key] = binascii.hexlify(marshal.dumps((issued, mac)))
- c[key]['path'] = self._cookie_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] = ''
- c[key]['path'] = self._cookie_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 == Defaults.AuthUser:
- if user:
- usernames = [user]
- else:
- usernames = []
- prefix = urllib.quote(self.fqdn_listname) + '+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(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 key not in c or not isinstance(secret, basestring):
- 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 + repr(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