summaryrefslogtreecommitdiff
path: root/src/mailman/web/Cgi/private.py
diff options
context:
space:
mode:
authorBarry Warsaw2009-01-25 13:01:41 -0500
committerBarry Warsaw2009-01-25 13:01:41 -0500
commiteefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch)
tree72c947fe16fce0e07e996ee74020b26585d7e846 /src/mailman/web/Cgi/private.py
parent07871212f74498abd56bef3919bf3e029eb8b930 (diff)
downloadmailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip
Diffstat (limited to 'src/mailman/web/Cgi/private.py')
-rw-r--r--src/mailman/web/Cgi/private.py190
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()