summaryrefslogtreecommitdiff
path: root/Mailman/SecurityManager.py
diff options
context:
space:
mode:
authorbwarsaw2006-10-30 02:56:10 +0000
committerbwarsaw2006-10-30 02:56:10 +0000
commit0e306bb5b88fe02d83f9964c90177491602fa158 (patch)
tree71244e23072953105f7b21689d98c6214d0b3b84 /Mailman/SecurityManager.py
parent3256c431e7bf966d3de49e4dc31dd01d57ffb02f (diff)
downloadmailman-0e306bb5b88fe02d83f9964c90177491602fa158.tar.gz
mailman-0e306bb5b88fe02d83f9964c90177491602fa158.tar.zst
mailman-0e306bb5b88fe02d83f9964c90177491602fa158.zip
Repair a problem with cookie paths reported by Tokio when HTTPRunner is used
but a reverse proxy maps our wsgiref server into an upstream server's web space. Here's the basic problem: the Set-Cookie header we return has a Path attribute which must match subsequent request uri's in order for the client to send the cookie data back to us later in a Cookie header of that subsequent request. The problem though is that we cannot guarantee that we know how our wsgi server is mapped into the upstream proxy. It's probably '/mailman/' for backward compatibility, but there's no way for us to tell, because there's nothing specifically included in the request that tells us what the originally requested uri is. If we get the cookie path wrong, the effect is to require a login every time an admin page is hit, because the client will not see a matching path prefix and will not send us the cookie data. We solve this (albeit, by hack) by looking at the HTTP_REFERER environment variable we see. This will point to the admin login page on which the admin password was entered. We'll pick this uri apart to attempt to find the prefix at which our wsgi server was mapped. If we find this, we'll use it to craft an appropriate cookie path. Hopefully. If that cgi environment variable is not available, we just return the path as we've seen it. This approach allows for accessing the admin pages either through a reverse proxy or directly, with no additional configuration necessary. In fact, both access mechanisms can work at the same time; try hitting these two uri's with different browsers: http://example.com/mailman/admin/mylist@example.com/general http://example.com:2580/admin/mylist@example.com/general The first will hit our reverse proxy, and the second will hit our wsgi server directly. The responses will included two different cookie paths, but both will work! This is an important principle to uphold, especially for debugging purposes. Note that I could have added a configuration variable to handle the cookie path remapping, but then we'd have to support either the reverse proxy or the wsgi server, but not (easily) both. Plus, it's another configuration variable. Yuck. Note that Apache 2.2 has something called the ProxyPassReverseCookiePath directive, which should probably be used if available. It's not in Apache 2.0, which is probably the most prevalent server in use with Mailman right now, so we can't count on it. Plus, if ProxyPassReverseCookiePath is available, our algorithm should still work. What about other proxy servers? Dunno. We'll have to wait for feedback from users. This change also fixes a buglet with MTA/Postfix.py when POSTFIX_STYLE_VIRTUAL_DOMAINS is used. You can end up trying to call _update_maps() before data/aliases exist. This just defers that call until it's guaranteed both the transport and the alias files have been created. Also, finish converting SecurityManager.py to use the configuration object (and the Defaults module where appropriate) instead of mm_cfg.py.
Diffstat (limited to 'Mailman/SecurityManager.py')
-rw-r--r--Mailman/SecurityManager.py54
1 files changed, 38 insertions, 16 deletions
diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py
index 8c55d2863..eb7676835 100644
--- a/Mailman/SecurityManager.py
+++ b/Mailman/SecurityManager.py
@@ -57,9 +57,12 @@ import logging
import marshal
import binascii
+from urlparse import urlparse
+
+from Mailman import Defaults
from Mailman import Errors
-from Mailman import mm_cfg
from Mailman import Utils
+from Mailman.configuration import config
try:
import crypt
@@ -93,23 +96,23 @@ class SecurityManager:
# NotAMemberError will be raised. If the user's secret is None, raise
# a MMBadUserError.
key = self.internal_name() + '+'
- if authcontext == mm_cfg.AuthUser:
+ 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 == mm_cfg.AuthListModerator:
+ elif authcontext == Defaults.AuthListModerator:
secret = self.mod_password
key += 'moderator'
- elif authcontext == mm_cfg.AuthListAdmin:
+ elif authcontext == Defaults.AuthListAdmin:
secret = self.password
key += 'admin'
# BAW: AuthCreator
- elif authcontext == mm_cfg.AuthSiteAdmin:
+ elif authcontext == Defaults.AuthSiteAdmin:
sitepass = Utils.get_global_password()
- if mm_cfg.ALLOW_SITE_ADMIN_COOKIES and sitepass:
+ if config.ALLOW_SITE_ADMIN_COOKIES and sitepass:
secret = sitepass
key = 'site'
else:
@@ -132,15 +135,15 @@ class SecurityManager:
# Return the authcontext from the argument sequence that matches the
# response, or UnAuthorized.
for ac in authcontexts:
- if ac == mm_cfg.AuthCreator:
+ if ac == Defaults.AuthCreator:
ok = Utils.check_global_password(response, siteadmin=0)
if ok:
- return mm_cfg.AuthCreator
- elif ac == mm_cfg.AuthSiteAdmin:
+ return Defaults.AuthCreator
+ elif ac == Defaults.AuthSiteAdmin:
ok = Utils.check_global_password(response)
if ok:
- return mm_cfg.AuthSiteAdmin
- elif ac == mm_cfg.AuthListAdmin:
+ return Defaults.AuthSiteAdmin
+ elif ac == Defaults.AuthListAdmin:
def cryptmatchp(response, secret):
try:
salt = secret[:2]
@@ -186,12 +189,12 @@ class SecurityManager:
self.Unlock()
if ok:
return ac
- elif ac == mm_cfg.AuthListModerator:
+ elif ac == Defaults.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:
+ elif ac == Defaults.AuthUser:
if user is not None:
try:
if self.authenticateMember(user, response):
@@ -202,7 +205,7 @@ class SecurityManager:
# What is this context???
log.error('Bad authcontext: %s', ac)
raise ValueError('Bad authcontext: %s' % ac)
- return mm_cfg.UnAuthorized
+ return Defaults.UnAuthorized
def WebAuthenticate(self, authcontexts, response, user=None):
# Given a list of authentication contexts, check to see if the cookie
@@ -224,7 +227,26 @@ class SecurityManager:
return False
def _cookie_path(self):
- return '/%s/%s' % (os.environ['SCRIPT_NAME'], self.fqdn_listname)
+ # We could be reverse proxied, in which case our cookie path must
+ # match the path as seen by the upstream server, otherwise the client
+ # won't send us our cookie data. We try to figure this out by looking
+ # at the HTTP_REFERER header, which should include the uri of the
+ # admin login screen as seen by the client. This is a hack because
+ # we're not guaranteed to see that envar, but there's really no other
+ # way to do it -- the original uri that's proxied to us is not
+ # included in the backend request. XXX what happens when Apache 2.2's
+ # ProxyPassReverseCookiePath is set?
+ target = '/%s/%s' % (os.environ['SCRIPT_NAME'], self.fqdn_listname)
+ referer = os.environ.get('HTTP_REFERER')
+ if not referer:
+ return target
+ # Python 2.5 XXX urlparse(referer).path
+ path = urlparse(referer)[2]
+ i = path.find(target)
+ if i < 0:
+ return target
+ prefix = path[:i]
+ return prefix + target
def MakeCookie(self, authcontext, user=None):
key, secret = self.AuthContextInfo(authcontext, user)
@@ -278,7 +300,7 @@ class SecurityManager:
# 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 authcontext == Defaults.AuthUser:
if user:
usernames = [user]
else: