summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Mailman/Cgi/admin.py79
-rw-r--r--Mailman/Cgi/admindb.py56
-rw-r--r--Mailman/Cgi/private.py148
-rw-r--r--Mailman/SecurityManager.py54
4 files changed, 171 insertions, 166 deletions
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
index 73a7b2cd9..a1602bc3c 100644
--- a/Mailman/Cgi/admin.py
+++ b/Mailman/Cgi/admin.py
@@ -26,7 +26,6 @@ import sys
import os, cgi, string, types, time
import paths # path hacking
from Mailman import Utils, MailList, Errors, MailCommandHandler
-from Mailman import Cookie
from Mailman.htmlformat import *
from Mailman.Crypt import crypt
from Mailman import mm_cfg
@@ -41,33 +40,6 @@ CATEGORIES = [('general', "General Options"),
('gateway', "Mail-News and News-Mail gateways")]
-
-def isAuthenticated(list, password=None, SECRET="SECRET"):
- if password is not None: # explicit login
- try:
- list.ConfirmAdminPassword(password)
- except Errors.MMBadPasswordError:
- AddErrorMessage(doc, 'Error: Incorrect admin password.')
- return 0
-
- token = list.MakeCookie()
- c = Cookie.Cookie()
- cookie_key = list_name + "-admin"
- c[cookie_key] = token
- c[cookie_key]['expires'] = mm_cfg.ADMIN_COOKIE_LIFE
- print c # Output the cookie
- return 1
- if os.environ.has_key('HTTP_COOKIE'):
- c = Cookie.Cookie( os.environ['HTTP_COOKIE'] )
- if c.has_key(list_name + "-admin"):
- if list.CheckCookie(c[list_name + "-admin"].value):
- return 1
- else:
- AddErrorMessage(doc, "error decoding authorization cookie")
- return 0
- return 0
-
-
def main():
"""Process and produce list options form.
@@ -110,26 +82,51 @@ def main():
category = 'general'
global cgi_data
cgi_data = cgi.FieldStorage()
+
+ # Authenticate.
is_auth = 0
- if cgi_data.has_key("adminpw"):
- is_auth = isAuthenticated(lst, cgi_data["adminpw"].value)
- message = FontAttr("Sorry, wrong password. Try again.",
- color="ff5060", size="+1").Format()
- else:
- is_auth = isAuthenticated(lst)
- message = ""
+ adminpw = None
+ message = ""
+
+ # If we get a password change request, we first authenticate
+ # by cookie here, and issue a new cookie later on iff the
+ # password change worked out.
+ #
+ # The idea is to set only _one_ cookie when the admin password
+ # changes. The new cookie is needed, because the checksum part
+ # of the cookie is based on (among other things) the list's
+ # admin password.
+ if cgi_data.has_key('adminpw') and not cgi_data.has_key('newpw'):
+ adminpw = cgi_data['adminpw'].value
+ try:
+ # admin uses cookies with -admin name suffix
+ is_auth = lst.WebAuthenticate(password=adminpw,
+ cookie='admin')
+ except Errors.MMBadPasswordError:
+ message = 'Sorry, wrong password. Try again.'
+ except Errors.MMExpiredCookieError:
+ message = 'Your cookie has gone stale, ' \
+ 'enter password to get a new one.',
+ except Errors.MMInvalidCookieError:
+ message = 'Error decoding authorization cookie.'
+ except Errors.MMAuthenticationError:
+ message = 'Authentication error.'
+
if not is_auth:
defaulturi = '/mailman/admin%s/%s' % (mm_cfg.CGIEXT, list_name)
- print "Content-type: text/html\n\n"
+ if message:
+ message = FontAttr(
+ message, color='FF5060', size='+1').Format()
+ print 'Content-type: text/html\n\n'
text = Utils.maketext(
'admlogin.txt',
- {"listname": list_name,
- "path" : Utils.GetRequestURI(defaulturi),
- "message" : message,
+ {'listname': list_name,
+ 'path' : Utils.GetRequestURI(defaulturi),
+ 'message' : message,
})
print text
return
-
+
# is the request for variable details?
varhelp = None
if cgi_data.has_key('VARHELP'):
@@ -743,6 +740,8 @@ def ChangeOptions(lst, category, cgi_info, document):
if new == confirm:
lst.password = crypt(new, Utils.GetRandomSeed())
dirty = 1
+ # Re-authenticate (to set new cookie)
+ lst.WebAuthenticate(password=new, cookie='admin')
else:
m = 'Error: Passwords did not match.'
document.AddItem(Header(3, Italic(FontAttr(m, color="ff5060"))))
diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py
index 0f76c032c..ac979a62b 100644
--- a/Mailman/Cgi/admindb.py
+++ b/Mailman/Cgi/admindb.py
@@ -22,35 +22,8 @@ import sys
import os, cgi, string, types
from Mailman import Utils, MailList, Errors
from Mailman.htmlformat import *
-from Mailman import Cookie
from Mailman import mm_cfg
-# copied from admin.py
-def isAuthenticated(mlist, password=None, SECRET="SECRET"):
- if password is not None: # explicit login
- try:
- mlist.ConfirmAdminPassword(password)
- except Errors.MMBadPasswordError:
- AddErrorMessage(doc, 'Error: Incorrect admin password.')
- return 0
-
- token = list.MakeCookie()
- c = Cookie.Cookie()
- cookie_key = list_name + "-admin"
- c[cookie_key] = token
- c[cookie_key]['expires'] = mm_cfg.ADMIN_COOKIE_LIFE
- print c # Output the cookie
- return 1
- if os.environ.has_key('HTTP_COOKIE'):
- c = Cookie.Cookie( os.environ['HTTP_COOKIE'] )
- if c.has_key(list_name + "-admin"):
- if list.CheckCookie(c[list_name + "-admin"].value):
- return 1
- else:
- AddErrorMessage(doc, "error decoding authorization cookie")
- return 0
- return 0
-
def main():
# XXX: Yuk, blech, ick
@@ -98,17 +71,32 @@ def main():
try:
form = cgi.FieldStorage()
- # authenticate. all copied from admin.py
+ # Authenticate.
is_auth = 0
+ adminpw = None
+ message = ""
+
if form.has_key('adminpw'):
- is_auth = isAuthenticated(list, form['adminpw'].value)
- message = FontAttr('Sorry, wrong password. Try again.',
- color='ff5060', size='+1').Format()
- else:
- is_auth = isAuthenticated(list)
- message = ''
+ adminpw = form['adminpw'].value
+ try:
+ # admindb uses the same cookie as admin
+ is_auth = list.WebAuthenticate(password=adminpw,
+ cookie='admin')
+ except Errors.MMBadPasswordError:
+ message = 'Sorry, wrong password. Try again.'
+ except Errors.MMExpiredCookieError:
+ message = 'Your cookie has gone stale, ' \
+ 'enter password to get a new one.',
+ except Errors.MMInvalidCookieError:
+ message = 'Error decoding authorization cookie.'
+ except Errors.MMAuthenticationError:
+ message = 'Authentication error.'
+
if not is_auth:
defaulturi = '/mailman/admindb%s/%s' % (mm_cfg.CGIEXT, list_name)
+ if message:
+ message = FontAttr(
+ message, color='FF5060', size='+1').Format()
print 'Content-type: text/html\n\n'
text = Utils.maketext(
'admlogin.txt',
diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py
index e46879371..24eff56e7 100644
--- a/Mailman/Cgi/private.py
+++ b/Mailman/Cgi/private.py
@@ -22,12 +22,11 @@ Currently this is organized to obtain passwords for Mailman mailing list
subscribers.
"""
-import sys, os, string
-from Mailman import MailList, Errors
-from Mailman import Cookie
+import sys, os, string, cgi
+from Mailman import Utils, MailList, Errors
+from Mailman.htmlformat import *
from Mailman.Logging.Utils import LogStdErr
-from Mailman import Utils
-import Mailman.mm_cfg
+from Mailman import mm_cfg
LogStdErr("error", "private")
@@ -70,58 +69,6 @@ PAGE = '''
login_attempted = 0
_list = None
-def GetListobj(list_name):
- """Return an unlocked instance of the named mailing list, if found."""
- global _list
- if _list:
- return _list
- _list = MailList.MailList(list_name, lock=0)
- return _list
-
-def isAuthenticated(list_name):
- try:
- listobj = GetListobj(list_name)
- except Errors.MMUnknownListError:
- print "\n<H3>List", repr(list_name), "not found.</h3>"
- raise SystemExit
- if os.environ.has_key('HTTP_COOKIE'):
- c = Cookie.Cookie( os.environ['HTTP_COOKIE'] )
- if c.has_key(list_name):
- if listobj.CheckCookie(c[list_name].value):
- return 1
- # No corresponding cookie. OK, then check for username, password
- # CGI variables
- import cgi
- v = cgi.FieldStorage()
- username = password = None
- if v.has_key('username'):
- username = v['username']
- if type(username) == type([]): username = username[0]
- username = username.value
- if v.has_key('password'):
- password = v['password']
- if type(password) == type([]): password = password[0]
- password = password.value
-
- if username is None or password is None: return 0
-
- # Record that this is a login attempt, so if it fails the form can
- # be displayed with an appropriate message.
- global login_attempted
- login_attempted=1
- try:
- listobj.ConfirmUserPassword( username, password)
- except (Errors.MMBadUserError, Errors.MMBadPasswordError,
- Errors.MMNotAMemberError):
- return 0
-
- token = listobj.MakeCookie()
- c = Cookie.Cookie()
- c[list_name] = token
- print c # Output the cookie
- return 1
-
-
def true_path(path):
"Ensure that the path is safe by removing .."
path = string.replace(path, "../", "")
@@ -138,15 +85,28 @@ def content_type(path):
def main():
- path = os.environ.get('PATH_INFO', "/index.html")
+ doc = Document()
+
+ try:
+ path = os.environ['PATH_INFO']
+ except KeyError:
+ doc.SetTitle("Private Archive Error")
+ doc.AddItem(Header(3, "You must specify a list."))
+ print doc.Format(bgcolor="#FFFFFF")
+ sys.exit(0)
+
true_filename = os.path.join(
- Mailman.mm_cfg.PRIVATE_ARCHIVE_FILE_DIR,
+ mm_cfg.PRIVATE_ARCHIVE_FILE_DIR,
true_path(path))
+
list_info = Utils.GetPathPieces(path)
- if len(list_info) == 0:
- list_name = None
- else:
- list_name = string.lower(list_info[0])
+
+ if len(list_info) < 1:
+ doc.SetTitle("Private Archive Error")
+ doc.AddItem(Header(3, "You must specify a list."))
+ print doc.Format(bgcolor="#FFFFFF")
+ sys.exit(0)
+ list_name = string.lower(list_info[0])
# If it's a directory, we have to append index.html in this script. We
# must also check for a gzipped file, because the text archives are
@@ -158,29 +118,55 @@ def main():
# then
true_filename = true_filename + '.gz'
- if not list_name or not isAuthenticated(list_name):
+ try:
+ listobj = MailList.MailList(list_name, lock=0)
+ except Errors.MMUnknownListError:
+ listobj = None
+ if not (listobj and listobj._ready):
+ msg = "%s: No such list." % list_name
+ doc.SetTitle("Private Archive Error - %s" % msg)
+ doc.AddItem(Header(2, msg))
+ print doc.Format(bgcolor="#FFFFFF")
+ sys.exit(0)
+
+ form = cgi.FieldStorage()
+ user = password = None
+ if form.has_key('username'):
+ user = form['username']
+ if type(user) == type([]): user = user[0]
+ user = user.value
+ if form.has_key('password'):
+ password = form['password']
+ if type(password) == type([]): password = password[0]
+ password = password.value
+
+ is_auth = 0
+ message = ("Please enter your %s subscription email address "
+ "and password." % listobj.real_name)
+ try:
+ is_auth = listobj.WebAuthenticate(user=user,
+ password=password,
+ cookie='archive')
+ except (Errors.MMBadUserError, Errors.MMBadPasswordError,
+ Errors.MMNotAMemberError):
+ message = ('Your email address or password were incorrect. '
+ 'Please try again.')
+ except Errors.MMExpiredCookieError:
+ message = 'Your cookie has gone stale, ' \
+ 'enter password to get a new one.',
+ except Errors.MMInvalidCookieError:
+ message = 'Error decoding authorization cookie.'
+ except Errors.MMAuthenticationError:
+ message = 'Authentication error.'
+
+ if not is_auth:
# Output the password form
- print 'Content-type: text/html\n'
+ print 'Content-type: text/html\n\n'
page = PAGE
-
- if not list_name:
- print '\n<h3>No list name found.</h3>'
- raise SystemExit
- try:
- listobj = GetListobj(list_name)
- except Errors.MMUnknownListError:
- print "\n<H3>List", repr(list_name), "not found.</h3>"
- raise SystemExit
- if login_attempted:
- message = ("Your email address or password were incorrect."
- " Please try again.")
- else:
- message = ("Please enter your %s subscription email address"
- " and password." % listobj.real_name)
while path and path[0] == '/': path=path[1:] # Remove leading /'s
basepath = os.path.split(listobj.GetBaseArchiveURL())[0]
listname = listobj.real_name
- print '\n\n', page % vars()
+ print page % vars()
sys.exit(0)
# Authorization confirmed... output the desired file
diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py
index d60a93179..289762834 100644
--- a/Mailman/SecurityManager.py
+++ b/Mailman/SecurityManager.py
@@ -25,6 +25,8 @@ import types
import Crypt
import Errors
import Utils
+import Cookie
+from urlparse import urlparse
import mm_cfg
# TBD: is this the best location for the site password?
@@ -68,30 +70,60 @@ class SecurityManager:
raise Errors.MMBadPasswordError
return 1
- def MakeCookie(self):
+ def WebAuthenticate(self, user=None, password=None, cookie=None):
+ if cookie:
+ cookie_key = self._internal_name + ':' + cookie
+ else:
+ cookie_key = self._internal_name
+ if password is not None: # explicit login
+ if user:
+ self.ConfirmUserPassword(user, password)
+ else:
+ self.ConfirmAdminPassword(password)
+ print self.MakeCookie(cookie_key)
+ return 1
+ else:
+ return self.CheckCookie(cookie_key)
+
+ def MakeCookie(self, key):
+ # Make sure we have the necessary ingredients for our cookie
client_ip = os.environ.get('REMOTE_ADDR') or '0.0.0.0'
issued = int(time.time())
expires = issued + mm_cfg.ADMIN_COOKIE_LIFE
+ # ... including the secret ingredient :)
secret = self.password
mac = hash(secret + client_ip + `issued` + `expires`)
- return [client_ip, issued, expires, mac]
+ # Mix all ingredients gently together,
+ c = Cookie.Cookie()
+ c[key] = [client_ip, issued, expires, mac]
+ # place in oven,
+ path = urlparse(mm_cfg.DEFAULT_URL)[2] # '/mailman'
+ c[key]['path'] = path
+ # and bake until golden brown
+ c[key]['expires'] = mm_cfg.ADMIN_COOKIE_LIFE
+ return c
- def CheckCookie(self, cookie):
- if type(cookie) <> type([]):
+ def CheckCookie(self, key):
+ if not os.environ.has_key('HTTP_COOKIE'):
return 0
- if len(cookie) <> 4:
+ c = Cookie.Cookie(os.environ['HTTP_COOKIE'])
+ if not c.has_key(key):
return 0
+ cookie = c[key].value
+ if (type(cookie) <> type([]) or
+ len(cookie) <> 4):
+ raise Errors.MMInvalidCookieError
client_ip = os.environ.get('REMOTE_ADDR') or '0.0.0.0'
- [for_ip, issued, expires, received_mac] = cookie
- if for_ip <> client_ip:
- return 0
now = time.time()
- if not issued < now < expires:
- return 0
+ [for_ip, issued, expires, received_mac] = cookie
+ if (for_ip <> client_ip or now < issued):
+ raise Errors.MMInvalidCookieError
+ if now > expires:
+ raise Errors.MMExpiredCookieError
secret = self.password
mac = hash(secret + client_ip + `issued` + `expires`)
if mac <> received_mac:
- return 0
+ raise Errors.MMInvalidCookieError
return 1
def ConfirmUserPassword(self, user, pw):