diff options
Diffstat (limited to 'src/mailman/web/Cgi/private.py')
| -rw-r--r-- | src/mailman/web/Cgi/private.py | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/src/mailman/web/Cgi/private.py b/src/mailman/web/Cgi/private.py new file mode 100644 index 000000000..87cad7966 --- /dev/null +++ b/src/mailman/web/Cgi/private.py @@ -0,0 +1,190 @@ +# 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/>. + +"""Provide a password-interface wrapper around private archives.""" + +import os +import sys +import cgi +import logging +import mimetypes + +from Mailman import Errors +from Mailman import MailList +from Mailman import Utils +from Mailman import i18n +from Mailman.configuration import config +from Mailman.htmlformat import * + +# Set up i18n. Until we know which list is being requested, we use the +# server's default. +_ = i18n._ +i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) + +SLASH = '/' + +log = logging.getLogger('mailman.error') +mlog = logging.getLogger('mailman.mischief') + + + +def true_path(path): + "Ensure that the path is safe by removing .." + # Workaround for path traverse vulnerability. Unsuccessful attempts will + # be logged in logs/error. + parts = [x for x in path.split(SLASH) if x not in ('.', '..')] + return SLASH.join(parts)[1:] + + + +def guess_type(url, strict): + if hasattr(mimetypes, 'common_types'): + return mimetypes.guess_type(url, strict) + return mimetypes.guess_type(url) + + + +def main(): + doc = Document() + doc.set_language(config.DEFAULT_SERVER_LANGUAGE) + + parts = Utils.GetPathPieces() + if not parts: + doc.SetTitle(_("Private Archive Error")) + doc.AddItem(Header(3, _("You must specify a list."))) + print doc.Format() + return + + path = os.environ.get('PATH_INFO') + tpath = true_path(path) + if tpath <> path[1:]: + msg = _('Private archive - "./" and "../" not allowed in URL.') + doc.SetTitle(msg) + doc.AddItem(Header(2, msg)) + print doc.Format() + mlog.error('Private archive hostile path: %s', path) + return + # BAW: This needs to be converted to the Site module abstraction + true_filename = os.path.join( + config.PRIVATE_ARCHIVE_FILE_DIR, tpath) + + listname = parts[0].lower() + mboxfile = '' + if len(parts) > 1: + mboxfile = parts[1] + + # See if it's the list's mbox file is being requested + if listname.endswith('.mbox') and mboxfile.endswith('.mbox') and \ + listname[:-5] == mboxfile[:-5]: + listname = listname[:-5] + else: + mboxfile = '' + + # 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 + # usually stored in compressed form. + if os.path.isdir(true_filename): + true_filename = true_filename + '/index.html' + if not os.path.exists(true_filename) and \ + os.path.exists(true_filename + '.gz'): + true_filename = true_filename + '.gz' + + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + msg = _('No such list <em>%(safelistname)s</em>') + doc.SetTitle(_("Private Archive Error - %(msg)s")) + doc.AddItem(Header(2, msg)) + print doc.Format() + log.error('No such list "%s": %s\n', listname, e) + return + + i18n.set_language(mlist.preferred_language) + doc.set_language(mlist.preferred_language) + + cgidata = cgi.FieldStorage() + username = cgidata.getvalue('username', '') + password = cgidata.getvalue('password', '') + + is_auth = 0 + realname = mlist.real_name + message = '' + + if not mlist.WebAuthenticate((config.AuthUser, + config.AuthListModerator, + config.AuthListAdmin, + config.AuthSiteAdmin), + password, username): + if cgidata.has_key('submit'): + # This is a re-authorization attempt + message = Bold(FontSize('+1', _('Authorization failed.'))).Format() + # Output the password form + charset = Utils.GetCharSet(mlist.preferred_language) + print 'Content-type: text/html; charset=' + charset + '\n\n' + # Put the original full path in the authorization form, but avoid + # trailing slash if we're not adding parts. We add it below. + action = mlist.GetScriptURL('private') + if parts[1:]: + action = os.path.join(action, SLASH.join(parts[1:])) + # If we added '/index.html' to true_filename, add a slash to the URL. + # We need this because we no longer add the trailing slash in the + # private.html template. It's always OK to test parts[-1] since we've + # already verified parts[0] is listname. The basic rule is if the + # post URL (action) is a directory, it must be slash terminated, but + # not if it's a file. Otherwise, relative links in the target archive + # page don't work. + if true_filename.endswith('/index.html') and parts[-1] <> 'index.html': + action += SLASH + # Escape web input parameter to avoid cross-site scripting. + print Utils.maketext( + 'private.html', + {'action' : Utils.websafe(action), + 'realname': mlist.real_name, + 'message' : message, + }, mlist=mlist) + return + + lang = mlist.getMemberLanguage(username) + i18n.set_language(lang) + doc.set_language(lang) + + # Authorization confirmed... output the desired file + try: + ctype, enc = guess_type(path, strict=0) + if ctype is None: + ctype = 'text/html' + if mboxfile: + f = open(os.path.join(mlist.archive_dir() + '.mbox', + mlist.internal_name() + '.mbox')) + ctype = 'text/plain' + elif true_filename.endswith('.gz'): + import gzip + f = gzip.open(true_filename, 'r') + else: + f = open(true_filename, 'r') + except IOError: + msg = _('Private archive file not found') + doc.SetTitle(msg) + doc.AddItem(Header(2, msg)) + print doc.Format() + log.error('Private archive file not found: %s', true_filename) + else: + print 'Content-type: %s\n' % ctype + sys.stdout.write(f.read()) + f.close() |
