diff options
Diffstat (limited to 'Mailman/Cgi/options.py')
-rw-r--r-- | Mailman/Cgi/options.py | 92 |
1 files changed, 71 insertions, 21 deletions
diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 9a2389a9..cdc2bef3 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2011 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2015 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,6 +17,7 @@ """Produce and handle the member options.""" +import re import sys import os import cgi @@ -33,8 +34,12 @@ from Mailman import i18n from Mailman.htmlformat import * from Mailman.Logging.Syslog import syslog +OR = '|' SLASH = '/' SETLANGUAGE = -1 +DIGRE = re.compile( + '<!--Start-Digests-Delete-->.*<!--End-Digests-Delete-->', + re.DOTALL) # Set up i18n _ = i18n._ @@ -52,6 +57,18 @@ def main(): doc = Document() doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + method = Utils.GetRequestMethod() + if method.lower() not in ('get', 'post'): + title = _('CGI script error') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + doc.addError(_('Invalid request method: %(method)s')) + doc.AddItem('<hr>') + doc.AddItem(MailmanLogo()) + print 'Status: 405 Method Not Allowed' + print doc.Format() + return + parts = Utils.GetPathPieces() lenparts = parts and len(parts) if not parts or lenparts < 1: @@ -81,7 +98,7 @@ def main(): # Send this with a 404 status. print 'Status: 404 Not Found' print doc.Format() - syslog('error', 'No such list "%s": %s\n', listname, e) + syslog('error', 'options: No such list "%s": %s\n', listname, e) return # The total contents of the user's response @@ -112,6 +129,14 @@ def main(): return else: user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:]))) + # If a user submits a form or URL with post data or query fragments + # with multiple occurrences of the same variable, we can get a list + # here. Be as careful as possible. + if isinstance(user, list) or isinstance(user, tuple): + if len(user) == 0: + user = '' + else: + user = user[-1] # Avoid cross-site scripting attacks safeuser = Utils.websafe(user) @@ -164,6 +189,9 @@ def main(): return # Are we processing an unsubscription request from the login screen? + msgc = _('If you are a list member, a confirmation email has been sent.') + msga = _("""If you are a list member, your unsubscription request has been + forwarded to the list administrator for approval.""") if cgidata.has_key('login-unsub'): # Because they can't supply a password for unsubscribing, we'll need # to do the confirmation dance. @@ -175,14 +203,14 @@ def main(): # be held. Otherwise, send a confirmation. if mlist.unsubscribe_policy: mlist.HoldUnsubscription(user) - doc.addError(_("""Your unsubscription request has been - forwarded to the list administrator for approval."""), - tag='') + doc.addError(msga, tag='') else: - ip = os.environ.get('REMOTE_ADDR') + ip = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) mlist.ConfirmUnsubscription(user, userlang, remote=ip) - doc.addError(_('The confirmation email has been sent.'), - tag='') + doc.addError(msgc, tag='') mlist.Save() finally: mlist.Unlock() @@ -195,19 +223,21 @@ def main(): syslog('mischief', 'Unsub attempt of non-member w/ private rosters: %s', user) - doc.addError(_('The confirmation email has been sent.'), - tag='') + if mlist.unsubscribe_policy: + doc.addError(msga, tag='') + else: + doc.addError(msgc, tag='') loginpage(mlist, doc, user, language) print doc.Format() return # Are we processing a password reminder from the login screen? + msg = _("""If you are a list member, + your password has been emailed to you.""") if cgidata.has_key('login-remind'): if mlist.isMember(user): mlist.MailUserPassword(user) - doc.addError( - _('A reminder of your password has been emailed to you.'), - tag='') + doc.addError(msg, tag='') else: # Not a member if mlist.private_roster == 0: @@ -217,9 +247,7 @@ def main(): syslog('mischief', 'Reminder attempt of non-member w/ private rosters: %s', user) - doc.addError( - _('A reminder of your password has been emailed to you.'), - tag='') + doc.addError(msg, tag='') loginpage(mlist, doc, user, language) print doc.Format() return @@ -251,9 +279,13 @@ def main(): # So as not to allow membership leakage, prompt for the email # address and the password here. if mlist.private_roster <> 0: + remote = os.environ.get('HTTP_FORWARDED_FOR', + os.environ.get('HTTP_X_FORWARDED_FOR', + os.environ.get('REMOTE_ADDR', + 'unidentified origin'))) syslog('mischief', - 'Login failure with private rosters: %s', - user) + 'Login failure with private rosters: %s from %s', + user, remote) user = None # give an HTTP 401 for authentication failure print 'Status: 401 Unauthorized' @@ -265,6 +297,14 @@ def main(): # options. The first set of checks does not require the list to be # locked. + # However, if a form is submitted for a user who has been asynchronously + # unsubscribed, uncaught NotAMemberError exceptions can be thrown. + + if not mlist.isMember(user): + loginpage(mlist, doc, user, language) + print doc.Format() + return + if cgidata.has_key('logout'): print mlist.ZapCookie(mm_cfg.AuthUser, user) loginpage(mlist, doc, user, language) @@ -506,6 +546,13 @@ address. Upon confirmation, any other mailing list containing the address user, 'via the member options page', userack=1) except Errors.MMNeedApproval: needapproval = True + except Errors.NotAMemberError: + # MAS This except should really be in the outer try so we + # don't save the list redundantly, but except and finally in + # the same try requires Python >= 2.5. + # Setting a switch and making the Save() conditional doesn't + # seem worth it as the Save() won't change anything. + pass mlist.Save() finally: mlist.Unlock() @@ -846,8 +893,10 @@ You are subscribed to this list with the case-preserved address else: replacements['<mm-case-preserved-user>'] = '' - doc.AddItem(mlist.ParseTags('options.html', replacements, userlang)) - + page_text = mlist.ParseTags('options.html', replacements, userlang) + if not (mlist.digestable or mlist.getMemberOption(user, mm_cfg.Digests)): + page_text = DIGRE.sub('', page_text) + doc.AddItem(page_text) def loginpage(mlist, doc, user, lang): @@ -1049,7 +1098,8 @@ def topic_details(mlist, doc, user, cpuser, userlang, varhelp): table.AddRow([Bold(Label(_('Name:'))), Utils.websafe(name)]) table.AddRow([Bold(Label(_('Pattern (as regexp):'))), - '<pre>' + Utils.websafe(pattern) + '</pre>']) + '<pre>' + Utils.websafe(OR.join(pattern.splitlines())) + + '</pre>']) table.AddRow([Bold(Label(_('Description:'))), Utils.websafe(description)]) # Make colors look nice |