Revision: 8083
http://svn.sourceforge.net/mailman/?rev=8083&view=rev
Author: bwarsaw
Date: 2006-10-29 18:56:10 -0800 (Sun, 29 Oct 2006)
Log Message:
-----------
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/[EMAIL PROTECTED]/general
http://example.com:2580/admin/[EMAIL PROTECTED]/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.
Modified Paths:
--------------
trunk/mailman/Mailman/Defaults.py.in
trunk/mailman/Mailman/MTA/Postfix.py
trunk/mailman/Mailman/SecurityManager.py
Modified: trunk/mailman/Mailman/Defaults.py.in
===================================================================
--- trunk/mailman/Mailman/Defaults.py.in 2006-10-24 03:55:24 UTC (rev
8082)
+++ trunk/mailman/Mailman/Defaults.py.in 2006-10-30 02:56:10 UTC (rev
8083)
@@ -732,10 +732,14 @@
# CGI requests to this WSGI Server:
#
# ProxyPass /mailman/ http://localhost:2580/
+#
+# XXX If you are running Apache 2.2, you will probably also want to set
+# ProxyPassReverseCookiePath
+#
+# Also you have to add following line to <prefix>/etc/mailman.cfg
+# QRUNNERS.append(('HTTPRunner', 1))
HTTP_HOST = 'localhost'
HTTP_PORT = 2580
-# Also you have to add following line to <prefix>/etc/mailman.cfg
-# QRUNNERS.append(('HTTPRunner', 1))
# After processing every file in the qrunner's slice, how long should the
# runner sleep for before checking the queue directory again for new files?
Modified: trunk/mailman/Mailman/MTA/Postfix.py
===================================================================
--- trunk/mailman/Mailman/MTA/Postfix.py 2006-10-24 03:55:24 UTC (rev
8082)
+++ trunk/mailman/Mailman/MTA/Postfix.py 2006-10-30 02:56:10 UTC (rev
8083)
@@ -267,10 +267,11 @@
lock = makelock()
lock.lock()
# Create transport file if USE_LMTP
+ update_maps = False
if config.USE_LMTP:
try:
_do_create(mlist, TRPTFILE, _addtransport)
- _update_maps()
+ update_maps = True
finally:
if lock:
lock.unlock(unconditionally=True)
@@ -279,10 +280,12 @@
_do_create(mlist, ALIASFILE, _addlist)
if mlist and mlist.host_name in config.POSTFIX_STYLE_VIRTUAL_DOMAINS:
_do_create(mlist, VIRTFILE, _addvirtual)
- _update_maps()
+ update_maps = True
finally:
if lock:
lock.unlock(unconditionally=True)
+ if update_maps:
+ _update_maps()
Modified: trunk/mailman/Mailman/SecurityManager.py
===================================================================
--- trunk/mailman/Mailman/SecurityManager.py 2006-10-24 03:55:24 UTC (rev
8082)
+++ trunk/mailman/Mailman/SecurityManager.py 2006-10-30 02:56:10 UTC (rev
8083)
@@ -57,9 +57,12 @@
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 @@
# 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 @@
# 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 @@
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 @@
# 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 @@
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 @@
# 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:
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
_______________________________________________
Mailman-checkins mailing list
[email protected]
Unsubscribe:
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org