diff options
Diffstat (limited to 'mailman/web/Cgi')
| -rw-r--r-- | mailman/web/Cgi/Auth.py | 60 | ||||
| -rw-r--r-- | mailman/web/Cgi/__init__.py | 0 | ||||
| -rw-r--r-- | mailman/web/Cgi/admin.py | 1433 | ||||
| -rw-r--r-- | mailman/web/Cgi/admindb.py | 813 | ||||
| -rw-r--r-- | mailman/web/Cgi/confirm.py | 834 | ||||
| -rw-r--r-- | mailman/web/Cgi/create.py | 400 | ||||
| -rw-r--r-- | mailman/web/Cgi/edithtml.py | 175 | ||||
| -rw-r--r-- | mailman/web/Cgi/listinfo.py | 207 | ||||
| -rw-r--r-- | mailman/web/Cgi/options.py | 1000 | ||||
| -rw-r--r-- | mailman/web/Cgi/private.py | 190 | ||||
| -rw-r--r-- | mailman/web/Cgi/rmlist.py | 243 | ||||
| -rw-r--r-- | mailman/web/Cgi/roster.py | 130 | ||||
| -rw-r--r-- | mailman/web/Cgi/subscribe.py | 252 | ||||
| -rw-r--r-- | mailman/web/Cgi/wsgi_app.py | 286 |
14 files changed, 0 insertions, 6023 deletions
diff --git a/mailman/web/Cgi/Auth.py b/mailman/web/Cgi/Auth.py deleted file mode 100644 index 825d972f4..000000000 --- a/mailman/web/Cgi/Auth.py +++ /dev/null @@ -1,60 +0,0 @@ -# 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/>. - -"""Common routines for logging in and logging out of the list administrator -and list moderator interface. -""" - -from Mailman import Utils -from Mailman.htmlformat import FontAttr -from Mailman.i18n import _ - - - -class NotLoggedInError(Exception): - """Exception raised when no matching admin cookie was found.""" - def __init__(self, message): - Exception.__init__(self, message) - self.message = message - - - -def loginpage(mlist, scriptname, msg='', frontpage=False): - url = mlist.GetScriptURL(scriptname) - if frontpage: - actionurl = url - else: - request = Utils.GetRequestURI(url).lstrip('/') - up = '../' * request.count('/') - actionurl = up + request - if msg: - msg = FontAttr(msg, color='#ff0000', size='+1').Format() - if scriptname == 'admindb': - who = _('Moderator') - else: - who = _('Administrator') - # Language stuff - charset = Utils.GetCharSet(mlist.preferred_language) - print 'Content-type: text/html; charset=' + charset + '\n\n' - print Utils.maketext( - 'admlogin.html', - {'listname': mlist.real_name, - 'path' : actionurl, - 'message' : msg, - 'who' : who, - }, mlist=mlist).encode(charset) - print mlist.GetMailmanFooter().encode(charset) diff --git a/mailman/web/Cgi/__init__.py b/mailman/web/Cgi/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/mailman/web/Cgi/__init__.py +++ /dev/null diff --git a/mailman/web/Cgi/admin.py b/mailman/web/Cgi/admin.py deleted file mode 100644 index e5c6ee14b..000000000 --- a/mailman/web/Cgi/admin.py +++ /dev/null @@ -1,1433 +0,0 @@ -# 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/>. - -"""Process and produce the list-administration options forms.""" - -import os -import re -import cgi -import sha -import sys -import urllib -import logging - -from email.Utils import unquote, parseaddr, formataddr -from string import lowercase, digits - -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.Cgi import Auth -from Mailman.UserDesc import UserDesc -from Mailman.configuration import config -from Mailman.htmlformat import * - -# Set up i18n -_ = i18n._ -i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) - -NL = '\n' -OPTCOLUMNS = 11 - -log = logging.getLogger('mailman.error') - - - -def main(): - # Try to find out which list is being administered - parts = Utils.GetPathPieces() - if not parts: - # None, so just do the admin overview and be done with it - admin_overview() - return - # Get the list object - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=False) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - admin_overview(_('No such list <em>%(safelistname)s</em>')) - log.error('admin.py access for non-existent list: %s', listname) - return - # Now that we know what list has been requested, all subsequent admin - # pages are shown in that list's preferred language. - i18n.set_language(mlist.preferred_language) - # If the user is not authenticated, we're done. - cgidata = cgi.FieldStorage(keep_blank_values=1) - - if not mlist.WebAuthenticate((config.AuthListAdmin, - config.AuthSiteAdmin), - cgidata.getvalue('adminpw', '')): - if cgidata.has_key('adminpw'): - # This is a re-authorization attempt - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - else: - msg = '' - Auth.loginpage(mlist, 'admin', msg=msg) - return - - # Which subcategory was requested? Default is `general' - if len(parts) == 1: - category = 'general' - subcat = None - elif len(parts) == 2: - category = parts[1] - subcat = None - else: - category = parts[1] - subcat = parts[2] - - # Is this a log-out request? - if category == 'logout': - print mlist.ZapCookie(config.AuthListAdmin) - Auth.loginpage(mlist, 'admin', frontpage=True) - return - - # Sanity check - if category not in mlist.GetConfigCategories().keys(): - category = 'general' - - # Is the request for variable details? - varhelp = None - qsenviron = os.environ.get('QUERY_STRING') - parsedqs = None - if qsenviron: - parsedqs = cgi.parse_qs(qsenviron) - if cgidata.has_key('VARHELP'): - varhelp = cgidata.getvalue('VARHELP') - elif parsedqs: - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = parsedqs.get('VARHELP') - if qs and isinstance(qs, list): - varhelp = qs[0] - if varhelp: - option_help(mlist, varhelp) - return - - # The html page document - doc = Document() - doc.set_language(mlist.preferred_language) - mlist.Lock() - try: - if cgidata.keys(): - # There are options to change - change_options(mlist, category, subcat, cgidata, doc) - # Let the list sanity check the changed values - mlist.CheckValues() - # Additional sanity checks - if not mlist.digestable and not mlist.nondigestable: - doc.addError( - _('''You have turned off delivery of both digest and - non-digest messages. This is an incompatible state of - affairs. You must turn on either digest delivery or - non-digest delivery or your mailing list will basically be - unusable.'''), tag=_('Warning: ')) - - if not mlist.digestable and mlist.getDigestMemberKeys(): - doc.addError( - _('''You have digest members, but digests are turned - off. Those people will not receive mail.'''), - tag=_('Warning: ')) - if not mlist.nondigestable and mlist.getRegularMemberKeys(): - doc.addError( - _('''You have regular list members but non-digestified mail is - turned off. They will receive mail until you fix this - problem.'''), tag=_('Warning: ')) - # Glom up the results page and print it out - show_results(mlist, doc, category, subcat, cgidata) - print doc.Format() - mlist.Save() - finally: - mlist.Unlock() - - - -def admin_overview(msg=''): - # Show the administrative overview page, with the list of all the lists on - # this host. msg is an optional error message to display at the top of - # the page. - # - # This page should be displayed in the server's default language, which - # should have already been set. - hostname = Utils.get_request_domain() - legend = _('%(hostname)s mailing lists - Admin Links') - # The html `document' - doc = Document() - doc.set_language(config.DEFAULT_SERVER_LANGUAGE) - doc.SetTitle(legend) - # The table that will hold everything - table = Table(border=0, width="100%") - table.AddRow([Center(Header(2, legend))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - # Skip any mailing list that isn't advertised. - advertised = [] - for name in sorted(config.list_manager.names): - mlist = MailList.MailList(name, lock=False) - if mlist.advertised: - if hostname not in mlist.web_page_url: - # This list is situated in a different virtual domain - continue - else: - advertised.append((mlist.GetScriptURL('admin'), - mlist.real_name, - mlist.description)) - # Greeting depends on whether there was an error or not - if msg: - greeting = FontAttr(msg, color="ff5060", size="+1") - else: - greeting = _("Welcome!") - - welcome = [] - mailmanlink = Link(config.MAILMAN_URL, _('Mailman')).Format() - if not advertised: - welcome.extend([ - greeting, - _('''<p>There currently are no publicly-advertised %(mailmanlink)s - mailing lists on %(hostname)s.'''), - ]) - else: - welcome.extend([ - greeting, - _('''<p>Below is the collection of publicly-advertised - %(mailmanlink)s mailing lists on %(hostname)s. Click on a list - name to visit the configuration pages for that list.'''), - ]) - - creatorurl = Utils.ScriptURL('create') - mailman_owner = Utils.get_site_noreply() - extra = msg and _('right ') or '' - welcome.extend([ - _('''To visit the administrators configuration page for an - unadvertised list, open a URL similar to this one, but with a '/' and - the %(extra)slist name appended. If you have the proper authority, - you can also <a href="%(creatorurl)s">create a new mailing list</a>. - - <p>General list information can be found at '''), - Link(Utils.ScriptURL('listinfo'), - _('the mailing list overview page')), - '.', - _('<p>(Send questions and comments to '), - Link('mailto:%s' % mailman_owner, mailman_owner), - '.)<p>', - ]) - - table.AddRow([Container(*welcome)]) - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) - - if advertised: - table.AddRow([' ', ' ']) - table.AddRow([Bold(FontAttr(_('List'), size='+2')), - Bold(FontAttr(_('Description'), size='+2')) - ]) - highlight = 1 - for url, real_name, description in advertised: - table.AddRow( - [Link(url, Bold(real_name)), - description or Italic(_('[no description available]'))]) - if highlight and config.WEB_HIGHLIGHT_COLOR: - table.AddRowInfo(table.GetCurrentRowIndex(), - bgcolor=config.WEB_HIGHLIGHT_COLOR) - highlight = not highlight - - doc.AddItem(table) - doc.AddItem('<hr>') - doc.AddItem(MailmanLogo()) - print doc.Format() - - - -def option_help(mlist, varhelp): - # The html page document - doc = Document() - doc.set_language(mlist.preferred_language) - # Find out which category and variable help is being requested for. - item = None - reflist = varhelp.split('/') - if len(reflist) >= 2: - category = subcat = None - if len(reflist) == 2: - category, varname = reflist - elif len(reflist) == 3: - category, subcat, varname = reflist - options = mlist.GetConfigInfo(category, subcat) - if options: - for i in options: - if i and i[0] == varname: - item = i - break - # Print an error message if we couldn't find a valid one - if not item: - bad = _('No valid variable name found.') - doc.addError(bad) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - # Get the details about the variable - varname, kind, params, dependancies, description, elaboration = \ - get_item_characteristics(item) - # Set up the document - realname = mlist.real_name - legend = _("""%(realname)s Mailing list Configuration Help - <br><em>%(varname)s</em> Option""") - - header = Table(width='100%') - header.AddRow([Center(Header(3, legend))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - doc.SetTitle(_("Mailman %(varname)s List Option Help")) - doc.AddItem(header) - doc.AddItem("<b>%s</b> (%s): %s<p>" % (varname, category, description)) - if elaboration: - doc.AddItem("%s<p>" % elaboration) - - if subcat: - url = '%s/%s/%s' % (mlist.GetScriptURL('admin'), category, subcat) - else: - url = '%s/%s' % (mlist.GetScriptURL('admin'), category) - form = Form(url) - valtab = Table(cellspacing=3, cellpadding=4, width='100%') - add_options_table_item(mlist, category, subcat, valtab, item, detailsp=0) - form.AddItem(valtab) - form.AddItem('<p>') - form.AddItem(Center(submit_button())) - doc.AddItem(Center(form)) - - doc.AddItem(_("""<em><strong>Warning:</strong> changing this option here - could cause other screens to be out-of-sync. Be sure to reload any other - pages that are displaying this option for this mailing list. You can also - """)) - - adminurl = mlist.GetScriptURL('admin') - if subcat: - url = '%s/%s/%s' % (adminurl, category, subcat) - else: - url = '%s/%s' % (adminurl, category) - categoryname = mlist.GetConfigCategories()[category][0] - doc.AddItem(Link(url, _('return to the %(categoryname)s options page.'))) - doc.AddItem('</em>') - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def show_results(mlist, doc, category, subcat, cgidata): - # Produce the results page - adminurl = mlist.GetScriptURL('admin') - categories = mlist.GetConfigCategories() - label = _(categories[category][0]) - - # Set up the document's headers - realname = mlist.real_name - doc.SetTitle(_('%(realname)s Administration (%(label)s)')) - doc.AddItem(Center(Header(2, _( - '%(realname)s mailing list administration<br>%(label)s Section')))) - doc.AddItem('<hr>') - # Now we need to craft the form that will be submitted, which will contain - # all the variable settings, etc. This is a bit of a kludge because we - # know that the autoreply and members categories supports file uploads. - encoding = None - if category in ('autoreply', 'members'): - encoding = 'multipart/form-data' - if subcat: - form = Form('%s/%s/%s' % (adminurl, category, subcat), - encoding=encoding) - else: - form = Form('%s/%s' % (adminurl, category), encoding=encoding) - # This holds the two columns of links - linktable = Table(valign='top', width='100%') - linktable.AddRow([Center(Bold(_("Configuration Categories"))), - Center(Bold(_("Other Administrative Activities")))]) - # The `other links' are stuff in the right column. - otherlinks = UnorderedList() - otherlinks.AddItem(Link(mlist.GetScriptURL('admindb'), - _('Tend to pending moderator requests'))) - otherlinks.AddItem(Link(mlist.GetScriptURL('listinfo'), - _('Go to the general list information page'))) - otherlinks.AddItem(Link(mlist.GetScriptURL('edithtml'), - _('Edit the public HTML pages and text files'))) - otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(), - _('Go to list archives')).Format() + - '<br> <br>') - if config.OWNERS_CAN_DELETE_THEIR_OWN_LISTS: - otherlinks.AddItem(Link(mlist.GetScriptURL('rmlist'), - _('Delete this mailing list')).Format() + - _(' (requires confirmation)<br> <br>')) - otherlinks.AddItem(Link('%s/logout' % adminurl, - # BAW: What I really want is a blank line, but - # adding an won't do it because of the - # bullet added to the list item. - '<FONT SIZE="+2"><b>%s</b></FONT>' % - _('Logout'))) - # These are links to other categories and live in the left column - categorylinks_1 = categorylinks = UnorderedList() - categorylinks_2 = '' - categorykeys = categories.keys() - half = len(categorykeys) / 2 - counter = 0 - subcat = None - for k in categorykeys: - label = _(categories[k][0]) - url = '%s/%s' % (adminurl, k) - if k == category: - # Handle subcategories - subcats = mlist.GetConfigSubCategories(k) - if subcats: - subcat = Utils.GetPathPieces()[-1] - for k, v in subcats: - if k == subcat: - break - else: - # The first subcategory in the list is the default - subcat = subcats[0][0] - subcat_items = [] - for sub, text in subcats: - if sub == subcat: - text = Bold('[%s]' % text).Format() - subcat_items.append(Link(url + '/' + sub, text)) - categorylinks.AddItem( - Bold(label).Format() + - UnorderedList(*subcat_items).Format()) - else: - categorylinks.AddItem(Link(url, Bold('[%s]' % label))) - else: - categorylinks.AddItem(Link(url, label)) - counter += 1 - if counter >= half: - categorylinks_2 = categorylinks = UnorderedList() - counter = -len(categorykeys) - # Make the emergency stop switch a rude solo light - etable = Table() - # Add all the links to the links table... - etable.AddRow([categorylinks_1, categorylinks_2]) - etable.AddRowInfo(etable.GetCurrentRowIndex(), valign='top') - if mlist.emergency: - label = _('Emergency moderation of all list traffic is enabled') - etable.AddRow([Center( - Link('?VARHELP=general/emergency', Bold(label)))]) - color = config.WEB_ERROR_COLOR - etable.AddCellInfo(etable.GetCurrentRowIndex(), 0, - colspan=2, bgcolor=color) - linktable.AddRow([etable, otherlinks]) - # ...and add the links table to the document. - form.AddItem(linktable) - form.AddItem('<hr>') - form.AddItem( - _('''Make your changes in the following section, then submit them - using the <em>Submit Your Changes</em> button below.''') - + '<p>') - - # The members and passwords categories are special in that they aren't - # defined in terms of gui elements. Create those pages here. - if category == 'members': - # Figure out which subcategory we should display - subcat = Utils.GetPathPieces()[-1] - if subcat not in ('list', 'add', 'remove'): - subcat = 'list' - # Add member category specific tables - form.AddItem(membership_options(mlist, subcat, cgidata, doc, form)) - form.AddItem(Center(submit_button('setmemberopts_btn'))) - # In "list" subcategory, we can also search for members - if subcat == 'list': - form.AddItem('<hr>\n') - table = Table(width='100%') - table.AddRow([Center(Header(2, _('Additional Member Tasks')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - # Add a blank separator row - table.AddRow([' ', ' ']) - # Add a section to set the moderation bit for all members - table.AddRow([_("""<li>Set everyone's moderation bit, including - those members not currently visible""")]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([RadioButtonArray('allmodbit_val', - (_('Off'), _('On')), - mlist.default_member_moderation), - SubmitButton('allmodbit_btn', _('Set'))]) - form.AddItem(table) - elif category == 'passwords': - form.AddItem(Center(password_inputs(mlist))) - form.AddItem(Center(submit_button())) - else: - form.AddItem(show_variables(mlist, category, subcat, cgidata, doc)) - form.AddItem(Center(submit_button())) - # And add the form - doc.AddItem(form) - doc.AddItem(mlist.GetMailmanFooter()) - - - -def show_variables(mlist, category, subcat, cgidata, doc): - options = mlist.GetConfigInfo(category, subcat) - - # The table containing the results - table = Table(cellspacing=3, cellpadding=4, width='100%') - - # Get and portray the text label for the category. - categories = mlist.GetConfigCategories() - label = _(categories[category][0]) - - table.AddRow([Center(Header(2, label))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - - # The very first item in the config info will be treated as a general - # description if it is a string - description = options[0] - if isinstance(description, basestring): - table.AddRow([description]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - options = options[1:] - - if not options: - return table - - # Add the global column headers - table.AddRow([Center(Bold(_('Description'))), - Center(Bold(_('Value')))]) - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, - width='15%') - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 1, - width='85%') - - for item in options: - if isinstance(item, basestring): - # The very first banner option (string in an options list) is - # treated as a general description, while any others are - # treated as section headers - centered and italicized... - table.AddRow([Center(Italic(item))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - else: - add_options_table_item(mlist, category, subcat, table, item) - table.AddRow(['<br>']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - return table - - - -def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): - # Add a row to an options table with the item description and value. - varname, kind, params, extra, descr, elaboration = \ - get_item_characteristics(item) - if elaboration is None: - elaboration = descr - descr = get_item_gui_description(mlist, category, subcat, - varname, descr, elaboration, detailsp) - val = get_item_gui_value(mlist, category, kind, varname, params, extra) - table.AddRow([descr, val]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=config.WEB_ADMINITEM_COLOR) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, - bgcolor=config.WEB_ADMINITEM_COLOR) - - - -def get_item_characteristics(record): - # Break out the components of an item description from its description - # record: - # - # 0 -- option-var name - # 1 -- type - # 2 -- entry size - # 3 -- ?dependancies? - # 4 -- Brief description - # 5 -- Optional description elaboration - if len(record) == 5: - elaboration = None - varname, kind, params, dependancies, descr = record - elif len(record) == 6: - varname, kind, params, dependancies, descr, elaboration = record - else: - raise ValueError, _('Badly formed options entry:\n %(record)s') - return varname, kind, params, dependancies, descr, elaboration - - - -def get_item_gui_value(mlist, category, kind, varname, params, extra): - """Return a representation of an item's settings.""" - # Give the category a chance to return the value for the variable - value = None - label, gui = mlist.GetConfigCategories()[category] - if hasattr(gui, 'getValue'): - value = gui.getValue(mlist, kind, varname, params) - # Filter out None, and volatile attributes - if value is None and not varname.startswith('_'): - value = getattr(mlist, varname) - # Now create the widget for this value - if kind == config.Radio or kind == config.Toggle: - # If we are returning the option for subscribe policy and this site - # doesn't allow open subscribes, then we have to alter the value of - # mlist.subscribe_policy as passed to RadioButtonArray in order to - # compensate for the fact that there is one fewer option. - # Correspondingly, we alter the value back in the change options - # function -scott - # - # TBD: this is an ugly ugly hack. - if varname.startswith('_'): - checked = 0 - else: - checked = value - if varname == 'subscribe_policy' and not config.ALLOW_OPEN_SUBSCRIBE: - checked = checked - 1 - # For Radio buttons, we're going to interpret the extra stuff as a - # horizontal/vertical flag. For backwards compatibility, the value 0 - # means horizontal, so we use "not extra" to get the parity right. - return RadioButtonArray(varname, params, checked, not extra) - elif (kind == config.String or kind == config.Email or - kind == config.Host or kind == config.Number): - return TextBox(varname, value, params) - elif kind == config.Text: - if params: - r, c = params - else: - r, c = None, None - return TextArea(varname, value or '', r, c) - elif kind in (config.EmailList, config.EmailListEx): - if params: - r, c = params - else: - r, c = None, None - res = NL.join(value) - return TextArea(varname, res, r, c, wrap='off') - elif kind == config.FileUpload: - # like a text area, but also with uploading - if params: - r, c = params - else: - r, c = None, None - container = Container() - container.AddItem(_('<em>Enter the text below, or...</em><br>')) - container.AddItem(TextArea(varname, value or '', r, c)) - container.AddItem(_('<br><em>...specify a file to upload</em><br>')) - container.AddItem(FileUpload(varname+'_upload', r, c)) - return container - elif kind == config.Select: - if params: - values, legend, selected = params - else: - codes = mlist.language_codes - legend = [config.languages.get_description(code) for code in codes] - selected = codes.index(mlist.preferred_language) - return SelectOptions(varname, values, legend, selected) - elif kind == config.Topics: - # A complex and specialized widget type that allows for setting of a - # topic name, a mark button, a regexp text box, an "add after mark", - # and a delete button. Yeesh! params are ignored. - table = Table(border=0) - # This adds the html for the entry widget - def makebox(i, name, pattern, desc, empty=False, table=table): - deltag = 'topic_delete_%02d' % i - boxtag = 'topic_box_%02d' % i - reboxtag = 'topic_rebox_%02d' % i - desctag = 'topic_desc_%02d' % i - wheretag = 'topic_where_%02d' % i - addtag = 'topic_add_%02d' % i - newtag = 'topic_new_%02d' % i - if empty: - table.AddRow([Center(Bold(_('Topic %(i)d'))), - Hidden(newtag)]) - else: - table.AddRow([Center(Bold(_('Topic %(i)d'))), - SubmitButton(deltag, _('Delete'))]) - table.AddRow([Label(_('Topic name:')), - TextBox(boxtag, value=name, size=30)]) - table.AddRow([Label(_('Regexp:')), - TextArea(reboxtag, text=pattern, - rows=4, cols=30, wrap='off')]) - table.AddRow([Label(_('Description:')), - TextArea(desctag, text=desc, - rows=4, cols=30, wrap='soft')]) - if not empty: - table.AddRow([SubmitButton(addtag, _('Add new item...')), - SelectOptions(wheretag, ('before', 'after'), - (_('...before this one.'), - _('...after this one.')), - selected=1), - ]) - table.AddRow(['<hr>']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - # Now for each element in the existing data, create a widget - i = 1 - data = getattr(mlist, varname) - for name, pattern, desc, empty in data: - makebox(i, name, pattern, desc, empty) - i += 1 - # Add one more non-deleteable widget as the first blank entry, but - # only if there are no real entries. - if i == 1: - makebox(i, '', '', '', empty=True) - return table - elif kind == config.HeaderFilter: - # A complex and specialized widget type that allows for setting of a - # spam filter rule including, a mark button, a regexp text box, an - # "add after mark", up and down buttons, and a delete button. Yeesh! - # params are ignored. - table = Table(border=0) - # This adds the html for the entry widget - def makebox(i, pattern, action, empty=False, table=table): - deltag = 'hdrfilter_delete_%02d' % i - reboxtag = 'hdrfilter_rebox_%02d' % i - actiontag = 'hdrfilter_action_%02d' % i - wheretag = 'hdrfilter_where_%02d' % i - addtag = 'hdrfilter_add_%02d' % i - newtag = 'hdrfilter_new_%02d' % i - uptag = 'hdrfilter_up_%02d' % i - downtag = 'hdrfilter_down_%02d' % i - if empty: - table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), - Hidden(newtag)]) - else: - table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), - SubmitButton(deltag, _('Delete'))]) - table.AddRow([Label(_('Spam Filter Regexp:')), - TextArea(reboxtag, text=pattern, - rows=4, cols=30, wrap='off')]) - values = [config.DEFER, config.HOLD, config.REJECT, - config.DISCARD, config.ACCEPT] - try: - checked = values.index(action) - except ValueError: - checked = 0 - radio = RadioButtonArray( - actiontag, - (_('Defer'), _('Hold'), _('Reject'), - _('Discard'), _('Accept')), - values=values, - checked=checked).Format() - table.AddRow([Label(_('Action:')), radio]) - if not empty: - table.AddRow([SubmitButton(addtag, _('Add new item...')), - SelectOptions(wheretag, ('before', 'after'), - (_('...before this one.'), - _('...after this one.')), - selected=1), - ]) - # BAW: IWBNI we could disable the up and down buttons for the - # first and last item respectively, but it's not easy to know - # which is the last item, so let's not worry about that for - # now. - table.AddRow([SubmitButton(uptag, _('Move rule up')), - SubmitButton(downtag, _('Move rule down'))]) - table.AddRow(['<hr>']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - # Now for each element in the existing data, create a widget - i = 1 - data = getattr(mlist, varname) - for pattern, action, empty in data: - makebox(i, pattern, action, empty) - i += 1 - # Add one more non-deleteable widget as the first blank entry, but - # only if there are no real entries. - if i == 1: - makebox(i, '', config.DEFER, empty=True) - return table - elif kind == config.Checkbox: - return CheckBoxArray(varname, *params) - else: - assert 0, 'Bad gui widget type: %s' % kind - - - -def get_item_gui_description(mlist, category, subcat, - varname, descr, elaboration, detailsp): - # Return the item's description, with link to details. - # - # Details are not included if this is a VARHELP page, because that /is/ - # the details page! - if detailsp: - if subcat: - varhelp = '?VARHELP=%s/%s/%s' % (category, subcat, varname) - else: - varhelp = '?VARHELP=%s/%s' % (category, varname) - if descr == elaboration: - linktext = _('<br>(Edit <b>%(varname)s</b>)') - else: - linktext = _('<br>(Details for <b>%(varname)s</b>)') - link = Link(mlist.GetScriptURL('admin') + varhelp, - linktext).Format() - text = Label('%s %s' % (descr, link)).Format() - else: - text = Label(descr).Format() - if varname[0] == '_': - text += Label(_('''<br><em><strong>Note:</strong> - setting this value performs an immediate action but does not modify - permanent state.</em>''')).Format() - return text - - - -def membership_options(mlist, subcat, cgidata, doc, form): - # Show the main stuff - adminurl = mlist.GetScriptURL('admin') - container = Container() - header = Table(width="100%") - # If we're in the list subcategory, show the membership list - if subcat == 'add': - header.AddRow([Center(Header(2, _('Mass Subscriptions')))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - container.AddItem(header) - mass_subscribe(mlist, container) - return container - if subcat == 'remove': - header.AddRow([Center(Header(2, _('Mass Removals')))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - container.AddItem(header) - mass_remove(mlist, container) - return container - # Otherwise... - header.AddRow([Center(Header(2, _('Membership List')))]) - header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - container.AddItem(header) - # Add a "search for member" button - table = Table(width='100%') - link = Link('http://www.python.org/doc/current/lib/re-syntax.html', - _('(help)')).Format() - table.AddRow([Label(_('Find member %(link)s:')), - TextBox('findmember', - value=cgidata.getvalue('findmember', '')), - SubmitButton('findmember_btn', _('Search...'))]) - container.AddItem(table) - container.AddItem('<hr><p>') - usertable = Table(width="90%", border='2') - # If there are more members than allowed by chunksize, then we split the - # membership up alphabetically. Otherwise just display them all. - chunksz = mlist.admin_member_chunksize - # The email addresses had /better/ be ASCII, but might be encoded in the - # database as Unicodes. - all = [_m.encode() for _m in mlist.getMembers()] - all.sort(lambda x, y: cmp(x.lower(), y.lower())) - # See if the query has a regular expression - regexp = cgidata.getvalue('findmember', '').strip() - if regexp: - try: - cre = re.compile(regexp, re.IGNORECASE) - except re.error: - doc.addError(_('Bad regular expression: ') + regexp) - else: - # BAW: There's got to be a more efficient way of doing this! - names = [mlist.getMemberName(s) or '' for s in all] - all = [a for n, a in zip(names, all) - if cre.search(n) or cre.search(a)] - chunkindex = None - bucket = None - actionurl = None - if len(all) < chunksz: - members = all - else: - # Split them up alphabetically, and then split the alphabetical - # listing by chunks - buckets = {} - for addr in all: - members = buckets.setdefault(addr[0].lower(), []) - members.append(addr) - # Now figure out which bucket we want - bucket = None - qs = {} - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qsenviron = os.environ.get('QUERY_STRING') - if qsenviron: - qs = cgi.parse_qs(qsenviron) - bucket = qs.get('letter', 'a')[0].lower() - if bucket not in digits + lowercase: - bucket = None - if not bucket or not buckets.has_key(bucket): - keys = buckets.keys() - keys.sort() - bucket = keys[0] - members = buckets[bucket] - action = adminurl + '/members?letter=%s' % bucket - if len(members) <= chunksz: - form.set_action(action) - else: - i, r = divmod(len(members), chunksz) - numchunks = i + (not not r * 1) - # Now chunk them up - chunkindex = 0 - if qs.has_key('chunk'): - try: - chunkindex = int(qs['chunk'][0]) - except ValueError: - chunkindex = 0 - if chunkindex < 0 or chunkindex > numchunks: - chunkindex = 0 - members = members[chunkindex*chunksz:(chunkindex+1)*chunksz] - # And set the action URL - form.set_action(action + '&chunk=%s' % chunkindex) - # So now members holds all the addresses we're going to display - allcnt = len(all) - if bucket: - membercnt = len(members) - usertable.AddRow([Center(Italic(_( - '%(allcnt)s members total, %(membercnt)s shown')))]) - else: - usertable.AddRow([Center(Italic(_('%(allcnt)s members total')))]) - usertable.AddCellInfo(usertable.GetCurrentRowIndex(), - usertable.GetCurrentCellIndex(), - colspan=OPTCOLUMNS, - bgcolor=config.WEB_ADMINITEM_COLOR) - # Add the alphabetical links - if bucket: - cells = [] - for letter in digits + lowercase: - if not buckets.get(letter): - continue - url = adminurl + '/members?letter=%s' % letter - if letter == bucket: - show = Bold('[%s]' % letter.upper()).Format() - else: - show = letter.upper() - cells.append(Link(url, show).Format()) - joiner = ' '*2 + '\n' - usertable.AddRow([Center(joiner.join(cells))]) - usertable.AddCellInfo(usertable.GetCurrentRowIndex(), - usertable.GetCurrentCellIndex(), - colspan=OPTCOLUMNS, - bgcolor=config.WEB_ADMINITEM_COLOR) - usertable.AddRow([Center(h) for h in (_('unsub'), - _('member address<br>member name'), - _('mod'), _('hide'), - _('nomail<br>[reason]'), - _('ack'), _('not metoo'), - _('nodupes'), - _('digest'), _('plain'), - _('language'))]) - rowindex = usertable.GetCurrentRowIndex() - for i in range(OPTCOLUMNS): - usertable.AddCellInfo(rowindex, i, bgcolor=config.WEB_ADMINITEM_COLOR) - # Find the longest name in the list - longest = 0 - if members: - names = filter(None, [mlist.getMemberName(s) for s in members]) - # Make the name field at least as long as the longest email address - longest = max([len(s) for s in names + members]) - # Abbreviations for delivery status details - ds_abbrevs = {MemberAdaptor.UNKNOWN : _('?'), - MemberAdaptor.BYUSER : _('U'), - MemberAdaptor.BYADMIN : _('A'), - MemberAdaptor.BYBOUNCE: _('B'), - } - # Now populate the rows - for addr in members: - link = Link(mlist.GetOptionsURL(addr, obscure=1), - mlist.getMemberCPAddress(addr)) - fullname = mlist.getMemberName(addr) - name = TextBox(addr + '_realname', fullname, size=longest).Format() - cells = [Center(CheckBox(addr + '_unsub', 'off', 0).Format()), - link.Format() + '<br>' + - name + - Hidden('user', urllib.quote(addr)).Format(), - ] - # Do the `mod' option - if mlist.getMemberOption(addr, config.Moderate): - value = 'on' - checked = 1 - else: - value = 'off' - checked = 0 - box = CheckBox('%s_mod' % addr, value, checked) - cells.append(Center(box).Format()) - for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes'): - extra = '' - if opt == 'nomail': - status = mlist.getDeliveryStatus(addr) - if status == MemberAdaptor.ENABLED: - value = 'off' - checked = 0 - else: - value = 'on' - checked = 1 - extra = '[%s]' % ds_abbrevs[status] - elif mlist.getMemberOption(addr, config.OPTINFO[opt]): - value = 'on' - checked = 1 - else: - value = 'off' - checked = 0 - box = CheckBox('%s_%s' % (addr, opt), value, checked) - cells.append(Center(box.Format() + extra)) - # This code is less efficient than the original which did a has_key on - # the underlying dictionary attribute. This version is slower and - # less memory efficient. It points to a new MemberAdaptor interface - # method. - if addr in mlist.getRegularMemberKeys(): - cells.append(Center(CheckBox(addr + '_digest', 'off', 0).Format())) - else: - cells.append(Center(CheckBox(addr + '_digest', 'on', 1).Format())) - if mlist.getMemberOption(addr, config.OPTINFO['plain']): - value = 'on' - checked = 1 - else: - value = 'off' - checked = 0 - cells.append(Center(CheckBox('%s_plain' % addr, value, checked))) - # User's preferred language - langpref = mlist.getMemberLanguage(addr) - langs = mlist.language_codes - langdescs = [_(config.languges.get_description(code)) - for code in langs] - try: - selected = langs.index(langpref) - except ValueError: - selected = 0 - cells.append(Center(SelectOptions(addr + '_language', langs, - langdescs, selected)).Format()) - usertable.AddRow(cells) - # Add the usertable and a legend - legend = UnorderedList() - legend.AddItem( - _('<b>unsub</b> -- Click on this to unsubscribe the member.')) - legend.AddItem( - _("""<b>mod</b> -- The user's personal moderation flag. If this is - set, postings from them will be moderated, otherwise they will be - approved.""")) - legend.AddItem( - _("""<b>hide</b> -- Is the member's address concealed on - the list of subscribers?""")) - legend.AddItem(_( - """<b>nomail</b> -- Is delivery to the member disabled? If so, an - abbreviation will be given describing the reason for the disabled - delivery: - <ul><li><b>U</b> -- Delivery was disabled by the user via their - personal options page. - <li><b>A</b> -- Delivery was disabled by the list - administrators. - <li><b>B</b> -- Delivery was disabled by the system due to - excessive bouncing from the member's address. - <li><b>?</b> -- The reason for disabled delivery isn't known. - This is the case for all memberships which were disabled - in older versions of Mailman. - </ul>""")) - legend.AddItem( - _('''<b>ack</b> -- Does the member get acknowledgements of their - posts?''')) - legend.AddItem( - _('''<b>not metoo</b> -- Does the member want to avoid copies of their - own postings?''')) - legend.AddItem( - _('''<b>nodupes</b> -- Does the member want to avoid duplicates of the - same message?''')) - legend.AddItem( - _('''<b>digest</b> -- Does the member get messages in digests? - (otherwise, individual messages)''')) - legend.AddItem( - _('''<b>plain</b> -- If getting digests, does the member get plain - text digests? (otherwise, MIME)''')) - legend.AddItem(_("<b>language</b> -- Language preferred by the user")) - addlegend = '' - parsedqs = 0 - qsenviron = os.environ.get('QUERY_STRING') - if qsenviron: - qs = cgi.parse_qs(qsenviron).get('legend') - if qs and isinstance(qs, list): - qs = qs[0] - if qs == 'yes': - addlegend = 'legend=yes&' - if addlegend: - container.AddItem(legend.Format() + '<p>') - container.AddItem( - Link(adminurl + '/members/list', - _('Click here to hide the legend for this table.'))) - else: - container.AddItem( - Link(adminurl + '/members/list?legend=yes', - _('Click here to include the legend for this table.'))) - container.AddItem(Center(usertable)) - - # There may be additional chunks - if chunkindex is not None: - buttons = [] - url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) - footer = _('''<p><em>To view more members, click on the appropriate - range listed below:</em>''') - chunkmembers = buckets[bucket] - last = len(chunkmembers) - for i in range(numchunks): - if i == chunkindex: - continue - start = chunkmembers[i*chunksz] - end = chunkmembers[min((i+1)*chunksz, last)-1] - link = Link(url + 'chunk=%d' % i, _('from %(start)s to %(end)s')) - buttons.append(link) - buttons = UnorderedList(*buttons) - container.AddItem(footer + buttons.Format() + '<p>') - return container - - - -def mass_subscribe(mlist, container): - # MASS SUBSCRIBE - GREY = config.WEB_ADMINITEM_COLOR - table = Table(width='90%') - table.AddRow([ - Label(_('Subscribe these users now or invite them?')), - RadioButtonArray('subscribe_or_invite', - (_('Subscribe'), _('Invite')), - 0, values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([ - Label(_('Send welcome messages to new subscribees?')), - RadioButtonArray('send_welcome_msg_to_this_batch', - (_('No'), _('Yes')), - mlist.send_welcome_msg, - values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([ - Label(_('Send notifications of new subscriptions to the list owner?')), - RadioButtonArray('send_notifications_to_list_owner', - (_('No'), _('Yes')), - mlist.admin_notify_mchanges, - values=(0,1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([Italic(_('Enter one address per line below...'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Center(TextArea(name='subscribees', - rows=10, cols='70%', wrap=None))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Italic(Label(_('...or specify a file to upload:'))), - FileUpload('subscribees_upload', cols='50')]) - container.AddItem(Center(table)) - # Invitation text - table.AddRow([' ', ' ']) - table.AddRow([Italic(_("""Below, enter additional text to be added to the - top of your invitation or the subscription notification. Include at least - one blank line at the end..."""))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Center(TextArea(name='invitation', - rows=10, cols='70%', wrap=None))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - - - -def mass_remove(mlist, container): - # MASS UNSUBSCRIBE - GREY = config.WEB_ADMINITEM_COLOR - table = Table(width='90%') - table.AddRow([ - Label(_('Send unsubscription acknowledgement to the user?')), - RadioButtonArray('send_unsub_ack_to_this_batch', - (_('No'), _('Yes')), - 0, values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([ - Label(_('Send notifications to the list owner?')), - RadioButtonArray('send_unsub_notifications_to_list_owner', - (_('No'), _('Yes')), - mlist.admin_notify_mchanges, - values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([Italic(_('Enter one address per line below...'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Center(TextArea(name='unsubscribees', - rows=10, cols='70%', wrap=None))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Italic(Label(_('...or specify a file to upload:'))), - FileUpload('unsubscribees_upload', cols='50')]) - container.AddItem(Center(table)) - - - -def password_inputs(mlist): - adminurl = mlist.GetScriptURL('admin') - table = Table(cellspacing=3, cellpadding=4) - table.AddRow([Center(Header(2, _('Change list ownership passwords')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - table.AddRow([_("""\ -The <em>list administrators</em> are the people who have ultimate control over -all parameters of this mailing list. They are able to change any list -configuration variable available through these administration web pages. - -<p>The <em>list moderators</em> have more limited permissions; they are not -able to change any list configuration variable, but they are allowed to tend -to pending administration requests, including approving or rejecting held -subscription requests, and disposing of held postings. Of course, the -<em>list administrators</em> can also tend to pending requests. - -<p>In order to split the list ownership duties into administrators and -moderators, you must set a separate moderator password in the fields below, -and also provide the email addresses of the list moderators in the -<a href="%(adminurl)s/general">general options section</a>.""")]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - # Set up the admin password table on the left - atable = Table(border=0, cellspacing=3, cellpadding=4, - bgcolor=config.WEB_ADMINPW_COLOR) - atable.AddRow([Label(_('Enter new administrator password:')), - PasswordBox('newpw', size=20)]) - atable.AddRow([Label(_('Confirm administrator password:')), - PasswordBox('confirmpw', size=20)]) - # Set up the moderator password table on the right - mtable = Table(border=0, cellspacing=3, cellpadding=4, - bgcolor=config.WEB_ADMINPW_COLOR) - mtable.AddRow([Label(_('Enter new moderator password:')), - PasswordBox('newmodpw', size=20)]) - mtable.AddRow([Label(_('Confirm moderator password:')), - PasswordBox('confirmmodpw', size=20)]) - # Add these tables to the overall password table - table.AddRow([atable, mtable]) - return table - - - -def submit_button(name='submit'): - table = Table(border=0, cellspacing=0, cellpadding=2) - table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, align='middle') - return table - - - -def change_options(mlist, category, subcat, cgidata, doc): - def safeint(formvar, defaultval=None): - try: - return int(cgidata.getvalue(formvar)) - except (ValueError, TypeError): - return defaultval - confirmed = 0 - # Handle changes to the list moderator password. Do this before checking - # the new admin password, since the latter will force a reauthentication. - new = cgidata.getvalue('newmodpw', '').strip() - confirm = cgidata.getvalue('confirmmodpw', '').strip() - if new or confirm: - if new == confirm: - mlist.mod_password = passwords.make_secret( - new, config.PASSWORD_SCHEME) - # No re-authentication necessary because the moderator's - # password doesn't get you into these pages. - else: - doc.addError(_('Moderator passwords did not match')) - # Handle changes to the list administrator password - new = cgidata.getvalue('newpw', '').strip() - confirm = cgidata.getvalue('confirmpw', '').strip() - if new or confirm: - if new == confirm: - mlist.password = passwords.make_secret(new, config.PASSWORD_SCHEME) - # Set new cookie - print mlist.MakeCookie(config.AuthListAdmin) - else: - doc.addError(_('Administrator passwords did not match')) - # Give the individual gui item a chance to process the form data - categories = mlist.GetConfigCategories() - label, gui = categories[category] - # BAW: We handle the membership page special... for now. - if category <> 'members': - gui.handleForm(mlist, category, subcat, cgidata, doc) - # mass subscription, removal processing for members category - subscribers = '' - subscribers += cgidata.getvalue('subscribees', '') - subscribers += cgidata.getvalue('subscribees_upload', '') - if subscribers: - entries = filter(None, [n.strip() for n in subscribers.splitlines()]) - send_welcome_msg = safeint('send_welcome_msg_to_this_batch', - mlist.send_welcome_msg) - send_admin_notif = safeint('send_notifications_to_list_owner', - mlist.admin_notify_mchanges) - # Default is to subscribe - subscribe_or_invite = safeint('subscribe_or_invite', 0) - invitation = cgidata.getvalue('invitation', '') - digest = mlist.digest_is_default - if not mlist.digestable: - digest = 0 - if not mlist.nondigestable: - digest = 1 - subscribe_errors = [] - subscribe_success = [] - # Now cruise through all the subscribees and do the deed. BAW: we - # should limit the number of "Successfully subscribed" status messages - # we display. Try uploading a file with 10k names -- it takes a while - # to render the status page. - for entry in entries: - fullname, address = parseaddr(entry) - # Canonicalize the full name - fullname = Utils.canonstr(fullname, mlist.preferred_language) - userdesc = UserDesc(address, fullname, - Utils.MakeRandomPassword(), - digest, mlist.preferred_language) - try: - if subscribe_or_invite: - if mlist.isMember(address): - raise Errors.MMAlreadyAMember - else: - mlist.InviteNewMember(userdesc, invitation) - else: - mlist.ApprovedAddMember(userdesc, send_welcome_msg, - send_admin_notif, invitation, - whence='admin mass sub') - except Errors.MMAlreadyAMember: - subscribe_errors.append((entry, _('Already a member'))) - except Errors.InvalidEmailAddress: - if userdesc.address == '': - subscribe_errors.append((_('<blank line>'), - _('Bad/Invalid email address'))) - else: - subscribe_errors.append((entry, - _('Bad/Invalid email address'))) - except Errors.MembershipIsBanned, pattern: - subscribe_errors.append( - (entry, _('Banned address (matched %(pattern)s)'))) - else: - member = Utils.uncanonstr(formataddr((fullname, address))) - subscribe_success.append(Utils.websafe(member)) - if subscribe_success: - if subscribe_or_invite: - doc.AddItem(Header(5, _('Successfully invited:'))) - else: - doc.AddItem(Header(5, _('Successfully subscribed:'))) - doc.AddItem(UnorderedList(*subscribe_success)) - doc.AddItem('<p>') - if subscribe_errors: - if subscribe_or_invite: - doc.AddItem(Header(5, _('Error inviting:'))) - else: - doc.AddItem(Header(5, _('Error subscribing:'))) - items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] - doc.AddItem(UnorderedList(*items)) - doc.AddItem('<p>') - # Unsubscriptions - removals = '' - if cgidata.has_key('unsubscribees'): - removals += cgidata['unsubscribees'].value - if cgidata.has_key('unsubscribees_upload') and \ - cgidata['unsubscribees_upload'].value: - removals += cgidata['unsubscribees_upload'].value - if removals: - names = filter(None, [n.strip() for n in removals.splitlines()]) - send_unsub_notifications = int( - cgidata['send_unsub_notifications_to_list_owner'].value) - userack = int( - cgidata['send_unsub_ack_to_this_batch'].value) - unsubscribe_errors = [] - unsubscribe_success = [] - for addr in names: - try: - mlist.ApprovedDeleteMember( - addr, whence='admin mass unsub', - admin_notif=send_unsub_notifications, - userack=userack) - unsubscribe_success.append(addr) - except Errors.NotAMemberError: - unsubscribe_errors.append(addr) - if unsubscribe_success: - doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) - doc.AddItem(UnorderedList(*unsubscribe_success)) - doc.AddItem('<p>') - if unsubscribe_errors: - doc.AddItem(Header(3, Bold(FontAttr( - _('Cannot unsubscribe non-members:'), - color='#ff0000', size='+2')).Format())) - doc.AddItem(UnorderedList(*unsubscribe_errors)) - doc.AddItem('<p>') - # See if this was a moderation bit operation - if cgidata.has_key('allmodbit_btn'): - val = cgidata.getvalue('allmodbit_val') - try: - val = int(val) - except VallueError: - val = None - if val not in (0, 1): - doc.addError(_('Bad moderation flag value')) - else: - for member in mlist.getMembers(): - mlist.setMemberOption(member, config.Moderate, val) - # do the user options for members category - if cgidata.has_key('setmemberopts_btn') and cgidata.has_key('user'): - user = cgidata['user'] - if isinstance(user, list): - users = [] - for ui in range(len(user)): - users.append(urllib.unquote(user[ui].value)) - else: - users = [urllib.unquote(user.value)] - errors = [] - removes = [] - for user in users: - if cgidata.has_key('%s_unsub' % user): - try: - mlist.ApprovedDeleteMember(user, whence='member mgt page') - removes.append(user) - except Errors.NotAMemberError: - errors.append((user, _('Not subscribed'))) - continue - if not mlist.isMember(user): - doc.addError(_('Ignoring changes to deleted member: %(user)s'), - tag=_('Warning: ')) - continue - value = cgidata.has_key('%s_digest' % user) - try: - mlist.setMemberOption(user, config.Digests, value) - except (Errors.AlreadyReceivingDigests, - Errors.AlreadyReceivingRegularDeliveries, - Errors.CantDigestError, - Errors.MustDigestError): - # BAW: Hmm... - pass - - newname = cgidata.getvalue(user+'_realname', '') - newname = Utils.canonstr(newname, mlist.preferred_language) - mlist.setMemberName(user, newname) - - newlang = cgidata.getvalue(user+'_language') - oldlang = mlist.getMemberLanguage(user) - if (newlang not in config.languages.enabled_codes - and newlang <> oldlang): - # Then - mlist.setMemberLanguage(user, newlang) - - moderate = not not cgidata.getvalue(user+'_mod') - mlist.setMemberOption(user, config.Moderate, moderate) - - # Set the `nomail' flag, but only if the user isn't already - # disabled (otherwise we might change BYUSER into BYADMIN). - if cgidata.has_key('%s_nomail' % user): - if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED: - mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) - else: - mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) - for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): - opt_code = config.OPTINFO[opt] - if cgidata.has_key('%s_%s' % (user, opt)): - mlist.setMemberOption(user, opt_code, 1) - else: - mlist.setMemberOption(user, opt_code, 0) - # Give some feedback on who's been removed - if removes: - doc.AddItem(Header(5, _('Successfully Removed:'))) - doc.AddItem(UnorderedList(*removes)) - doc.AddItem('<p>') - if errors: - doc.AddItem(Header(5, _("Error Unsubscribing:"))) - items = ['%s -- %s' % (x[0], x[1]) for x in errors] - doc.AddItem(apply(UnorderedList, tuple((items)))) - doc.AddItem("<p>") diff --git a/mailman/web/Cgi/admindb.py b/mailman/web/Cgi/admindb.py deleted file mode 100644 index 5d756fc78..000000000 --- a/mailman/web/Cgi/admindb.py +++ /dev/null @@ -1,813 +0,0 @@ -# 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 process the pending-approval items for a list.""" - -import os -import cgi -import sys -import time -import email -import errno -import logging - -from urllib import quote_plus, unquote_plus - -from Mailman import Errors -from Mailman import MailList -from Mailman import Message -from Mailman import Utils -from Mailman import i18n -from Mailman.Cgi import Auth -from Mailman.Handlers.Moderate import ModeratedMemberPost -from Mailman.ListAdmin import readMessage -from Mailman.configuration import config -from Mailman.htmlformat import * -from Mailman.interfaces import RequestType - -EMPTYSTRING = '' -NL = '\n' - -# 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) - -EXCERPT_HEIGHT = 10 -EXCERPT_WIDTH = 76 - -log = logging.getLogger('mailman.error') - - - -def helds_by_sender(mlist): - bysender = {} - requests = config.db.get_list_requests(mlist) - for request in requests.of_type(RequestType.held_message): - key, data = requests.get_request(request.id) - sender = data.get('sender') - assert sender is not None, ( - 'No sender for held message: %s' % request.id) - bysender.setdefault(sender, []).append(request.id) - return bysender - - -def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): - # We can't use a RadioButtonArray here because horizontal placement can be - # confusing to the user and vertical placement takes up too much - # real-estate. This is a hack! - space = ' ' * spacing - btns = Table(cellspacing='5', cellpadding='0') - btns.AddRow([space + text + space for text in labels]) - btns.AddRow([Center(RadioButton(btnname, value, default)) - for value, default in zip(values, defaults)]) - return btns - - - -def main(): - # Figure out which list is being requested - parts = Utils.GetPathPieces() - if not parts: - handle_no_list() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - handle_no_list(_('No such list <em>%(safelistname)s</em>')) - log.error('No such list "%s": %s\n', listname, e) - return - - # Now that we know which list to use, set the system's language to it. - i18n.set_language(mlist.preferred_language) - - # Make sure the user is authorized to see this page. - cgidata = cgi.FieldStorage(keep_blank_values=1) - - if not mlist.WebAuthenticate((config.AuthListAdmin, - config.AuthListModerator, - config.AuthSiteAdmin), - cgidata.getvalue('adminpw', '')): - if cgidata.has_key('adminpw'): - # This is a re-authorization attempt - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - else: - msg = '' - Auth.loginpage(mlist, 'admindb', msg=msg) - return - - # Set up the results document - doc = Document() - doc.set_language(mlist.preferred_language) - - # See if we're requesting all the messages for a particular sender, or if - # we want a specific held message. - sender = None - msgid = None - details = None - envar = os.environ.get('QUERY_STRING') - if envar: - # POST methods, even if their actions have a query string, don't get - # put into FieldStorage's keys :-( - qs = cgi.parse_qs(envar).get('sender') - if qs and isinstance(qs, list): - sender = qs[0] - qs = cgi.parse_qs(envar).get('msgid') - if qs and isinstance(qs, list): - msgid = qs[0] - qs = cgi.parse_qs(envar).get('details') - if qs and isinstance(qs, list): - details = qs[0] - - mlist.Lock() - try: - realname = mlist.real_name - if not cgidata.keys() or cgidata.has_key('admlogin'): - # If this is not a form submission (i.e. there are no keys in the - # form) or it's a login, then we don't need to do much special. - doc.SetTitle(_('%(realname)s Administrative Database')) - elif not details: - # This is a form submission - doc.SetTitle(_('%(realname)s Administrative Database Results')) - process_form(mlist, doc, cgidata) - # Now print the results and we're done. Short circuit for when there - # are no pending requests, but be sure to save the results! - if config.db.requests.get_list_requests(mlist).count == 0: - title = _('%(realname)s Administrative Database') - doc.SetTitle(title) - doc.AddItem(Header(2, title)) - doc.AddItem(_('There are no pending requests.')) - doc.AddItem(' ') - doc.AddItem(Link(mlist.GetScriptURL('admindb'), - _('Click here to reload this page.'))) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - mlist.Save() - return - - admindburl = mlist.GetScriptURL('admindb') - form = Form(admindburl) - # Add the instructions template - if details == 'instructions': - doc.AddItem(Header( - 2, _('Detailed instructions for the administrative database'))) - else: - doc.AddItem(Header( - 2, - _('Administrative requests for mailing list:') - + ' <em>%s</em>' % mlist.real_name)) - if details <> 'instructions': - form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) - requestsdb = config.db.get_list_requests(mlist) - message_count = requestsdb.count_of(RequestType.held_message) - if not (details or sender or msgid or message_count == 0): - form.AddItem(Center( - CheckBox('discardalldefersp', 0).Format() + - ' ' + - _('Discard all messages marked <em>Defer</em>') - )) - # Add a link back to the overview, if we're not viewing the overview! - adminurl = mlist.GetScriptURL('admin') - d = {'listname' : mlist.real_name, - 'detailsurl': admindburl + '?details=instructions', - 'summaryurl': admindburl, - 'viewallurl': admindburl + '?details=all', - 'adminurl' : adminurl, - 'filterurl' : adminurl + '/privacy/sender', - } - addform = 1 - if sender: - esender = Utils.websafe(sender) - d['description'] = _("all of %(esender)s's held messages.") - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_sender_requests(mlist, form, sender) - elif msgid: - d['description'] = _('a single held message.') - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_message_requests(mlist, form, msgid) - elif details == 'all': - d['description'] = _('all held messages.') - doc.AddItem(Utils.maketext('admindbpreamble.html', d, - raw=1, mlist=mlist)) - show_detailed_requests(mlist, form) - elif details == 'instructions': - doc.AddItem(Utils.maketext('admindbdetails.html', d, - raw=1, mlist=mlist)) - addform = 0 - else: - # Show a summary of all requests - doc.AddItem(Utils.maketext('admindbsummary.html', d, - raw=1, mlist=mlist)) - num = show_pending_subs(mlist, form) - num += show_pending_unsubs(mlist, form) - num += show_helds_overview(mlist, form) - addform = num > 0 - # Finish up the document, adding buttons to the form - if addform: - doc.AddItem(form) - form.AddItem('<hr>') - if not (details or sender or msgid or nomessages): - form.AddItem(Center( - CheckBox('discardalldefersp', 0).Format() + - ' ' + - _('Discard all messages marked <em>Defer</em>') - )) - form.AddItem(Center(SubmitButton('submit', _('Submit All Data')))) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - # Commit all changes - mlist.Save() - finally: - mlist.Unlock() - - - -def handle_no_list(msg=''): - # Print something useful if no list was given. - doc = Document() - doc.set_language(config.DEFAULT_SERVER_LANGUAGE) - - header = _('Mailman Administrative Database Error') - doc.SetTitle(header) - doc.AddItem(Header(2, header)) - doc.AddItem(msg) - url = Utils.ScriptURL('admin') - link = Link(url, _('list of available mailing lists.')).Format() - doc.AddItem(_('You must specify a list name. Here is the %(link)s')) - doc.AddItem('<hr>') - doc.AddItem(MailmanLogo()) - print doc.Format() - - - -def show_pending_subs(mlist, form): - # Add the subscription request section - requestsdb = config.db.get_list_requests(mlist) - if requestsdb.count_of(RequestType.subscription) == 0: - return 0 - form.AddItem('<hr>') - form.AddItem(Center(Header(2, _('Subscription Requests')))) - table = Table(border=2) - table.AddRow([Center(Bold(_('Address/name'))), - Center(Bold(_('Your decision'))), - Center(Bold(_('Reason for refusal'))) - ]) - # Alphabetical order by email address - byaddrs = {} - for request in requestsdb.of_type(RequestType.subscription): - key, data = requestsdb.get_request(requst.id) - addr = data['addr'] - byaddrs.setdefault(addr, []).append(request.id) - addrs = sorted(byaddrs) - num = 0 - for addr, ids in byaddrs.items(): - # Eliminate duplicates - for id in ids[1:]: - mlist.HandleRequest(id, config.DISCARD) - id = ids[0] - key, data = requestsdb.get_request(id) - time = data['time'] - addr = data['addr'] - fullname = data['fullname'] - passwd = data['passwd'] - digest = data['digest'] - lang = data['lang'] - fullname = Utils.uncanonstr(fullname, mlist.preferred_language) - radio = RadioButtonArray(id, (_('Defer'), - _('Approve'), - _('Reject'), - _('Discard')), - values=(config.DEFER, - config.SUBSCRIBE, - config.REJECT, - config.DISCARD), - checked=0).Format() - if addr not in mlist.ban_list: - radio += '<br>' + CheckBox('ban-%d' % id, 1).Format() + \ - ' ' + _('Permanently ban from this list') - # While the address may be a unicode, it must be ascii - paddr = addr.encode('us-ascii', 'replace') - table.AddRow(['%s<br><em>%s</em>' % (paddr, fullname), - radio, - TextBox('comment-%d' % id, size=40) - ]) - num += 1 - if num > 0: - form.AddItem(table) - return num - - - -def show_pending_unsubs(mlist, form): - # Add the pending unsubscription request section - lang = mlist.preferred_language - requestsdb = config.db.get_list_requests(mlist) - if requestsdb.count_of(RequestType.unsubscription) == 0: - return 0 - table = Table(border=2) - table.AddRow([Center(Bold(_('User address/name'))), - Center(Bold(_('Your decision'))), - Center(Bold(_('Reason for refusal'))) - ]) - # Alphabetical order by email address - byaddrs = {} - for request in requestsdb.of_type(RequestType.unsubscription): - key, data = requestsdb.get_request(request.id) - addr = data['addr'] - byaddrs.setdefault(addr, []).append(request.id) - addrs = sorted(byaddrs) - num = 0 - for addr, ids in byaddrs.items(): - # Eliminate duplicates - for id in ids[1:]: - mlist.HandleRequest(id, config.DISCARD) - id = ids[0] - key, data = requestsdb.get_record(id) - addr = data['addr'] - try: - fullname = Utils.uncanonstr(mlist.getMemberName(addr), lang) - except Errors.NotAMemberError: - # They must have been unsubscribed elsewhere, so we can just - # discard this record. - mlist.HandleRequest(id, config.DISCARD) - continue - num += 1 - table.AddRow(['%s<br><em>%s</em>' % (addr, fullname), - RadioButtonArray(id, (_('Defer'), - _('Approve'), - _('Reject'), - _('Discard')), - values=(config.DEFER, - config.UNSUBSCRIBE, - config.REJECT, - config.DISCARD), - checked=0), - TextBox('comment-%d' % id, size=45) - ]) - if num > 0: - form.AddItem('<hr>') - form.AddItem(Center(Header(2, _('Unsubscription Requests')))) - form.AddItem(table) - return num - - - -def show_helds_overview(mlist, form): - # Sort the held messages by sender - bysender = helds_by_sender(mlist) - if not bysender: - return 0 - form.AddItem('<hr>') - form.AddItem(Center(Header(2, _('Held Messages')))) - # Add the by-sender overview tables - admindburl = mlist.GetScriptURL('admindb') - table = Table(border=0) - form.AddItem(table) - senders = bysender.keys() - senders.sort() - for sender in senders: - qsender = quote_plus(sender) - esender = Utils.websafe(sender) - senderurl = admindburl + '?sender=' + qsender - # The encompassing sender table - stable = Table(border=1) - stable.AddRow([Center(Bold(_('From:')).Format() + esender)]) - stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2) - left = Table(border=0) - left.AddRow([_('Action to take on all these held messages:')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - btns = hacky_radio_buttons( - 'senderaction-' + qsender, - (_('Defer'), _('Accept'), _('Reject'), _('Discard')), - (config.DEFER, config.APPROVE, config.REJECT, config.DISCARD), - (1, 0, 0, 0)) - left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - left.AddRow([ - CheckBox('senderpreserve-' + qsender, 1).Format() + - ' ' + - _('Preserve messages for the site administrator') - ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - left.AddRow([ - CheckBox('senderforward-' + qsender, 1).Format() + - ' ' + - _('Forward messages (individually) to:') - ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - left.AddRow([ - TextBox('senderforwardto-' + qsender, - value=mlist.GetOwnerEmail()) - ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - # If the sender is a member and the message is being held due to a - # moderation bit, give the admin a chance to clear the member's mod - # bit. If this sender is not a member and is not already on one of - # the sender filters, then give the admin a chance to add this sender - # to one of the filters. - if mlist.isMember(sender): - if mlist.getMemberOption(sender, config.Moderate): - left.AddRow([ - CheckBox('senderclearmodp-' + qsender, 1).Format() + - ' ' + - _("Clear this member's <em>moderate</em> flag") - ]) - else: - left.AddRow( - [_('<em>The sender is now a member of this list</em>')]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - elif sender not in (mlist.accept_these_nonmembers + - mlist.hold_these_nonmembers + - mlist.reject_these_nonmembers + - mlist.discard_these_nonmembers): - left.AddRow([ - CheckBox('senderfilterp-' + qsender, 1).Format() + - ' ' + - _('Add <b>%(esender)s</b> to one of these sender filters:') - ]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - btns = hacky_radio_buttons( - 'senderfilter-' + qsender, - (_('Accepts'), _('Holds'), _('Rejects'), _('Discards')), - (config.ACCEPT, config.HOLD, config.REJECT, config.DISCARD), - (0, 0, 0, 1)) - left.AddRow([btns]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - if sender not in mlist.ban_list: - left.AddRow([ - CheckBox('senderbanp-' + qsender, 1).Format() + - ' ' + - _("""Ban <b>%(esender)s</b> from ever subscribing to this - mailing list""")]) - left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2) - right = Table(border=0) - right.AddRow([ - _("""Click on the message number to view the individual - message, or you can """) + - Link(senderurl, _('view all messages from %(esender)s')).Format() - ]) - right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2) - right.AddRow([' ', ' ']) - counter = 1 - for id in bysender[sender]: - key, data = requestsdb.get_record(id) - ptime = data['ptime'] - sender = data['sender'] - subject = data['subject'] - reason = data['reason'] - filename = data['filename'] - msgdata = data['msgdata'] - # BAW: This is really the size of the message pickle, which should - # be close, but won't be exact. Sigh, good enough. - try: - size = os.path.getsize(os.path.join(config.DATA_DIR, filename)) - except OSError, e: - if e.errno <> errno.ENOENT: raise - # This message must have gotten lost, i.e. it's already been - # handled by the time we got here. - mlist.HandleRequest(id, config.DISCARD) - continue - dispsubj = Utils.oneline( - subject, Utils.GetCharSet(mlist.preferred_language)) - t = Table(border=0) - t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter), - Bold(_('Subject:')), - Utils.websafe(dispsubj) - ]) - t.AddRow([' ', Bold(_('Size:')), str(size) + _(' bytes')]) - if reason: - reason = _(reason) - else: - reason = _('not available') - t.AddRow([' ', Bold(_('Reason:')), reason]) - # Include the date we received the message, if available - when = msgdata.get('received_time') - if when: - t.AddRow([' ', Bold(_('Received:')), - time.ctime(when)]) - counter += 1 - right.AddRow([t]) - stable.AddRow([left, right]) - table.AddRow([stable]) - return 1 - - - -def show_sender_requests(mlist, form, sender): - bysender = helds_by_sender(mlist) - if not bysender: - return - sender_ids = bysender.get(sender) - if sender_ids is None: - # BAW: should we print an error message? - return - total = len(sender_ids) - requestsdb = config.db.get_list_requests(mlist) - for i, id in enumerate(sender_ids): - key, data = requestsdb.get_record(id) - show_post_requests(mlist, id, data, total, count + 1, form) - - - -def show_message_requests(mlist, form, id): - requestdb = config.db.get_list_requests(mlist) - try: - id = int(id) - info = requestdb.get_record(id) - except (ValueError, KeyError): - # BAW: print an error message? - return - show_post_requests(mlist, id, info, 1, 1, form) - - - -def show_detailed_requests(mlist, form): - requestsdb = config.db.get_list_requests(mlist) - total = requestsdb.count_of(RequestType.held_message) - all = requestsdb.of_type(RequestType.held_message) - for i, request in enumerate(all): - key, data = requestdb.get_request(request.id) - show_post_requests(mlist, request.id, data, total, i + 1, form) - - - -def show_post_requests(mlist, id, info, total, count, form): - # For backwards compatibility with pre 2.0beta3 - if len(info) == 5: - ptime, sender, subject, reason, filename = info - msgdata = {} - else: - ptime, sender, subject, reason, filename, msgdata = info - form.AddItem('<hr>') - # Header shown on each held posting (including count of total) - msg = _('Posting Held for Approval') - if total <> 1: - msg += _(' (%(count)d of %(total)d)') - form.AddItem(Center(Header(2, msg))) - # We need to get the headers and part of the textual body of the message - # being held. The best way to do this is to use the email Parser to get - # an actual object, which will be easier to deal with. We probably could - # just do raw reads on the file. - try: - msg = readMessage(os.path.join(config.DATA_DIR, filename)) - except IOError, e: - if e.errno <> errno.ENOENT: - raise - form.AddItem(_('<em>Message with id #%(id)d was lost.')) - form.AddItem('<p>') - # BAW: kludge to remove id from requests.db. - try: - mlist.HandleRequest(id, config.DISCARD) - except Errors.LostHeldMessage: - pass - return - except email.Errors.MessageParseError: - form.AddItem(_('<em>Message with id #%(id)d is corrupted.')) - # BAW: Should we really delete this, or shuttle it off for site admin - # to look more closely at? - form.AddItem('<p>') - # BAW: kludge to remove id from requests.db. - try: - mlist.HandleRequest(id, config.DISCARD) - except Errors.LostHeldMessage: - pass - return - # Get the header text and the message body excerpt - lines = [] - chars = 0 - # A negative value means, include the entire message regardless of size - limit = config.ADMINDB_PAGE_TEXT_LIMIT - for line in email.Iterators.body_line_iterator(msg): - lines.append(line) - chars += len(line) - if chars > limit > 0: - break - # Negative values mean display the entire message, regardless of size - if limit > 0: - body = EMPTYSTRING.join(lines)[:config.ADMINDB_PAGE_TEXT_LIMIT] - else: - body = EMPTYSTRING.join(lines) - # Get message charset and try encode in list charset - mcset = msg.get_param('charset', 'us-ascii').lower() - lcset = Utils.GetCharSet(mlist.preferred_language) - if mcset <> lcset: - try: - body = unicode(body, mcset).encode(lcset) - except (LookupError, UnicodeError, ValueError): - pass - hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in msg.items()]) - hdrtxt = Utils.websafe(hdrtxt) - # Okay, we've reconstituted the message just fine. Now for the fun part! - t = Table(cellspacing=0, cellpadding=0, width='100%') - t.AddRow([Bold(_('From:')), sender]) - row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() - t.AddCellInfo(row, col-1, align='right') - t.AddRow([Bold(_('Subject:')), - Utils.websafe(Utils.oneline(subject, lcset))]) - t.AddCellInfo(row+1, col-1, align='right') - t.AddRow([Bold(_('Reason:')), _(reason)]) - t.AddCellInfo(row+2, col-1, align='right') - when = msgdata.get('received_time') - if when: - t.AddRow([Bold(_('Received:')), time.ctime(when)]) - t.AddCellInfo(row+2, col-1, align='right') - # We can't use a RadioButtonArray here because horizontal placement can be - # confusing to the user and vertical placement takes up too much - # real-estate. This is a hack! - buttons = Table(cellspacing="5", cellpadding="0") - buttons.AddRow(map(lambda x, s=' '*5: s+x+s, - (_('Defer'), _('Approve'), _('Reject'), _('Discard')))) - buttons.AddRow([Center(RadioButton(id, config.DEFER, 1)), - Center(RadioButton(id, config.APPROVE, 0)), - Center(RadioButton(id, config.REJECT, 0)), - Center(RadioButton(id, config.DISCARD, 0)), - ]) - t.AddRow([Bold(_('Action:')), buttons]) - t.AddCellInfo(row+3, col-1, align='right') - t.AddRow([' ', - CheckBox('preserve-%d' % id, 'on', 0).Format() + - ' ' + _('Preserve message for site administrator') - ]) - t.AddRow([' ', - CheckBox('forward-%d' % id, 'on', 0).Format() + - ' ' + _('Additionally, forward this message to: ') + - TextBox('forward-addr-%d' % id, size=47, - value=mlist.GetOwnerEmail()).Format() - ]) - notice = msgdata.get('rejection_notice', _('[No explanation given]')) - t.AddRow([ - Bold(_('If you reject this post,<br>please explain (optional):')), - TextArea('comment-%d' % id, rows=4, cols=EXCERPT_WIDTH, - text = Utils.wrap(_(notice), column=80)) - ]) - row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() - t.AddCellInfo(row, col-1, align='right') - t.AddRow([Bold(_('Message Headers:')), - TextArea('headers-%d' % id, hdrtxt, - rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)]) - row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex() - t.AddCellInfo(row, col-1, align='right') - t.AddRow([Bold(_('Message Excerpt:')), - TextArea('fulltext-%d' % id, Utils.websafe(body), - rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)]) - t.AddCellInfo(row+1, col-1, align='right') - form.AddItem(t) - form.AddItem('<p>') - - - -def process_form(mlist, doc, cgidata): - senderactions = {} - # Sender-centric actions - for k in cgidata.keys(): - for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-', - 'senderforwardto-', 'senderfilterp-', 'senderfilter-', - 'senderclearmodp-', 'senderbanp-'): - if k.startswith(prefix): - action = k[:len(prefix)-1] - sender = unquote_plus(k[len(prefix):]) - value = cgidata.getvalue(k) - senderactions.setdefault(sender, {})[action] = value - # discard-all-defers - try: - discardalldefersp = cgidata.getvalue('discardalldefersp', 0) - except ValueError: - discardalldefersp = 0 - for sender in senderactions.keys(): - actions = senderactions[sender] - # Handle what to do about all this sender's held messages - try: - action = int(actions.get('senderaction', config.DEFER)) - except ValueError: - action = config.DEFER - if action == config.DEFER and discardalldefersp: - action = config.DISCARD - if action in (config.DEFER, config.APPROVE, - config.REJECT, config.DISCARD): - preserve = actions.get('senderpreserve', 0) - forward = actions.get('senderforward', 0) - forwardaddr = actions.get('senderforwardto', '') - comment = _('No reason given') - bysender = helds_by_sender(mlist) - for id in bysender.get(sender, []): - try: - mlist.HandleRequest(id, action, comment, preserve, - forward, forwardaddr) - except (KeyError, Errors.LostHeldMessage): - # That's okay, it just means someone else has already - # updated the database while we were staring at the page, - # so just ignore it - continue - # Now see if this sender should be added to one of the nonmember - # sender filters. - if actions.get('senderfilterp', 0): - try: - which = int(actions.get('senderfilter')) - except ValueError: - # Bogus form - which = 'ignore' - if which == config.ACCEPT: - mlist.accept_these_nonmembers.append(sender) - elif which == config.HOLD: - mlist.hold_these_nonmembers.append(sender) - elif which == config.REJECT: - mlist.reject_these_nonmembers.append(sender) - elif which == config.DISCARD: - mlist.discard_these_nonmembers.append(sender) - # Otherwise, it's a bogus form, so ignore it - # And now see if we're to clear the member's moderation flag. - if actions.get('senderclearmodp', 0): - try: - mlist.setMemberOption(sender, config.Moderate, 0) - except Errors.NotAMemberError: - # This person's not a member any more. Oh well. - pass - # And should this address be banned? - if actions.get('senderbanp', 0): - if sender not in mlist.ban_list: - mlist.ban_list.append(sender) - # Now, do message specific actions - banaddrs = [] - erroraddrs = [] - for k in cgidata.keys(): - formv = cgidata[k] - if isinstance(formv, list): - continue - try: - v = int(formv.value) - request_id = int(k) - except ValueError: - continue - if v not in (config.DEFER, config.APPROVE, config.REJECT, - config.DISCARD, config.SUBSCRIBE, config.UNSUBSCRIBE, - config.ACCEPT, config.HOLD): - continue - # Get the action comment and reasons if present. - commentkey = 'comment-%d' % request_id - preservekey = 'preserve-%d' % request_id - forwardkey = 'forward-%d' % request_id - forwardaddrkey = 'forward-addr-%d' % request_id - bankey = 'ban-%d' % request_id - # Defaults - comment = _('[No reason given]') - preserve = 0 - forward = 0 - forwardaddr = '' - if cgidata.has_key(commentkey): - comment = cgidata[commentkey].value - if cgidata.has_key(preservekey): - preserve = cgidata[preservekey].value - if cgidata.has_key(forwardkey): - forward = cgidata[forwardkey].value - if cgidata.has_key(forwardaddrkey): - forwardaddr = cgidata[forwardaddrkey].value - # Should we ban this address? Do this check before handling the - # request id because that will evict the record. - requestsdb = config.db.get_list_requests(mlist) - if cgidata.getvalue(bankey): - key, data = requestsdb.get_record(request_id) - sender = data['sender'] - if sender not in mlist.ban_list: - mlist.ban_list.append(sender) - # Handle the request id - try: - mlist.HandleRequest(request_id, v, comment, - preserve, forward, forwardaddr) - except (KeyError, Errors.LostHeldMessage): - # That's okay, it just means someone else has already updated the - # database while we were staring at the page, so just ignore it - continue - except Errors.MMAlreadyAMember, v: - erroraddrs.append(v) - except Errors.MembershipIsBanned, pattern: - data = requestsdb.get_record(request_id) - sender = data['sender'] - banaddrs.append((sender, pattern)) - # save the list and print the results - doc.AddItem(Header(2, _('Database Updated...'))) - if erroraddrs: - for addr in erroraddrs: - doc.AddItem(`addr` + _(' is already a member') + '<br>') - if banaddrs: - for addr, patt in banaddrs: - doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '<br>') diff --git a/mailman/web/Cgi/confirm.py b/mailman/web/Cgi/confirm.py deleted file mode 100644 index 188e43068..000000000 --- a/mailman/web/Cgi/confirm.py +++ /dev/null @@ -1,834 +0,0 @@ -# Copyright (C) 2001-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/>. - -"""Confirm a pending action via URL.""" - -import cgi -import time -import signal -import logging - -from Mailman import Errors -from Mailman import MailList -from Mailman import Pending -from Mailman import i18n -from Mailman.UserDesc import UserDesc -from Mailman.configuration import config -from Mailman.htmlformat import * - -# Set up i18n -_ = i18n._ -i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) - -log = logging.getLogger('mailman.error') - - - -def main(): - doc = Document() - doc.set_language(config.DEFAULT_SERVER_LANGUAGE) - - parts = Utils.GetPathPieces() - if not parts or len(parts) < 1: - bad_confirmation(doc) - doc.AddItem(MailmanLogo()) - print doc.Format() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - bad_confirmation(doc, _('No such list <em>%(safelistname)s</em>')) - doc.AddItem(MailmanLogo()) - print doc.Format() - log.error('No such list "%s": %s', listname, e) - return - - # Set the language for the list - i18n.set_language(mlist.preferred_language) - doc.set_language(mlist.preferred_language) - - # Get the form data to see if this is a second-step confirmation - cgidata = cgi.FieldStorage(keep_blank_values=1) - cookie = cgidata.getvalue('cookie') - if cookie == '': - ask_for_cookie(mlist, doc, _('Confirmation string was empty.')) - return - - if not cookie and len(parts) == 2: - cookie = parts[1] - - if len(parts) > 2: - bad_confirmation(doc) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - if not cookie: - ask_for_cookie(mlist, doc) - return - - days = int(config.PENDING_REQUEST_LIFE / config.days(1) + 0.5) - confirmurl = mlist.GetScriptURL('confirm') - # Avoid cross-site scripting attacks - safecookie = Utils.websafe(cookie) - badconfirmstr = _('''<b>Invalid confirmation string:</b> - %(safecookie)s. - - <p>Note that confirmation strings expire approximately - %(days)s days after the initial subscription request. If your - confirmation has expired, please try to re-submit your subscription. - Otherwise, <a href="%(confirmurl)s">re-enter</a> your confirmation - string.''') - - content = mlist.pend_confirm(cookie, expunge=False) - if content is None: - bad_confirmation(doc, badconfirmstr) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - try: - if content[0] == Pending.SUBSCRIPTION: - if cgidata.getvalue('cancel'): - subscription_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - subscription_confirm(mlist, doc, cookie, cgidata) - else: - subscription_prompt(mlist, doc, cookie, content[1]) - elif content[0] == Pending.UNSUBSCRIPTION: - try: - if cgidata.getvalue('cancel'): - unsubscription_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - unsubscription_confirm(mlist, doc, cookie) - else: - unsubscription_prompt(mlist, doc, cookie, *content[1:]) - except Errors.NotAMemberError: - doc.addError(_("""The address requesting unsubscription is not - a member of the mailing list. Perhaps you have already been - unsubscribed, e.g. by the list administrator?""")) - # Expunge this record from the pending database. - expunge(mlist, cookie) - elif content[0] == Pending.CHANGE_OF_ADDRESS: - if cgidata.getvalue('cancel'): - addrchange_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - addrchange_confirm(mlist, doc, cookie) - else: - # Watch out for users who have unsubscribed themselves in the - # meantime! - try: - addrchange_prompt(mlist, doc, cookie, *content[1:]) - except Errors.NotAMemberError: - doc.addError(_("""The address requesting to be changed has - been subsequently unsubscribed. This request has been - cancelled.""")) - # Expunge this record from the pending database. - expunge(mlist, cookie) - elif content[0] == Pending.HELD_MESSAGE: - if cgidata.getvalue('cancel'): - heldmsg_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - heldmsg_confirm(mlist, doc, cookie) - else: - heldmsg_prompt(mlist, doc, cookie, *content[1:]) - elif content[0] == Pending.RE_ENABLE: - if cgidata.getvalue('cancel'): - reenable_cancel(mlist, doc, cookie) - elif cgidata.getvalue('submit'): - reenable_confirm(mlist, doc, cookie) - else: - reenable_prompt(mlist, doc, cookie, *content[1:]) - else: - bad_confirmation(doc, _('System error, bad content: %(content)s')) - except Errors.MMBadConfirmation: - bad_confirmation(doc, badconfirmstr) - - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def bad_confirmation(doc, extra=''): - title = _('Bad confirmation string') - doc.SetTitle(title) - doc.AddItem(Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) - doc.AddItem(extra) - - -def expunge(mlist, cookie): - # Expunge this record from the list's pending database. This requires - # that the list lock be acquired, however the list doesn't need to be - # saved because this operation doesn't touch the config.pck file. - mlist.Lock() - try: - mlist.pend_confirm(cookie, expunge=True) - finally: - mlist.Unlock() - - - -def ask_for_cookie(mlist, doc, extra=''): - title = _('Enter confirmation cookie') - doc.SetTitle(title) - form = Form(mlist.GetScriptURL('confirm')) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - colspan=2, bgcolor=config.WEB_HEADER_COLOR) - - if extra: - table.AddRow([Bold(FontAttr(extra, size='+1'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - - # Add cookie entry box - table.AddRow([_("""Please enter the confirmation string - (i.e. <em>cookie</em>) that you received in your email message, in the box - below. Then hit the <em>Submit</em> button to proceed to the next - confirmation step.""")]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Label(_('Confirmation string:')), - TextBox('cookie')]) - table.AddRow([Center(SubmitButton('submit_cookie', _('Submit')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - form.AddItem(table) - doc.AddItem(form) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def subscription_prompt(mlist, doc, cookie, userdesc): - email = userdesc.address - password = userdesc.password - digest = userdesc.digest - lang = userdesc.language - name = Utils.uncanonstr(userdesc.fullname, lang) - i18n.set_language(lang) - doc.set_language(lang) - title = _('Confirm subscription request') - doc.SetTitle(title) - - form = Form(mlist.GetScriptURL('confirm')) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - colspan=2, bgcolor=config.WEB_HEADER_COLOR) - - listname = mlist.real_name - # This is the normal, no-confirmation required results text. - # - # We do things this way so we don't have to reformat this paragraph, which - # would mess up translations. If you modify this text for other reasons, - # please refill the paragraph, and clean up the logic. - result = _("""Your confirmation is required in order to complete the - subscription request to the mailing list <em>%(listname)s</em>. Your - subscription settings are shown below; make any necessary changes and hit - <em>Subscribe</em> to complete the confirmation process. Once you've - confirmed your subscription request, you will be shown your account - options page which you can use to further customize your membership - options. - - <p>Note: your password will be emailed to you once your subscription is - confirmed. You can change it by visiting your personal options page. - - <p>Or hit <em>Cancel my subscription request</em> if you no longer want to - subscribe to this list.""") + '<p><hr>' - if mlist.subscribe_policy in (2, 3): - # Confirmation is required - result = _("""Your confirmation is required in order to continue with - the subscription request to the mailing list <em>%(listname)s</em>. - Your subscription settings are shown below; make any necessary changes - and hit <em>Subscribe to list ...</em> to complete the confirmation - process. Once you've confirmed your subscription request, the - moderator must approve or reject your membership request. You will - receive notice of their decision. - - <p>Note: your password will be emailed to you once your subscription - is confirmed. You can change it by visiting your personal options - page. - - <p>Or, if you've changed your mind and do not want to subscribe to - this mailing list, you can hit <em>Cancel my subscription - request</em>.""") + '<p><hr>' - table.AddRow([result]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - - table.AddRow([Label(_('Your email address:')), email]) - table.AddRow([Label(_('Your real name:')), - TextBox('realname', name)]) -## table.AddRow([Label(_('Password:')), -## PasswordBox('password', password)]) -## table.AddRow([Label(_('Password (confirm):')), -## PasswordBox('pwconfirm', password)]) - # Only give them a choice to receive digests if they actually have a - # choice <wink>. - if mlist.nondigestable and mlist.digestable: - table.AddRow([Label(_('Receive digests?')), - RadioButtonArray('digests', (_('No'), _('Yes')), - checked=digest, values=(0, 1))]) - langs = mlist.language_codes - values = [_(config.languages.get_description(code)) for code in langs] - try: - selected = langs.index(lang) - except ValueError: - selected = lang.index(mlist.preferred_language) - table.AddRow([Label(_('Preferred language:')), - SelectOptions('language', langs, values, selected)]) - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([ - Label(SubmitButton('cancel', _('Cancel my subscription request'))), - SubmitButton('submit', _('Subscribe to list %(listname)s')) - ]) - form.AddItem(table) - doc.AddItem(form) - - - -def subscription_cancel(mlist, doc, cookie): - mlist.Lock() - try: - # Discard this cookie - userdesc = mlist.pend_confirm(cookie)[1] - finally: - mlist.Unlock() - lang = userdesc.language - i18n.set_language(lang) - doc.set_language(lang) - doc.AddItem(_('You have canceled your subscription request.')) - - - -def subscription_confirm(mlist, doc, cookie, cgidata): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - listname = mlist.real_name - mlist.Lock() - try: - try: - # Some pending values may be overridden in the form. email of - # course is hardcoded. ;) - lang = cgidata.getvalue('language') - if lang not in config.languages.enabled_codes: - lang = mlist.preferred_language - i18n.set_language(lang) - doc.set_language(lang) - if cgidata.has_key('digests'): - try: - digest = int(cgidata.getvalue('digests')) - except ValueError: - digest = None - else: - digest = None - userdesc = mlist.pend_confirm(cookie, expunge=False)[1] - fullname = cgidata.getvalue('realname', None) - if fullname is not None: - fullname = Utils.canonstr(fullname, lang) - overrides = UserDesc(fullname=fullname, digest=digest, lang=lang) - userdesc += overrides - op, addr, pw, digest, lang = mlist.ProcessConfirmation( - cookie, userdesc) - except Errors.MMNeedApproval: - title = _('Awaiting moderator approval') - doc.SetTitle(title) - doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_("""\ - You have successfully confirmed your subscription request to the - mailing list %(listname)s, however final approval is required from - the list moderator before you will be subscribed. Your request - has been forwarded to the list moderator, and you will be notified - of the moderator's decision.""")) - except Errors.NotAMemberError: - bad_confirmation(doc, _('''Invalid confirmation string. It is - possible that you are attempting to confirm a request for an - address that has already been unsubscribed.''')) - except Errors.MMAlreadyAMember: - doc.addError(_("You are already a member of this mailing list!")) - except Errors.MembershipIsBanned: - owneraddr = mlist.GetOwnerEmail() - doc.addError(_("""You are currently banned from subscribing to - this list. If you think this restriction is erroneous, please - contact the list owners at %(owneraddr)s.""")) - except Errors.HostileSubscriptionError: - doc.addError(_("""\ - You were not invited to this mailing list. The invitation has - been discarded, and both list administrators have been - alerted.""")) - else: - # Use the user's preferred language - i18n.set_language(lang) - doc.set_language(lang) - # The response - listname = mlist.real_name - title = _('Subscription request confirmed') - optionsurl = mlist.GetOptionsURL(addr) - doc.SetTitle(title) - doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_('''\ - You have successfully confirmed your subscription request for - "%(addr)s" to the %(listname)s mailing list. A separate - confirmation message will be sent to your email address, along - with your password, and other useful information and links. - - <p>You can now - <a href="%(optionsurl)s">proceed to your membership login - page</a>.''')) - mlist.Save() - finally: - mlist.Unlock() - - - -def unsubscription_cancel(mlist, doc, cookie): - # Expunge this record from the pending database - expunge(mlist, cookie) - doc.AddItem(_('You have canceled your unsubscription request.')) - - - -def unsubscription_confirm(mlist, doc, cookie): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - mlist.Lock() - try: - try: - # Do this in two steps so we can get the preferred language for - # the user who is unsubscribing. - op, addr = mlist.pend_confirm(cookie, expunge=False) - lang = mlist.getMemberLanguage(addr) - i18n.set_language(lang) - doc.set_language(lang) - op, addr = mlist.ProcessConfirmation(cookie) - except Errors.NotAMemberError: - bad_confirmation(doc, _('''Invalid confirmation string. It is - possible that you are attempting to confirm a request for an - address that has already been unsubscribed.''')) - else: - # The response - listname = mlist.real_name - title = _('Unsubscription request confirmed') - listinfourl = mlist.GetScriptURL('listinfo') - doc.SetTitle(title) - doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_("""\ - You have successfully unsubscribed from the %(listname)s mailing - list. You can now <a href="%(listinfourl)s">visit the list's main - information page</a>.""")) - mlist.Save() - finally: - mlist.Unlock() - - - -def unsubscription_prompt(mlist, doc, cookie, addr): - title = _('Confirm unsubscription request') - doc.SetTitle(title) - lang = mlist.getMemberLanguage(addr) - i18n.set_language(lang) - doc.set_language(lang) - - form = Form(mlist.GetScriptURL('confirm')) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - colspan=2, bgcolor=config.WEB_HEADER_COLOR) - - listname = mlist.real_name - fullname = mlist.getMemberName(addr) - if fullname is None: - fullname = _('<em>Not available</em>') - else: - fullname = Utils.uncanonstr(fullname, lang) - table.AddRow([_("""Your confirmation is required in order to complete the - unsubscription request from the mailing list <em>%(listname)s</em>. You - are currently subscribed with - - <ul><li><b>Real name:</b> %(fullname)s - <li><b>Email address:</b> %(addr)s - </ul> - - Hit the <em>Unsubscribe</em> button below to complete the confirmation - process. - - <p>Or hit <em>Cancel and discard</em> to cancel this unsubscription - request.""") + '<p><hr>']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([SubmitButton('submit', _('Unsubscribe')), - SubmitButton('cancel', _('Cancel and discard'))]) - - form.AddItem(table) - doc.AddItem(form) - - - -def addrchange_cancel(mlist, doc, cookie): - # Expunge this record from the pending database - expunge(mlist, cookie) - doc.AddItem(_('You have canceled your change of address request.')) - - - -def addrchange_confirm(mlist, doc, cookie): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - mlist.Lock() - try: - try: - # Do this in two steps so we can get the preferred language for - # the user who is unsubscribing. - op, oldaddr, newaddr, globally = mlist.pend_confirm( - cookie, expunge=False) - lang = mlist.getMemberLanguage(oldaddr) - i18n.set_language(lang) - doc.set_language(lang) - op, oldaddr, newaddr = mlist.ProcessConfirmation(cookie) - except Errors.NotAMemberError: - bad_confirmation(doc, _('''Invalid confirmation string. It is - possible that you are attempting to confirm a request for an - address that has already been unsubscribed.''')) - except Errors.MembershipIsBanned: - owneraddr = mlist.GetOwnerEmail() - realname = mlist.real_name - doc.addError(_("""%(newaddr)s is banned from subscribing to the - %(realname)s list. If you think this restriction is erroneous, - please contact the list owners at %(owneraddr)s.""")) - else: - # The response - listname = mlist.real_name - title = _('Change of address request confirmed') - optionsurl = mlist.GetOptionsURL(newaddr) - doc.SetTitle(title) - doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_("""\ - You have successfully changed your address on the %(listname)s - mailing list from <b>%(oldaddr)s</b> to <b>%(newaddr)s</b>. You - can now <a href="%(optionsurl)s">proceed to your membership - login page</a>.""")) - mlist.Save() - finally: - mlist.Unlock() - - - -def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally): - title = _('Confirm change of address request') - doc.SetTitle(title) - lang = mlist.getMemberLanguage(oldaddr) - i18n.set_language(lang) - doc.set_language(lang) - - form = Form(mlist.GetScriptURL('confirm')) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - colspan=2, bgcolor=config.WEB_HEADER_COLOR) - - listname = mlist.real_name - fullname = mlist.getMemberName(oldaddr) - if fullname is None: - fullname = _('<em>Not available</em>') - else: - fullname = Utils.uncanonstr(fullname, lang) - if globally: - globallys = _('globally') - else: - globallys = '' - table.AddRow([_("""Your confirmation is required in order to complete the - change of address request for the mailing list <em>%(listname)s</em>. You - are currently subscribed with - - <ul><li><b>Real name:</b> %(fullname)s - <li><b>Old email address:</b> %(oldaddr)s - </ul> - - and you have requested to %(globallys)s change your email address to - - <ul><li><b>New email address:</b> %(newaddr)s - </ul> - - Hit the <em>Change address</em> button below to complete the confirmation - process. - - <p>Or hit <em>Cancel and discard</em> to cancel this change of address - request.""") + '<p><hr>']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([SubmitButton('submit', _('Change address')), - SubmitButton('cancel', _('Cancel and discard'))]) - - form.AddItem(table) - doc.AddItem(form) - - - -def heldmsg_cancel(mlist, doc, cookie): - title = _('Continue awaiting approval') - doc.SetTitle(title) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=config.WEB_HEADER_COLOR) - # Expunge this record from the pending database. - expunge(mlist, cookie) - table.AddRow([_('''Okay, the list moderator will still have the - opportunity to approve or reject this message.''')]) - doc.AddItem(table) - - - -def heldmsg_confirm(mlist, doc, cookie): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - mlist.Lock() - try: - try: - # Do this in two steps so we can get the preferred language for - # the user who posted the message. - op, id = mlist.pend_confirm(cookie) - requestsdb = config.db.get_list_requests(mlist) - key, data = requestsdb.get_record(id) - sender = data['sender'] - msgsubject = data['msgsubject'] - subject = Utils.websafe(msgsubject) - lang = mlist.getMemberLanguage(sender) - i18n.set_language(lang) - doc.set_language(lang) - # Discard the message - mlist.HandleRequest(id, config.DISCARD, - _('Sender discarded message via web.')) - except Errors.LostHeldMessage: - bad_confirmation(doc, _('''The held message with the Subject: - header <em>%(subject)s</em> could not be found. The most likely - reason for this is that the list moderator has already approved or - rejected the message. You were not able to cancel it in - time.''')) - else: - # The response - listname = mlist.real_name - title = _('Posted message canceled') - doc.SetTitle(title) - doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_('''\ - You have successfully canceled the posting of your message with - the Subject: header <em>%(subject)s</em> to the mailing list - %(listname)s.''')) - mlist.Save() - finally: - mlist.Unlock() - - - -def heldmsg_prompt(mlist, doc, cookie, id): - title = _('Cancel held message posting') - doc.SetTitle(title) - form = Form(mlist.GetScriptURL('confirm')) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - colspan=2, bgcolor=config.WEB_HEADER_COLOR) - # Blarg. The list must be locked in order to interact with the ListAdmin - # database, even for read-only. See the comment in admin.py about the - # need for the signal handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - # Get the record, but watch for KeyErrors which mean the admin has already - # disposed of this message. - mlist.Lock() - requestdb = config.db.get_list_requests(mlist) - try: - try: - key, data = requestdb.get_record(id) - except KeyError: - data = None - finally: - mlist.Unlock() - - if data is None: - bad_confirmation(doc, _("""The held message you were referred to has - already been handled by the list administrator.""")) - return - - # Unpack the data and present the confirmation message - ign, sender, msgsubject, givenreason, ign, ign = data - # Now set the language to the sender's preferred. - lang = mlist.getMemberLanguage(sender) - i18n.set_language(lang) - doc.set_language(lang) - - subject = Utils.websafe(msgsubject) - reason = Utils.websafe(_(givenreason)) - listname = mlist.real_name - table.AddRow([_('''Your confirmation is required in order to cancel the - posting of your message to the mailing list <em>%(listname)s</em>: - - <ul><li><b>Sender:</b> %(sender)s - <li><b>Subject:</b> %(subject)s - <li><b>Reason:</b> %(reason)s - </ul> - - Hit the <em>Cancel posting</em> button to discard the posting. - - <p>Or hit the <em>Continue awaiting approval</em> button to continue to - allow the list moderator to approve or reject the message.''') - + '<p><hr>']) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([SubmitButton('submit', _('Cancel posting')), - SubmitButton('cancel', _('Continue awaiting approval'))]) - - form.AddItem(table) - doc.AddItem(form) - - - -def reenable_cancel(mlist, doc, cookie): - # Don't actually discard this cookie, since the user may decide to - # re-enable their membership at a future time, and we may be sending out - # future notifications with this cookie value. - doc.AddItem(_("""You have canceled the re-enabling of your membership. If - we continue to receive bounces from your address, it could be deleted from - this mailing list.""")) - - - -def reenable_confirm(mlist, doc, cookie): - # See the comment in admin.py about the need for the signal - # handler. - def sigterm_handler(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - mlist.Lock() - try: - try: - # Do this in two steps so we can get the preferred language for - # the user who is unsubscribing. - op, listname, addr = mlist.pend_confirm(cookie, expunge=False) - lang = mlist.getMemberLanguage(addr) - i18n.set_language(lang) - doc.set_language(lang) - op, addr = mlist.ProcessConfirmation(cookie) - except Errors.NotAMemberError: - bad_confirmation(doc, _('''Invalid confirmation string. It is - possible that you are attempting to confirm a request for an - address that has already been unsubscribed.''')) - else: - # The response - listname = mlist.real_name - title = _('Membership re-enabled.') - optionsurl = mlist.GetOptionsURL(addr) - doc.SetTitle(title) - doc.AddItem(Header(3, Bold(FontAttr(title, size='+2')))) - doc.AddItem(_("""\ - You have successfully re-enabled your membership in the - %(listname)s mailing list. You can now <a - href="%(optionsurl)s">visit your member options page</a>. - """)) - mlist.Save() - finally: - mlist.Unlock() - - - -def reenable_prompt(mlist, doc, cookie, list, member): - title = _('Re-enable mailing list membership') - doc.SetTitle(title) - form = Form(mlist.GetScriptURL('confirm')) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - colspan=2, bgcolor=config.WEB_HEADER_COLOR) - - lang = mlist.getMemberLanguage(member) - i18n.set_language(lang) - doc.set_language(lang) - - realname = mlist.real_name - info = mlist.getBounceInfo(member) - if not info: - listinfourl = mlist.GetScriptURL('listinfo') - # They've already be unsubscribed - table.AddRow([_("""We're sorry, but you have already been unsubscribed - from this mailing list. To re-subscribe, please visit the - <a href="%(listinfourl)s">list information page</a>.""")]) - return - - date = time.strftime('%A, %B %d, %Y', - time.localtime(time.mktime(info.date + (0,)*6))) - daysleft = int(info.noticesleft * - mlist.bounce_you_are_disabled_warnings_interval / - config.days(1)) - # BAW: for consistency this should be changed to 'fullname' or the above - # 'fullname's should be changed to 'username'. Don't want to muck with - # the i18n catalogs though. - username = mlist.getMemberName(member) - if username is None: - username = _('<em>not available</em>') - else: - username = Utils.uncanonstr(username, lang) - - table.AddRow([_("""Your membership in the %(realname)s mailing list is - currently disabled due to excessive bounces. Your confirmation is - required in order to re-enable delivery to your address. We have the - following information on file: - - <ul><li><b>Member address:</b> %(member)s - <li><b>Member name:</b> %(username)s - <li><b>Last bounce received on:</b> %(date)s - <li><b>Approximate number of days before you are permanently removed - from this list:</b> %(daysleft)s - </ul> - - Hit the <em>Re-enable membership</em> button to resume receiving postings - from the mailing list. Or hit the <em>Cancel</em> button to defer - re-enabling your membership. - """)]) - - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Hidden('cookie', cookie)]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([SubmitButton('submit', _('Re-enable membership')), - SubmitButton('cancel', _('Cancel'))]) - - form.AddItem(table) - doc.AddItem(form) diff --git a/mailman/web/Cgi/create.py b/mailman/web/Cgi/create.py deleted file mode 100644 index 4bb650957..000000000 --- a/mailman/web/Cgi/create.py +++ /dev/null @@ -1,400 +0,0 @@ -# Copyright (C) 2001-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/>. - -"""Create mailing lists through the web.""" - -import cgi -import sha -import sys -import logging - -from Mailman import Errors -from Mailman import MailList -from Mailman import Message -from Mailman import i18n -from Mailman import passwords -from Mailman.configuration import config -from Mailman.htmlformat import * - -# Set up i18n -_ = i18n._ -i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) -__i18n_templates__ = True - -log = logging.getLogger('mailman.error') - - - -def main(): - doc = Document() - doc.set_language(config.DEFAULT_SERVER_LANGUAGE) - - cgidata = cgi.FieldStorage() - parts = Utils.GetPathPieces() - if parts: - # Bad URL specification - title = _('Bad URL specification') - doc.SetTitle(title) - doc.AddItem( - Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) - log.error('Bad URL specification: %s', parts) - elif cgidata.has_key('doit'): - # We must be processing the list creation request - process_request(doc, cgidata) - elif cgidata.has_key('clear'): - request_creation(doc) - else: - # Put up the list creation request form - request_creation(doc) - doc.AddItem('<hr>') - # Always add the footer and print the document - doc.AddItem(_('Return to the ') + - Link(Utils.ScriptURL('listinfo'), - _('general list overview')).Format()) - doc.AddItem(_('<br>Return to the ') + - Link(Utils.ScriptURL('admin'), - _('administrative list overview')).Format()) - doc.AddItem(MailmanLogo()) - print doc.Format() - - - -def process_request(doc, cgidata): - # Lowercase the listname since this is treated as the 'internal' name. - listname = cgidata.getvalue('listname', '').strip().lower() - owner = cgidata.getvalue('owner', '').strip() - try: - autogen = bool(int(cgidata.getvalue('autogen', '0'))) - except ValueError: - autogen = False - try: - notify = bool(int(cgidata.getvalue('notify', '0'))) - except ValueError: - notify = False - try: - moderate = bool(int(cgidata.getvalue('moderate', - config.DEFAULT_DEFAULT_MEMBER_MODERATION))) - except ValueError: - moderate = config.DEFAULT_DEFAULT_MEMBER_MODERATION - - password = cgidata.getvalue('password', '').strip() - confirm = cgidata.getvalue('confirm', '').strip() - auth = cgidata.getvalue('auth', '').strip() - langs = cgidata.getvalue('langs', [config.DEFAULT_SERVER_LANGUAGE]) - - if not isinstance(langs, list): - langs = [langs] - # Sanity checks - safelistname = Utils.websafe(listname) - if '@' in listname: - request_creation(doc, cgidata, - _('List name must not include "@": $safelistname')) - return - if not listname: - request_creation(doc, cgidata, - _('You forgot to enter the list name')) - return - if not owner: - request_creation(doc, cgidata, - _('You forgot to specify the list owner')) - return - if autogen: - if password or confirm: - request_creation( - doc, cgidata, - _("""Leave the initial password (and confirmation) fields - blank if you want Mailman to autogenerate the list - passwords.""")) - return - password = confirm = Utils.MakeRandomPassword( - config.ADMIN_PASSWORD_LENGTH) - else: - if password <> confirm: - request_creation(doc, cgidata, - _('Initial list passwords do not match')) - return - if not password: - request_creation( - doc, cgidata, - # The little <!-- ignore --> tag is used so that this string - # differs from the one in bin/newlist. The former is destined - # for the web while the latter is destined for email, so they - # must be different entries in the message catalog. - _('The list password cannot be empty<!-- ignore -->')) - return - # The authorization password must be non-empty, and it must match either - # the list creation password or the site admin password - ok = False - if auth: - ok = Utils.check_global_password(auth, False) - if not ok: - ok = Utils.check_global_password(auth) - if not ok: - request_creation( - doc, cgidata, - _('You are not authorized to create new mailing lists')) - return - # Make sure the url host name matches one of our virtual domains. Then - # calculate the list's posting address. - url_host = Utils.get_request_domain() - # Find the IDomain matching this url_host if there is one. - for email_host, domain in config.domains: - if domain.url_host == url_host: - email_host = domain.email_host - break - else: - safehostname = Utils.websafe(url_host) - request_creation(doc, cgidata, - _('Unknown virtual host: $safehostname')) - return - fqdn_listname = '%s@%s' % (listname, email_host) - # We've got all the data we need, so go ahead and try to create the list - mlist = MailList.MailList() - try: - scheme = passwords.lookup_scheme(config.PASSWORD_SCHEME.lower()) - pw = passwords.make_secret(password, scheme) - try: - mlist.Create(fqdn_listname, owner, pw, langs) - except Errors.EmailAddressError, s: - request_creation(doc, cgidata, _('Bad owner email address: $s')) - return - except Errors.MMListAlreadyExistsError: - safelistname = Utils.websafe(listname) - request_creation(doc, cgidata, - _('List already exists: $safelistname')) - return - except Errors.InvalidEmailAddress, s: - request_creation(doc, cgidata, _('Illegal list name: $s')) - return - except Errors.MMListError: - request_creation( - doc, cgidata, - _("""Some unknown error occurred while creating the list. - Please contact the site administrator for assistance.""")) - return - # Initialize the host_name and web_page_url attributes, based on - # virtual hosting settings and the request environment variables. - mlist.default_member_moderation = moderate - mlist.Save() - finally: - mlist.Unlock() - # Now do the MTA-specific list creation tasks - if config.MTA: - modname = 'Mailman.MTA.' + config.MTA - __import__(modname) - sys.modules[modname].create(mlist, cgi=True) - # And send the notice to the list owner. - if notify: - text = Utils.maketext( - 'newlist.txt', - {'listname' : listname, - 'password' : password, - 'admin_url' : mlist.GetScriptURL('admin', absolute=True), - 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=True), - 'requestaddr' : mlist.GetRequestEmail(), - 'siteowner' : mlist.no_reply_address, - }, mlist=mlist) - msg = Message.UserNotification( - owner, mlist.no_reply_address, - _('Your new mailing list: $listname'), - text, mlist.preferred_language) - msg.send(mlist) - # Success! - listinfo_url = mlist.GetScriptURL('listinfo') - admin_url = mlist.GetScriptURL('admin') - create_url = Utils.ScriptURL('create') - - title = _('Mailing list creation results') - doc.SetTitle(title) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=config.WEB_HEADER_COLOR) - table.AddRow([_("""You have successfully created the mailing list - <b>$listname</b> and notification has been sent to the list owner - <b>$owner</b>. You can now:""")]) - ullist = UnorderedList() - ullist.AddItem(Link(listinfo_url, _("Visit the list's info page"))) - ullist.AddItem(Link(admin_url, _("Visit the list's admin page"))) - ullist.AddItem(Link(create_url, _('Create another list'))) - table.AddRow([ullist]) - doc.AddItem(table) - - - -# Because the cgi module blows -class Dummy: - def getvalue(self, name, default): - return default -dummy = Dummy() - - - -def request_creation(doc, cgidata=dummy, errmsg=None): - # What virtual domain are we using? - hostname = Utils.get_request_domain() - # Set up the document - title = _('Create a $hostname Mailing List') - doc.SetTitle(title) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=config.WEB_HEADER_COLOR) - # Add any error message - if errmsg: - table.AddRow([Header(3, Bold( - FontAttr(_('Error: '), color='#ff0000', size='+2').Format() + - Italic(errmsg).Format()))]) - table.AddRow([_("""You can create a new mailing list by entering the - relevant information into the form below. The name of the mailing list - will be used as the primary address for posting messages to the list, so - it should be lowercased. You will not be able to change this once the - list is created. - - <p>You also need to enter the email address of the initial list owner. - Once the list is created, the list owner will be given notification, along - with the initial list password. The list owner will then be able to - modify the password and add or remove additional list owners. - - <p>If you want Mailman to automatically generate the initial list admin - password, click on `Yes' in the autogenerate field below, and leave the - initial list password fields empty. - - <p>You must have the proper authorization to create new mailing lists. - Each site should have a <em>list creator's</em> password, which you can - enter in the field at the bottom. Note that the site administrator's - password can also be used for authentication. - """)]) - # Build the form for the necessary input - GREY = config.WEB_ADMINITEM_COLOR - form = Form(Utils.ScriptURL('create')) - ftable = Table(border=0, cols='2', width='100%', - cellspacing=3, cellpadding=4) - - ftable.AddRow([Center(Italic(_('List Identity')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - - safelistname = Utils.websafe(cgidata.getvalue('listname', '')) - ftable.AddRow([Label(_('Name of list:')), - TextBox('listname', safelistname)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - safeowner = Utils.websafe(cgidata.getvalue('owner', '')) - ftable.AddRow([Label(_('Initial list owner address:')), - TextBox('owner', safeowner)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - try: - autogen = bool(int(cgidata.getvalue('autogen', '0'))) - except ValueError: - autogen = False - ftable.AddRow([Label(_('Auto-generate initial list password?')), - RadioButtonArray('autogen', (_('No'), _('Yes')), - checked=autogen, - values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - safepasswd = Utils.websafe(cgidata.getvalue('password', '')) - ftable.AddRow([Label(_('Initial list password:')), - PasswordBox('password', safepasswd)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - safeconfirm = Utils.websafe(cgidata.getvalue('confirm', '')) - ftable.AddRow([Label(_('Confirm initial password:')), - PasswordBox('confirm', safeconfirm)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - try: - notify = bool(int(cgidata.getvalue('notify', '1'))) - except ValueError: - notify = True - try: - moderate = bool(int(cgidata.getvalue('moderate', - config.DEFAULT_DEFAULT_MEMBER_MODERATION))) - except ValueError: - moderate = config.DEFAULT_DEFAULT_MEMBER_MODERATION - - ftable.AddRow([Center(Italic(_('List Characteristics')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - - ftable.AddRow([ - Label(_("""Should new members be quarantined before they - are allowed to post unmoderated to this list? Answer <em>Yes</em> to hold - new member postings for moderator approval by default.""")), - RadioButtonArray('moderate', (_('No'), _('Yes')), - checked=moderate, - values=(0,1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - # Create the table of initially supported languages, sorted on the long - # name of the language. - revmap = {} - for key, (name, charset) in config.LC_DESCRIPTIONS.items(): - revmap[_(name)] = key - langnames = revmap.keys() - langnames.sort() - langs = [] - for name in langnames: - langs.append(revmap[name]) - try: - langi = langs.index(config.DEFAULT_SERVER_LANGUAGE) - except ValueError: - # Someone must have deleted the servers's preferred language. Could - # be other trouble lurking! - langi = 0 - # BAW: we should preserve the list of checked languages across form - # invocations. - checked = [0] * len(langs) - checked[langi] = 1 - deflang = _( - config.languages.get_description(config.DEFAULT_SERVER_LANGUAGE)) - ftable.AddRow([Label(_( - """Initial list of supported languages. <p>Note that if you do not - select at least one initial language, the list will use the server - default language of $deflang""")), - CheckBoxArray('langs', - [_(config.languges.get_description(code)) - for code in langs], - checked=checked, - values=langs)]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - ftable.AddRow([Label(_('Send "list created" email to list owner?')), - RadioButtonArray('notify', (_('No'), _('Yes')), - checked=notify, - values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - ftable.AddRow(['<hr>']) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - ftable.AddRow([Label(_("List creator's (authentication) password:")), - PasswordBox('auth')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - ftable.AddRow([Center(SubmitButton('doit', _('Create List'))), - Center(SubmitButton('clear', _('Clear Form')))]) - form.AddItem(ftable) - table.AddRow([form]) - doc.AddItem(table) diff --git a/mailman/web/Cgi/edithtml.py b/mailman/web/Cgi/edithtml.py deleted file mode 100644 index dfc871ec1..000000000 --- a/mailman/web/Cgi/edithtml.py +++ /dev/null @@ -1,175 +0,0 @@ -# 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/>. - -"""Script which implements admin editing of the list's html templates.""" - -import os -import re -import cgi -import errno -import logging - -from Mailman import Defaults -from Mailman import Errors -from Mailman import MailList -from Mailman import Utils -from Mailman import i18n -from Mailman.Cgi import Auth -from Mailman.HTMLFormatter import HTMLFormatter -from Mailman.configuration import config -from Mailman.htmlformat import * - -_ = i18n._ - -log = logging.getLogger('mailman.error') - - - -def main(): - # Trick out pygettext since we want to mark template_data as translatable, - # but we don't want to actually translate it here. - def _(s): - return s - - template_data = ( - ('listinfo.html', _('General list information page')), - ('subscribe.html', _('Subscribe results page')), - ('options.html', _('User specific options page')), - ('subscribeack.txt', _('Welcome email text file')), - ) - - _ = i18n._ - doc = Document() - - # Set up the system default language - i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) - doc.set_language(config.DEFAULT_SERVER_LANGUAGE) - - parts = Utils.GetPathPieces() - if not parts: - doc.AddItem(Header(2, _("List name is required."))) - print doc.Format() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>'))) - print doc.Format() - log.error('No such list "%s": %s', listname, e) - return - - # Now that we have a valid list, set the language to its default - i18n.set_language(mlist.preferred_language) - doc.set_language(mlist.preferred_language) - - # Must be authenticated to get any farther - cgidata = cgi.FieldStorage() - - # Editing the html for a list is limited to the list admin and site admin. - if not mlist.WebAuthenticate((Defaults.AuthListAdmin, - Defaults.AuthSiteAdmin), - cgidata.getvalue('adminpw', '')): - if cgidata.has_key('admlogin'): - # This is a re-authorization attempt - msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() - else: - msg = '' - Auth.loginpage(mlist, 'admin', msg=msg) - return - - realname = mlist.real_name - if len(parts) > 1: - template_name = parts[1] - for (template, info) in template_data: - if template == template_name: - template_info = _(info) - doc.SetTitle(_( - '%(realname)s -- Edit html for %(template_info)s')) - break - else: - # Avoid cross-site scripting attacks - safetemplatename = Utils.websafe(template_name) - doc.SetTitle(_('Edit HTML : Error')) - doc.AddItem(Header(2, _("%(safetemplatename)s: Invalid template"))) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - else: - doc.SetTitle(_('%(realname)s -- HTML Page Editing')) - doc.AddItem(Header(1, _('%(realname)s -- HTML Page Editing'))) - doc.AddItem(Header(2, _('Select page to edit:'))) - template_list = UnorderedList() - for (template, info) in template_data: - l = Link(mlist.GetScriptURL('edithtml') + '/' + template, _(info)) - template_list.AddItem(l) - doc.AddItem(FontSize("+2", template_list)) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - try: - if cgidata.keys(): - ChangeHTML(mlist, cgidata, template_name, doc) - FormatHTML(mlist, doc, template_name, template_info) - finally: - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def FormatHTML(mlist, doc, template_name, template_info): - doc.AddItem(Header(1,'%s:' % mlist.real_name)) - doc.AddItem(Header(1, template_info)) - doc.AddItem('<hr>') - - link = Link(mlist.GetScriptURL('admin'), - _('View or edit the list configuration information.')) - - doc.AddItem(FontSize("+1", link)) - doc.AddItem('<p>') - doc.AddItem('<hr>') - form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name) - text = Utils.websafe(Utils.maketext(template_name, raw=1, mlist=mlist)) - form.AddItem(TextArea('html_code', text, rows=40, cols=75)) - form.AddItem('<p>' + _('When you are done making changes...')) - form.AddItem(SubmitButton('submit', _('Submit Changes'))) - doc.AddItem(form) - - - -def ChangeHTML(mlist, cgi_info, template_name, doc): - if not cgi_info.has_key('html_code'): - doc.AddItem(Header(3,_("Can't have empty html page."))) - doc.AddItem(Header(3,_("HTML Unchanged."))) - doc.AddItem('<hr>') - return - code = cgi_info['html_code'].value - code = re.sub(r'<([/]?script.*?)>', r'<\1>', code) - langdir = os.path.join(mlist.fullpath(), mlist.preferred_language) - # Make sure the directory exists - Utils.makedirs(langdir) - fp = open(os.path.join(langdir, template_name), 'w') - try: - fp.write(code) - finally: - fp.close() - doc.AddItem(Header(3, _('HTML successfully updated.'))) - doc.AddItem('<hr>') diff --git a/mailman/web/Cgi/listinfo.py b/mailman/web/Cgi/listinfo.py deleted file mode 100644 index 149be715f..000000000 --- a/mailman/web/Cgi/listinfo.py +++ /dev/null @@ -1,207 +0,0 @@ -# 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 listinfo page, primary web entry-point to mailing lists.""" - -# No lock needed in this script, because we don't change data. - -import os -import cgi -import logging - -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 -_ = i18n._ -i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) - -log = logging.getLogger('mailman.error') - - - -def main(): - parts = Utils.GetPathPieces() - if not parts: - listinfo_overview() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - listinfo_overview(_('No such list <em>%(safelistname)s</em>')) - log.error('No such list "%s": %s', listname, e) - return - - # See if the user want to see this page in other language - cgidata = cgi.FieldStorage() - language = cgidata.getvalue('language') - if language not in config.languages.enabled_codes: - language = mlist.preferred_language - i18n.set_language(language) - list_listinfo(mlist, language) - - - -def listinfo_overview(msg=''): - # Present the general listinfo overview - hostname = Utils.get_request_domain() - # Set up the document and assign it the correct language. The only one we - # know about at the moment is the server's default. - doc = Document() - doc.set_language(config.DEFAULT_SERVER_LANGUAGE) - - legend = _("%(hostname)s Mailing Lists") - doc.SetTitle(legend) - - table = Table(border=0, width="100%") - table.AddRow([Center(Header(2, legend))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - - # Skip any mailing lists that isn't advertised. - advertised = [] - for name in sorted(config.list_manager.names): - mlist = MailList.MailList(name, lock=False) - if mlist.advertised: - if hostname not in mlist.web_page_url: - # This list is situated in a different virtual domain - continue - else: - advertised.append((mlist.GetScriptURL('listinfo'), - mlist.real_name, - mlist.description)) - if msg: - greeting = FontAttr(msg, color="ff5060", size="+1") - else: - greeting = FontAttr(_('Welcome!'), size='+2') - - welcome = [greeting] - mailmanlink = Link(config.MAILMAN_URL, _('Mailman')).Format() - if not advertised: - welcome.extend( - _('''<p>There currently are no publicly-advertised - %(mailmanlink)s mailing lists on %(hostname)s.''')) - else: - welcome.append( - _('''<p>Below is a listing of all the public mailing lists on - %(hostname)s. Click on a list name to get more information about - the list, or to subscribe, unsubscribe, and change the preferences - on your subscription.''')) - - # set up some local variables - adj = msg and _('right') or '' - siteowner = Utils.get_site_noreply() - welcome.extend( - (_(''' To visit the general information page for an unadvertised list, - open a URL similar to this one, but with a '/' and the %(adj)s - list name appended. - <p>List administrators, you can visit '''), - Link(Utils.ScriptURL('admin'), - _('the list admin overview page')), - _(''' to find the management interface for your list. - <p>If you are having trouble using the lists, please contact '''), - Link('mailto:' + siteowner, siteowner), - '.<p>')) - - table.AddRow([apply(Container, welcome)]) - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) - - if advertised: - table.AddRow([' ', ' ']) - table.AddRow([Bold(FontAttr(_('List'), size='+2')), - Bold(FontAttr(_('Description'), size='+2')) - ]) - highlight = 1 - for url, real_name, description in advertised: - table.AddRow( - [Link(url, Bold(real_name)), - description or Italic(_('[no description available]'))]) - if highlight and config.WEB_HIGHLIGHT_COLOR: - table.AddRowInfo(table.GetCurrentRowIndex(), - bgcolor=config.WEB_HIGHLIGHT_COLOR) - highlight = not highlight - - doc.AddItem(table) - doc.AddItem('<hr>') - doc.AddItem(MailmanLogo()) - print doc.Format() - - - -def list_listinfo(mlist, lang): - # Generate list specific listinfo - doc = HeadlessDocument() - doc.set_language(lang) - - replacements = mlist.GetStandardReplacements(lang) - - if not mlist.digestable or not mlist.nondigestable: - replacements['<mm-digest-radio-button>'] = "" - replacements['<mm-undigest-radio-button>'] = "" - replacements['<mm-digest-question-start>'] = '<!-- ' - replacements['<mm-digest-question-end>'] = ' -->' - else: - replacements['<mm-digest-radio-button>'] = mlist.FormatDigestButton() - replacements['<mm-undigest-radio-button>'] = \ - mlist.FormatUndigestButton() - replacements['<mm-digest-question-start>'] = '' - replacements['<mm-digest-question-end>'] = '' - replacements['<mm-plain-digests-button>'] = \ - mlist.FormatPlainDigestsButton() - replacements['<mm-mime-digests-button>'] = mlist.FormatMimeDigestsButton() - replacements['<mm-subscribe-box>'] = mlist.FormatBox('email', size=30) - replacements['<mm-subscribe-button>'] = mlist.FormatButton( - 'email-button', text=_('Subscribe')) - replacements['<mm-new-password-box>'] = mlist.FormatSecureBox('pw') - replacements['<mm-confirm-password>'] = mlist.FormatSecureBox('pw-conf') - replacements['<mm-subscribe-form-start>'] = mlist.FormatFormStart( - 'subscribe') - # Roster form substitutions - replacements['<mm-roster-form-start>'] = mlist.FormatFormStart('roster') - replacements['<mm-roster-option>'] = mlist.FormatRosterOptionForUser(lang) - # Options form substitutions - replacements['<mm-options-form-start>'] = mlist.FormatFormStart('options') - replacements['<mm-editing-options>'] = mlist.FormatEditingOption(lang) - replacements['<mm-info-button>'] = SubmitButton('UserOptions', - _('Edit Options')).Format() - # If only one language is enabled for this mailing list, omit the choice - # buttons. - if len(mlist.language_codes) == 1: - displang = '' - else: - displang = mlist.FormatButton('displang-button', - text = _("View this page in")) - replacements['<mm-displang-box>'] = displang - replacements['<mm-lang-form-start>'] = mlist.FormatFormStart('listinfo') - replacements['<mm-fullname-box>'] = mlist.FormatBox('fullname', size=30) - - # Do the expansion. - doc.AddItem(mlist.ParseTags('listinfo.html', replacements, lang)) - print doc.Format() - - - -if __name__ == "__main__": - main() diff --git a/mailman/web/Cgi/options.py b/mailman/web/Cgi/options.py deleted file mode 100644 index c529a3ea4..000000000 --- a/mailman/web/Cgi/options.py +++ /dev/null @@ -1,1000 +0,0 @@ -# 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() diff --git a/mailman/web/Cgi/private.py b/mailman/web/Cgi/private.py deleted file mode 100644 index 87cad7966..000000000 --- a/mailman/web/Cgi/private.py +++ /dev/null @@ -1,190 +0,0 @@ -# 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() diff --git a/mailman/web/Cgi/rmlist.py b/mailman/web/Cgi/rmlist.py deleted file mode 100644 index 4b62f09fb..000000000 --- a/mailman/web/Cgi/rmlist.py +++ /dev/null @@ -1,243 +0,0 @@ -# Copyright (C) 2001-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/>. - -"""Remove/delete mailing lists through the web.""" - -import os -import cgi -import sys -import errno -import shutil -import logging - -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 -_ = 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) - - cgidata = cgi.FieldStorage() - parts = Utils.GetPathPieces() - - if not parts: - # Bad URL specification - title = _('Bad URL specification') - doc.SetTitle(title) - doc.AddItem( - Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) - doc.AddItem('<hr>') - doc.AddItem(MailmanLogo()) - print doc.Format() - log.error('Bad URL specification: %s', parts) - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - title = _('No such list <em>%(safelistname)s</em>') - doc.SetTitle(title) - doc.AddItem( - Header(3, - Bold(FontAttr(title, color='#ff0000', size='+2')))) - doc.AddItem('<hr>') - doc.AddItem(MailmanLogo()) - print doc.Format() - log.error('No such list "%s": %s\n', listname, e) - return - - # Now that we have a valid mailing list, set the language - i18n.set_language(mlist.preferred_language) - doc.set_language(mlist.preferred_language) - - # Be sure the list owners are not sneaking around! - if not config.OWNERS_CAN_DELETE_THEIR_OWN_LISTS: - title = _("You're being a sneaky list owner!") - doc.SetTitle(title) - doc.AddItem( - Header(3, Bold(FontAttr(title, color='#ff0000', size='+2')))) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - mlog.error('Attempt to sneakily delete a list: %s', listname) - return - - if cgidata.has_key('doit'): - process_request(doc, cgidata, mlist) - print doc.Format() - return - - request_deletion(doc, mlist) - # Always add the footer and print the document - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - - - -def process_request(doc, cgidata, mlist): - password = cgidata.getvalue('password', '').strip() - try: - delarchives = int(cgidata.getvalue('delarchives', '0')) - except ValueError: - delarchives = 0 - - # Removing a list is limited to the list-creator (a.k.a. list-destroyer), - # the list-admin, or the site-admin. Don't use WebAuthenticate here - # because we want to be sure the actual typed password is valid, not some - # password sitting in a cookie. - if mlist.Authenticate((config.AuthCreator, - config.AuthListAdmin, - config.AuthSiteAdmin), - password) == config.UnAuthorized: - request_deletion( - doc, mlist, - _('You are not authorized to delete this mailing list')) - return - - # Do the MTA-specific list deletion tasks - if config.MTA: - modname = 'Mailman.MTA.' + config.MTA - __import__(modname) - sys.modules[modname].remove(mlist, cgi=1) - - REMOVABLES = ['lists/%s'] - - if delarchives: - REMOVABLES.extend(['archives/private/%s', - 'archives/private/%s.mbox', - 'archives/public/%s', - 'archives/public/%s.mbox', - ]) - - problems = 0 - listname = mlist.internal_name() - for dirtmpl in REMOVABLES: - dir = os.path.join(config.VAR_DIR, dirtmpl % listname) - if os.path.islink(dir): - try: - os.unlink(dir) - except OSError, e: - if e.errno not in (errno.EACCES, errno.EPERM): raise - problems += 1 - log.error('link %s not deleted due to permission problems', dir) - elif os.path.isdir(dir): - try: - shutil.rmtree(dir) - except OSError, e: - if e.errno not in (errno.EACCES, errno.EPERM): raise - problems += 1 - log.error('directory %s not deleted due to permission problems', - dir) - - title = _('Mailing list deletion results') - doc.SetTitle(title) - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=config.WEB_HEADER_COLOR) - if not problems: - table.AddRow([_('''You have successfully deleted the mailing list - <b>%(listname)s</b>.''')]) - else: - sitelist = mlist.no_reply_address - table.AddRow([_('''There were some problems deleting the mailing list - <b>%(listname)s</b>. Contact your site administrator at %(sitelist)s - for details.''')]) - doc.AddItem(table) - doc.AddItem('<hr>') - doc.AddItem(_('Return to the ') + - Link(Utils.ScriptURL('listinfo'), - _('general list overview')).Format()) - doc.AddItem(_('<br>Return to the ') + - Link(Utils.ScriptURL('admin'), - _('administrative list overview')).Format()) - doc.AddItem(MailmanLogo()) - - - -def request_deletion(doc, mlist, errmsg=None): - realname = mlist.real_name - title = _('Permanently remove mailing list <em>%(realname)s</em>') - doc.SetTitle(title) - - table = Table(border=0, width='100%') - table.AddRow([Center(Bold(FontAttr(title, size='+1')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, - bgcolor=config.WEB_HEADER_COLOR) - - # Add any error message - if errmsg: - table.AddRow([Header(3, Bold( - FontAttr(_('Error: '), color='#ff0000', size='+2').Format() + - Italic(errmsg).Format()))]) - - table.AddRow([_("""This page allows you as the list owner, to permanent - remove this mailing list from the system. <strong>This action is not - undoable</strong> so you should undertake it only if you are absolutely - sure this mailing list has served its purpose and is no longer necessary. - - <p>Note that no warning will be sent to your list members and after this - action, any subsequent messages sent to the mailing list, or any of its - administrative addreses will bounce. - - <p>You also have the option of removing the archives for this mailing list - at this time. It is almost always recommended that you do - <strong>not</strong> remove the archives, since they serve as the - historical record of your mailing list. - - <p>For your safety, you will be asked to reconfirm the list password. - """)]) - GREY = config.WEB_ADMINITEM_COLOR - form = Form(mlist.GetScriptURL('rmlist')) - ftable = Table(border=0, cols='2', width='100%', - cellspacing=3, cellpadding=4) - - ftable.AddRow([Label(_('List password:')), PasswordBox('password')]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - ftable.AddRow([Label(_('Also delete archives?')), - RadioButtonArray('delarchives', (_('No'), _('Yes')), - checked=0, values=(0, 1))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY) - - ftable.AddRow([Center(Link( - mlist.GetScriptURL('admin'), - _('<b>Cancel</b> and return to list administration')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - - ftable.AddRow([Center(SubmitButton('doit', _('Delete this list')))]) - ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2) - form.AddItem(ftable) - table.AddRow([form]) - doc.AddItem(table) diff --git a/mailman/web/Cgi/roster.py b/mailman/web/Cgi/roster.py deleted file mode 100644 index 2351d6915..000000000 --- a/mailman/web/Cgi/roster.py +++ /dev/null @@ -1,130 +0,0 @@ -# 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 subscriber roster, using listinfo form data, roster.html template. - -Takes listname in PATH_INFO. -""" - - -# We don't need to lock in this script, because we're never going to change -# data. - -import os -import cgi -import sys -import urllib -import logging - -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 -_ = i18n._ -i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) - -log = logging.getLogger('mailman.error') - - - -def main(): - parts = Utils.GetPathPieces() - if not parts: - error_page(_('Invalid options to CGI script')) - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - error_page(_('No such list <em>%(safelistname)s</em>')) - log.error('roster: no such list "%s": %s', listname, e) - return - - cgidata = cgi.FieldStorage() - - # messages in form should go in selected language (if any...) - lang = cgidata.getvalue('language') - if lang not in config.languages.enabled_codes: - lang = mlist.preferred_language - i18n.set_language(lang) - - # Perform authentication for protected rosters. If the roster isn't - # protected, then anybody can see the pages. If members-only or - # "admin"-only, then we try to cookie authenticate the user, and failing - # that, we check roster-email and roster-pw fields for a valid password. - # (also allowed: the list moderator, the list admin, and the site admin). - if mlist.private_roster == 0: - # No privacy - ok = 1 - elif mlist.private_roster == 1: - # Members only - addr = cgidata.getvalue('roster-email', '') - password = cgidata.getvalue('roster-pw', '') - ok = mlist.WebAuthenticate((config.AuthUser, - config.AuthListModerator, - config.AuthListAdmin, - config.AuthSiteAdmin), - password, addr) - else: - # Admin only, so we can ignore the address field - password = cgidata.getvalue('roster-pw', '') - ok = mlist.WebAuthenticate((config.AuthListModerator, - config.AuthListAdmin, - config.AuthSiteAdmin), - password) - if not ok: - realname = mlist.real_name - doc = Document() - doc.set_language(lang) - error_page_doc(doc, _('%(realname)s roster authentication failed.')) - doc.AddItem(mlist.GetMailmanFooter()) - print doc.Format() - return - - # The document and its language - doc = HeadlessDocument() - doc.set_language(lang) - - replacements = mlist.GetAllReplacements(lang) - replacements['<mm-displang-box>'] = mlist.FormatButton( - 'displang-button', - text = _('View this page in')) - replacements['<mm-lang-form-start>'] = mlist.FormatFormStart('roster') - doc.AddItem(mlist.ParseTags('roster.html', replacements, lang)) - print doc.Format() - - - -def error_page(errmsg): - doc = Document() - doc.set_language(config.DEFAULT_SERVER_LANGUAGE) - error_page_doc(doc, errmsg) - print doc.Format() - - -def error_page_doc(doc, errmsg, *args): - # Produce a simple error-message page on stdout and exit. - doc.SetTitle(_("Error")) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(errmsg % args)) diff --git a/mailman/web/Cgi/subscribe.py b/mailman/web/Cgi/subscribe.py deleted file mode 100644 index 0eefd0111..000000000 --- a/mailman/web/Cgi/subscribe.py +++ /dev/null @@ -1,252 +0,0 @@ -# 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/>. - -"""Process subscription or roster requests from listinfo form.""" - -from __future__ import with_statement - -import os -import cgi -import sys -import logging - -from Mailman import Errors -from Mailman import MailList -from Mailman import Message -from Mailman import Utils -from Mailman import i18n -from Mailman.UserDesc import UserDesc -from Mailman.configuration import config -from Mailman.htmlformat import * - -SLASH = '/' -ERRORSEP = '\n\n<p>' - -# 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() - if not parts: - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('Invalid options to CGI script'))) - print doc.Format() - return - - listname = parts[0].lower() - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - # Avoid cross-site scripting attacks - safelistname = Utils.websafe(listname) - doc.AddItem(Header(2, _("Error"))) - doc.AddItem(Bold(_('No such list <em>%(safelistname)s</em>'))) - print doc.Format() - log.error('No such list "%s": %s\n', listname, e) - return - - # See if the form data has a preferred language set, in which case, use it - # for the results. If not, use the list's preferred language. - cgidata = cgi.FieldStorage() - language = cgidata.getvalue('language') - if language not in config.languages.enabled_codes: - language = mlist.preferred_language - i18n.set_language(language) - doc.set_language(language) - - mlist.Lock() - try: - process_form(mlist, doc, cgidata, language) - mlist.Save() - finally: - mlist.Unlock() - - - -def process_form(mlist, doc, cgidata, lang): - listowner = mlist.GetOwnerEmail() - realname = mlist.real_name - results = [] - - # The email address being subscribed, required - email = cgidata.getvalue('email', '') - if not email: - results.append(_('You must supply a valid email address.')) - - fullname = cgidata.getvalue('fullname', '') - # Canonicalize the full name - fullname = Utils.canonstr(fullname, lang) - # Who was doing the subscribing? - remote = os.environ.get('REMOTE_HOST', - os.environ.get('REMOTE_ADDR', - 'unidentified origin')) - # Was an attempt made to subscribe the list to itself? - if email == mlist.GetListEmail(): - mlog.error('Attempt to self subscribe %s: %s', email, remote) - results.append(_('You may not subscribe a list to itself!')) - # If the user did not supply a password, generate one for him - password = cgidata.getvalue('pw') - confirmed = cgidata.getvalue('pw-conf') - - if password is None and confirmed is None: - password = Utils.MakeRandomPassword() - elif password is None or confirmed is None: - results.append(_('If you supply a password, you must confirm it.')) - elif password <> confirmed: - results.append(_('Your passwords did not match.')) - - # Get the digest option for the subscription. - digestflag = cgidata.getvalue('digest') - if digestflag: - try: - digest = int(digestflag) - except ValueError: - digest = 0 - else: - digest = mlist.digest_is_default - - # Sanity check based on list configuration. BAW: It's actually bogus that - # the page allows you to set the digest flag if you don't really get the - # choice. :/ - if not mlist.digestable: - digest = 0 - elif not mlist.nondigestable: - digest = 1 - - if results: - print_results(mlist, ERRORSEP.join(results), doc, lang) - return - - # If this list has private rosters, we have to be careful about the - # message that gets printed, otherwise the subscription process can be - # used to mine for list members. It may be inefficient, but it's still - # possible, and that kind of defeats the purpose of private rosters. - # We'll use this string for all successful or unsuccessful subscription - # results. - if mlist.private_roster == 0: - # Public rosters - privacy_results = '' - else: - privacy_results = _("""\ -Your subscription request has been received, and will soon be acted upon. -Depending on the configuration of this mailing list, your subscription request -may have to be first confirmed by you via email, or approved by the list -moderator. If confirmation is required, you will soon get a confirmation -email which contains further instructions.""") - - try: - userdesc = UserDesc(email, fullname, password, digest, lang) - mlist.AddMember(userdesc, remote) - results = '' - # Check for all the errors that mlist.AddMember can throw options on the - # web page for this cgi - except Errors.MembershipIsBanned: - results = _("""The email address you supplied is banned from this - mailing list. If you think this restriction is erroneous, please - contact the list owners at %(listowner)s.""") - except Errors.InvalidEmailAddress: - results = _('The email address you supplied is not valid.') - except Errors.MMSubscribeNeedsConfirmation: - # Results string depends on whether we have private rosters or not - if privacy_results: - results = privacy_results - else: - results = _("""\ -Confirmation from your email address is required, to prevent anyone from -subscribing you without permission. Instructions are being sent to you at -%(email)s. Please note your subscription will not start until you confirm -your subscription.""") - except Errors.MMNeedApproval, x: - # Results string depends on whether we have private rosters or not - if privacy_results: - results = privacy_results - else: - # We need to interpolate into x - x = _(x) - results = _("""\ -Your subscription request was deferred because %(x)s. Your request has been -forwarded to the list moderator. You will receive email informing you of the -moderator's decision when they get to your request.""") - except Errors.MMAlreadyAMember: - # Results string depends on whether we have private rosters or not - if not privacy_results: - results = _('You are already subscribed.') - else: - results = privacy_results - # This could be a membership probe. For safety, let the user know - # a probe occurred. BAW: should we inform the list moderator? - listaddr = mlist.GetListEmail() - # Set the language for this email message to the member's language. - mlang = mlist.getMemberLanguage(email) - with i18n.using_language(mlang): - msg = Message.UserNotification( - mlist.getMemberCPAddress(email), - mlist.GetBouncesEmail(), - _('Mailman privacy alert'), - _("""\ -An attempt was made to subscribe your address to the mailing list -%(listaddr)s. You are already subscribed to this mailing list. - -Note that the list membership is not public, so it is possible that a bad -person was trying to probe the list for its membership. This would be a -privacy violation if we let them do this, but we didn't. - -If you submitted the subscription request and forgot that you were already -subscribed to the list, then you can ignore this message. If you suspect that -an attempt is being made to covertly discover whether you are a member of this -list, and you are worried about your privacy, then feel free to send a message -to the list administrator at %(listowner)s. -"""), lang=mlang) - msg.send(mlist) - # These shouldn't happen unless someone's tampering with the form - except Errors.MMCantDigestError: - results = _('This list does not support digest delivery.') - except Errors.MMMustDigestError: - results = _('This list only supports digest delivery.') - else: - # Everything's cool. Our return string actually depends on whether - # this list has private rosters or not - if privacy_results: - results = privacy_results - else: - results = _("""\ -You have been successfully subscribed to the %(realname)s mailing list.""") - # Show the results - print_results(mlist, results, doc, lang) - - - -def print_results(mlist, results, doc, lang): - # The bulk of the document will come from the options.html template, which - # includes its own html armor (head tags, etc.). Suppress the head that - # Document() derived pages get automatically. - doc.suppress_head = 1 - - replacements = mlist.GetStandardReplacements(lang) - replacements['<mm-results>'] = results - output = mlist.ParseTags('subscribe.html', replacements, lang) - doc.AddItem(output) - print doc.Format() diff --git a/mailman/web/Cgi/wsgi_app.py b/mailman/web/Cgi/wsgi_app.py deleted file mode 100644 index b22dbf452..000000000 --- a/mailman/web/Cgi/wsgi_app.py +++ /dev/null @@ -1,286 +0,0 @@ -# Copyright (C) 2006-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/>. - -import os -import re -import sys - -from cStringIO import StringIO -from email import message_from_string -from urlparse import urlparse - -from Mailman.configuration import config - -# XXX Should this be configurable in Defaults.py? -STEALTH_MODE = False -MOVED_RESPONSE = '302 Found' -# Above is for debugging convenience. We should use: -# MOVED_RESPONSE = '301 Moved Permanently' - - - -def websafe(s): - return s - - -SCRIPTS = ['admin', 'admindb', 'confirm', 'create', - 'edithtml', 'listinfo', 'options', 'private', - 'rmlist', 'roster', 'subscribe'] -ARCHVIEW = ['private'] - -SLASH = '/' -NL2 = '\n\n' -CRLF2 = '\r\n\r\n' - -dotonly = re.compile(r'^\.+$') - - - -# WSGI to CGI wrapper. Mostly copied from scripts/driver. -def mailman_app(environ, start_response): - """Wrapper to *.py CGI commands""" - global STEALTH_MODE, websafe - try: - try: - if not STEALTH_MODE: - from Mailman.Utils import websafe - except: - STEALTH_MODE = True - raise - - import logging - log = logging.getLogger('mailman.error') - - from Mailman import i18n - i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) - - path = environ['PATH_INFO'] - paths = path.split(SLASH) - # sanity check for paths - spaths = [ i for i in paths[1:] if i and not dotonly.match(i) ] - if spaths and spaths != paths[1:]: - newpath = SLASH + SLASH.join(spaths) - start_response(MOVED_RESPONSE, [('Location', newpath)]) - return 'Location: ' + newpath - # find script name - for script in SCRIPTS: - if script in spaths: - # Get script position in spaths and break. - scrpos = spaths.index(script) - break - else: - # Can't find valid script. - start_response('404 Not Found', []) - return '404 Not Found' - # Compose CGI SCRIPT_NAME and PATH_INFO from WSGI path. - script_name = SLASH + SLASH.join(spaths[:scrpos+1]) - environ['SCRIPT_NAME'] = script_name - if len(paths) > scrpos+2: - path_info = SLASH + SLASH.join(paths[scrpos+2:]) - if script in ARCHVIEW \ - and path_info.count('/') in (1,2) \ - and not paths[-1].split('.')[-1] in ('html', 'txt', 'gz'): - # Add index.html if /private/listname or - # /private/listname/YYYYmm is requested. - newpath = script_name + path_info + '/index.html' - start_response(MOVED_RESPONSE, [('Location', newpath)]) - return 'Location: ' + newpath - environ['PATH_INFO'] = path_info - else: - environ['PATH_INFO'] = '' - # Reverse proxy environment. - if environ.has_key('HTTP_X_FORWARDED_HOST'): - environ['HTTP_HOST'] = environ['HTTP_X_FORWARDED_HOST'] - if environ.has_key('HTTP_X_FORWARDED_FOR'): - environ['REMOTE_HOST'] = environ['HTTP_X_FORWARDED_FOR'] - modname = 'Mailman.Cgi.' + script - # Clear previous cookie before setting new one. - os.environ['HTTP_COOKIE'] = '' - for k, v in environ.items(): - os.environ[k] = str(v) - # Prepare for redirection - save_stdin = sys.stdin - # CGI writes its output to sys.stdout, while wsgi app should - # return (list of) strings. - save_stdout = sys.stdout - save_stderr = sys.stderr - tmpstdout = StringIO() - tmpstderr = StringIO() - response = '' - try: - try: - sys.stdin = environ['wsgi.input'] - sys.stdout = tmpstdout - sys.stderr = tmpstderr - __import__(modname) - sys.modules[modname].main() - response = sys.stdout.getvalue() - finally: - sys.stdin = save_stdin - sys.stdout = save_stdout - sys.stderr = save_stderr - except SystemExit: - sys.stdout.write(tmpstdout.getvalue()) - if response: - try: - head, content = response.split(NL2, 1) - except ValueError: - head, content = response.split(CRLF2, 1) - m = message_from_string(head + CRLF2) - start_response('200 OK', m.items()) - return [content] - else: - # TBD: Error Code/Message - start_response('500 Server Error', []) - return '500 Internal Server Error' - except: - start_response('200 OK', [('Content-Type', 'text/html')]) - retstring = print_traceback(log) - retstring += print_environment(log) - return retstring - - - -# These functions are extracted and modified from scripts/driver. -# -# If possible, we print the error to two places. One will always be stdout -# and the other will be the log file if a log file was created. It is assumed -# that stdout is an HTML sink. -def print_traceback(log=None): - try: - import traceback - except ImportError: - traceback = None - try: - from mailman.version import VERSION - except ImportError: - VERSION = '<undetermined>' - - # Write to the log file first. - if log: - outfp = StringIO() - - print >> outfp, '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@' - print >> outfp, '[----- Mailman Version: %s -----]' % VERSION - print >> outfp, '[----- Traceback ------]' - if traceback: - traceback.print_exc(file=outfp) - else: - print >> outfp, '[failed to import module traceback]' - print >> outfp, '[exc: %s, var: %s]' % sys.exc_info()[0:2] - # Don't use .exception() since that'll give us the exception twice. - # IWBNI we could print directly to the log's stream, or treat a log - # like an output stream. - log.error('%s', outfp.getvalue()) - - # return HTML sink. - htfp = StringIO() - print >> htfp, """\ -<head><title>Bug in Mailman version %(VERSION)s</title></head> -<body bgcolor=#ffffff><h2>Bug in Mailman version %(VERSION)s</h2> -<p><h3>We're sorry, we hit a bug!</h3> -""" % locals() - if not STEALTH_MODE: - print >> htfp, '''<p>If you would like to help us identify the problem, -please email a copy of this page to the webmaster for this site with -a description of what happened. Thanks! - -<h4>Traceback:</h4><p><pre>''' - exc_info = sys.exc_info() - if traceback: - for line in traceback.format_exception(*exc_info): - print >> htfp, websafe(line) - else: - print >> htfp, '[failed to import module traceback]' - print >> htfp, '[exc: %s, var: %s]' %\ - [websafe(x) for x in exc_info[0:2]] - print >> htfp, '\n\n</pre></body>' - else: - print >> htfp, '''<p>Please inform the webmaster for this site of this -problem. Printing of traceback and other system information has been -explicitly inhibited, but the webmaster can find this information in the -Mailman error logs.''' - return htfp.getvalue() - - - -def print_environment(log=None): - try: - import os - except ImportError: - os = None - - if log: - outfp = StringIO() - - # Write some information about our Python executable to the log file. - print >> outfp, '[----- Python Information -----]' - print >> outfp, 'sys.version =', sys.version - print >> outfp, 'sys.executable =', sys.executable - print >> outfp, 'sys.prefix =', sys.prefix - print >> outfp, 'sys.exec_prefix =', sys.exec_prefix - print >> outfp, 'sys.path =', sys.exec_prefix - print >> outfp, 'sys.platform =', sys.platform - - # Write the same information to the HTML sink. - htfp = StringIO() - if not STEALTH_MODE: - print >> htfp, """\ -<p><hr><h4>Python information:</h4> - -<p><table> -<tr><th>Variable</th><th>Value</th></tr> -<tr><td><tt>sys.version</tt></td><td> %s </td></tr> -<tr><td><tt>sys.executable</tt></td><td> %s </td></tr> -<tr><td><tt>sys.prefix</tt></td><td> %s </td></tr> -<tr><td><tt>sys.exec_prefix</tt></td><td> %s </td></tr> -<tr><td><tt>sys.path</tt></td><td> %s </td></tr> -<tr><td><tt>sys.platform</tt></td><td> %s </td></tr> -</table>""" % (sys.version, sys.executable, sys.prefix, - sys.exec_prefix, sys.path, sys.platform) - - # Write environment variables to the log file. - if log: - print >> outfp, '[----- Environment Variables -----]' - if os: - for k, v in os.environ.items(): - print >> outfp, '\t%s: %s' % (k, v) - else: - print >> outfp, '[failed to import module os]' - - # Write environment variables to the HTML sink. - if not STEALTH_MODE: - print >> htfp, """\ -<p><hr><h4>Environment variables:</h4> - -<p><table> -<tr><th>Variable</th><th>Value</th></tr> -""" - if os: - for k, v in os.environ.items(): - print >> htfp, '<tr><td><tt>' + websafe(k) + \ - '</tt></td><td>' + websafe(v) + \ - '</td></tr>' - print >> htfp, '</table>' - else: - print >> htfp, '<p><hr>[failed to import module os]' - - # Dump the log output - if log: - log.error('%s', outfp.getvalue()) - - return htfp.getvalue() |
