#! /usr/bin/env python # # Copyright (C) 1998 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """Process and produce the list-administration options forms. To run stand-alone for debugging, set env var PATH_INFO to name of list and, optionally, options category.""" import sys import os, cgi, string, types, time import paths # path hacking from Mailman import Utils, MailList, Errors, MailCommandHandler from Mailman import Cookie from Mailman.htmlformat import * from Mailman.Crypt import crypt from Mailman import mm_cfg CATEGORIES = [('general', "General Options"), ('members', "Membership Management"), ('privacy', "Privacy Options"), ('nondigest', "Regular-member (non-digest) Options"), ('digest', "Digest-member Options"), ('bounce', "Bounce Options"), ('archive', "Archival Options"), ('gateway', "Mail-News and News-Mail gateways")] def isAuthenticated(list, password=None, SECRET="SECRET"): if password is not None: # explicit login try: list.ConfirmAdminPassword(password) except Errors.MMBadPasswordError: AddErrorMessage(doc, 'Error: Incorrect admin password.') return 0 token = list.MakeCookie() c = Cookie.Cookie() cookie_key = list_name + "-admin" c[cookie_key] = token c[cookie_key]['expires'] = mm_cfg.ADMIN_COOKIE_LIFE print c # Output the cookie return 1 if os.environ.has_key('HTTP_COOKIE'): c = Cookie.Cookie( os.environ['HTTP_COOKIE'] ) if c.has_key(list_name + "-admin"): if list.CheckCookie(c[list_name + "-admin"].value): return 1 else: AddErrorMessage(doc, "error decoding authorization cookie") return 0 return 0 def main(): """Process and produce list options form. CGI input indicates that we're returning from submission of some new settings, which is processed before producing the new version.""" global list_name, list_info, doc doc = Document() try: path = os.environ['PATH_INFO'] except KeyError: path = "" list_info = Utils.GetPathPieces(path) # How many ../'s we need to get back to http://host/mailman if len(list_info) == 0: FormatAdminOverview() return list_name = string.lower(list_info[0]) try: lst = MailList.MailList(list_name) except Errors.MMUnknownListError: lst = None try: if not (lst and lst._ready): FormatAdminOverview(error="List %s not found." % list_name) return if len(list_info) == 1: category = 'general' category_suffix = '' else: category = list_info[1] category_suffix = category if category not in map(lambda x: x[0], CATEGORIES): category = 'general' global cgi_data cgi_data = cgi.FieldStorage() is_auth = 0 if cgi_data.has_key("adminpw"): is_auth = isAuthenticated(lst, cgi_data["adminpw"].value) message = FontAttr("Sorry, wrong password. Try again.", color="ff5060", size="+1").Format() else: is_auth = isAuthenticated(lst) message = "" if not is_auth: defaulturi = '/mailman/admin%s/%s' % (mm_cfg.CGIEXT, list_name) print "Content-type: text/html\n\n" text = Utils.maketext( 'admlogin.txt', {"listname": list_name, "path" : Utils.GetRequestURI(defaulturi), "message" : message, }) print text return # is the request for variable details? varhelp = None if cgi_data.has_key('VARHELP'): varhelp = cgi_data['VARHELP'].value elif cgi_data.has_key('request_login') and \ 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 type(qs) == types.ListType: varhelp = qs[0] if varhelp: FormatOptionHelp(doc, varhelp, lst) print doc.Format(bgcolor="#ffffff") return if cgi_data.has_key('bounce_matching_headers'): try: pairs = lst.parse_matching_header_opt() except Errors.MMBadConfigError, line: AddErrorMessage(doc, 'Warning: bad matching-header line' ' (does it have the colon?)
" " There currently are no publicly-advertised ", Link(mm_cfg.MAILMAN_URL, "mailman"), " mailing lists on %s." % mm_cfg.DEFAULT_HOST_NAME, ) else: welcome_items = ( greeting, "
" " Below is the collection of publicly-advertised ", Link(mm_cfg.MAILMAN_URL, "mailman"), " mailing lists on %s." % mm_cfg.DEFAULT_HOST_NAME, (' Click on a list name to visit the configuration pages' ' for that list.' ) ) welcome_items = (welcome_items + (" To visit the administrators configuration page for" " an unadvertised list, open a URL similar to this" + (" one, but with a '/' and the %slist name appended.
" % ((error and "the right ") or "")) + " General list information can be found at ", Link("%slistinfo%s" % ('../' * Utils.GetNestingLevel(), mm_cfg.CGIEXT), "the mailing list overview page"), "." "
(Send questions and comments to ", Link("mailto:%s" % mm_cfg.MAILMAN_OWNER, mm_cfg.MAILMAN_OWNER), ".)
" ) ) table.AddRow([apply(Container, welcome_items)]) table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) if advertised: table.AddRow([Italic("List"), Italic("Description")]) for l in advertised: table.AddRow([Link(l.GetRelativeScriptURL('admin'), Bold(l.real_name)),l.description]) doc.AddItem(table) print doc.Format(bgcolor="#ffffff") def FormatConfiguration(doc, lst, category, category_suffix): """Produce the overall doc, *except* any processing error messages.""" for k, v in CATEGORIES: if k == category: label = v doc.SetTitle('%s Administration' % lst.real_name) doc.AddItem(Center(Header(2, ('%s Mailing list Configuration - %s Section' % (lst.real_name, label))))) doc.AddItem('
" % andpassmsg) form.AddItem(FormatOptionsSection(category, lst)) if category == 'general': form.AddItem(Center(FormatPasswordStuff())) form.AddItem("
") form.AddItem(Center(FormatSubmit())) form.AddItem(lst.GetMailmanFooter()) def FormatOptionsSection(category, lst): """Produce the category-specific options table.""" if category == 'members': # Special case for members section. return FormatMembershipOptions(lst) options = GetConfigOptions(lst, category) big_table = Table(cellspacing=3, cellpadding=4) # Get and portray the text label for the category. for k, v in CATEGORIES: if k == category: label = v big_table.AddRow([Center(Header(2, label))]) big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 0, colspan=2, bgcolor="#99ccff") def ColHeader(big_table = big_table): big_table.AddRow([Center(Bold('Description')), Center(Bold('Value'))]) big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 0, width="15%") big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 1, width="85%") did_col_header = 0 for item in options: if type(item) == types.StringType: # 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... if did_col_header: item = "
" % (varname, category, item[4])) doc.AddItem("%s
" % item[5]) form = Form("%s/%s" % (lst.GetRelativeScriptURL('admin'), category)) valtab = Table(cellspacing=3, cellpadding=4) AddOptionsTableItem(valtab, item, category, lst, nodetails=1) form.AddItem(valtab) # XXX I don't think we want to be able to set options from two places, # since they'll go out of sync. #form.AddItem(Center(FormatPasswordStuff())) doc.AddItem(Center(form)) def GetItemCharacteristics(table_entry): """Break out the components of an item description from its table entry: 0 option-var name 1 type 2 entry size 3 ?dependancies? 4 Brief description 5 Optional description elaboration""" if len(table_entry) == 5: elaboration = None varname, kind, params, dependancies, descr = table_entry elif len(table_entry) == 6: varname, kind, params, dependancies, descr, elaboration = table_entry else: raise ValueError, ("Badly formed options entry:\n %s" % table_entry) return (varname, kind, params, dependancies, descr, elaboration) def GetItemGuiValue(lst, kind, varname, params): """Return a representation of an item's settings.""" if kind == mm_cfg.Radio or kind == mm_cfg.Toggle: # # if we are sending returning the option for subscribe # policy and this site doesn't allow open subscribes, # then we have to alter the value of lst.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 # if varname == "subscribe_policy" and not mm_cfg.ALLOW_OPEN_SUBSCRIBE: return RadioButtonArray(varname, params, getattr(lst, varname) - 1) else: return RadioButtonArray(varname, params, getattr(lst, varname)) elif (kind == mm_cfg.String or kind == mm_cfg.Email or kind == mm_cfg.Host or kind == mm_cfg.Number): return TextBox(varname, getattr(lst, varname), params) elif kind == mm_cfg.Text: if params: r, c = params else: r, c = None, None val = getattr(lst, varname) if not val: val = '' return TextArea(varname, val, r, c) elif kind == mm_cfg.EmailList: if params: r, c = params else: r, c = None, None res = string.join(getattr(lst, varname), '\n') return TextArea(varname, res, r, c, wrap='off') def GetItemGuiDescr(lst, category, varname, descr, elaboration, nodetails): """Return a representation of an item's description, with link to elaboration if any.""" descr = '
To View other sections, " "click on the appropriate range listed below") chunk_indices = range(len(chunks)) chunk_indices.remove(chunk) buttons = [] for ci in chunk_indices: start, end = chunks[ci][0], chunks[ci][-1] url = lst.GetAbsoluteScriptURL('admin') buttons.append(" from %s to %s " % ( url, ci, start, end)) buttons = apply(UnorderedList, tuple(buttons)) footer = footer + buttons.Format() + "
" else: all.sort() footer = "
" for member in all: mtext = '%s' % ( lst.GetAbsoluteOptionsURL(member, obscured=1), lst.GetUserSubscribedAddress(member)) cells = [mtext + "" % (member), Center(CheckBox(member + "_subscribed", "on", 1).Format())] for opt in ("hide", "nomail", "ack", "notmetoo"): if lst.GetUserOption(member, MailCommandHandler.option_info[opt]): value = "on" checked = 1 else: value = "off" checked = 0 box = CheckBox("%s_%s" % (member, opt), value, checked) cells.append(Center(box.Format())) if lst.members.has_key(member): cells.append(Center(CheckBox(member + "_digest", "off", 0).Format())) else: cells.append(Center(CheckBox(member + "_digest", "on", 1).Format())) if lst.GetUserOption(member, MailCommandHandler.option_info['plain']): value = 'on' checked = 1 else: value = 'off' checked = 0 cells.append(Center(CheckBox('%s_plain' % member, value, checked))) user_table.AddRow(cells) container.AddItem(Center(user_table)) legend = UnorderedList() legend.AddItem('subscr -- Is the member subscribed?') legend.AddItem("hide -- Is the member's address " "concealed on the list of subscribers?") legend.AddItem('nomail -- Is delivery to the member disabled?') legend.AddItem('ack -- ' 'Does the member get acknowledgements of their posts?') legend.AddItem('not metoo -- ' 'Does the member avoid copies of their own posts?') legend.AddItem('digest -- ' 'Does the member get messages in digests? ' '(otherwise, individual messages)') legend.AddItem( 'plain -- ' 'If getting digests, does the member get plain text digests? ' '(otherwise, MIME)') container.AddItem(legend.Format()) container.AddItem(footer) t = Table(width="90%") t.AddRow([Center(Header(4, "Mass Subscribe Members"))]) t.AddCellInfo(t.GetCurrentRowIndex(), t.GetCurrentCellIndex(), bgcolor="#cccccc", colspan=8) if lst.send_welcome_msg: nochecked = 0 yeschecked = 1 else: nochecked = 1 yeschecked = 0 t.AddRow([("1. Send Welcome message to this batch? " + RadioButton("send_welcome_msg_to_this_batch", 0, nochecked).Format() + " no " + RadioButton("send_welcome_msg_to_this_batch", 1, yeschecked).Format() + " yes ")]) t.AddRow(["2. Enter one address per line:
"]) container.AddItem(Center(t)) container.AddItem(Center(TextArea(name='subscribees', rows=10,cols=60,wrap=None))) return container def FormatPasswordStuff(): change_pw_table = Table(bgcolor="#99cccc", border=0, cellspacing=0, cellpadding=2, valign="top") change_pw_table.AddRow( [Bold(Center('To Change The Administrator Password'))]) change_pw_table.AddCellInfo(0, 0, align="left", colspan=2) old = Table(bgcolor="#99cccc", border=1, cellspacing=0, cellpadding=2, valign="top") old.AddRow(['
") # ApprovedAddMembers will already have saved the list for us. # dirty = 1 if subscribe_errors: document.AddItem(Header(5, "Error Subscribing:")) items = map(lambda x: "%s -- %s" % (x[0], x[1]), subscribe_errors) document.AddItem(apply(UnorderedList, tuple((items)))) document.AddItem("
") # # do the user options for members category # if cgi_info.has_key('user'): user = cgi_info["user"] if type(user) is type([]): users = [] for ui in range(len(user)): users.append(user[ui].value) else: users = [user.value] unsubscribe_errors = [] for user in users: if not cgi_info.has_key('%s_subscribed' % (user)): try: lst.DeleteMember(user) dirty = 1 except Errors.MMNoSuchUserError: unsubscribe_errors.append((user, 'Not subscribed')) continue if not cgi_info.has_key("%s_digest" % (user)): if lst.digest_members.has_key(user): del lst.digest_members[user] dirty = 1 if not lst.members.has_key(user): lst.members[user] = 0 dirty = 1 else: if not lst.digest_members.has_key(user): lst.digest_members[user] = 0 dirty = 1 if lst.members.has_key(user): del lst.members[user] dirty = 1 for opt in ("hide", "nomail", "ack", "notmetoo", "plain"): okey = MailCommandHandler.option_info[opt] if cgi_info.has_key("%s_%s" % (user, opt)): lst.SetUserOption(user, okey, 1) dirty = 1 else: lst.SetUserOption(user, okey, 0) dirty = 1 if unsubscribe_errors: document.AddItem(Header(5, "Error Unsubscribing:")) items = map(lambda x: "%s -- %s" % (x[0], x[1]), unsubscribe_errors) document.AddItem(apply(UnorderedList, tuple((items)))) document.AddItem("
") if dirty: lst.Save() def AddErrorMessage(doc, errmsg, *args): doc.AddItem(Header(3, Italic(FontAttr(errmsg % args, color="#ff66cc")))) _config_info = None def GetConfigOptions(lst, category): global _config_info if _config_info == None: _config_info = lst.GetConfigInfo() return _config_info[category]