diff options
Diffstat (limited to 'src/mailman/web/Cgi/options.py')
| -rw-r--r-- | src/mailman/web/Cgi/options.py | 1000 |
1 files changed, 1000 insertions, 0 deletions
diff --git a/src/mailman/web/Cgi/options.py b/src/mailman/web/Cgi/options.py new file mode 100644 index 000000000..c529a3ea4 --- /dev/null +++ b/src/mailman/web/Cgi/options.py @@ -0,0 +1,1000 @@ +# 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/>. + +"""Produce and handle the member options.""" + +import os +import cgi +import sys +import urllib +import logging + +from Mailman import Errors +from Mailman import MailList +from Mailman import MemberAdaptor +from Mailman import Utils +from Mailman import i18n +from Mailman import passwords +from Mailman.configuration import config +from Mailman.htmlformat import * + + +OR = '|' +SLASH = '/' +SETLANGUAGE = -1 + +# Set up i18n +_ = i18n._ +i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) + +log = logging.getLogger('mailman.error') +mlog = logging.getLogger('mailman.mischief') + + + +def main(): + doc = Document() + doc.set_language(config.DEFAULT_SERVER_LANGUAGE) + + parts = Utils.GetPathPieces() + lenparts = parts and len(parts) + if not parts or lenparts < 1: + title = _('CGI script error') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + doc.addError(_('Invalid options to CGI script.')) + doc.AddItem('<hr>') + doc.AddItem(MailmanLogo()) + print doc.Format() + return + + # get the list and user's name + listname = parts[0].lower() + # open list + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + # Avoid cross-site scripting attacks + safelistname = Utils.websafe(listname) + title = _('CGI script error') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + doc.addError(_('No such list <em>%(safelistname)s</em>')) + doc.AddItem('<hr>') + doc.AddItem(MailmanLogo()) + print doc.Format() + log.error('No such list "%s": %s\n', listname, e) + return + + # The total contents of the user's response + cgidata = cgi.FieldStorage(keep_blank_values=1) + + # Set the language for the page. If we're coming from the listinfo cgi, + # we might have a 'language' key in the cgi data. That was an explicit + # preference to view the page in, so we should honor that here. If that's + # not available, use the list's default language. + language = cgidata.getvalue('language') + if language not in config.languages.enabled_codes: + language = mlist.preferred_language + i18n.set_language(language) + doc.set_language(language) + + if lenparts < 2: + user = cgidata.getvalue('email') + if not user: + # If we're coming from the listinfo page and we left the email + # address field blank, it's not an error. listinfo.html names the + # button UserOptions; we can use that as the descriminator. + if not cgidata.getvalue('UserOptions'): + doc.addError(_('No address given')) + loginpage(mlist, doc, None, language) + print doc.Format() + return + else: + user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:]))) + + # Avoid cross-site scripting attacks + safeuser = Utils.websafe(user) + try: + Utils.ValidateEmail(user) + except Errors.EmailAddressError: + doc.addError(_('Illegal Email Address: %(safeuser)s')) + loginpage(mlist, doc, None, language) + print doc.Format() + return + # Sanity check the user, but only give the "no such member" error when + # using public rosters, otherwise, we'll leak membership information. + if not mlist.isMember(user) and mlist.private_roster == 0: + doc.addError(_('No such member: %(safeuser)s.')) + loginpage(mlist, doc, None, language) + print doc.Format() + return + + # Find the case preserved email address (the one the user subscribed with) + lcuser = user.lower() + try: + cpuser = mlist.getMemberCPAddress(lcuser) + except Errors.NotAMemberError: + # This happens if the user isn't a member but we've got private rosters + cpuser = None + if lcuser == cpuser: + cpuser = None + + # And now we know the user making the request, so set things up to for the + # user's stored preferred language, overridden by any form settings for + # their new language preference. + userlang = cgidata.getvalue('language') + if userlang not in config.languages.enabled_codes: + userlang = mlist.getMemberLanguage(user) + doc.set_language(userlang) + i18n.set_language(userlang) + + # See if this is VARHELP on topics. + varhelp = None + if cgidata.has_key('VARHELP'): + varhelp = cgidata['VARHELP'].value + elif os.environ.get('QUERY_STRING'): + # POST methods, even if their actions have a query string, don't get + # put into FieldStorage's keys :-( + qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') + if qs and isinstance(qs, list): + varhelp = qs[0] + if varhelp: + topic_details(mlist, doc, user, cpuser, userlang, varhelp) + return + + # Are we processing an unsubscription request from the login screen? + if cgidata.has_key('login-unsub'): + # Because they can't supply a password for unsubscribing, we'll need + # to do the confirmation dance. + if mlist.isMember(user): + # We must acquire the list lock in order to pend a request. + try: + mlist.Lock() + # If unsubs require admin approval, then this request has to + # 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='') + else: + mlist.ConfirmUnsubscription(user, userlang) + doc.addError(_('The confirmation email has been sent.'), + tag='') + mlist.Save() + finally: + mlist.Unlock() + else: + # Not a member + if mlist.private_roster == 0: + # Public rosters + doc.addError(_('No such member: %(safeuser)s.')) + else: + mlog.error('Unsub attempt of non-member w/ private rosters: %s', + user) + doc.addError(_('The confirmation email has been sent.'), + tag='') + loginpage(mlist, doc, user, language) + print doc.Format() + return + + # Are we processing a password reminder from the login screen? + 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='') + else: + # Not a member + if mlist.private_roster == 0: + # Public rosters + doc.addError(_('No such member: %(safeuser)s.')) + else: + mlog.error( + 'Reminder attempt of non-member w/ private rosters: %s', + user) + doc.addError( + _('A reminder of your password has been emailed to you.'), + tag='') + loginpage(mlist, doc, user, language) + print doc.Format() + return + + # Get the password from the form. + password = cgidata.getvalue('password', '').strip() + # Check authentication. We need to know if the credentials match the user + # or the site admin, because they are the only ones who are allowed to + # change things globally. Specifically, the list admin may not change + # values globally. + if config.ALLOW_SITE_ADMIN_COOKIES: + user_or_siteadmin_context = (config.AuthUser, config.AuthSiteAdmin) + else: + # Site and list admins are treated equal so that list admin can pass + # site admin test. :-( + user_or_siteadmin_context = (config.AuthUser,) + is_user_or_siteadmin = mlist.WebAuthenticate( + user_or_siteadmin_context, password, user) + # Authenticate, possibly using the password supplied in the login page + if not is_user_or_siteadmin and \ + not mlist.WebAuthenticate((config.AuthListAdmin, + config.AuthSiteAdmin), + password, user): + # Not authenticated, so throw up the login page again. If they tried + # to authenticate via cgi (instead of cookie), then print an error + # message. + if cgidata.has_key('password'): + doc.addError(_('Authentication failed.')) + # So as not to allow membership leakage, prompt for the email + # address and the password here. + if mlist.private_roster <> 0: + mlog.error('Login failure with private rosters: %s', user) + user = None + loginpage(mlist, doc, user, language) + print doc.Format() + return + + # From here on out, the user is okay to view and modify their membership + # options. The first set of checks does not require the list to be + # locked. + + if cgidata.has_key('logout'): + print mlist.ZapCookie(config.AuthUser, user) + loginpage(mlist, doc, user, language) + print doc.Format() + return + + if cgidata.has_key('emailpw'): + mlist.MailUserPassword(user) + options_page( + mlist, doc, user, cpuser, userlang, + _('A reminder of your password has been emailed to you.')) + print doc.Format() + return + + if cgidata.has_key('othersubs'): + # Only the user or site administrator can view all subscriptions. + if not is_user_or_siteadmin: + doc.addError(_("""The list administrator may not view the other + subscriptions for this user."""), _('Note: ')) + options_page(mlist, doc, user, cpuser, userlang) + print doc.Format() + return + hostname = mlist.host_name + title = _('List subscriptions for %(safeuser)s on %(hostname)s') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + doc.AddItem(_('''Click on a link to visit your options page for the + requested mailing list.''')) + + # Troll through all the mailing lists that match host_name and see if + # the user is a member. If so, add it to the list. + onlists = [] + for gmlist in lists_of_member(mlist, user) + [mlist]: + url = gmlist.GetOptionsURL(user) + link = Link(url, gmlist.real_name) + onlists.append((gmlist.real_name, link)) + onlists.sort() + items = OrderedList(*[link for name, link in onlists]) + doc.AddItem(items) + print doc.Format() + return + + if cgidata.has_key('change-of-address'): + # We could be changing the user's full name, email address, or both. + # Watch out for non-ASCII characters in the member's name. + membername = cgidata.getvalue('fullname') + # Canonicalize the member's name + membername = Utils.canonstr(membername, language) + newaddr = cgidata.getvalue('new-address') + confirmaddr = cgidata.getvalue('confirm-address') + + oldname = mlist.getMemberName(user) + set_address = set_membername = 0 + + # See if the user wants to change their email address globally. The + # list admin is /not/ allowed to make global changes. + globally = cgidata.getvalue('changeaddr-globally') + if globally and not is_user_or_siteadmin: + doc.addError(_("""The list administrator may not change the names + or addresses for this user's other subscriptions. However, the + subscription for this mailing list has been changed."""), + _('Note: ')) + globally = False + # We will change the member's name under the following conditions: + # - membername has a value + # - membername has no value, but they /used/ to have a membername + if membername and membername <> oldname: + # Setting it to a new value + set_membername = 1 + if not membername and oldname: + # Unsetting it + set_membername = 1 + # We will change the user's address if both newaddr and confirmaddr + # are non-blank, have the same value, and aren't the currently + # subscribed email address (when compared case-sensitively). If both + # are blank, but membername is set, we ignore it, otherwise we print + # an error. + msg = '' + if newaddr and confirmaddr: + if newaddr <> confirmaddr: + options_page(mlist, doc, user, cpuser, userlang, + _('Addresses did not match!')) + print doc.Format() + return + if newaddr == cpuser: + options_page(mlist, doc, user, cpuser, userlang, + _('You are already using that email address')) + print doc.Format() + return + # If they're requesting to subscribe an address which is already a + # member, and they're /not/ doing it globally, then refuse. + # Otherwise, we'll agree to do it globally (with a warning + # message) and let ApprovedChangeMemberAddress() handle already a + # member issues. + if mlist.isMember(newaddr): + safenewaddr = Utils.websafe(newaddr) + if globally: + listname = mlist.real_name + msg += _("""\ +The new address you requested %(newaddr)s is already a member of the +%(listname)s mailing list, however you have also requested a global change of +address. Upon confirmation, any other mailing list containing the address +%(safeuser)s will be changed. """) + # Don't return + else: + options_page( + mlist, doc, user, cpuser, userlang, + _('The new address is already a member: %(newaddr)s')) + print doc.Format() + return + set_address = 1 + elif (newaddr or confirmaddr) and not set_membername: + options_page(mlist, doc, user, cpuser, userlang, + _('Addresses may not be blank')) + print doc.Format() + return + if set_address: + if cpuser is None: + cpuser = user + # Register the pending change after the list is locked + msg += _('A confirmation message has been sent to %(newaddr)s. ') + mlist.Lock() + try: + try: + mlist.ChangeMemberAddress(cpuser, newaddr, globally) + mlist.Save() + finally: + mlist.Unlock() + except Errors.InvalidEmailAddress: + msg = _('Invalid email address provided') + except Errors.MMAlreadyAMember: + msg = _('%(newaddr)s is already a member of the list.') + except Errors.MembershipIsBanned: + owneraddr = mlist.GetOwnerEmail() + msg = _("""%(newaddr)s is banned from this list. If you + think this restriction is erroneous, please contact + the list owners at %(owneraddr)s.""") + + if set_membername: + mlist.Lock() + try: + mlist.ChangeMemberName(user, membername, globally) + mlist.Save() + finally: + mlist.Unlock() + msg += _('Member name successfully changed. ') + + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + + if cgidata.has_key('changepw'): + newpw = cgidata.getvalue('newpw') + confirmpw = cgidata.getvalue('confpw') + if not newpw or not confirmpw: + options_page(mlist, doc, user, cpuser, userlang, + _('Passwords may not be blank')) + print doc.Format() + return + if newpw <> confirmpw: + options_page(mlist, doc, user, cpuser, userlang, + _('Passwords did not match!')) + print doc.Format() + return + + # See if the user wants to change their passwords globally, however + # the list admin is /not/ allowed to change passwords globally. + pw_globally = cgidata.getvalue('pw-globally') + if pw_globally and not is_user_or_siteadmin: + doc.addError(_("""The list administrator may not change the + password for this user's other subscriptions. However, the + password for this mailing list has been changed."""), + _('Note: ')) + pw_globally = False + + mlists = [mlist] + + if pw_globally: + mlists.extend(lists_of_member(mlist, user)) + + pw = passwords.make_secret(newpw, config.PASSWORD_SCHEME) + for gmlist in mlists: + change_password(gmlist, user, pw) + + # Regenerate the cookie so a re-authorization isn't necessary + print mlist.MakeCookie(config.AuthUser, user) + options_page(mlist, doc, user, cpuser, userlang, + _('Password successfully changed.')) + print doc.Format() + return + + if cgidata.has_key('unsub'): + # Was the confirming check box turned on? + if not cgidata.getvalue('unsubconfirm'): + options_page( + mlist, doc, user, cpuser, userlang, + _('''You must confirm your unsubscription request by turning + on the checkbox below the <em>Unsubscribe</em> button. You + have not been unsubscribed!''')) + print doc.Format() + return + + mlist.Lock() + needapproval = False + try: + try: + mlist.DeleteMember( + user, 'via the member options page', userack=1) + except Errors.MMNeedApproval: + needapproval = True + mlist.Save() + finally: + mlist.Unlock() + # Now throw up some results page, with appropriate links. We can't + # drop them back into their options page, because that's gone now! + fqdn_listname = mlist.GetListEmail() + owneraddr = mlist.GetOwnerEmail() + url = mlist.GetScriptURL('listinfo') + + title = _('Unsubscription results') + doc.SetTitle(title) + doc.AddItem(Header(2, title)) + if needapproval: + doc.AddItem(_("""Your unsubscription request has been received and + forwarded on to the list moderators for approval. You will + receive notification once the list moderators have made their + decision.""")) + else: + doc.AddItem(_("""You have been successfully unsubscribed from the + mailing list %(fqdn_listname)s. If you were receiving digest + deliveries you may get one more digest. If you have any questions + about your unsubscription, please contact the list owners at + %(owneraddr)s.""")) + doc.AddItem(mlist.GetMailmanFooter()) + print doc.Format() + return + + if cgidata.has_key('options-submit'): + # Digest action flags + digestwarn = 0 + cantdigest = 0 + mustdigest = 0 + + newvals = [] + # First figure out which options have changed. The item names come + # from FormatOptionButton() in HTMLFormatter.py + for item, flag in (('digest', config.Digests), + ('mime', config.DisableMime), + ('dontreceive', config.DontReceiveOwnPosts), + ('ackposts', config.AcknowledgePosts), + ('disablemail', config.DisableDelivery), + ('conceal', config.ConcealSubscription), + ('remind', config.SuppressPasswordReminder), + ('rcvtopic', config.ReceiveNonmatchingTopics), + ('nodupes', config.DontReceiveDuplicates), + ): + try: + newval = int(cgidata.getvalue(item)) + except (TypeError, ValueError): + newval = None + + # Skip this option if there was a problem or it wasn't changed. + # Note that delivery status is handled separate from the options + # flags. + if newval is None: + continue + elif flag == config.DisableDelivery: + status = mlist.getDeliveryStatus(user) + # Here, newval == 0 means enable, newval == 1 means disable + if not newval and status <> MemberAdaptor.ENABLED: + newval = MemberAdaptor.ENABLED + elif newval and status == MemberAdaptor.ENABLED: + newval = MemberAdaptor.BYUSER + else: + continue + elif newval == mlist.getMemberOption(user, flag): + continue + # Should we warn about one more digest? + if flag == config.Digests and \ + newval == 0 and mlist.getMemberOption(user, flag): + digestwarn = 1 + + newvals.append((flag, newval)) + + # The user language is handled a little differently + if userlang not in mlist.language_codes: + newvals.append((SETLANGUAGE, mlist.preferred_language)) + else: + newvals.append((SETLANGUAGE, userlang)) + + # Process user selected topics, but don't make the changes to the + # MailList object; we must do that down below when the list is + # locked. + topicnames = cgidata.getvalue('usertopic') + if topicnames: + # Some topics were selected. topicnames can actually be a string + # or a list of strings depending on whether more than one topic + # was selected or not. + if not isinstance(topicnames, list): + # Assume it was a bare string, so listify it + topicnames = [topicnames] + # unquote the topic names + topicnames = [urllib.unquote_plus(n) for n in topicnames] + + # The standard sigterm handler (see above) + def sigterm_handler(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + # Now, lock the list and perform the changes + mlist.Lock() + try: + # `values' is a tuple of flags and the web values + for flag, newval in newvals: + # Handle language settings differently + if flag == SETLANGUAGE: + mlist.setMemberLanguage(user, newval) + # Handle delivery status separately + elif flag == config.DisableDelivery: + mlist.setDeliveryStatus(user, newval) + else: + try: + mlist.setMemberOption(user, flag, newval) + except Errors.CantDigestError: + cantdigest = 1 + except Errors.MustDigestError: + mustdigest = 1 + # Set the topics information. + mlist.setMemberTopics(user, topicnames) + mlist.Save() + finally: + mlist.Unlock() + + # A bag of attributes for the global options + class Global: + enable = None + remind = None + nodupes = None + mime = None + def __nonzero__(self): + return len(self.__dict__.keys()) > 0 + + globalopts = Global() + + # The enable/disable option and the password remind option may have + # their global flags sets. + if cgidata.getvalue('deliver-globally'): + # Yes, this is inefficient, but the list is so small it shouldn't + # make much of a difference. + for flag, newval in newvals: + if flag == config.DisableDelivery: + globalopts.enable = newval + break + + if cgidata.getvalue('remind-globally'): + for flag, newval in newvals: + if flag == config.SuppressPasswordReminder: + globalopts.remind = newval + break + + if cgidata.getvalue('nodupes-globally'): + for flag, newval in newvals: + if flag == config.DontReceiveDuplicates: + globalopts.nodupes = newval + break + + if cgidata.getvalue('mime-globally'): + for flag, newval in newvals: + if flag == config.DisableMime: + globalopts.mime = newval + break + + # Change options globally, but only if this is the user or site admin, + # /not/ if this is the list admin. + if globalopts: + if not is_user_or_siteadmin: + doc.addError(_("""The list administrator may not change the + options for this user's other subscriptions. However the + options for this mailing list subscription has been + changed."""), _('Note: ')) + else: + for gmlist in lists_of_member(mlist, user): + global_options(gmlist, user, globalopts) + + # Now print the results + if cantdigest: + msg = _('''The list administrator has disabled digest delivery for + this list, so your delivery option has not been set. However your + other options have been set successfully.''') + elif mustdigest: + msg = _('''The list administrator has disabled non-digest delivery + for this list, so your delivery option has not been set. However + your other options have been set successfully.''') + else: + msg = _('You have successfully set your options.') + + if digestwarn: + msg += _('You may get one last digest.') + + options_page(mlist, doc, user, cpuser, userlang, msg) + print doc.Format() + return + + if mlist.isMember(user): + options_page(mlist, doc, user, cpuser, userlang) + else: + loginpage(mlist, doc, user, userlang) + print doc.Format() + + + +def options_page(mlist, doc, user, cpuser, userlang, message=''): + # The bulk of the document will come from the options.html template, which + # includes it's own html armor (head tags, etc.). Suppress the head that + # Document() derived pages get automatically. + doc.suppress_head = 1 + + if mlist.obscure_addresses: + presentable_user = Utils.ObscureEmail(user, for_text=1) + if cpuser is not None: + cpuser = Utils.ObscureEmail(cpuser, for_text=1) + else: + presentable_user = user + + fullname = Utils.uncanonstr(mlist.getMemberName(user), userlang) + if fullname: + presentable_user += ', %s' % fullname + + # Do replacements + replacements = mlist.GetStandardReplacements(userlang) + replacements['<mm-results>'] = Bold(FontSize('+1', message)).Format() + replacements['<mm-digest-radio-button>'] = mlist.FormatOptionButton( + config.Digests, 1, user) + replacements['<mm-undigest-radio-button>'] = mlist.FormatOptionButton( + config.Digests, 0, user) + replacements['<mm-plain-digests-button>'] = mlist.FormatOptionButton( + config.DisableMime, 1, user) + replacements['<mm-mime-digests-button>'] = mlist.FormatOptionButton( + config.DisableMime, 0, user) + replacements['<mm-global-mime-button>'] = ( + CheckBox('mime-globally', 1, checked=0).Format()) + replacements['<mm-delivery-enable-button>'] = mlist.FormatOptionButton( + config.DisableDelivery, 0, user) + replacements['<mm-delivery-disable-button>'] = mlist.FormatOptionButton( + config.DisableDelivery, 1, user) + replacements['<mm-disabled-notice>'] = mlist.FormatDisabledNotice(user) + replacements['<mm-dont-ack-posts-button>'] = mlist.FormatOptionButton( + config.AcknowledgePosts, 0, user) + replacements['<mm-ack-posts-button>'] = mlist.FormatOptionButton( + config.AcknowledgePosts, 1, user) + replacements['<mm-receive-own-mail-button>'] = mlist.FormatOptionButton( + config.DontReceiveOwnPosts, 0, user) + replacements['<mm-dont-receive-own-mail-button>'] = ( + mlist.FormatOptionButton(config.DontReceiveOwnPosts, 1, user)) + replacements['<mm-dont-get-password-reminder-button>'] = ( + mlist.FormatOptionButton(config.SuppressPasswordReminder, 1, user)) + replacements['<mm-get-password-reminder-button>'] = ( + mlist.FormatOptionButton(config.SuppressPasswordReminder, 0, user)) + replacements['<mm-public-subscription-button>'] = ( + mlist.FormatOptionButton(config.ConcealSubscription, 0, user)) + replacements['<mm-hide-subscription-button>'] = mlist.FormatOptionButton( + config.ConcealSubscription, 1, user) + replacements['<mm-dont-receive-duplicates-button>'] = ( + mlist.FormatOptionButton(config.DontReceiveDuplicates, 1, user)) + replacements['<mm-receive-duplicates-button>'] = ( + mlist.FormatOptionButton(config.DontReceiveDuplicates, 0, user)) + replacements['<mm-unsubscribe-button>'] = ( + mlist.FormatButton('unsub', _('Unsubscribe')) + '<br>' + + CheckBox('unsubconfirm', 1, checked=0).Format() + + _('<em>Yes, I really want to unsubscribe</em>')) + replacements['<mm-new-pass-box>'] = mlist.FormatSecureBox('newpw') + replacements['<mm-confirm-pass-box>'] = mlist.FormatSecureBox('confpw') + replacements['<mm-change-pass-button>'] = ( + mlist.FormatButton('changepw', _("Change My Password"))) + replacements['<mm-other-subscriptions-submit>'] = ( + mlist.FormatButton('othersubs', + _('List my other subscriptions'))) + replacements['<mm-form-start>'] = ( + mlist.FormatFormStart('options', user)) + replacements['<mm-user>'] = user + replacements['<mm-presentable-user>'] = presentable_user + replacements['<mm-email-my-pw>'] = mlist.FormatButton( + 'emailpw', (_('Email My Password To Me'))) + replacements['<mm-umbrella-notice>'] = ( + mlist.FormatUmbrellaNotice(user, _("password"))) + replacements['<mm-logout-button>'] = ( + mlist.FormatButton('logout', _('Log out'))) + replacements['<mm-options-submit-button>'] = mlist.FormatButton( + 'options-submit', _('Submit My Changes')) + replacements['<mm-global-pw-changes-button>'] = ( + CheckBox('pw-globally', 1, checked=0).Format()) + replacements['<mm-global-deliver-button>'] = ( + CheckBox('deliver-globally', 1, checked=0).Format()) + replacements['<mm-global-remind-button>'] = ( + CheckBox('remind-globally', 1, checked=0).Format()) + replacements['<mm-global-nodupes-button>'] = ( + CheckBox('nodupes-globally', 1, checked=0).Format()) + + days = int(config.PENDING_REQUEST_LIFE / config.days(1)) + if days > 1: + units = _('days') + else: + units = _('day') + replacements['<mm-pending-days>'] = _('%(days)d %(units)s') + + replacements['<mm-new-address-box>'] = mlist.FormatBox('new-address') + replacements['<mm-confirm-address-box>'] = mlist.FormatBox( + 'confirm-address') + replacements['<mm-change-address-button>'] = mlist.FormatButton( + 'change-of-address', _('Change My Address and Name')) + replacements['<mm-global-change-of-address>'] = CheckBox( + 'changeaddr-globally', 1, checked=0).Format() + replacements['<mm-fullname-box>'] = mlist.FormatBox( + 'fullname', value=fullname) + + # Create the topics radios. BAW: what if the list admin deletes a topic, + # but the user still wants to get that topic message? + usertopics = mlist.getMemberTopics(user) + if mlist.topics: + table = Table(border="0") + for name, pattern, description, emptyflag in mlist.topics: + if emptyflag: + continue + quotedname = urllib.quote_plus(name) + details = Link(mlist.GetScriptURL('options') + + '/%s/?VARHELP=%s' % (user, quotedname), + ' (Details)') + if name in usertopics: + checked = 1 + else: + checked = 0 + table.AddRow([CheckBox('usertopic', quotedname, checked=checked), + name + details.Format()]) + topicsfield = table.Format() + else: + topicsfield = _('<em>No topics defined</em>') + replacements['<mm-topics>'] = topicsfield + replacements['<mm-suppress-nonmatching-topics>'] = ( + mlist.FormatOptionButton(config.ReceiveNonmatchingTopics, 0, user)) + replacements['<mm-receive-nonmatching-topics>'] = ( + mlist.FormatOptionButton(config.ReceiveNonmatchingTopics, 1, user)) + + if cpuser is not None: + replacements['<mm-case-preserved-user>'] = _(''' +You are subscribed to this list with the case-preserved address +<em>%(cpuser)s</em>.''') + else: + replacements['<mm-case-preserved-user>'] = '' + + doc.AddItem(mlist.ParseTags('options.html', replacements, userlang)) + + + +def loginpage(mlist, doc, user, lang): + realname = mlist.real_name + actionurl = mlist.GetScriptURL('options') + if user is None: + title = _('%(realname)s list: member options login page') + extra = _('email address and ') + else: + safeuser = Utils.websafe(user) + title = _('%(realname)s list: member options for user %(safeuser)s') + obuser = Utils.ObscureEmail(user) + extra = '' + # Set up the title + doc.SetTitle(title) + # We use a subtable here so we can put a language selection box in + table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) + # If only one language is enabled for this mailing list, omit the choice + # buttons. + table.AddRow([Center(Header(2, title))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, + bgcolor=config.WEB_HEADER_COLOR) + if len(mlist.language_codes) > 1: + langform = Form(actionurl) + langform.AddItem(SubmitButton('displang-button', + _('View this page in'))) + langform.AddItem(mlist.GetLangSelectBox(lang)) + if user: + langform.AddItem(Hidden('email', user)) + table.AddRow([Center(langform)]) + doc.AddItem(table) + # Preamble + # Set up the login page + form = Form(actionurl) + table = Table(width='100%', border=0, cellspacing=4, cellpadding=5) + table.AddRow([_("""In order to change your membership option, you must + first log in by giving your %(extra)smembership password in the section + below. If you don't remember your membership password, you can have it + emailed to you by clicking on the button below. If you just want to + unsubscribe from this list, click on the <em>Unsubscribe</em> button and a + confirmation message will be sent to you. + + <p><strong><em>Important:</em></strong> From this point on, you must have + cookies enabled in your browser, otherwise none of your changes will take + effect. + """)]) + # Password and login button + ptable = Table(width='50%', border=0, cellspacing=4, cellpadding=5) + if user is None: + ptable.AddRow([Label(_('Email address:')), + TextBox('email', size=20)]) + else: + ptable.AddRow([Hidden('email', user)]) + ptable.AddRow([Label(_('Password:')), + PasswordBox('password', size=20)]) + ptable.AddRow([Center(SubmitButton('login', _('Log in')))]) + ptable.AddCellInfo(ptable.GetCurrentRowIndex(), 0, colspan=2) + table.AddRow([Center(ptable)]) + # Unsubscribe section + table.AddRow([Center(Header(2, _('Unsubscribe')))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, + bgcolor=config.WEB_HEADER_COLOR) + + table.AddRow([_("""By clicking on the <em>Unsubscribe</em> button, a + confirmation message will be emailed to you. This message will have a + link that you should click on to complete the removal process (you can + also confirm by email; see the instructions in the confirmation + message).""")]) + + table.AddRow([Center(SubmitButton('login-unsub', _('Unsubscribe')))]) + # Password reminder section + table.AddRow([Center(Header(2, _('Password reminder')))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, + bgcolor=config.WEB_HEADER_COLOR) + + table.AddRow([_("""By clicking on the <em>Remind</em> button, your + password will be emailed to you.""")]) + + table.AddRow([Center(SubmitButton('login-remind', _('Remind')))]) + # Finish up glomming together the login page + form.AddItem(table) + doc.AddItem(form) + doc.AddItem(mlist.GetMailmanFooter()) + + + +def lists_of_member(mlist, user): + hostname = mlist.host_name + onlists = [] + for listname in config.list_manager.names: + # The current list will always handle things in the mainline + if listname == mlist.internal_name(): + continue + glist = MailList.MailList(listname, lock=0) + if glist.host_name <> hostname: + continue + if not glist.isMember(user): + continue + onlists.append(glist) + return onlists + + + +def change_password(mlist, user, newpw): + # Must own the list lock! + mlist.Lock() + try: + # Change the user's password. The password must already have been + # compared to the confirmpw and otherwise been vetted for + # acceptability. + mlist.setMemberPassword(user, newpw) + mlist.Save() + finally: + mlist.Unlock() + + + +def global_options(mlist, user, globalopts): + # Is there anything to do? + for attr in dir(globalopts): + if attr.startswith('_'): + continue + if getattr(globalopts, attr) is not None: + break + else: + return + + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + # Must own the list lock! + mlist.Lock() + try: + if globalopts.enable is not None: + mlist.setDeliveryStatus(user, globalopts.enable) + + if globalopts.remind is not None: + mlist.setMemberOption(user, config.SuppressPasswordReminder, + globalopts.remind) + + if globalopts.nodupes is not None: + mlist.setMemberOption(user, config.DontReceiveDuplicates, + globalopts.nodupes) + + if globalopts.mime is not None: + mlist.setMemberOption(user, config.DisableMime, globalopts.mime) + + mlist.Save() + finally: + mlist.Unlock() + + + +def topic_details(mlist, doc, user, cpuser, userlang, varhelp): + # Find out which topic the user wants to get details of + reflist = varhelp.split('/') + name = None + topicname = _('<missing>') + if len(reflist) == 1: + topicname = urllib.unquote_plus(reflist[0]) + for name, pattern, description, emptyflag in mlist.topics: + if name == topicname: + break + else: + name = None + + if not name: + options_page(mlist, doc, user, cpuser, userlang, + _('Requested topic is not valid: %(topicname)s')) + print doc.Format() + return + + table = Table(border=3, width='100%') + table.AddRow([Center(Bold(_('Topic filter details')))]) + table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, + bgcolor=config.WEB_SUBHEADER_COLOR) + table.AddRow([Bold(Label(_('Name:'))), + Utils.websafe(name)]) + table.AddRow([Bold(Label(_('Pattern (as regexp):'))), + '<pre>' + Utils.websafe(OR.join(pattern.splitlines())) + + '</pre>']) + table.AddRow([Bold(Label(_('Description:'))), + Utils.websafe(description)]) + # Make colors look nice + for row in range(1, 4): + table.AddCellInfo(row, 0, bgcolor=config.WEB_ADMINITEM_COLOR) + + options_page(mlist, doc, user, cpuser, userlang, table.Format()) + print doc.Format() |
