summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorviega1998-06-14 00:48:39 +0000
committerviega1998-06-14 00:48:39 +0000
commit05cf09209ce15e0801379f8ab5566b43d3d3c9a6 (patch)
tree5f6e820f91c4ed28e617de5adc1ebef39769246f
parent7f9a9be4291b1c350251535788436d1f2a301e7b (diff)
downloadmailman-05cf09209ce15e0801379f8ab5566b43d3d3c9a6.tar.gz
mailman-05cf09209ce15e0801379f8ab5566b43d3d3c9a6.tar.zst
mailman-05cf09209ce15e0801379f8ab5566b43d3d3c9a6.zip
-rw-r--r--Mailman/Cgi/__init__.py0
-rw-r--r--Mailman/Cgi/admin.py835
-rw-r--r--Mailman/Cgi/admindb.py232
-rw-r--r--Mailman/Cgi/archives.py86
-rw-r--r--Mailman/Cgi/edithtml.py167
-rw-r--r--Mailman/Cgi/handle_opts.py207
-rw-r--r--Mailman/Cgi/listinfo.py178
-rw-r--r--Mailman/Cgi/options.py125
-rw-r--r--Mailman/Cgi/private.py228
-rw-r--r--Mailman/Cgi/roster.py110
-rw-r--r--Mailman/Cgi/subscribe.py188
-rw-r--r--modules/Cgi/__init__.py0
-rwxr-xr-xmodules/Cgi/admin.py835
-rwxr-xr-xmodules/Cgi/admindb.py232
-rwxr-xr-xmodules/Cgi/archives.py86
-rwxr-xr-xmodules/Cgi/edithtml.py167
-rwxr-xr-xmodules/Cgi/handle_opts.py207
-rwxr-xr-xmodules/Cgi/listinfo.py178
-rwxr-xr-xmodules/Cgi/options.py125
-rwxr-xr-xmodules/Cgi/private.py228
-rwxr-xr-xmodules/Cgi/roster.py110
-rwxr-xr-xmodules/Cgi/subscribe.py188
22 files changed, 4712 insertions, 0 deletions
diff --git a/Mailman/Cgi/__init__.py b/Mailman/Cgi/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/Mailman/Cgi/__init__.py
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
new file mode 100644
index 000000000..c9155c87e
--- /dev/null
+++ b/Mailman/Cgi/admin.py
@@ -0,0 +1,835 @@
+#! /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."""
+
+__version__ = "$Revision: 734 $"
+
+import sys
+import os, cgi, string, crypt, types, time
+import paths # path hacking
+import mm_utils, maillist, mm_cfg, mm_err, mm_mailcmd, Cookie
+from htmlformat import *
+
+try:
+ sys.stderr = mm_utils.StampedLogger("error", label = 'admin',
+ manual_reprime=1, nofail=0)
+except IOError:
+ pass # Oh well - SOL on redirect, errors show thru.
+
+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")]
+
+
+LOGIN_PAGE = """
+<html>
+<head>
+ <title>%(listname)s Administrative Authentication</title>
+</head>
+<body bgcolor="#ffffff">
+<FORM METHOD=POST ACTION="%(path)s">
+%(message)s
+ <TABLE WIDTH="100%%" BORDER="0" CELLSPACING="4" CELLPADDING="5">
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%%" BGCOLOR="#99CCFF" ALIGN="CENTER">
+ <B><FONT COLOR="#000000" SIZE="+1">%(listname)s Administrative
+ Authentication</FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <TD> <div ALIGN="Right"> List Administrative Password: </div> </TD>
+ <TD> <INPUT TYPE=password NAME=adminpw SIZE=30></TD>
+ </tr>
+ <tr>
+ <td colspan=2 align=middle> <INPUT TYPE=SUBMIT name="request_login">
+ </td>
+ </tr>
+ </TABLE>
+</FORM>
+"""
+
+# " <- icky emacs font-lock bug workaround
+
+SECRET="monty"
+
+def isAuthenticated(list, password=None, SECRET="SECRET"):
+ import base64, md5
+ if password is not None: # explicit login
+ try:
+ list.ConfirmAdminPassword(password)
+ except mm_err.MMBadPasswordError:
+ AddErrorMessage(doc, 'Error: Incorrect admin password.')
+ return 0
+ except:
+ print "Content-type: text/html\n"
+
+ print "<p><h3>We're sorry, we hit a bug!</h3>\n"
+ print "If you would like to help us identify the problem, please "
+ print "email a copy of this page to the webmaster for this site"
+ print 'with a description of what happened. Thanks!'
+ print "\n<PRE>"
+ try:
+ import traceback
+ sys.stderr = sys.stdout
+ traceback.print_exc()
+ except:
+ print "[failed to get traceback]"
+ print "\n\n</PRE>"
+
+ token = md5.new(SECRET + list_name + SECRET).digest()
+ token = base64.encodestring(token)
+ token = string.strip(token)
+ 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"):
+ try:
+ inp = base64.decodestring(c[list_name + "-admin"].value)
+ check = md5.new(SECRET+list_name+SECRET).digest()
+ except Error: # the decodestring may return incorrect padding?
+ raise 'Decode failed'
+ return 0
+ if inp == check:
+ return 1
+ else:
+ 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 = mm_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 mm_err.MMUnknownListError:
+ lst = None
+ try:
+ if not (lst and lst._ready):
+ FormatAdminOverview(error="List <em>%s</em> 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:
+ print "Content-type: text/html\n\n"
+ print LOGIN_PAGE % ({"listname": list_name,
+ "path": os.environ.get("REQUEST_URI", "/mailman/admin"),
+ "message": message})
+ return
+
+ if len(cgi_data.keys()):
+ if cgi_data.has_key('VARHELP'):
+ FormatOptionHelp(doc, cgi_data['VARHELP'].value, lst)
+ print doc.Format(bgcolor="#ffffff")
+ return
+ if (cgi_data.has_key('bounce_matching_headers')):
+ try:
+ pairs = lst.parse_matching_header_opt()
+ except mm_err.MMBadConfigError, line:
+ AddErrorMessage(doc,
+ 'Warning: bad matching-header line'
+ ' (does it have the colon?)<ul> %s </ul>',
+ line)
+
+ if not lst.digestable and len(lst.digest_members):
+ AddErrorMessage(doc,
+ 'Warning: you have digest members,'
+ ' but digests are turned off.'
+ ' Those people will not receive mail.')
+ if not lst.nondigestable and len(lst.members):
+ AddErrorMessage(doc,
+ 'Warning: you have lst members,'
+ ' but non-digestified mail is turned'
+ ' off. They will receive mail until'
+ ' you fix this problem.')
+ if len(cgi_data.keys()):
+ ChangeOptions(lst, category, cgi_data, doc)
+ FormatConfiguration(doc, lst, category, category_suffix)
+ print doc.Format(bgcolor="#ffffff")
+
+ finally:
+ if lst:
+ lst.Unlock()
+
+# Form Production:
+
+def FormatAdminOverview(error=None):
+ "Present a general welcome and itemize the (public) lists."
+ doc = Document()
+ legend = "%s maillists - Admin Links" % mm_cfg.DEFAULT_HOST_NAME
+ doc.SetTitle(legend)
+
+ table = Table(border=0, width="100%")
+ table.AddRow([Center(Header(2, legend))])
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0,
+ colspan=2, bgcolor="#99ccff")
+
+ advertised = []
+ names = mm_utils.list_names()
+ names.sort()
+ for n in names:
+ l = maillist.MailList(n, lock=0)
+ if l.advertised: advertised.append(l)
+
+ if error:
+ greeting = FontAttr(error, color="ff5060", size="+1")
+ else:
+ greeting = "Welcome!"
+
+ if not advertised:
+ welcome_items = (greeting,
+ "<p>"
+ " There currently are no publicly-advertised ",
+ Link(mm_cfg.MAILMAN_URL, "mailman"),
+ " maillists on %s." % mm_cfg.DEFAULT_HOST_NAME,
+ )
+ else:
+
+ welcome_items = (
+ greeting,
+ "<p>"
+ " Below is the collection of publicly-advertised ",
+ Link(mm_cfg.MAILMAN_URL, "mailman"),
+ " maillists 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.<p>"
+ % ((error and "the right ") or ""))
+ +
+ " General list information can be found at ",
+ Link(os.path.join('../'* mm_utils.GetNestingLevel(),
+ "listinfo/"), "the maillist overview page"),
+ "."
+ "<p>(Send questions and comments to ",
+ Link("mailto:%s" % mm_cfg.MAILMAN_OWNER,
+ mm_cfg.MAILMAN_OWNER),
+ ".)<p>"
+ )
+ )
+
+ 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 Maillist Configuration - %s Section'
+ % (lst.real_name, label)))))
+ doc.AddItem('<hr>')
+
+ links_table = Table(valign="top")
+
+ links_table.AddRow([Center(Bold("Configuration Categories")),
+ Center(Bold("Other Administrative Activities"))])
+ other_links = UnorderedList()
+ link = Link(lst.GetRelativeScriptURL('admindb'),
+ 'Tend to pending administrative requests.')
+ other_links.AddItem(link)
+ link = Link(lst.GetRelativeScriptURL('listinfo'),
+ 'Go to the general list information page.')
+ other_links.AddItem(link)
+ link = Link(lst.GetRelativeScriptURL('edithtml'),
+ 'Edit the HTML for the public list pages.')
+ other_links.AddItem(link)
+
+ these_links = UnorderedList()
+ for k, v in CATEGORIES:
+ if k == category:
+ these_links.AddItem("<b> =&gt; " + v + " &lt;= </b>")
+ else:
+ these_links.AddItem(Link("%s/%s" %
+ (lst.GetRelativeScriptURL('admin'),k),v))
+
+ links_table.AddRow([these_links, other_links])
+ links_table.AddRowInfo(max(links_table.GetCurrentRowIndex(), 0),
+ valign="top")
+
+ doc.AddItem(links_table)
+ doc.AddItem('<hr>')
+ if category_suffix:
+ form = Form("%s/%s" % (lst.GetRelativeScriptURL('admin'),
+ category_suffix))
+ else:
+ form = Form(lst.GetRelativeScriptURL('admin'))
+ doc.AddItem(form)
+
+ form.AddItem("Make your changes, below, and then submit it all at the"
+ " bottom. (You can also change your password there,"
+ " as well.)<p>")
+
+ form.AddItem(FormatOptionsSection(category, lst))
+
+ form.AddItem(Center(FormatPasswordStuff()))
+
+ 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 = "<center><i>" + item + "</i></center>"
+ big_table.AddRow([item])
+ big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0),
+ 0, colspan=2)
+ if not did_col_header:
+ # Do col header after very first string descr, if any...
+ ColHeader()
+ did_col_header = 1
+ else:
+ if not did_col_header:
+ # ... but do col header before anything else.
+ ColHeader()
+ did_col_header = 1
+ AddOptionsTableItem(big_table, item, category, lst)
+ big_table.AddRow(['<br>'])
+ big_table.AddCellInfo(big_table.GetCurrentRowIndex(), 0, colspan=2)
+ return big_table
+
+def AddOptionsTableItem(table, item, category, lst, nodetails=0):
+ """Add a row to an options table with the item description and value."""
+ try:
+ got = GetItemCharacteristics(item)
+ varname, kind, params, dependancies, descr, elaboration = got
+ except ValueError, msg:
+ lst.LogMsg("error", "admin: %s", msg)
+ return Italic("<malformed option>")
+ descr = GetItemGuiDescr(lst, category, varname, descr,
+ elaboration, nodetails)
+ val = GetItemGuiValue(lst, kind, varname, params)
+ table.AddRow([descr, val])
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 1,
+ bgcolor="#cccccc")
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0,
+ bgcolor="#cccccc")
+
+def FormatOptionHelp(doc, varref, lst):
+ item = bad = None
+ reflist = string.split(varref, '/')
+ if len(reflist) == 2:
+ category, varname = reflist
+ options = GetConfigOptions(lst, category)
+ for i in options:
+ if i and i[0] == varname:
+ item = i
+ break
+ if not item:
+ bad = ("Option %s/%s not found. %s"
+ % (category, varname, os.environ['PATH_INFO']))
+ else:
+ try:
+ got = GetItemCharacteristics(item)
+ varname, kind, params, dependancies, descr, elaboration = got
+ except ValueError, msg:
+ bad = msg
+ if not bad and not elaboration:
+ bad = "Option %s has no extended help." % varname
+ if bad:
+ AddErrorMessage(doc, bad)
+ return
+
+ header = Table(width="100%")
+ legend = ('%s Maillist Configuration Help<br><em>%s</em> Option'
+ % (lst.real_name, varname))
+ header.AddRow([Center(Header(3, legend))])
+ header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0,
+ colspan=2, bgcolor="#99ccff")
+ doc.SetTitle("Mailman %s List Option Help" % varname)
+ doc.AddItem(header)
+ doc.AddItem("<b>%s</b> (%s): %s<p>" % (varname, category, item[4]))
+ doc.AddItem("%s<p>" % item[5])
+
+ form = Form(os.path.join(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:
+ 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 = '<div ALIGN="right">' + descr
+ if not nodetails and elaboration:
+ ref = "../" * (mm_utils.GetNestingLevel()-1) + list_name + "/"
+ ref = ref + '?VARHELP=' + category + "/" + varname
+ descr = Container(descr,
+ Link(ref, " (Details)", target="MMHelp"),
+ "</div>")
+ else:
+ descr = descr + "</div>"
+ return descr
+
+def FormatMembershipOptions(lst):
+ container = Container()
+ header = Table(width="100%")
+ header.AddRow([Center(Header(2, "Membership Management"))])
+ header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0,
+ colspan=2, bgcolor="#99ccff")
+ container.AddItem(header)
+ user_table = Table(width="90%")
+ user_table.AddRow([Center(Header(4, "Membership List"))])
+ user_table.AddCellInfo(user_table.GetCurrentRowIndex(),
+ user_table.GetCurrentCellIndex(),
+ bgcolor="#cccccc", colspan=8)
+
+ members = {}
+ digests = {}
+ for member in lst.members:
+ members[member] = 1
+ for member in lst.digest_members:
+ digests[member] = 1
+ all = lst.members + lst.digest_members
+ if len(all) > mm_cfg.ADMIN_MEMBER_CHUNKSIZE:
+ chunks = mm_utils.chunkify(all)
+ if not cgi_data.has_key("chunk"):
+ chunk = 0
+ else:
+ chunk = string.atoi(cgi_data["chunk"].value)
+ all = chunks[chunk]
+ footer = ("<p><em>To View other sections, "
+ "click on the appropriate range listed below</em>")
+ chunk_indices = range(len(chunks))
+ chunk_indices.remove(chunk)
+ buttons = []
+ pi = os.environ["PATH_INFO"]
+ for ci in chunk_indices:
+ start, end = chunks[ci][0], chunks[ci][-1]
+ buttons.append("<a href=/mailman/admin%s?chunk=%d> from %s to %s </a>" % \
+ ( pi, ci, start, end))
+ buttons = apply(UnorderedList, tuple(buttons))
+ footer = footer + buttons.Format() + "<p>"
+ else:
+ all.sort()
+ footer = "<p>"
+ for member in all:
+ cells = [member + "<input type=hidden name=user value=%s>" % (member),
+ "subscribed " +CheckBox(member + "_subscribed", "on", 1).Format(),
+ ]
+ if members.get(member):
+ cells.append("digest " + CheckBox(member + "_digest", "off", 0).Format())
+ else:
+ cells.append("digest " + CheckBox(member + "_digest", "on", 1).Format())
+ for opt in ("hide", "nomail", "ack", "norcv", "plain"):
+ if lst.GetUserOption(member, mm_mailcmd.option_info[opt]):
+ value = "on"
+ checked = 1
+ else:
+ value = "off"
+ checked = 0
+ box = CheckBox("%s_%s" % (member, opt), value, checked)
+ cells.append("%s %s" % (opt, box.Format()))
+ user_table.AddRow(cells)
+ container.AddItem(Center(user_table))
+ 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)
+ container.AddItem(Center(t))
+ container.AddItem(Center(TextArea(name='subscribees', rows=10,cols=60,wrap=None)))
+ container.AddItem(Center("<em> Enter One address per line</em><p>"))
+ return container
+
+def FormatPasswordStuff():
+ submit = Table(bgcolor="#99ccff",
+ border=0, cellspacing=0, cellpadding=2, width="100%")
+ submit.AddRow([Bold(SubmitButton('submit', 'Submit Your Changes'))])
+ submit.AddCellInfo(submit.GetCurrentRowIndex(), 0, align="middle")
+ change_pw_table = Table(bgcolor="#99cccc", border=0,
+ cellspacing=0, cellpadding=2, width="90%")
+ change_pw_table.AddRow([Bold(Center('To Change The Administrator Password')),
+ '<div ALIGN="right"> Enter the new password: </div>',
+ PasswordBox('newpw'),])
+ change_pw_table.AddCellInfo(0, 0, align="middle", colspan=2)
+ change_pw_table.AddRow(['<div ALIGN="right"> Enter the current password </div>',
+ PasswordBox('adminpw'),
+ '<div ALIGN="right">Again to confirm it:</div>',
+ PasswordBox('confirmpw')])
+ password_stuff = Container()
+ password_stuff.AddItem(change_pw_table)
+ password_stuff.AddItem("<p>")
+ password_stuff.AddItem(submit)
+ return password_stuff
+
+# XXX klm - looks like turn_on_moderation is orphaned.
+turn_on_moderation = 0
+
+# Options processing
+
+def GetValidValue(lst, prop, my_type, val, dependant):
+ if my_type == mm_cfg.Radio or my_type == mm_cfg.Toggle:
+ if type(val) <> types.IntType:
+ try:
+ # XXX Security!?
+ val = eval(val)
+ except:
+ pass
+ # Don't know what to do here...
+ return val
+ elif my_type == mm_cfg.String or my_type == mm_cfg.Text:
+ return val
+ elif my_type == mm_cfg.Email:
+ try:
+ valid = mm_utils.ValidEmail(val)
+ if valid:
+ return val
+ except:
+ pass
+ # Revert to the old value.
+ return getattr(lst, prop)
+ elif my_type == mm_cfg.EmailList:
+ def SafeValidAddr(addr):
+ import mm_utils
+ try:
+ valid = mm_utils.ValidEmail(addr)
+ if valid:
+ return 1
+ else:
+ return 0
+ except:
+ return 0
+
+ val = filter(SafeValidAddr,
+ map(string.strip, string.split(val, '\n')))
+ if dependant and len(val):
+ # Wait till we've set everything to turn it on,
+ # as we don't want to clobber our special case.
+ # XXX klm - looks like turn_on_moderation is orphaned?
+ turn_on_moderation = 1
+ return val
+ elif my_type == mm_cfg.Host:
+ return val
+##
+## This code is sendmail dependant, so we'll just live w/o
+## the error checking for now.
+##
+## # Shouldn't have to read in the whole file.
+## file = open('/etc/sendmail.cf', 'r')
+## lines = string.split(file.read(), '\n')
+## file.close()
+## def ConfirmCWEntry(item):
+## return item[0:2] == 'Cw'
+## lines = filter(ConfirmCWEntry, lines)
+## if not len(lines):
+## # Revert to the old value.
+## return getattr(list, prop)
+## for line in lines:
+## if string.lower(string.strip(line[2:])) == string.lower(val):
+## return val
+## return getattr(list, prop)
+ elif my_type == mm_cfg.Number:
+ try:
+ num = eval(val)
+ if num < 0:
+ return getattr(lst, prop)
+ return num
+ except:
+ return getattr(lst, prop)
+ else:
+ # Should never get here...
+ return val
+
+
+def ChangeOptions(lst, category, cgi_info, document):
+ dirty = 0
+ confirmed = 0
+ if cgi_info.has_key('newpw'):
+ if cgi_info.has_key('confirmpw'):
+ if cgi_info.has_key('adminpw'):
+ try:
+ lst.ConfirmAdminPassword(cgi_info['adminpw'].value)
+ confirmed = 1
+ except mm_err.MMBadPasswordError:
+ m = "Error: incorrect administrator password"
+ document.AddItem(Header(3, Italic(FontAttr(m, color="ff5060"))))
+ confirmed = 0
+ if confirmed:
+ new = cgi_info['newpw'].value
+ confirm = cgi_info['confirmpw'].value
+ if new == confirm:
+ lst.password = crypt.crypt(new, mm_utils.GetRandomSeed())
+ dirty = 1
+ else:
+ m = 'Error: Passwords did not match.'
+ document.AddItem(Header(3, Italic(FontAttr(m, color="ff5060"))))
+
+ else:
+ m = 'Error: You must type in your new password twice.'
+ document.AddItem(
+ Header(3, Italic(FontAttr(m, color="ff5060"))))
+ #
+ # for some reason, the login page mangles important values for the list
+ # such as .real_name so we only process these changes if the category
+ # is not "members" and the request is not from the login page
+ # -scott 19980515
+ #
+ if category != 'members' and not cgi_info.has_key("request_login") and\
+ len(cgi_info.keys()) > 1:
+ opt_list = GetConfigOptions(lst, category)
+ for item in opt_list:
+ if len(item) < 5:
+ continue
+ property, kind, args, deps, desc = (item[0], item[1], item[2],
+ item[3], item[4])
+ if not cgi_info.has_key(property):
+ if (kind <> mm_cfg.Text and
+ kind <> mm_cfg.String and
+ kind <> mm_cfg.EmailList):
+ continue
+ else:
+ val = ''
+ else:
+ val = cgi_info[property].value
+ value = GetValidValue(lst, property, kind, val, deps)
+ if getattr(lst, property) != value:
+ setattr(lst, property, value)
+ dirty = 1
+ #
+ # mass subscription processing for members category
+ #
+ if cgi_info.has_key('subscribees'):
+ name_text = cgi_info['subscribees'].value
+ name_text = string.replace(name_text, '\r', '')
+ names = string.split(name_text, '\n')
+ if '' in names:
+ names.remove('')
+ subscribe_success = []
+ subscribe_errors = []
+ for new_name in map(string.strip,names):
+ digest = 0
+ if not lst.digestable:
+ digest = 0
+ if not lst.nondigestable:
+ digest = 1
+ try:
+ lst.ApprovedAddMember(new_name, (mm_utils.GetRandomSeed() +
+ mm_utils.GetRandomSeed()), digest)
+ subscribe_success.append(new_name)
+ except mm_err.MMAlreadyAMember:
+ subscribe_errors.append((new_name, 'Already a member'))
+
+ except mm_err.MMBadEmailError:
+ subscribe_errors.append((new_name, "Bad/Invalid email address"))
+ except mm_err.MMHostileAddress:
+ subscribe_errors.append((new_name, "Hostile Address (illegal characters)"))
+ if subscribe_success:
+ document.AddItem(Header(5, "Successfully Subscribed:"))
+ document.AddItem(apply(UnorderedList, tuple((subscribe_success))))
+ document.AddItem("<p>")
+ 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("<p>")
+ #
+ # 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]
+ for user in users:
+ if not cgi_info.has_key('%s_subscribed' % (user)):
+ lst.DeleteMember(user)
+ dirty = 1
+ continue
+ if not cgi_info.has_key("%s_digest" % (user)):
+ if user in lst.digest_members:
+ list.digest_members.remove(user)
+ dirty = 1
+ if user not in lst.members:
+ lst.members.append(user)
+ dirty = 1
+ else:
+ if user not in lst.digest_members:
+ lst.digest_members.append(user)
+ dirty = 1
+ if user in lst.members:
+ lst.members.remove(user)
+ dirty = 1
+
+ for opt in ("hide", "nomail", "ack", "norcv", "plain"):
+ if cgi_info.has_key("%s_%s" % (user, opt)):
+ lst.SetUserOption(user, mm_mailcmd.option_info[opt], 1)
+ dirty = 1
+ else:
+ lst.SetUserOption(user, mm_mailcmd.option_info[opt], 0)
+ dirty = 1
+
+
+ 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]
+
diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py
new file mode 100644
index 000000000..415184d3b
--- /dev/null
+++ b/Mailman/Cgi/admindb.py
@@ -0,0 +1,232 @@
+#! /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.
+
+"""Produce and process the pending-approval items for a list."""
+
+import sys
+import os, cgi, string, crypt, types
+import mm_utils, maillist, mm_err, htmlformat
+
+doc = htmlformat.Document()
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+
+
+if len(list_info) < 1:
+ doc.SetTitle("Admindb Error")
+ doc.AddItem(htmlformat.Header(2, "Invalid options to CGI script."))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+list_name = string.lower(list_info[0])
+
+try:
+ list = maillist.MailList(list_name)
+except:
+ msg = "%s: No such list." % list_name
+ doc.SetTitle("Admindb Error - %s" % msg)
+ doc.AddItem(htmlformat.Header(2, msg))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+if not list._ready:
+ msg = "%s: No such list." % list_name
+ doc.SetTitle("Admindb Error - %s" % msg)
+ doc.AddItem(htmlformat.Header(2, msg))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+# Note, these 2 functions use i only to count the number of times to
+# go around. We always operate on the first element of the list
+# because we're going to delete the element after we operate on it.
+
+def SubscribeAll():
+ for i in range(len(list.requests['add_member'])):
+ comment_key = 'comment-%d' % list.requests['add_member'][0][0]
+ if form.has_key(comment_key):
+ list.HandleRequest(('add_member', 0), 1, form[comment_key].value)
+ else:
+ list.HandleRequest(('add_member', 0), 1)
+
+def SubscribeNone():
+ for i in range(len(list.requests['add_member'])):
+ comment_key = 'comment-%d' % list.requests['add_member'][0][0]
+ if form.has_key(comment_key):
+ list.HandleRequest(('add_member', 0), 0, form[comment_key].value)
+ else:
+ list.HandleRequest(('add_member', 0), 0)
+
+def PrintHeader(str, error=0):
+ if error:
+ it = htmlformat.FontAttr(str, color="ff5060")
+ else:
+ it = str
+ doc.AddItem(htmlformat.Header(3, htmlformat.Italic(it)))
+ doc.AddItem('<hr>')
+
+def HandleRequests(doc):
+ if not form.has_key('adminpw'):
+ PrintHeader('You need to supply the admin password '
+ 'to answer requests.', error=1)
+ return
+ try:
+ list.ConfirmAdminPassword(form['adminpw'].value)
+ except:
+ PrintHeader('Incorrect admin password.', error=1)
+ return
+ ignore_subscribes = 0
+ if form.has_key('subscribe_all'):
+ ignore_subscribes = 1
+ SubscribeAll()
+ elif form.has_key('subscribe_none'):
+ ignore_subscribes = 1
+ SubscribeNone()
+ for k in form.keys():
+ try:
+ # XXX Security?!
+ v = eval(form[k].value)
+ request_id = eval(k)
+ except: # For stuff like adminpw
+ continue
+ if type(request_id) <> types.IntType:
+ continue
+ try:
+ request = list.GetRequest(request_id)
+ except mm_err.MMBadRequestId:
+ continue # You've already changed the database. No biggie.
+ if ignore_subscribes and request[0] == 'add_member':
+ # We already handled this request.
+ continue
+ comment_key = 'comment-%d' % request_id
+ if form.has_key(comment_key):
+ list.HandleRequest(request, v, form[comment_key].value)
+ else:
+ list.HandleRequest(request, v)
+ list.Save()
+ PrintHeader('Database Updated...')
+
+
+def PrintAddMemberRequest(val, table):
+ table.AddRow([
+ val[3],
+ htmlformat.RadioButtonArray(val[0], ("Refuse", "Subscribe")),
+ htmlformat.TextBox("comment-%d" % val[0], size=50)
+ ])
+
+def PrintPostRequest(val, form):
+ t = htmlformat.Table(cellspacing=10)
+ t.AddRow([
+ htmlformat.FontSize("+1",
+ htmlformat.Bold('Post held because: ')),
+ val[3]])
+ t.AddRow([
+ htmlformat.FontSize("+1",
+ htmlformat.Bold('Action to take on this post:')),
+ htmlformat.RadioButtonArray(val[0], ("Approve", "Reject",
+ "Discard (eg, spam)")),
+ htmlformat.SubmitButton('submit', 'Submit All Data')
+ ])
+ t.AddRow([
+ htmlformat.FontSize("+1",
+ htmlformat.Bold('If you reject this post, '
+ 'explain (optional):')),
+ htmlformat.TextBox("comment-%d" % val[0], size=50)])
+
+ cur_row = t.GetCurrentRowIndex()
+ cur_col = t.GetCurrentCellIndex()
+ t.AddCellInfo(cur_row, cur_col, colspan=3)
+
+ t.AddRow([
+ htmlformat.FontSize("+1",
+ htmlformat.Bold('Contents:'))])
+ form.AddItem(t)
+ form.AddItem(htmlformat.Preformatted(val[2][1]))
+ form.AddItem('<p>')
+
+
+def PrintRequests(doc):
+ # The only types of requests we know about are add_member and post.
+ # Anything else that might have gotten in here somehow we'll just
+ # ignore (This should never happen unless someone is hacking at
+ # the code).
+
+ doc.AddItem(htmlformat.Header(2, "Administrative requests for "
+ "'%s' mailing list" % list.real_name))
+ doc.AddItem(htmlformat.FontSize("+1", htmlformat.Link(
+ list.GetRelativeScriptURL('admin'), htmlformat.Italic(
+ 'View or edit the list configuration information'))))
+ doc.AddItem('<p><hr>')
+ if not list.RequestsPending():
+ doc.AddItem(htmlformat.Header(3,'There are no pending requests.'))
+ doc.AddItem(list.GetMailmanFooter())
+ return
+ form = htmlformat.Form(list.GetRelativeScriptURL('admindb'))
+ doc.AddItem(form)
+ form.AddItem('Admin password: ')
+ form.AddItem(htmlformat.PasswordBox('adminpw'))
+ form.AddItem('<p>')
+ if list.requests.has_key('add_member'):
+## form.AddItem('<hr>')
+## t = htmlformat.Table(cellspacing=10)
+## t.AddRow([
+## htmlformat.SubmitButton('submit', 'Submit All Data'),
+## htmlformat.SubmitButton('subscribe_all', 'Subscribe Everybody'),
+## htmlformat.SubmitButton('subscribe_none', 'Refuse Everybody')
+## ])
+## form.AddItem(t)
+ form.AddItem('<hr>')
+ form.AddItem(htmlformat.Center(
+ htmlformat.Header(2, 'Subscription Requests')))
+ t = htmlformat.Table(border=2)
+ t.AddRow([
+ htmlformat.Bold('Email'),
+ htmlformat.Bold('Descision'),
+ htmlformat.Bold('Reasoning for subscription refusal (optional)')])
+ for request in list.requests['add_member']:
+ PrintAddMemberRequest(request, t)
+
+ form.AddItem(t)
+ t = htmlformat.Table(cellspacing=10)
+ t.AddRow([
+ htmlformat.SubmitButton('submit', 'Submit All Data'),
+ htmlformat.SubmitButton('subscribe_all', 'Subscribe Everybody'),
+ htmlformat.SubmitButton('subscribe_none', 'Refuse Everybody')
+ ])
+ form.AddItem(t)
+
+ # Print submitit buttons...
+ if list.requests.has_key('post'):
+ for request in list.requests['post']:
+ form.AddItem('<hr>')
+ form.AddItem(htmlformat.Center(htmlformat.Header(2, "Held Message")))
+ PrintPostRequest(request, form)
+ doc.AddItem(list.GetMailmanFooter())
+
+try:
+ form = cgi.FieldStorage()
+ if len(form.keys()):
+ doc.SetTitle("%s Admindb Results" % list.real_name)
+ HandleRequests(doc)
+ else:
+ doc.SetTitle("%s Admindb" % list.real_name)
+ PrintRequests(doc)
+ text = doc.Format(bgcolor="#ffffff")
+ print text
+ sys.stdout.flush()
+finally:
+ list.Unlock()
diff --git a/Mailman/Cgi/archives.py b/Mailman/Cgi/archives.py
new file mode 100644
index 000000000..2b0ed0fa9
--- /dev/null
+++ b/Mailman/Cgi/archives.py
@@ -0,0 +1,86 @@
+#! /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.
+
+# This script is being deprecated, in favor hookups for an external archiver.
+
+# We don't need to lock in this script, because we're never going to change
+# data.
+
+import sys
+import os, types, posix, string
+import mm_utils, maillist, htmlformat
+
+print "Content-type: text/html"
+print
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+
+if len(list_info) < 1:
+ print "<h2>Invalid options to CGI script.</h2>"
+ sys.exit(0)
+
+list_name = string.lower(list_info[0])
+
+try:
+ list = maillist.MailList(list_name)
+except:
+ print "<h2>%s: No such list.</h2>" % list_name
+ sys.exit(0)
+
+if not list._ready:
+ print "<h2>%s: No such list.</h2>" % list_name
+ sys.exit(0)
+
+def GetArchiveList(list):
+ archive_list = htmlformat.UnorderedList()
+
+ def ArchiveFilter(str):
+ if str[:7] <> 'volume_':
+ return 0
+ try:
+ x = eval(str[7:])
+ if type(x) <> types.IntType:
+ return 0
+ if x < 1:
+ return 0
+ return 1
+ except:
+ return 0
+ try:
+ dir_listing = filter(ArchiveFilter, os.listdir(list.archive_directory))
+ except posix.error:
+ return "<h3><em>No archives are currently available.</em></h3>"
+ if not len(dir_listing):
+ return "<h3><em>No archives are currently available.</em></h3>"
+ for dir in dir_listing:
+ link = htmlformat.Link(os.path.join(list._base_archive_url, dir),
+ "Volume %s" % dir[7:])
+ archive_list.AddItem(link)
+
+ return archive_list.Format()
+
+
+
+
+replacements = list.GetStandardReplacements()
+replacements['<mm-archive-list>'] = GetArchiveList(list)
+
+# Just doing print list.ParseTags(...) calls ParseTags twice???
+text = list.ParseTags('archives.html', replacements)
+print text
diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py
new file mode 100644
index 000000000..faf35e715
--- /dev/null
+++ b/Mailman/Cgi/edithtml.py
@@ -0,0 +1,167 @@
+#! /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.
+
+"""Script which implements admin editing of the list's html templates."""
+
+import sys
+import os, cgi, string, crypt, types
+import mm_utils, maillist, mm_cfg
+import htmlformat
+
+#Editable templates. We should also be able to edit the archive index, which
+#currently isn't a working template, but will be soon.
+
+template_data = (('listinfo.html', 'General list information page'),
+ ('subscribe.html', 'Subscribe results page'),
+ ('options.html', 'User specific options page'),
+ ('handle_opts.html', 'Changing user options results page'),
+ ('archives.html', 'Archives index page')
+ )
+
+
+def InitDocument():
+ return htmlformat.HeadlessDocument()
+
+doc = InitDocument()
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+
+if len(list_info) < 1:
+ doc.AddItem(htmlformat.Header(2, "Invalid options to CGI script."))
+ print doc.Format()
+ sys.exit(0)
+
+list_name = string.lower(list_info[0])
+
+try:
+ list = maillist.MailList(list_name, lock=0)
+except:
+ doc.AddItem(htmlformat.Header(2, "%s : No such list" % list_name))
+ print doc.Format()
+ sys.exit(0)
+
+if not list._ready:
+ doc.AddItem(htmlformat.Header(2, "%s : No such list" % list_name))
+ print doc.Format()
+ sys.exit(0)
+
+
+if len(list_info) > 1:
+ template_name = list_info[1]
+ for (template, info) in template_data:
+ if template == template_name:
+ template_info = info
+ doc.SetTitle('%s -- Edit html for %s' %
+ (list.real_name, template_info))
+ break
+ else:
+ doc.SetTitle('Edit HTML : Error')
+ doc.AddItem(htmlformat.Header(2, "%s: Invalid template" % template_name))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+else:
+ doc.SetTitle('%s -- HTML Page Editing' % list.real_name)
+ doc.AddItem(htmlformat.Header(1, '%s -- HTML Page Editing' % list.real_name))
+ doc.AddItem(htmlformat.Header(2, 'Select page to edit:'))
+ template_list = htmlformat.UnorderedList()
+ for (template, info) in template_data:
+ l = htmlformat.Link(os.path.join(list.GetRelativeScriptURL('edithtml'),
+ template), info)
+
+ template_list.AddItem(l)
+ doc.AddItem(htmlformat.FontSize("+2", template_list))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+
+
+def FormatHTML(doc):
+ doc.AddItem(htmlformat.Header(1,'%s:' % list.real_name))
+ doc.AddItem(htmlformat.Header(1, template_info))
+
+ doc.AddItem('<hr>')
+
+ link = htmlformat.Link(list.GetRelativeScriptURL('admin'),
+ 'View or edit the list configuration information.')
+ doc.AddItem(htmlformat.FontSize("+1", link))
+ doc.AddItem('<p>')
+
+ doc.AddItem('<hr>')
+
+ form = htmlformat.Form(os.path.join(list.GetRelativeScriptURL('edithtml'),
+ template_name))
+ doc.AddItem(form)
+
+ password_table = htmlformat.Table()
+ password_table.AddRow(['Enter the admin password to edit html:',
+ htmlformat.PasswordBox('adminpw')])
+ password_table.AddRow(['When you are done making changes...',
+ htmlformat.SubmitButton('submit', 'Submit Changes')])
+
+ form.AddItem(password_table)
+
+ text = mm_utils.QuoteHyperChars(list.SnarfHTMLTemplate(template_name))
+ form.AddItem(htmlformat.TextArea('html_code', text, rows=40, cols=75))
+
+def ChangeHTML(list, cgi_info, template_name, doc):
+ if not cgi_info.has_key('html_code'):
+ doc.AddItem(htmlformat.Header(3,"Can't have empty html page."))
+ doc.AddItem(htmlformat.Header(3,"HTML Unchanged."))
+ doc.AddItem('<hr>')
+ return
+ code = cgi_info['html_code'].value
+ f = open(os.path.join(list._template_dir, template_name), 'w')
+ f.write(code)
+ f.close()
+ doc.AddItem(htmlformat.Header(3, 'HTML successfully updated.'))
+ doc.AddItem('<hr>')
+
+try:
+ cgi_data = cgi.FieldStorage()
+ if len(cgi_data.keys()):
+ if not cgi_data.has_key('adminpw'):
+ m = 'Error: You must supply the admin password to edit html.'
+ doc.AddItem(htmlformat.Header(3,
+ htmlformat.Italic(
+ htmlformat.FontAttr(
+ m, color="ff5060"))))
+ doc.AddItem('<hr>')
+ else:
+ try:
+ list.ConfirmAdminPassword(cgi_data['adminpw'].value)
+ ChangeHTML(list, cgi_data, template_name, doc)
+ except:
+ m = 'Error: Incorrect admin password.'
+ doc.AddItem(htmlformat.Header(3,
+ htmlformat.Italic(
+ htmlformat.FontAttr(
+ m, color="ff5060"))))
+ doc.AddItem('<hr>')
+
+
+
+ FormatHTML(doc)
+
+finally:
+ try:
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ except:
+ pass
diff --git a/Mailman/Cgi/handle_opts.py b/Mailman/Cgi/handle_opts.py
new file mode 100644
index 000000000..7b5747f4f
--- /dev/null
+++ b/Mailman/Cgi/handle_opts.py
@@ -0,0 +1,207 @@
+#! /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 input to user options form."""
+
+import sys
+import os, cgi, string
+import mm_utils, maillist, mm_err, mm_cfg, htmlformat
+
+
+doc = htmlformat.Document()
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+
+if len(list_info) < 2:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("Invalid options to CGI script."))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+list_name = string.lower(list_info[0])
+user = list_info[1]
+
+if len(list_info) < 2:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("Invalid options to CGI script."))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+try:
+ list = maillist.MailList(list_name)
+except:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+if not list._ready:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name))
+ print doc.Format(bgcolor="#ffffff")
+ list.Unlock()
+ sys.exit(0)
+
+
+def PrintResults(results):
+ replacements = list.GetStandardReplacements()
+ replacements['<mm-results>'] = results
+ replacements['<mm-operation>'] = operation
+ output = list.ParseTags('handle_opts.html', replacements)
+
+ doc.AddItem(output)
+ print doc.Format(bgcolor="#ffffff")
+ list.Unlock()
+ sys.exit(0)
+
+form = cgi.FieldStorage()
+
+error = 0
+operation = ""
+
+if string.lower(user) not in list.members + list.digest_members:
+ PrintResults("%s not a member!<p>" % user)
+
+if form.has_key("unsub"):
+ operation = "Unsubscribe"
+ if not form.has_key("upw"):
+ PrintResults("You must give your password to unsubscribe.<p>")
+ else:
+ try:
+ pw = form["upw"].value
+ if list.ConfirmUserPassword(user, pw):
+ list.DeleteMember(user, "web cmd")
+ except mm_err.MMListNotReady:
+ PrintResults("List is not functional.")
+ except mm_err.MMNoSuchUserError:
+ PrintResults("You seem to already be not a member.<p>")
+ except mm_err.MMBadUserError:
+ PrintResults("Your account has gone awry - "
+ "please contact the list administrator!<p>")
+ except mm_err.MMBadPasswordError:
+ PrintResults("That password was incorrect.<p>")
+# except:
+# PrintResults('''An unknown error occured. <p>
+#Please send mail to <a href=%s>%s</a> explaining
+#exactly what you did to get this error.<p>''' % (mm_cfg.MAILMAN_OWNER,
+# mm_cfg.MAILMAN_OWNER))
+
+ PrintResults("You have been unsubscribed.<p>")
+elif form.has_key("emailpw"):
+ try:
+ list.MailUserPassword(user)
+ PrintResults("A reminder of your password "
+ "has been emailed to you.<p>")
+ except mm_err.MMBadUserError:
+ PrintResults("Your password entry has not been found. The list "
+ "manager is being notified.<p>")
+
+elif form.has_key("changepw"):
+ if (form.has_key('opw')
+ and form.has_key('newpw')
+ and form.has_key('confpw')):
+ try:
+ list.ConfirmUserPassword(user, form['opw'].value)
+ list.ChangeUserPassword(user,
+ form['newpw'].value, form['confpw'].value)
+ except mm_err.MMListNotReady:
+ PrintResults("The list is currently not funcitonal.")
+ except mm_err.MMNotAMemberError:
+ PrintResults("You seem to no longer be a list member.")
+ except mm_err.MMBadPasswordError:
+ PrintResults("The old password you supplied was incorrect.")
+ except mm_err.MMPasswordsMustMatch:
+ PrintResults("Passwords must match.")
+ except:
+ PrintResults('''An unknown error occured. <p>
+Please send mail to <a href=%s>%s</a> explaining
+exactly what you did to get this error.<p>''' % (mm_cfg.MAILMAN_OWNER, mm_cfg.MAILMAN_OWNER))
+
+
+ PrintResults("Your password has been changed.")
+ else:
+ PrintResults("You must supply your old password,"
+ " and your new password twice.")
+
+else:
+ # If keys don't exist, set them to whatever they were. (essentially a noop)
+ if form.has_key("digest"):
+ digest_value = eval(form["digest"].value)
+ else:
+ digest_value = list.GetUserOption(user, mm_cfg.Digests)
+ if form.has_key("mime"):
+ mime = eval(form["mime"].value)
+ else:
+ mime = list.GetUserOption(user, mm_cfg.DisableMime)
+ if form.has_key("dontreceive"):
+ dont_receive = eval(form["dontreceive"].value)
+ else:
+ dont_receive = list.GetUserOption(user, mm_cfg.DontReceiveOwnPosts)
+ if form.has_key("ackposts"):
+ ack_posts = eval(form["ackposts"].value)
+ else:
+ ack_posts = list.GetUserOption(user, mm_cfg.AcknowlegePosts)
+ if form.has_key("disablemail"):
+ disable_mail = eval(form["disablemail"].value)
+ else:
+ disable_mail = list.GetUserOption(user, mm_cfg.DisableDelivery)
+ if form.has_key("conceal"):
+ conceal = eval(form["conceal"].value)
+ else:
+ conceal = list.GetUserOption(user, mm_cfg.ConcealSubscription)
+
+ if not form.has_key("digpw"):
+ PrintResults("You must supply a password to change options.")
+ try:
+ list.ConfirmUserPassword(user, form['digpw'].value)
+ except mm_err.MMAlreadyDigested:
+ pass
+ except mm_err.MMAlreadyUndigested:
+ pass
+ except mm_err.MMMustDigestError:
+ PrintResults("List only accepts digest members.")
+ except mm_err.MMCantDigestError:
+ PrintResults("List doesn't accept digest members.")
+ except mm_err.MMNotAMemberError:
+ PrintResults("%s isn't subscribed to this list." % mail.GetSender())
+ except mm_err.MMListNotReady:
+ PrintResults("List is not functional.")
+ except mm_err.MMNoSuchUserError:
+ PrintResults("%s is not subscribed to this list." %mail.GetSender())
+ except mm_err.MMBadPasswordError:
+ PrintResults("You gave the wrong password.")
+ except:
+ PrintResults('''An unknown error occured. <p>
+Please send mail to <a href=%s>%s</a> explaining
+exactly what you did to get this error.<p>''' % (mm_cfg.MAILMAN_OWNER))
+
+
+ list.SetUserOption(user, mm_cfg.DisableDelivery, disable_mail)
+ list.SetUserOption(user, mm_cfg.DontReceiveOwnPosts, dont_receive)
+ list.SetUserOption(user, mm_cfg.AcknowlegePosts, ack_posts)
+ list.SetUserOption(user, mm_cfg.DisableMime, mime)
+ try:
+ list.SetUserDigest(user, digest_value)
+ except (mm_err.MMAlreadyDigested, mm_err.MMAlreadyUndigested):
+ pass
+ list.SetUserOption(user, mm_cfg.ConcealSubscription, conceal)
+ PrintResults("You have successfully set your options.")
+
+
+list.Unlock() \ No newline at end of file
diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py
new file mode 100644
index 000000000..b1aae2d92
--- /dev/null
+++ b/Mailman/Cgi/listinfo.py
@@ -0,0 +1,178 @@
+#! /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.
+
+"""Produce listinfo page, primary web entry-point to maillists.
+"""
+
+# No lock needed in this script, because we don't change data.
+
+import sys
+import os, string
+from regsub import gsub
+import mm_utils, maillist, mm_cfg
+from htmlformat import *
+
+def main():
+ try:
+ path = os.environ['PATH_INFO']
+ except KeyError:
+ path = ""
+
+ list_info = mm_utils.GetPathPieces(path)
+
+ if len(list_info) == 0:
+ FormatListinfoOverview()
+ return
+
+ list_name = string.lower(list_info[0])
+
+ try:
+ list = maillist.MailList(list_name, lock=0)
+ except:
+ list = None
+
+ if not (list and list._ready):
+ FormatListinfoOverview(error="List <em>%s</em> not found." % list_name)
+ return
+
+ FormatListListinfo(list)
+
+def FormatListinfoOverview(error=None):
+ "Present a general welcome and itemize the (public) lists for this host."
+ doc = Document()
+ legend = "%s maillists" % mm_cfg.DEFAULT_HOST_NAME
+ doc.SetTitle(legend)
+
+ table = Table(border=0, width="100%")
+ table.AddRow([Center(Header(2, legend))])
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0,
+ colspan=2, bgcolor="#99ccff")
+
+ advertised = []
+ names = mm_utils.list_names()
+ names.sort()
+
+ # XXX We need a portable way to determine the host by which we are being
+ # visited! An absolute URL would do...
+ if os.environ.has_key('HTTP_HOST'):
+ http_host = os.environ['HTTP_HOST']
+ else:
+ http_host = None
+
+ for n in names:
+ l = maillist.MailList(n, lock = 0)
+ if l.advertised:
+ if (http_host
+ and (string.find(http_host, l.host_name) == -1
+ and string.find(l.host_name, http_host) == -1)):
+ # List is for different identity for this host - skip it.
+ continue
+ else:
+ advertised.append(l)
+
+ if error:
+ greeting = FontAttr(error, color="ff5060", size="+1")
+ else:
+ greeting = "Welcome!"
+
+ if not advertised:
+ welcome_items = (greeting,
+ "<p>"
+ " There currently are no publicly-advertised ",
+ Link(mm_cfg.MAILMAN_URL, "mailman"),
+ " maillists on %s." % mm_cfg.DEFAULT_HOST_NAME,
+ )
+ else:
+
+ welcome_items = (
+ greeting,
+ "<p>"
+ " Below is the collection of publicly-advertised ",
+ Link(mm_cfg.MAILMAN_URL, "mailman"),
+ " maillists on %s." % mm_cfg.DEFAULT_HOST_NAME,
+ (' Click on a list name to visit the info page'
+ ' for that list. There you can learn more about the list,'
+ ' subscribe to it, or find the roster of current subscribers.'),
+ )
+
+ welcome_items = (welcome_items +
+ (" To visit the info page for an unadvertised list,"
+ " open a URL similar to this one, but with a '/' and"
+ +
+ (" the %slist name appended."
+ % ((error and "right ") or ""))
+ +
+ '<p> List administrators, you can visit ',
+ Link(os.path.join('../' * mm_utils.GetNestingLevel(),
+ 'admin/'), "the list admin overview page"),
+ " to find the management interface for your list."
+ "<p>(Send questions or comments to ",
+ Link("mailto:%s" % mm_cfg.MAILMAN_OWNER,
+ mm_cfg.MAILMAN_OWNER),
+ ".)<p>"))
+
+ 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('listinfo'),
+ Bold(l.real_name)), l.description])
+
+ doc.AddItem(table)
+
+ print doc.Format(bgcolor="#ffffff")
+
+def FormatListListinfo(list):
+ "Expand the listinfo template against the list's settings, and print."
+
+ doc = HeadlessDocument()
+
+ replacements = list.GetStandardReplacements()
+
+ if not list.digestable or not list.nondigestable:
+ replacements['<mm-digest-radio-button>'] = ""
+ replacements['<mm-undigest-radio-button>'] = ""
+ else:
+ replacements['<mm-digest-radio-button>'] = list.FormatDigestButton()
+ replacements['<mm-undigest-radio-button>'] = \
+ list.FormatUndigestButton()
+ replacements['<mm-plain-digests-button>'] = list.FormatPlainDigestsButton()
+ replacements['<mm-mime-digests-button>'] = list.FormatMimeDigestsButton()
+ replacements['<mm-subscribe-box>'] = list.FormatBox('email')
+ replacements['<mm-subscribe-button>'] = list.FormatButton('email-button',
+ text='Subscribe')
+ replacements['<mm-new-password-box>'] = list.FormatSecureBox('pw')
+ replacements['<mm-confirm-password>'] = list.FormatSecureBox('pw-conf')
+ replacements['<mm-subscribe-form-start>'] = \
+ list.FormatFormStart('subscribe')
+ replacements['<mm-roster-form-start>'] = list.FormatFormStart('roster')
+ replacements['<mm-editing-options>'] = list.FormatEditingOption()
+ replacements['<mm-info-button>'] = SubmitButton('UserOptions',
+ 'Edit Options').Format()
+ replacements['<mm-roster-option>'] = list.FormatRosterOptionForUser()
+
+ # Do the expansion.
+ doc.AddItem(list.ParseTags('listinfo.html', replacements))
+
+ print doc.Format()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
new file mode 100644
index 000000000..07664bbb3
--- /dev/null
+++ b/Mailman/Cgi/options.py
@@ -0,0 +1,125 @@
+#! /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.
+
+"""Produce user options form, from list options.html template.
+
+Takes listname/userid in PATH_INFO, expecting an `obscured' userid. Depending
+on the mm_utils.{O,Uno}bscureEmail utilities tolerance, will work fine with an
+unobscured ids as well.
+
+"""
+
+# We don't need to lock in this script, because we're never going to change
+# data.
+
+import sys
+import os, string
+import mm_utils, maillist, htmlformat, mm_cfg
+
+doc = htmlformat.HeadlessDocument()
+
+try:
+ path = os.environ['PATH_INFO']
+except KeyError:
+ path = ""
+list_info = mm_utils.GetPathPieces(path)
+
+if len(list_info) < 2:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("Invalid options to CGI script."))
+ print doc.Format()
+ sys.exit(0)
+
+list_name = string.lower(list_info[0])
+user = mm_utils.UnobscureEmail(list_info[1])
+
+try:
+ list = maillist.MailList(list_name, lock=0)
+except:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name ))
+ print doc.Format()
+ sys.exit(0)
+
+if not list._ready:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name ))
+ print doc.Format()
+ sys.exit(0)
+
+if string.lower(user) not in list.members + list.digest_members:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such member %s."
+ % (list_name, `user`)))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+
+# Re-obscure the user's address for the page banner if obscure_addresses set.
+if list.obscure_addresses:
+ presentable_user = mm_utils.ObscureEmail(user, for_text=1)
+else:
+ presentable_user = user
+
+replacements = list.GetStandardReplacements()
+replacements['<mm-digest-radio-button>'] = list.FormatOptionButton(
+ mm_cfg.Digests, 1, user)
+replacements['<mm-undigest-radio-button>'] = list.FormatOptionButton(
+ mm_cfg.Digests, 0, user)
+replacements['<mm-plain-digests-button>'] = list.FormatOptionButton(
+ mm_cfg.DisableMime, 1, user)
+replacements['<mm-mime-digests-button>'] = list.FormatOptionButton(
+ mm_cfg.DisableMime, 0, user)
+replacements['<mm-delivery-enable-button>'] = list.FormatOptionButton(
+ mm_cfg.DisableDelivery, 0, user)
+replacements['<mm-delivery-disable-button>'] = list.FormatOptionButton(
+ mm_cfg.DisableDelivery, 1, user)
+replacements['<mm-disabled-notice>'] = list.FormatDisabledNotice(user)
+replacements['<mm-dont-ack-posts-button>'] = list.FormatOptionButton(
+ mm_cfg.AcknowlegePosts, 0, user)
+replacements['<mm-ack-posts-button>'] = list.FormatOptionButton(
+ mm_cfg.AcknowlegePosts, 1, user)
+replacements['<mm-receive-own-mail-button>'] = list.FormatOptionButton(
+ mm_cfg.DontReceiveOwnPosts, 0, user)
+replacements['<mm-dont-receive-own-mail-button>'] = list.FormatOptionButton(
+ mm_cfg.DontReceiveOwnPosts, 1, user)
+replacements['<mm-public-subscription-button>'] = list.FormatOptionButton(
+ mm_cfg.ConcealSubscription, 0, user)
+replacements['<mm-hide-subscription-button>'] = list.FormatOptionButton(
+ mm_cfg.ConcealSubscription, 1, user)
+
+replacements['<mm-digest-submit>'] = list.FormatButton('setdigest',
+ 'Submit My Changes')
+replacements['<mm-unsubscribe-button>'] = list.FormatButton('unsub', 'Unsubscribe')
+replacements['<mm-digest-pw-box>'] = list.FormatSecureBox('digpw')
+replacements['<mm-unsub-pw-box>'] = list.FormatSecureBox('upw')
+replacements['<mm-old-pw-box>'] = list.FormatSecureBox('opw')
+replacements['<mm-new-pass-box>'] = list.FormatSecureBox('newpw')
+replacements['<mm-confirm-pass-box>'] = list.FormatSecureBox('confpw')
+replacements['<mm-change-pass-button>'] = list.FormatButton('changepw',
+ "Change My Password")
+replacements['<mm-form-start>'] = list.FormatFormStart('handle_opts', user)
+replacements['<mm-user>'] = user
+replacements['<mm-presentable-user>'] = presentable_user
+replacements['<mm-email-my-pw>'] = list.FormatButton('emailpw',
+ 'Email My Password To Me')
+
+
+doc.AddItem(list.ParseTags('options.html', replacements))
+print doc.Format()
+
diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py
new file mode 100644
index 000000000..4a06a7270
--- /dev/null
+++ b/Mailman/Cgi/private.py
@@ -0,0 +1,228 @@
+#! /usr/bin/env python -u
+#
+# 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.
+
+"""Provide a password-interface wrapper around a hierarchy of web pages.
+
+Currently this is organized to obtain passwords for mailman maillist
+subscribers.
+
+ - Set the ROOT variable to point to the root of your archives private
+ hierarchy. The script will look there for the private archive files.
+ - Put the ../misc/Cookie.py script in ../../cgi-bin (where the wrapper
+ executables are).
+"""
+
+import sys, os, string, re
+import maillist, mm_err, mm_utils
+import Cookie
+
+ROOT = "/local/pipermail/private/"
+SECRET = "secret" # XXX used for hashing
+
+PAGE = '''
+<html>
+<head>
+ <title>%(listname)s Private Archives Authentication</title>
+</head>
+<body>
+<FORM METHOD=POST ACTION="%(basepath)s/%(path)s">
+ <TABLE WIDTH="100%" BORDER="0" CELLSPACING="4" CELLPADDING="5">
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#99CCFF" ALIGN="CENTER">
+ <B><FONT COLOR="#000000" SIZE="+1">%(listname)s Private Archives
+ Authentication</FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <td COLSPAN="2"> <P>%(message)s </td>
+ <tr>
+ </tr>
+ <TD> <div ALIGN="Right">Address: </div></TD>
+ <TD> <INPUT TYPE=TEXT NAME=username SIZE=30> </TD>
+ <tr>
+ </tr>
+ <TD> <div ALIGN="Right"> Password: </div> </TD>
+ <TD> <INPUT TYPE=password NAME=password SIZE=30></TD>
+ <tr>
+ </tr>
+ <td></td>
+ <td> <INPUT TYPE=SUBMIT>
+ </td>
+ </tr>
+ </TABLE>
+</FORM>
+'''
+
+login_attempted = 0
+_list = None
+
+name_pat = re.compile(r"""
+(?: / (?: \d{4} q \d\. )? # Match "/", and, optionally, 1998q1."
+ ( [^/]* ) /? # The SIG name
+ /[^/]*$ # The trailing 12345.html portion
+) | (?:
+ / ( [^/.]* ) # Match matrix-sig
+ (?:\.html)? # Optionally match .html
+ /? # Optionally match a trailing slash
+ $ # Must match to end of string
+
+)
+""", re.VERBOSE)
+
+def getListName(path):
+ match = name_pat.search(path)
+ if match is None: return
+ if match.group(1): return match.group(1)
+ if match.group(2): return match.group(2)
+ raise ValueError, "Can't identify SIG name"
+
+
+def GetListobj(list_name):
+ """Return an unlocked instance of the named maillist, if found."""
+ global _list
+ if _list:
+ return _list
+ try:
+ _list = maillist.MailList(list_name, lock=0)
+ except mm_err.MMUnknownListError:
+ _list = None
+ return None
+ return _list
+
+def isAuthenticated(list_name):
+ if os.environ.has_key('HTTP_COOKIE'):
+ c = Cookie.Cookie( os.environ['HTTP_COOKIE'] )
+ if c.has_key(list_name):
+ # The user has a token like 'c++-sig=AE23446AB...'; verify
+ # that it's correct.
+ token = c[list_name].value
+ import base64, md5
+ if base64.decodestring(token) != md5.new(SECRET
+ + list_name
+ + SECRET).digest():
+ return 0
+ return 1
+
+ # No corresponding cookie. OK, then check for username, password
+ # CGI variables
+ import cgi
+ v = cgi.FieldStorage()
+ username = password = None
+ if v.has_key('username'):
+ username = v['username']
+ if type(username) == type([]): username = username[0]
+ username = username.value
+ if v.has_key('password'):
+ password = v['password']
+ if type(password) == type([]): password = password[0]
+ password = password.value
+
+ if username is None or password is None: return 0
+
+ # Record that this is a login attempt, so if it fails the form can
+ # be displayed with an appropriate message.
+ global login_attempted
+ login_attempted=1
+
+ listobj = GetListobj(list_name)
+ if not listobj:
+ print '\n<P>A list named,', repr(list_name), "was not found."
+ return 0
+
+ try:
+ listobj.ConfirmUserPassword( username, password)
+ except (mm_err.MMBadUserError, mm_err.MMBadPasswordError):
+ return 1
+
+ import base64, md5
+ token = md5.new(SECRET + list_name + SECRET).digest()
+ token = base64.encodestring(token)
+ token = string.strip(token)
+ c = Cookie.Cookie()
+ c[list_name] = token
+ print c # Output the cookie
+ return 1
+
+
+def true_path(path):
+ "Ensure that the path is safe by removing .."
+ path = string.split(path, '/')
+ for i in range(len(path)):
+ if path[i] == ".": path[i] = "" # ./ is just redundant
+ elif path[i] == "..":
+ # Remove any .. components
+ path[i] = ""
+ j=i-1
+ while j>0 and path[j] == "": j=j-1
+ path[j] = ""
+
+ path = filter(None, path)
+ return string.join(path, '/')
+
+def processPage(page):
+ """Change any URLs that start with ../ to work properly when output from
+ /cgi-bin/private"""
+ # Escape any % signs not followed by (
+ page = re.sub('%([^(])', r'%%\1', page)
+
+ # Convert references like HREF="../doc" to just /doc.
+ page = re.sub('([\'="])../', r'\1/', page)
+
+ return page
+
+def main():
+ print 'Content-type: text/html\n'
+ path = os.environ.get('PATH_INFO', "/index.html")
+ true_filename = os.path.join(ROOT, true_path(path) )
+ list_name = getListName(path)
+
+ if os.path.isdir(true_filename):
+ true_filename = true_filename + '/index.html'
+
+ if not isAuthenticated(list_name):
+ # Output the password form
+ page = processPage( PAGE )
+
+ listobj = GetListobj(list_name)
+ if login_attempted:
+ message = ("Your email address or password were incorrect."
+ " Please try again.")
+ else:
+ message = ("Please enter your %s subscription email address"
+ " and password." % listobj.real_name)
+ while path and path[0] == '/': path=path[1:] # Remove leading /'s
+ basepath = os.path.split(listobj.GetBaseArchiveURL())[0]
+ listname = listobj.real_name
+ print '\n\n', page % vars()
+ sys.exit(0)
+
+ print '\n\n'
+ # Authorization confirmed... output the desired file
+ try:
+ f = open(true_filename, 'r')
+ except IOError:
+ print "<H3>Archive File Not Found</H3>"
+ print "No file", path
+ else:
+ while (1):
+ data = f.read(16384)
+ if data == "": break
+ sys.stdout.write(data)
+ f.close()
+
+
diff --git a/Mailman/Cgi/roster.py b/Mailman/Cgi/roster.py
new file mode 100644
index 000000000..b150df68d
--- /dev/null
+++ b/Mailman/Cgi/roster.py
@@ -0,0 +1,110 @@
+#! /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.
+
+"""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 sys
+import os, string
+import cgi
+import mm_utils, maillist, htmlformat, mm_cfg, mm_err
+
+def main():
+ doc = htmlformat.HeadlessDocument()
+ form = cgi.FieldStorage()
+ list = get_list()
+
+ bad = ""
+ # These nested conditionals constituted a cascading authentication
+ # check, yielding a
+ if not list.private_roster:
+ # No privacy.
+ bad = ""
+ else:
+ auth_req = ("%s subscriber list requires authentication."
+ % list.real_name)
+ if not form.has_key("roster-pw"):
+ bad = auth_req
+ else:
+ pw = form['roster-pw'].value
+ # Just the admin password is sufficient - check it early.
+ if not list.ValidAdminPassword(pw):
+ if not form.has_key('roster-email'):
+ # No admin password and no user id, nogo.
+ bad = auth_req
+ else:
+ id = form['roster-email'].value
+ if list.private_roster == 1:
+ # Private list - members visible.
+ try:
+ list.ConfirmUserPassword(id, pw)
+ except (mm_err.MMBadUserError,
+ mm_err.MMBadPasswordError):
+ bad = ("%s subscriber authentication failed."
+ % list.real_name)
+ else:
+ # Anonymous list - admin-only visible
+ # - and we already tried admin password, above.
+ bad = ("%s admin authentication failed."
+ % list.real_name)
+ if bad:
+ doc = error_page_doc(bad)
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+
+ replacements = list.GetStandardReplacements()
+
+ doc.AddItem(list.ParseTags('roster.html', replacements))
+ print doc.Format()
+
+def get_list():
+ "Return list or bail out with error page."
+
+ list_info = mm_utils.GetPathPieces(os.environ['PATH_INFO'])
+ if len(list_info) != 1:
+ error_page("Invalid options to CGI script.")
+ sys.exit(0)
+ list_name = string.lower(list_info[0])
+ try:
+ list = maillist.MailList(list_name, lock = 0)
+ except mm_err.MMUnknownListError:
+ error_page("%s: No such list.", list_name)
+ sys.exit(0)
+ if not list._ready:
+ error_page("%s: No such list.", list_name)
+ sys.exit(0)
+ return list
+
+def error_page(errmsg, *args):
+ print apply(error_page_doc, (errmsg,) + args).Format()
+
+def error_page_doc(errmsg, *args):
+ """Produce a simple error-message page on stdout and exit.
+
+ Optional arg justreturn means just return the doc, don't print it."""
+ doc = htmlformat.Document()
+ doc.SetTitle("Error")
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold(errmsg % args))
+ return doc
diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py
new file mode 100644
index 000000000..aa69b959f
--- /dev/null
+++ b/Mailman/Cgi/subscribe.py
@@ -0,0 +1,188 @@
+#! /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 listinfo form submission, ie subscriptions or roster requests."""
+
+import sys
+import os, cgi, string
+from regsub import gsub
+import mm_utils, maillist, mm_err, mm_message, mm_cfg, mm_pending, htmlformat
+
+doc = htmlformat.Document()
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+list_name = string.lower(list_info[0])
+
+if len(list_info) < 1:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("Invalid options to CGI script."))
+ print doc.Format()
+ sys.exit(0)
+
+try:
+ list = maillist.MailList(list_name)
+except:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name ))
+ print doc.Format()
+ sys.exit(0)
+
+
+if not list._ready:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name ))
+ print doc.Format()
+ list.Unlock()
+ sys.exit(0)
+
+form = cgi.FieldStorage()
+
+error = 0
+results = ''
+
+def call_script(which, pathinfo):
+ "A little bit of a hack to call one of the scripts..."
+ os.environ['PATH_INFO'] = string.join(pathinfo, '/')
+ file = os.path.join(mm_cfg.SCRIPTS_DIR, which)
+ list.Unlock()
+ execfile(file)
+ sys.exit(0)
+
+#######
+# Preliminaries done, actual processing of the form input below.
+
+if (form.has_key("UserOptions")
+ or (form.has_key("info") and not form.has_key("email"))):
+ # Go to user options section.
+ if not form.has_key("info"):
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("You must supply your email address."))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ list.Unlock()
+ sys.exit(0)
+ addr = form['info'].value
+ member = list.FindUser(addr)
+ if not list.FindUser(addr):
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s has no subscribed addr <i>%s</i>."
+ % (list.real_name, addr)))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ list.Unlock()
+ sys.exit(0)
+ call_script('options', [list._internal_name, member])
+if not form.has_key("email"):
+ error = 1
+ results = results + "You must supply a valid email address.<br>"
+else:
+ email = form["email"].value
+if not form.has_key("pw") or not form.has_key("pw-conf"):
+ error = 1
+ results = results + "You must supply a valid password, and confirm it.<br>"
+else:
+ pw = form["pw"].value
+ pwc = form["pw-conf"].value
+
+if not error and (pw <> pwc):
+ error = 1
+ results = results + "Your passwords did not match.<br>"
+
+if form.has_key("digest"):
+ digest = eval(form["digest"].value)
+
+if not list.digestable:
+ digest = 0
+elif not list.nondigestable:
+ digest = 1
+
+
+def PrintResults():
+ replacements = list.GetStandardReplacements()
+ replacements['<mm-results>'] = results
+ output = list.ParseTags('subscribe.html', replacements)
+
+ doc.AddItem(output)
+ print doc.Format()
+ list.Unlock()
+ sys.exit(0)
+
+if error:
+ PrintResults()
+
+else:
+ try:
+ results = results + ("Confirmation from your email address is "
+ "required, to prevent anyone from covertly "
+ "subscribing you. Instructions are being "
+ "sent to you at %s." % email)
+ if os.environ.has_key('REMOTE_HOST'):
+ remote = os.environ['REMOTE_HOST']
+ elif os.environ.has_key('REMOTE_ADDR'):
+ remote = os.environ['REMOTE_ADDR']
+ else:
+ remote = "."
+ if digest:
+ digesting = " digest"
+ else:
+ digesting = ""
+ cookie = mm_pending.gencookie()
+ mm_pending.add2pending(email, pw, digest, cookie)
+ list.SendTextToUser(subject = "%s -- confirmation of subscription -- request %d" % \
+ (list.real_name, cookie),
+ recipient = email,
+ sender = list.GetRequestEmail(),
+ text = mm_pending.VERIFY_FMT % ({"email": email,
+ "listaddress": list.GetListEmail(),
+ "listname": list.real_name,
+ "cookie": cookie,
+ "requestor": remote,
+ "request_addr": list.GetRequestEmail()}),
+
+ add_headers = ["Reply-to: %s"
+ % list.GetRequestEmail(),
+ "Errors-To: %s"
+ % list.GetAdminEmail()])
+ except mm_err.MMBadEmailError:
+ results = results + ("Mailman won't accept the given email "
+ "address as a valid address. (Does it "
+ "have an @ in it???)<p>")
+ except mm_err.MMListNotReady:
+ results = results + ("The list is not fully functional, and "
+ "can not accept subscription requests.<p>")
+#
+# deprecating this, it might be useful if we decide to
+# allow approved based subscriptions without confirmation
+#
+## except mm_err.MMNeedApproval, x:
+## results = results + ("Subscription was <em>deferred</em> "
+## "because:<br> %s<p>Your request must "
+## "be approved by the list admin. "
+## "You will receive email informing you "
+## "of the moderator's descision when they "
+## "get to your request.<p>" % x)
+ except mm_err.MMHostileAddress:
+ results = results + ("Your subscription is not allowed because "
+ "the email address you gave is insecure.<p>")
+ except mm_err.MMAlreadyAMember:
+ results = results + "You are already subscribed!<p>"
+
+
+PrintResults()
+list.Unlock() \ No newline at end of file
diff --git a/modules/Cgi/__init__.py b/modules/Cgi/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/modules/Cgi/__init__.py
diff --git a/modules/Cgi/admin.py b/modules/Cgi/admin.py
new file mode 100755
index 000000000..c9155c87e
--- /dev/null
+++ b/modules/Cgi/admin.py
@@ -0,0 +1,835 @@
+#! /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."""
+
+__version__ = "$Revision: 734 $"
+
+import sys
+import os, cgi, string, crypt, types, time
+import paths # path hacking
+import mm_utils, maillist, mm_cfg, mm_err, mm_mailcmd, Cookie
+from htmlformat import *
+
+try:
+ sys.stderr = mm_utils.StampedLogger("error", label = 'admin',
+ manual_reprime=1, nofail=0)
+except IOError:
+ pass # Oh well - SOL on redirect, errors show thru.
+
+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")]
+
+
+LOGIN_PAGE = """
+<html>
+<head>
+ <title>%(listname)s Administrative Authentication</title>
+</head>
+<body bgcolor="#ffffff">
+<FORM METHOD=POST ACTION="%(path)s">
+%(message)s
+ <TABLE WIDTH="100%%" BORDER="0" CELLSPACING="4" CELLPADDING="5">
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%%" BGCOLOR="#99CCFF" ALIGN="CENTER">
+ <B><FONT COLOR="#000000" SIZE="+1">%(listname)s Administrative
+ Authentication</FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <TD> <div ALIGN="Right"> List Administrative Password: </div> </TD>
+ <TD> <INPUT TYPE=password NAME=adminpw SIZE=30></TD>
+ </tr>
+ <tr>
+ <td colspan=2 align=middle> <INPUT TYPE=SUBMIT name="request_login">
+ </td>
+ </tr>
+ </TABLE>
+</FORM>
+"""
+
+# " <- icky emacs font-lock bug workaround
+
+SECRET="monty"
+
+def isAuthenticated(list, password=None, SECRET="SECRET"):
+ import base64, md5
+ if password is not None: # explicit login
+ try:
+ list.ConfirmAdminPassword(password)
+ except mm_err.MMBadPasswordError:
+ AddErrorMessage(doc, 'Error: Incorrect admin password.')
+ return 0
+ except:
+ print "Content-type: text/html\n"
+
+ print "<p><h3>We're sorry, we hit a bug!</h3>\n"
+ print "If you would like to help us identify the problem, please "
+ print "email a copy of this page to the webmaster for this site"
+ print 'with a description of what happened. Thanks!'
+ print "\n<PRE>"
+ try:
+ import traceback
+ sys.stderr = sys.stdout
+ traceback.print_exc()
+ except:
+ print "[failed to get traceback]"
+ print "\n\n</PRE>"
+
+ token = md5.new(SECRET + list_name + SECRET).digest()
+ token = base64.encodestring(token)
+ token = string.strip(token)
+ 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"):
+ try:
+ inp = base64.decodestring(c[list_name + "-admin"].value)
+ check = md5.new(SECRET+list_name+SECRET).digest()
+ except Error: # the decodestring may return incorrect padding?
+ raise 'Decode failed'
+ return 0
+ if inp == check:
+ return 1
+ else:
+ 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 = mm_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 mm_err.MMUnknownListError:
+ lst = None
+ try:
+ if not (lst and lst._ready):
+ FormatAdminOverview(error="List <em>%s</em> 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:
+ print "Content-type: text/html\n\n"
+ print LOGIN_PAGE % ({"listname": list_name,
+ "path": os.environ.get("REQUEST_URI", "/mailman/admin"),
+ "message": message})
+ return
+
+ if len(cgi_data.keys()):
+ if cgi_data.has_key('VARHELP'):
+ FormatOptionHelp(doc, cgi_data['VARHELP'].value, lst)
+ print doc.Format(bgcolor="#ffffff")
+ return
+ if (cgi_data.has_key('bounce_matching_headers')):
+ try:
+ pairs = lst.parse_matching_header_opt()
+ except mm_err.MMBadConfigError, line:
+ AddErrorMessage(doc,
+ 'Warning: bad matching-header line'
+ ' (does it have the colon?)<ul> %s </ul>',
+ line)
+
+ if not lst.digestable and len(lst.digest_members):
+ AddErrorMessage(doc,
+ 'Warning: you have digest members,'
+ ' but digests are turned off.'
+ ' Those people will not receive mail.')
+ if not lst.nondigestable and len(lst.members):
+ AddErrorMessage(doc,
+ 'Warning: you have lst members,'
+ ' but non-digestified mail is turned'
+ ' off. They will receive mail until'
+ ' you fix this problem.')
+ if len(cgi_data.keys()):
+ ChangeOptions(lst, category, cgi_data, doc)
+ FormatConfiguration(doc, lst, category, category_suffix)
+ print doc.Format(bgcolor="#ffffff")
+
+ finally:
+ if lst:
+ lst.Unlock()
+
+# Form Production:
+
+def FormatAdminOverview(error=None):
+ "Present a general welcome and itemize the (public) lists."
+ doc = Document()
+ legend = "%s maillists - Admin Links" % mm_cfg.DEFAULT_HOST_NAME
+ doc.SetTitle(legend)
+
+ table = Table(border=0, width="100%")
+ table.AddRow([Center(Header(2, legend))])
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0,
+ colspan=2, bgcolor="#99ccff")
+
+ advertised = []
+ names = mm_utils.list_names()
+ names.sort()
+ for n in names:
+ l = maillist.MailList(n, lock=0)
+ if l.advertised: advertised.append(l)
+
+ if error:
+ greeting = FontAttr(error, color="ff5060", size="+1")
+ else:
+ greeting = "Welcome!"
+
+ if not advertised:
+ welcome_items = (greeting,
+ "<p>"
+ " There currently are no publicly-advertised ",
+ Link(mm_cfg.MAILMAN_URL, "mailman"),
+ " maillists on %s." % mm_cfg.DEFAULT_HOST_NAME,
+ )
+ else:
+
+ welcome_items = (
+ greeting,
+ "<p>"
+ " Below is the collection of publicly-advertised ",
+ Link(mm_cfg.MAILMAN_URL, "mailman"),
+ " maillists 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.<p>"
+ % ((error and "the right ") or ""))
+ +
+ " General list information can be found at ",
+ Link(os.path.join('../'* mm_utils.GetNestingLevel(),
+ "listinfo/"), "the maillist overview page"),
+ "."
+ "<p>(Send questions and comments to ",
+ Link("mailto:%s" % mm_cfg.MAILMAN_OWNER,
+ mm_cfg.MAILMAN_OWNER),
+ ".)<p>"
+ )
+ )
+
+ 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 Maillist Configuration - %s Section'
+ % (lst.real_name, label)))))
+ doc.AddItem('<hr>')
+
+ links_table = Table(valign="top")
+
+ links_table.AddRow([Center(Bold("Configuration Categories")),
+ Center(Bold("Other Administrative Activities"))])
+ other_links = UnorderedList()
+ link = Link(lst.GetRelativeScriptURL('admindb'),
+ 'Tend to pending administrative requests.')
+ other_links.AddItem(link)
+ link = Link(lst.GetRelativeScriptURL('listinfo'),
+ 'Go to the general list information page.')
+ other_links.AddItem(link)
+ link = Link(lst.GetRelativeScriptURL('edithtml'),
+ 'Edit the HTML for the public list pages.')
+ other_links.AddItem(link)
+
+ these_links = UnorderedList()
+ for k, v in CATEGORIES:
+ if k == category:
+ these_links.AddItem("<b> =&gt; " + v + " &lt;= </b>")
+ else:
+ these_links.AddItem(Link("%s/%s" %
+ (lst.GetRelativeScriptURL('admin'),k),v))
+
+ links_table.AddRow([these_links, other_links])
+ links_table.AddRowInfo(max(links_table.GetCurrentRowIndex(), 0),
+ valign="top")
+
+ doc.AddItem(links_table)
+ doc.AddItem('<hr>')
+ if category_suffix:
+ form = Form("%s/%s" % (lst.GetRelativeScriptURL('admin'),
+ category_suffix))
+ else:
+ form = Form(lst.GetRelativeScriptURL('admin'))
+ doc.AddItem(form)
+
+ form.AddItem("Make your changes, below, and then submit it all at the"
+ " bottom. (You can also change your password there,"
+ " as well.)<p>")
+
+ form.AddItem(FormatOptionsSection(category, lst))
+
+ form.AddItem(Center(FormatPasswordStuff()))
+
+ 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 = "<center><i>" + item + "</i></center>"
+ big_table.AddRow([item])
+ big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0),
+ 0, colspan=2)
+ if not did_col_header:
+ # Do col header after very first string descr, if any...
+ ColHeader()
+ did_col_header = 1
+ else:
+ if not did_col_header:
+ # ... but do col header before anything else.
+ ColHeader()
+ did_col_header = 1
+ AddOptionsTableItem(big_table, item, category, lst)
+ big_table.AddRow(['<br>'])
+ big_table.AddCellInfo(big_table.GetCurrentRowIndex(), 0, colspan=2)
+ return big_table
+
+def AddOptionsTableItem(table, item, category, lst, nodetails=0):
+ """Add a row to an options table with the item description and value."""
+ try:
+ got = GetItemCharacteristics(item)
+ varname, kind, params, dependancies, descr, elaboration = got
+ except ValueError, msg:
+ lst.LogMsg("error", "admin: %s", msg)
+ return Italic("<malformed option>")
+ descr = GetItemGuiDescr(lst, category, varname, descr,
+ elaboration, nodetails)
+ val = GetItemGuiValue(lst, kind, varname, params)
+ table.AddRow([descr, val])
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 1,
+ bgcolor="#cccccc")
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0,
+ bgcolor="#cccccc")
+
+def FormatOptionHelp(doc, varref, lst):
+ item = bad = None
+ reflist = string.split(varref, '/')
+ if len(reflist) == 2:
+ category, varname = reflist
+ options = GetConfigOptions(lst, category)
+ for i in options:
+ if i and i[0] == varname:
+ item = i
+ break
+ if not item:
+ bad = ("Option %s/%s not found. %s"
+ % (category, varname, os.environ['PATH_INFO']))
+ else:
+ try:
+ got = GetItemCharacteristics(item)
+ varname, kind, params, dependancies, descr, elaboration = got
+ except ValueError, msg:
+ bad = msg
+ if not bad and not elaboration:
+ bad = "Option %s has no extended help." % varname
+ if bad:
+ AddErrorMessage(doc, bad)
+ return
+
+ header = Table(width="100%")
+ legend = ('%s Maillist Configuration Help<br><em>%s</em> Option'
+ % (lst.real_name, varname))
+ header.AddRow([Center(Header(3, legend))])
+ header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0,
+ colspan=2, bgcolor="#99ccff")
+ doc.SetTitle("Mailman %s List Option Help" % varname)
+ doc.AddItem(header)
+ doc.AddItem("<b>%s</b> (%s): %s<p>" % (varname, category, item[4]))
+ doc.AddItem("%s<p>" % item[5])
+
+ form = Form(os.path.join(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:
+ 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 = '<div ALIGN="right">' + descr
+ if not nodetails and elaboration:
+ ref = "../" * (mm_utils.GetNestingLevel()-1) + list_name + "/"
+ ref = ref + '?VARHELP=' + category + "/" + varname
+ descr = Container(descr,
+ Link(ref, " (Details)", target="MMHelp"),
+ "</div>")
+ else:
+ descr = descr + "</div>"
+ return descr
+
+def FormatMembershipOptions(lst):
+ container = Container()
+ header = Table(width="100%")
+ header.AddRow([Center(Header(2, "Membership Management"))])
+ header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0,
+ colspan=2, bgcolor="#99ccff")
+ container.AddItem(header)
+ user_table = Table(width="90%")
+ user_table.AddRow([Center(Header(4, "Membership List"))])
+ user_table.AddCellInfo(user_table.GetCurrentRowIndex(),
+ user_table.GetCurrentCellIndex(),
+ bgcolor="#cccccc", colspan=8)
+
+ members = {}
+ digests = {}
+ for member in lst.members:
+ members[member] = 1
+ for member in lst.digest_members:
+ digests[member] = 1
+ all = lst.members + lst.digest_members
+ if len(all) > mm_cfg.ADMIN_MEMBER_CHUNKSIZE:
+ chunks = mm_utils.chunkify(all)
+ if not cgi_data.has_key("chunk"):
+ chunk = 0
+ else:
+ chunk = string.atoi(cgi_data["chunk"].value)
+ all = chunks[chunk]
+ footer = ("<p><em>To View other sections, "
+ "click on the appropriate range listed below</em>")
+ chunk_indices = range(len(chunks))
+ chunk_indices.remove(chunk)
+ buttons = []
+ pi = os.environ["PATH_INFO"]
+ for ci in chunk_indices:
+ start, end = chunks[ci][0], chunks[ci][-1]
+ buttons.append("<a href=/mailman/admin%s?chunk=%d> from %s to %s </a>" % \
+ ( pi, ci, start, end))
+ buttons = apply(UnorderedList, tuple(buttons))
+ footer = footer + buttons.Format() + "<p>"
+ else:
+ all.sort()
+ footer = "<p>"
+ for member in all:
+ cells = [member + "<input type=hidden name=user value=%s>" % (member),
+ "subscribed " +CheckBox(member + "_subscribed", "on", 1).Format(),
+ ]
+ if members.get(member):
+ cells.append("digest " + CheckBox(member + "_digest", "off", 0).Format())
+ else:
+ cells.append("digest " + CheckBox(member + "_digest", "on", 1).Format())
+ for opt in ("hide", "nomail", "ack", "norcv", "plain"):
+ if lst.GetUserOption(member, mm_mailcmd.option_info[opt]):
+ value = "on"
+ checked = 1
+ else:
+ value = "off"
+ checked = 0
+ box = CheckBox("%s_%s" % (member, opt), value, checked)
+ cells.append("%s %s" % (opt, box.Format()))
+ user_table.AddRow(cells)
+ container.AddItem(Center(user_table))
+ 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)
+ container.AddItem(Center(t))
+ container.AddItem(Center(TextArea(name='subscribees', rows=10,cols=60,wrap=None)))
+ container.AddItem(Center("<em> Enter One address per line</em><p>"))
+ return container
+
+def FormatPasswordStuff():
+ submit = Table(bgcolor="#99ccff",
+ border=0, cellspacing=0, cellpadding=2, width="100%")
+ submit.AddRow([Bold(SubmitButton('submit', 'Submit Your Changes'))])
+ submit.AddCellInfo(submit.GetCurrentRowIndex(), 0, align="middle")
+ change_pw_table = Table(bgcolor="#99cccc", border=0,
+ cellspacing=0, cellpadding=2, width="90%")
+ change_pw_table.AddRow([Bold(Center('To Change The Administrator Password')),
+ '<div ALIGN="right"> Enter the new password: </div>',
+ PasswordBox('newpw'),])
+ change_pw_table.AddCellInfo(0, 0, align="middle", colspan=2)
+ change_pw_table.AddRow(['<div ALIGN="right"> Enter the current password </div>',
+ PasswordBox('adminpw'),
+ '<div ALIGN="right">Again to confirm it:</div>',
+ PasswordBox('confirmpw')])
+ password_stuff = Container()
+ password_stuff.AddItem(change_pw_table)
+ password_stuff.AddItem("<p>")
+ password_stuff.AddItem(submit)
+ return password_stuff
+
+# XXX klm - looks like turn_on_moderation is orphaned.
+turn_on_moderation = 0
+
+# Options processing
+
+def GetValidValue(lst, prop, my_type, val, dependant):
+ if my_type == mm_cfg.Radio or my_type == mm_cfg.Toggle:
+ if type(val) <> types.IntType:
+ try:
+ # XXX Security!?
+ val = eval(val)
+ except:
+ pass
+ # Don't know what to do here...
+ return val
+ elif my_type == mm_cfg.String or my_type == mm_cfg.Text:
+ return val
+ elif my_type == mm_cfg.Email:
+ try:
+ valid = mm_utils.ValidEmail(val)
+ if valid:
+ return val
+ except:
+ pass
+ # Revert to the old value.
+ return getattr(lst, prop)
+ elif my_type == mm_cfg.EmailList:
+ def SafeValidAddr(addr):
+ import mm_utils
+ try:
+ valid = mm_utils.ValidEmail(addr)
+ if valid:
+ return 1
+ else:
+ return 0
+ except:
+ return 0
+
+ val = filter(SafeValidAddr,
+ map(string.strip, string.split(val, '\n')))
+ if dependant and len(val):
+ # Wait till we've set everything to turn it on,
+ # as we don't want to clobber our special case.
+ # XXX klm - looks like turn_on_moderation is orphaned?
+ turn_on_moderation = 1
+ return val
+ elif my_type == mm_cfg.Host:
+ return val
+##
+## This code is sendmail dependant, so we'll just live w/o
+## the error checking for now.
+##
+## # Shouldn't have to read in the whole file.
+## file = open('/etc/sendmail.cf', 'r')
+## lines = string.split(file.read(), '\n')
+## file.close()
+## def ConfirmCWEntry(item):
+## return item[0:2] == 'Cw'
+## lines = filter(ConfirmCWEntry, lines)
+## if not len(lines):
+## # Revert to the old value.
+## return getattr(list, prop)
+## for line in lines:
+## if string.lower(string.strip(line[2:])) == string.lower(val):
+## return val
+## return getattr(list, prop)
+ elif my_type == mm_cfg.Number:
+ try:
+ num = eval(val)
+ if num < 0:
+ return getattr(lst, prop)
+ return num
+ except:
+ return getattr(lst, prop)
+ else:
+ # Should never get here...
+ return val
+
+
+def ChangeOptions(lst, category, cgi_info, document):
+ dirty = 0
+ confirmed = 0
+ if cgi_info.has_key('newpw'):
+ if cgi_info.has_key('confirmpw'):
+ if cgi_info.has_key('adminpw'):
+ try:
+ lst.ConfirmAdminPassword(cgi_info['adminpw'].value)
+ confirmed = 1
+ except mm_err.MMBadPasswordError:
+ m = "Error: incorrect administrator password"
+ document.AddItem(Header(3, Italic(FontAttr(m, color="ff5060"))))
+ confirmed = 0
+ if confirmed:
+ new = cgi_info['newpw'].value
+ confirm = cgi_info['confirmpw'].value
+ if new == confirm:
+ lst.password = crypt.crypt(new, mm_utils.GetRandomSeed())
+ dirty = 1
+ else:
+ m = 'Error: Passwords did not match.'
+ document.AddItem(Header(3, Italic(FontAttr(m, color="ff5060"))))
+
+ else:
+ m = 'Error: You must type in your new password twice.'
+ document.AddItem(
+ Header(3, Italic(FontAttr(m, color="ff5060"))))
+ #
+ # for some reason, the login page mangles important values for the list
+ # such as .real_name so we only process these changes if the category
+ # is not "members" and the request is not from the login page
+ # -scott 19980515
+ #
+ if category != 'members' and not cgi_info.has_key("request_login") and\
+ len(cgi_info.keys()) > 1:
+ opt_list = GetConfigOptions(lst, category)
+ for item in opt_list:
+ if len(item) < 5:
+ continue
+ property, kind, args, deps, desc = (item[0], item[1], item[2],
+ item[3], item[4])
+ if not cgi_info.has_key(property):
+ if (kind <> mm_cfg.Text and
+ kind <> mm_cfg.String and
+ kind <> mm_cfg.EmailList):
+ continue
+ else:
+ val = ''
+ else:
+ val = cgi_info[property].value
+ value = GetValidValue(lst, property, kind, val, deps)
+ if getattr(lst, property) != value:
+ setattr(lst, property, value)
+ dirty = 1
+ #
+ # mass subscription processing for members category
+ #
+ if cgi_info.has_key('subscribees'):
+ name_text = cgi_info['subscribees'].value
+ name_text = string.replace(name_text, '\r', '')
+ names = string.split(name_text, '\n')
+ if '' in names:
+ names.remove('')
+ subscribe_success = []
+ subscribe_errors = []
+ for new_name in map(string.strip,names):
+ digest = 0
+ if not lst.digestable:
+ digest = 0
+ if not lst.nondigestable:
+ digest = 1
+ try:
+ lst.ApprovedAddMember(new_name, (mm_utils.GetRandomSeed() +
+ mm_utils.GetRandomSeed()), digest)
+ subscribe_success.append(new_name)
+ except mm_err.MMAlreadyAMember:
+ subscribe_errors.append((new_name, 'Already a member'))
+
+ except mm_err.MMBadEmailError:
+ subscribe_errors.append((new_name, "Bad/Invalid email address"))
+ except mm_err.MMHostileAddress:
+ subscribe_errors.append((new_name, "Hostile Address (illegal characters)"))
+ if subscribe_success:
+ document.AddItem(Header(5, "Successfully Subscribed:"))
+ document.AddItem(apply(UnorderedList, tuple((subscribe_success))))
+ document.AddItem("<p>")
+ 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("<p>")
+ #
+ # 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]
+ for user in users:
+ if not cgi_info.has_key('%s_subscribed' % (user)):
+ lst.DeleteMember(user)
+ dirty = 1
+ continue
+ if not cgi_info.has_key("%s_digest" % (user)):
+ if user in lst.digest_members:
+ list.digest_members.remove(user)
+ dirty = 1
+ if user not in lst.members:
+ lst.members.append(user)
+ dirty = 1
+ else:
+ if user not in lst.digest_members:
+ lst.digest_members.append(user)
+ dirty = 1
+ if user in lst.members:
+ lst.members.remove(user)
+ dirty = 1
+
+ for opt in ("hide", "nomail", "ack", "norcv", "plain"):
+ if cgi_info.has_key("%s_%s" % (user, opt)):
+ lst.SetUserOption(user, mm_mailcmd.option_info[opt], 1)
+ dirty = 1
+ else:
+ lst.SetUserOption(user, mm_mailcmd.option_info[opt], 0)
+ dirty = 1
+
+
+ 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]
+
diff --git a/modules/Cgi/admindb.py b/modules/Cgi/admindb.py
new file mode 100755
index 000000000..415184d3b
--- /dev/null
+++ b/modules/Cgi/admindb.py
@@ -0,0 +1,232 @@
+#! /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.
+
+"""Produce and process the pending-approval items for a list."""
+
+import sys
+import os, cgi, string, crypt, types
+import mm_utils, maillist, mm_err, htmlformat
+
+doc = htmlformat.Document()
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+
+
+if len(list_info) < 1:
+ doc.SetTitle("Admindb Error")
+ doc.AddItem(htmlformat.Header(2, "Invalid options to CGI script."))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+list_name = string.lower(list_info[0])
+
+try:
+ list = maillist.MailList(list_name)
+except:
+ msg = "%s: No such list." % list_name
+ doc.SetTitle("Admindb Error - %s" % msg)
+ doc.AddItem(htmlformat.Header(2, msg))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+if not list._ready:
+ msg = "%s: No such list." % list_name
+ doc.SetTitle("Admindb Error - %s" % msg)
+ doc.AddItem(htmlformat.Header(2, msg))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+# Note, these 2 functions use i only to count the number of times to
+# go around. We always operate on the first element of the list
+# because we're going to delete the element after we operate on it.
+
+def SubscribeAll():
+ for i in range(len(list.requests['add_member'])):
+ comment_key = 'comment-%d' % list.requests['add_member'][0][0]
+ if form.has_key(comment_key):
+ list.HandleRequest(('add_member', 0), 1, form[comment_key].value)
+ else:
+ list.HandleRequest(('add_member', 0), 1)
+
+def SubscribeNone():
+ for i in range(len(list.requests['add_member'])):
+ comment_key = 'comment-%d' % list.requests['add_member'][0][0]
+ if form.has_key(comment_key):
+ list.HandleRequest(('add_member', 0), 0, form[comment_key].value)
+ else:
+ list.HandleRequest(('add_member', 0), 0)
+
+def PrintHeader(str, error=0):
+ if error:
+ it = htmlformat.FontAttr(str, color="ff5060")
+ else:
+ it = str
+ doc.AddItem(htmlformat.Header(3, htmlformat.Italic(it)))
+ doc.AddItem('<hr>')
+
+def HandleRequests(doc):
+ if not form.has_key('adminpw'):
+ PrintHeader('You need to supply the admin password '
+ 'to answer requests.', error=1)
+ return
+ try:
+ list.ConfirmAdminPassword(form['adminpw'].value)
+ except:
+ PrintHeader('Incorrect admin password.', error=1)
+ return
+ ignore_subscribes = 0
+ if form.has_key('subscribe_all'):
+ ignore_subscribes = 1
+ SubscribeAll()
+ elif form.has_key('subscribe_none'):
+ ignore_subscribes = 1
+ SubscribeNone()
+ for k in form.keys():
+ try:
+ # XXX Security?!
+ v = eval(form[k].value)
+ request_id = eval(k)
+ except: # For stuff like adminpw
+ continue
+ if type(request_id) <> types.IntType:
+ continue
+ try:
+ request = list.GetRequest(request_id)
+ except mm_err.MMBadRequestId:
+ continue # You've already changed the database. No biggie.
+ if ignore_subscribes and request[0] == 'add_member':
+ # We already handled this request.
+ continue
+ comment_key = 'comment-%d' % request_id
+ if form.has_key(comment_key):
+ list.HandleRequest(request, v, form[comment_key].value)
+ else:
+ list.HandleRequest(request, v)
+ list.Save()
+ PrintHeader('Database Updated...')
+
+
+def PrintAddMemberRequest(val, table):
+ table.AddRow([
+ val[3],
+ htmlformat.RadioButtonArray(val[0], ("Refuse", "Subscribe")),
+ htmlformat.TextBox("comment-%d" % val[0], size=50)
+ ])
+
+def PrintPostRequest(val, form):
+ t = htmlformat.Table(cellspacing=10)
+ t.AddRow([
+ htmlformat.FontSize("+1",
+ htmlformat.Bold('Post held because: ')),
+ val[3]])
+ t.AddRow([
+ htmlformat.FontSize("+1",
+ htmlformat.Bold('Action to take on this post:')),
+ htmlformat.RadioButtonArray(val[0], ("Approve", "Reject",
+ "Discard (eg, spam)")),
+ htmlformat.SubmitButton('submit', 'Submit All Data')
+ ])
+ t.AddRow([
+ htmlformat.FontSize("+1",
+ htmlformat.Bold('If you reject this post, '
+ 'explain (optional):')),
+ htmlformat.TextBox("comment-%d" % val[0], size=50)])
+
+ cur_row = t.GetCurrentRowIndex()
+ cur_col = t.GetCurrentCellIndex()
+ t.AddCellInfo(cur_row, cur_col, colspan=3)
+
+ t.AddRow([
+ htmlformat.FontSize("+1",
+ htmlformat.Bold('Contents:'))])
+ form.AddItem(t)
+ form.AddItem(htmlformat.Preformatted(val[2][1]))
+ form.AddItem('<p>')
+
+
+def PrintRequests(doc):
+ # The only types of requests we know about are add_member and post.
+ # Anything else that might have gotten in here somehow we'll just
+ # ignore (This should never happen unless someone is hacking at
+ # the code).
+
+ doc.AddItem(htmlformat.Header(2, "Administrative requests for "
+ "'%s' mailing list" % list.real_name))
+ doc.AddItem(htmlformat.FontSize("+1", htmlformat.Link(
+ list.GetRelativeScriptURL('admin'), htmlformat.Italic(
+ 'View or edit the list configuration information'))))
+ doc.AddItem('<p><hr>')
+ if not list.RequestsPending():
+ doc.AddItem(htmlformat.Header(3,'There are no pending requests.'))
+ doc.AddItem(list.GetMailmanFooter())
+ return
+ form = htmlformat.Form(list.GetRelativeScriptURL('admindb'))
+ doc.AddItem(form)
+ form.AddItem('Admin password: ')
+ form.AddItem(htmlformat.PasswordBox('adminpw'))
+ form.AddItem('<p>')
+ if list.requests.has_key('add_member'):
+## form.AddItem('<hr>')
+## t = htmlformat.Table(cellspacing=10)
+## t.AddRow([
+## htmlformat.SubmitButton('submit', 'Submit All Data'),
+## htmlformat.SubmitButton('subscribe_all', 'Subscribe Everybody'),
+## htmlformat.SubmitButton('subscribe_none', 'Refuse Everybody')
+## ])
+## form.AddItem(t)
+ form.AddItem('<hr>')
+ form.AddItem(htmlformat.Center(
+ htmlformat.Header(2, 'Subscription Requests')))
+ t = htmlformat.Table(border=2)
+ t.AddRow([
+ htmlformat.Bold('Email'),
+ htmlformat.Bold('Descision'),
+ htmlformat.Bold('Reasoning for subscription refusal (optional)')])
+ for request in list.requests['add_member']:
+ PrintAddMemberRequest(request, t)
+
+ form.AddItem(t)
+ t = htmlformat.Table(cellspacing=10)
+ t.AddRow([
+ htmlformat.SubmitButton('submit', 'Submit All Data'),
+ htmlformat.SubmitButton('subscribe_all', 'Subscribe Everybody'),
+ htmlformat.SubmitButton('subscribe_none', 'Refuse Everybody')
+ ])
+ form.AddItem(t)
+
+ # Print submitit buttons...
+ if list.requests.has_key('post'):
+ for request in list.requests['post']:
+ form.AddItem('<hr>')
+ form.AddItem(htmlformat.Center(htmlformat.Header(2, "Held Message")))
+ PrintPostRequest(request, form)
+ doc.AddItem(list.GetMailmanFooter())
+
+try:
+ form = cgi.FieldStorage()
+ if len(form.keys()):
+ doc.SetTitle("%s Admindb Results" % list.real_name)
+ HandleRequests(doc)
+ else:
+ doc.SetTitle("%s Admindb" % list.real_name)
+ PrintRequests(doc)
+ text = doc.Format(bgcolor="#ffffff")
+ print text
+ sys.stdout.flush()
+finally:
+ list.Unlock()
diff --git a/modules/Cgi/archives.py b/modules/Cgi/archives.py
new file mode 100755
index 000000000..2b0ed0fa9
--- /dev/null
+++ b/modules/Cgi/archives.py
@@ -0,0 +1,86 @@
+#! /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.
+
+# This script is being deprecated, in favor hookups for an external archiver.
+
+# We don't need to lock in this script, because we're never going to change
+# data.
+
+import sys
+import os, types, posix, string
+import mm_utils, maillist, htmlformat
+
+print "Content-type: text/html"
+print
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+
+if len(list_info) < 1:
+ print "<h2>Invalid options to CGI script.</h2>"
+ sys.exit(0)
+
+list_name = string.lower(list_info[0])
+
+try:
+ list = maillist.MailList(list_name)
+except:
+ print "<h2>%s: No such list.</h2>" % list_name
+ sys.exit(0)
+
+if not list._ready:
+ print "<h2>%s: No such list.</h2>" % list_name
+ sys.exit(0)
+
+def GetArchiveList(list):
+ archive_list = htmlformat.UnorderedList()
+
+ def ArchiveFilter(str):
+ if str[:7] <> 'volume_':
+ return 0
+ try:
+ x = eval(str[7:])
+ if type(x) <> types.IntType:
+ return 0
+ if x < 1:
+ return 0
+ return 1
+ except:
+ return 0
+ try:
+ dir_listing = filter(ArchiveFilter, os.listdir(list.archive_directory))
+ except posix.error:
+ return "<h3><em>No archives are currently available.</em></h3>"
+ if not len(dir_listing):
+ return "<h3><em>No archives are currently available.</em></h3>"
+ for dir in dir_listing:
+ link = htmlformat.Link(os.path.join(list._base_archive_url, dir),
+ "Volume %s" % dir[7:])
+ archive_list.AddItem(link)
+
+ return archive_list.Format()
+
+
+
+
+replacements = list.GetStandardReplacements()
+replacements['<mm-archive-list>'] = GetArchiveList(list)
+
+# Just doing print list.ParseTags(...) calls ParseTags twice???
+text = list.ParseTags('archives.html', replacements)
+print text
diff --git a/modules/Cgi/edithtml.py b/modules/Cgi/edithtml.py
new file mode 100755
index 000000000..faf35e715
--- /dev/null
+++ b/modules/Cgi/edithtml.py
@@ -0,0 +1,167 @@
+#! /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.
+
+"""Script which implements admin editing of the list's html templates."""
+
+import sys
+import os, cgi, string, crypt, types
+import mm_utils, maillist, mm_cfg
+import htmlformat
+
+#Editable templates. We should also be able to edit the archive index, which
+#currently isn't a working template, but will be soon.
+
+template_data = (('listinfo.html', 'General list information page'),
+ ('subscribe.html', 'Subscribe results page'),
+ ('options.html', 'User specific options page'),
+ ('handle_opts.html', 'Changing user options results page'),
+ ('archives.html', 'Archives index page')
+ )
+
+
+def InitDocument():
+ return htmlformat.HeadlessDocument()
+
+doc = InitDocument()
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+
+if len(list_info) < 1:
+ doc.AddItem(htmlformat.Header(2, "Invalid options to CGI script."))
+ print doc.Format()
+ sys.exit(0)
+
+list_name = string.lower(list_info[0])
+
+try:
+ list = maillist.MailList(list_name, lock=0)
+except:
+ doc.AddItem(htmlformat.Header(2, "%s : No such list" % list_name))
+ print doc.Format()
+ sys.exit(0)
+
+if not list._ready:
+ doc.AddItem(htmlformat.Header(2, "%s : No such list" % list_name))
+ print doc.Format()
+ sys.exit(0)
+
+
+if len(list_info) > 1:
+ template_name = list_info[1]
+ for (template, info) in template_data:
+ if template == template_name:
+ template_info = info
+ doc.SetTitle('%s -- Edit html for %s' %
+ (list.real_name, template_info))
+ break
+ else:
+ doc.SetTitle('Edit HTML : Error')
+ doc.AddItem(htmlformat.Header(2, "%s: Invalid template" % template_name))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+else:
+ doc.SetTitle('%s -- HTML Page Editing' % list.real_name)
+ doc.AddItem(htmlformat.Header(1, '%s -- HTML Page Editing' % list.real_name))
+ doc.AddItem(htmlformat.Header(2, 'Select page to edit:'))
+ template_list = htmlformat.UnorderedList()
+ for (template, info) in template_data:
+ l = htmlformat.Link(os.path.join(list.GetRelativeScriptURL('edithtml'),
+ template), info)
+
+ template_list.AddItem(l)
+ doc.AddItem(htmlformat.FontSize("+2", template_list))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+
+
+def FormatHTML(doc):
+ doc.AddItem(htmlformat.Header(1,'%s:' % list.real_name))
+ doc.AddItem(htmlformat.Header(1, template_info))
+
+ doc.AddItem('<hr>')
+
+ link = htmlformat.Link(list.GetRelativeScriptURL('admin'),
+ 'View or edit the list configuration information.')
+ doc.AddItem(htmlformat.FontSize("+1", link))
+ doc.AddItem('<p>')
+
+ doc.AddItem('<hr>')
+
+ form = htmlformat.Form(os.path.join(list.GetRelativeScriptURL('edithtml'),
+ template_name))
+ doc.AddItem(form)
+
+ password_table = htmlformat.Table()
+ password_table.AddRow(['Enter the admin password to edit html:',
+ htmlformat.PasswordBox('adminpw')])
+ password_table.AddRow(['When you are done making changes...',
+ htmlformat.SubmitButton('submit', 'Submit Changes')])
+
+ form.AddItem(password_table)
+
+ text = mm_utils.QuoteHyperChars(list.SnarfHTMLTemplate(template_name))
+ form.AddItem(htmlformat.TextArea('html_code', text, rows=40, cols=75))
+
+def ChangeHTML(list, cgi_info, template_name, doc):
+ if not cgi_info.has_key('html_code'):
+ doc.AddItem(htmlformat.Header(3,"Can't have empty html page."))
+ doc.AddItem(htmlformat.Header(3,"HTML Unchanged."))
+ doc.AddItem('<hr>')
+ return
+ code = cgi_info['html_code'].value
+ f = open(os.path.join(list._template_dir, template_name), 'w')
+ f.write(code)
+ f.close()
+ doc.AddItem(htmlformat.Header(3, 'HTML successfully updated.'))
+ doc.AddItem('<hr>')
+
+try:
+ cgi_data = cgi.FieldStorage()
+ if len(cgi_data.keys()):
+ if not cgi_data.has_key('adminpw'):
+ m = 'Error: You must supply the admin password to edit html.'
+ doc.AddItem(htmlformat.Header(3,
+ htmlformat.Italic(
+ htmlformat.FontAttr(
+ m, color="ff5060"))))
+ doc.AddItem('<hr>')
+ else:
+ try:
+ list.ConfirmAdminPassword(cgi_data['adminpw'].value)
+ ChangeHTML(list, cgi_data, template_name, doc)
+ except:
+ m = 'Error: Incorrect admin password.'
+ doc.AddItem(htmlformat.Header(3,
+ htmlformat.Italic(
+ htmlformat.FontAttr(
+ m, color="ff5060"))))
+ doc.AddItem('<hr>')
+
+
+
+ FormatHTML(doc)
+
+finally:
+ try:
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ except:
+ pass
diff --git a/modules/Cgi/handle_opts.py b/modules/Cgi/handle_opts.py
new file mode 100755
index 000000000..7b5747f4f
--- /dev/null
+++ b/modules/Cgi/handle_opts.py
@@ -0,0 +1,207 @@
+#! /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 input to user options form."""
+
+import sys
+import os, cgi, string
+import mm_utils, maillist, mm_err, mm_cfg, htmlformat
+
+
+doc = htmlformat.Document()
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+
+if len(list_info) < 2:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("Invalid options to CGI script."))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+list_name = string.lower(list_info[0])
+user = list_info[1]
+
+if len(list_info) < 2:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("Invalid options to CGI script."))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+try:
+ list = maillist.MailList(list_name)
+except:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name))
+ print doc.Format(bgcolor="#ffffff")
+ sys.exit(0)
+
+if not list._ready:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name))
+ print doc.Format(bgcolor="#ffffff")
+ list.Unlock()
+ sys.exit(0)
+
+
+def PrintResults(results):
+ replacements = list.GetStandardReplacements()
+ replacements['<mm-results>'] = results
+ replacements['<mm-operation>'] = operation
+ output = list.ParseTags('handle_opts.html', replacements)
+
+ doc.AddItem(output)
+ print doc.Format(bgcolor="#ffffff")
+ list.Unlock()
+ sys.exit(0)
+
+form = cgi.FieldStorage()
+
+error = 0
+operation = ""
+
+if string.lower(user) not in list.members + list.digest_members:
+ PrintResults("%s not a member!<p>" % user)
+
+if form.has_key("unsub"):
+ operation = "Unsubscribe"
+ if not form.has_key("upw"):
+ PrintResults("You must give your password to unsubscribe.<p>")
+ else:
+ try:
+ pw = form["upw"].value
+ if list.ConfirmUserPassword(user, pw):
+ list.DeleteMember(user, "web cmd")
+ except mm_err.MMListNotReady:
+ PrintResults("List is not functional.")
+ except mm_err.MMNoSuchUserError:
+ PrintResults("You seem to already be not a member.<p>")
+ except mm_err.MMBadUserError:
+ PrintResults("Your account has gone awry - "
+ "please contact the list administrator!<p>")
+ except mm_err.MMBadPasswordError:
+ PrintResults("That password was incorrect.<p>")
+# except:
+# PrintResults('''An unknown error occured. <p>
+#Please send mail to <a href=%s>%s</a> explaining
+#exactly what you did to get this error.<p>''' % (mm_cfg.MAILMAN_OWNER,
+# mm_cfg.MAILMAN_OWNER))
+
+ PrintResults("You have been unsubscribed.<p>")
+elif form.has_key("emailpw"):
+ try:
+ list.MailUserPassword(user)
+ PrintResults("A reminder of your password "
+ "has been emailed to you.<p>")
+ except mm_err.MMBadUserError:
+ PrintResults("Your password entry has not been found. The list "
+ "manager is being notified.<p>")
+
+elif form.has_key("changepw"):
+ if (form.has_key('opw')
+ and form.has_key('newpw')
+ and form.has_key('confpw')):
+ try:
+ list.ConfirmUserPassword(user, form['opw'].value)
+ list.ChangeUserPassword(user,
+ form['newpw'].value, form['confpw'].value)
+ except mm_err.MMListNotReady:
+ PrintResults("The list is currently not funcitonal.")
+ except mm_err.MMNotAMemberError:
+ PrintResults("You seem to no longer be a list member.")
+ except mm_err.MMBadPasswordError:
+ PrintResults("The old password you supplied was incorrect.")
+ except mm_err.MMPasswordsMustMatch:
+ PrintResults("Passwords must match.")
+ except:
+ PrintResults('''An unknown error occured. <p>
+Please send mail to <a href=%s>%s</a> explaining
+exactly what you did to get this error.<p>''' % (mm_cfg.MAILMAN_OWNER, mm_cfg.MAILMAN_OWNER))
+
+
+ PrintResults("Your password has been changed.")
+ else:
+ PrintResults("You must supply your old password,"
+ " and your new password twice.")
+
+else:
+ # If keys don't exist, set them to whatever they were. (essentially a noop)
+ if form.has_key("digest"):
+ digest_value = eval(form["digest"].value)
+ else:
+ digest_value = list.GetUserOption(user, mm_cfg.Digests)
+ if form.has_key("mime"):
+ mime = eval(form["mime"].value)
+ else:
+ mime = list.GetUserOption(user, mm_cfg.DisableMime)
+ if form.has_key("dontreceive"):
+ dont_receive = eval(form["dontreceive"].value)
+ else:
+ dont_receive = list.GetUserOption(user, mm_cfg.DontReceiveOwnPosts)
+ if form.has_key("ackposts"):
+ ack_posts = eval(form["ackposts"].value)
+ else:
+ ack_posts = list.GetUserOption(user, mm_cfg.AcknowlegePosts)
+ if form.has_key("disablemail"):
+ disable_mail = eval(form["disablemail"].value)
+ else:
+ disable_mail = list.GetUserOption(user, mm_cfg.DisableDelivery)
+ if form.has_key("conceal"):
+ conceal = eval(form["conceal"].value)
+ else:
+ conceal = list.GetUserOption(user, mm_cfg.ConcealSubscription)
+
+ if not form.has_key("digpw"):
+ PrintResults("You must supply a password to change options.")
+ try:
+ list.ConfirmUserPassword(user, form['digpw'].value)
+ except mm_err.MMAlreadyDigested:
+ pass
+ except mm_err.MMAlreadyUndigested:
+ pass
+ except mm_err.MMMustDigestError:
+ PrintResults("List only accepts digest members.")
+ except mm_err.MMCantDigestError:
+ PrintResults("List doesn't accept digest members.")
+ except mm_err.MMNotAMemberError:
+ PrintResults("%s isn't subscribed to this list." % mail.GetSender())
+ except mm_err.MMListNotReady:
+ PrintResults("List is not functional.")
+ except mm_err.MMNoSuchUserError:
+ PrintResults("%s is not subscribed to this list." %mail.GetSender())
+ except mm_err.MMBadPasswordError:
+ PrintResults("You gave the wrong password.")
+ except:
+ PrintResults('''An unknown error occured. <p>
+Please send mail to <a href=%s>%s</a> explaining
+exactly what you did to get this error.<p>''' % (mm_cfg.MAILMAN_OWNER))
+
+
+ list.SetUserOption(user, mm_cfg.DisableDelivery, disable_mail)
+ list.SetUserOption(user, mm_cfg.DontReceiveOwnPosts, dont_receive)
+ list.SetUserOption(user, mm_cfg.AcknowlegePosts, ack_posts)
+ list.SetUserOption(user, mm_cfg.DisableMime, mime)
+ try:
+ list.SetUserDigest(user, digest_value)
+ except (mm_err.MMAlreadyDigested, mm_err.MMAlreadyUndigested):
+ pass
+ list.SetUserOption(user, mm_cfg.ConcealSubscription, conceal)
+ PrintResults("You have successfully set your options.")
+
+
+list.Unlock() \ No newline at end of file
diff --git a/modules/Cgi/listinfo.py b/modules/Cgi/listinfo.py
new file mode 100755
index 000000000..b1aae2d92
--- /dev/null
+++ b/modules/Cgi/listinfo.py
@@ -0,0 +1,178 @@
+#! /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.
+
+"""Produce listinfo page, primary web entry-point to maillists.
+"""
+
+# No lock needed in this script, because we don't change data.
+
+import sys
+import os, string
+from regsub import gsub
+import mm_utils, maillist, mm_cfg
+from htmlformat import *
+
+def main():
+ try:
+ path = os.environ['PATH_INFO']
+ except KeyError:
+ path = ""
+
+ list_info = mm_utils.GetPathPieces(path)
+
+ if len(list_info) == 0:
+ FormatListinfoOverview()
+ return
+
+ list_name = string.lower(list_info[0])
+
+ try:
+ list = maillist.MailList(list_name, lock=0)
+ except:
+ list = None
+
+ if not (list and list._ready):
+ FormatListinfoOverview(error="List <em>%s</em> not found." % list_name)
+ return
+
+ FormatListListinfo(list)
+
+def FormatListinfoOverview(error=None):
+ "Present a general welcome and itemize the (public) lists for this host."
+ doc = Document()
+ legend = "%s maillists" % mm_cfg.DEFAULT_HOST_NAME
+ doc.SetTitle(legend)
+
+ table = Table(border=0, width="100%")
+ table.AddRow([Center(Header(2, legend))])
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0,
+ colspan=2, bgcolor="#99ccff")
+
+ advertised = []
+ names = mm_utils.list_names()
+ names.sort()
+
+ # XXX We need a portable way to determine the host by which we are being
+ # visited! An absolute URL would do...
+ if os.environ.has_key('HTTP_HOST'):
+ http_host = os.environ['HTTP_HOST']
+ else:
+ http_host = None
+
+ for n in names:
+ l = maillist.MailList(n, lock = 0)
+ if l.advertised:
+ if (http_host
+ and (string.find(http_host, l.host_name) == -1
+ and string.find(l.host_name, http_host) == -1)):
+ # List is for different identity for this host - skip it.
+ continue
+ else:
+ advertised.append(l)
+
+ if error:
+ greeting = FontAttr(error, color="ff5060", size="+1")
+ else:
+ greeting = "Welcome!"
+
+ if not advertised:
+ welcome_items = (greeting,
+ "<p>"
+ " There currently are no publicly-advertised ",
+ Link(mm_cfg.MAILMAN_URL, "mailman"),
+ " maillists on %s." % mm_cfg.DEFAULT_HOST_NAME,
+ )
+ else:
+
+ welcome_items = (
+ greeting,
+ "<p>"
+ " Below is the collection of publicly-advertised ",
+ Link(mm_cfg.MAILMAN_URL, "mailman"),
+ " maillists on %s." % mm_cfg.DEFAULT_HOST_NAME,
+ (' Click on a list name to visit the info page'
+ ' for that list. There you can learn more about the list,'
+ ' subscribe to it, or find the roster of current subscribers.'),
+ )
+
+ welcome_items = (welcome_items +
+ (" To visit the info page for an unadvertised list,"
+ " open a URL similar to this one, but with a '/' and"
+ +
+ (" the %slist name appended."
+ % ((error and "right ") or ""))
+ +
+ '<p> List administrators, you can visit ',
+ Link(os.path.join('../' * mm_utils.GetNestingLevel(),
+ 'admin/'), "the list admin overview page"),
+ " to find the management interface for your list."
+ "<p>(Send questions or comments to ",
+ Link("mailto:%s" % mm_cfg.MAILMAN_OWNER,
+ mm_cfg.MAILMAN_OWNER),
+ ".)<p>"))
+
+ 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('listinfo'),
+ Bold(l.real_name)), l.description])
+
+ doc.AddItem(table)
+
+ print doc.Format(bgcolor="#ffffff")
+
+def FormatListListinfo(list):
+ "Expand the listinfo template against the list's settings, and print."
+
+ doc = HeadlessDocument()
+
+ replacements = list.GetStandardReplacements()
+
+ if not list.digestable or not list.nondigestable:
+ replacements['<mm-digest-radio-button>'] = ""
+ replacements['<mm-undigest-radio-button>'] = ""
+ else:
+ replacements['<mm-digest-radio-button>'] = list.FormatDigestButton()
+ replacements['<mm-undigest-radio-button>'] = \
+ list.FormatUndigestButton()
+ replacements['<mm-plain-digests-button>'] = list.FormatPlainDigestsButton()
+ replacements['<mm-mime-digests-button>'] = list.FormatMimeDigestsButton()
+ replacements['<mm-subscribe-box>'] = list.FormatBox('email')
+ replacements['<mm-subscribe-button>'] = list.FormatButton('email-button',
+ text='Subscribe')
+ replacements['<mm-new-password-box>'] = list.FormatSecureBox('pw')
+ replacements['<mm-confirm-password>'] = list.FormatSecureBox('pw-conf')
+ replacements['<mm-subscribe-form-start>'] = \
+ list.FormatFormStart('subscribe')
+ replacements['<mm-roster-form-start>'] = list.FormatFormStart('roster')
+ replacements['<mm-editing-options>'] = list.FormatEditingOption()
+ replacements['<mm-info-button>'] = SubmitButton('UserOptions',
+ 'Edit Options').Format()
+ replacements['<mm-roster-option>'] = list.FormatRosterOptionForUser()
+
+ # Do the expansion.
+ doc.AddItem(list.ParseTags('listinfo.html', replacements))
+
+ print doc.Format()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/modules/Cgi/options.py b/modules/Cgi/options.py
new file mode 100755
index 000000000..07664bbb3
--- /dev/null
+++ b/modules/Cgi/options.py
@@ -0,0 +1,125 @@
+#! /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.
+
+"""Produce user options form, from list options.html template.
+
+Takes listname/userid in PATH_INFO, expecting an `obscured' userid. Depending
+on the mm_utils.{O,Uno}bscureEmail utilities tolerance, will work fine with an
+unobscured ids as well.
+
+"""
+
+# We don't need to lock in this script, because we're never going to change
+# data.
+
+import sys
+import os, string
+import mm_utils, maillist, htmlformat, mm_cfg
+
+doc = htmlformat.HeadlessDocument()
+
+try:
+ path = os.environ['PATH_INFO']
+except KeyError:
+ path = ""
+list_info = mm_utils.GetPathPieces(path)
+
+if len(list_info) < 2:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("Invalid options to CGI script."))
+ print doc.Format()
+ sys.exit(0)
+
+list_name = string.lower(list_info[0])
+user = mm_utils.UnobscureEmail(list_info[1])
+
+try:
+ list = maillist.MailList(list_name, lock=0)
+except:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name ))
+ print doc.Format()
+ sys.exit(0)
+
+if not list._ready:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name ))
+ print doc.Format()
+ sys.exit(0)
+
+if string.lower(user) not in list.members + list.digest_members:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such member %s."
+ % (list_name, `user`)))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+
+# Re-obscure the user's address for the page banner if obscure_addresses set.
+if list.obscure_addresses:
+ presentable_user = mm_utils.ObscureEmail(user, for_text=1)
+else:
+ presentable_user = user
+
+replacements = list.GetStandardReplacements()
+replacements['<mm-digest-radio-button>'] = list.FormatOptionButton(
+ mm_cfg.Digests, 1, user)
+replacements['<mm-undigest-radio-button>'] = list.FormatOptionButton(
+ mm_cfg.Digests, 0, user)
+replacements['<mm-plain-digests-button>'] = list.FormatOptionButton(
+ mm_cfg.DisableMime, 1, user)
+replacements['<mm-mime-digests-button>'] = list.FormatOptionButton(
+ mm_cfg.DisableMime, 0, user)
+replacements['<mm-delivery-enable-button>'] = list.FormatOptionButton(
+ mm_cfg.DisableDelivery, 0, user)
+replacements['<mm-delivery-disable-button>'] = list.FormatOptionButton(
+ mm_cfg.DisableDelivery, 1, user)
+replacements['<mm-disabled-notice>'] = list.FormatDisabledNotice(user)
+replacements['<mm-dont-ack-posts-button>'] = list.FormatOptionButton(
+ mm_cfg.AcknowlegePosts, 0, user)
+replacements['<mm-ack-posts-button>'] = list.FormatOptionButton(
+ mm_cfg.AcknowlegePosts, 1, user)
+replacements['<mm-receive-own-mail-button>'] = list.FormatOptionButton(
+ mm_cfg.DontReceiveOwnPosts, 0, user)
+replacements['<mm-dont-receive-own-mail-button>'] = list.FormatOptionButton(
+ mm_cfg.DontReceiveOwnPosts, 1, user)
+replacements['<mm-public-subscription-button>'] = list.FormatOptionButton(
+ mm_cfg.ConcealSubscription, 0, user)
+replacements['<mm-hide-subscription-button>'] = list.FormatOptionButton(
+ mm_cfg.ConcealSubscription, 1, user)
+
+replacements['<mm-digest-submit>'] = list.FormatButton('setdigest',
+ 'Submit My Changes')
+replacements['<mm-unsubscribe-button>'] = list.FormatButton('unsub', 'Unsubscribe')
+replacements['<mm-digest-pw-box>'] = list.FormatSecureBox('digpw')
+replacements['<mm-unsub-pw-box>'] = list.FormatSecureBox('upw')
+replacements['<mm-old-pw-box>'] = list.FormatSecureBox('opw')
+replacements['<mm-new-pass-box>'] = list.FormatSecureBox('newpw')
+replacements['<mm-confirm-pass-box>'] = list.FormatSecureBox('confpw')
+replacements['<mm-change-pass-button>'] = list.FormatButton('changepw',
+ "Change My Password")
+replacements['<mm-form-start>'] = list.FormatFormStart('handle_opts', user)
+replacements['<mm-user>'] = user
+replacements['<mm-presentable-user>'] = presentable_user
+replacements['<mm-email-my-pw>'] = list.FormatButton('emailpw',
+ 'Email My Password To Me')
+
+
+doc.AddItem(list.ParseTags('options.html', replacements))
+print doc.Format()
+
diff --git a/modules/Cgi/private.py b/modules/Cgi/private.py
new file mode 100755
index 000000000..4a06a7270
--- /dev/null
+++ b/modules/Cgi/private.py
@@ -0,0 +1,228 @@
+#! /usr/bin/env python -u
+#
+# 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.
+
+"""Provide a password-interface wrapper around a hierarchy of web pages.
+
+Currently this is organized to obtain passwords for mailman maillist
+subscribers.
+
+ - Set the ROOT variable to point to the root of your archives private
+ hierarchy. The script will look there for the private archive files.
+ - Put the ../misc/Cookie.py script in ../../cgi-bin (where the wrapper
+ executables are).
+"""
+
+import sys, os, string, re
+import maillist, mm_err, mm_utils
+import Cookie
+
+ROOT = "/local/pipermail/private/"
+SECRET = "secret" # XXX used for hashing
+
+PAGE = '''
+<html>
+<head>
+ <title>%(listname)s Private Archives Authentication</title>
+</head>
+<body>
+<FORM METHOD=POST ACTION="%(basepath)s/%(path)s">
+ <TABLE WIDTH="100%" BORDER="0" CELLSPACING="4" CELLPADDING="5">
+ <TR>
+ <TD COLSPAN="2" WIDTH="100%" BGCOLOR="#99CCFF" ALIGN="CENTER">
+ <B><FONT COLOR="#000000" SIZE="+1">%(listname)s Private Archives
+ Authentication</FONT></B>
+ </TD>
+ </TR>
+ <tr>
+ <td COLSPAN="2"> <P>%(message)s </td>
+ <tr>
+ </tr>
+ <TD> <div ALIGN="Right">Address: </div></TD>
+ <TD> <INPUT TYPE=TEXT NAME=username SIZE=30> </TD>
+ <tr>
+ </tr>
+ <TD> <div ALIGN="Right"> Password: </div> </TD>
+ <TD> <INPUT TYPE=password NAME=password SIZE=30></TD>
+ <tr>
+ </tr>
+ <td></td>
+ <td> <INPUT TYPE=SUBMIT>
+ </td>
+ </tr>
+ </TABLE>
+</FORM>
+'''
+
+login_attempted = 0
+_list = None
+
+name_pat = re.compile(r"""
+(?: / (?: \d{4} q \d\. )? # Match "/", and, optionally, 1998q1."
+ ( [^/]* ) /? # The SIG name
+ /[^/]*$ # The trailing 12345.html portion
+) | (?:
+ / ( [^/.]* ) # Match matrix-sig
+ (?:\.html)? # Optionally match .html
+ /? # Optionally match a trailing slash
+ $ # Must match to end of string
+
+)
+""", re.VERBOSE)
+
+def getListName(path):
+ match = name_pat.search(path)
+ if match is None: return
+ if match.group(1): return match.group(1)
+ if match.group(2): return match.group(2)
+ raise ValueError, "Can't identify SIG name"
+
+
+def GetListobj(list_name):
+ """Return an unlocked instance of the named maillist, if found."""
+ global _list
+ if _list:
+ return _list
+ try:
+ _list = maillist.MailList(list_name, lock=0)
+ except mm_err.MMUnknownListError:
+ _list = None
+ return None
+ return _list
+
+def isAuthenticated(list_name):
+ if os.environ.has_key('HTTP_COOKIE'):
+ c = Cookie.Cookie( os.environ['HTTP_COOKIE'] )
+ if c.has_key(list_name):
+ # The user has a token like 'c++-sig=AE23446AB...'; verify
+ # that it's correct.
+ token = c[list_name].value
+ import base64, md5
+ if base64.decodestring(token) != md5.new(SECRET
+ + list_name
+ + SECRET).digest():
+ return 0
+ return 1
+
+ # No corresponding cookie. OK, then check for username, password
+ # CGI variables
+ import cgi
+ v = cgi.FieldStorage()
+ username = password = None
+ if v.has_key('username'):
+ username = v['username']
+ if type(username) == type([]): username = username[0]
+ username = username.value
+ if v.has_key('password'):
+ password = v['password']
+ if type(password) == type([]): password = password[0]
+ password = password.value
+
+ if username is None or password is None: return 0
+
+ # Record that this is a login attempt, so if it fails the form can
+ # be displayed with an appropriate message.
+ global login_attempted
+ login_attempted=1
+
+ listobj = GetListobj(list_name)
+ if not listobj:
+ print '\n<P>A list named,', repr(list_name), "was not found."
+ return 0
+
+ try:
+ listobj.ConfirmUserPassword( username, password)
+ except (mm_err.MMBadUserError, mm_err.MMBadPasswordError):
+ return 1
+
+ import base64, md5
+ token = md5.new(SECRET + list_name + SECRET).digest()
+ token = base64.encodestring(token)
+ token = string.strip(token)
+ c = Cookie.Cookie()
+ c[list_name] = token
+ print c # Output the cookie
+ return 1
+
+
+def true_path(path):
+ "Ensure that the path is safe by removing .."
+ path = string.split(path, '/')
+ for i in range(len(path)):
+ if path[i] == ".": path[i] = "" # ./ is just redundant
+ elif path[i] == "..":
+ # Remove any .. components
+ path[i] = ""
+ j=i-1
+ while j>0 and path[j] == "": j=j-1
+ path[j] = ""
+
+ path = filter(None, path)
+ return string.join(path, '/')
+
+def processPage(page):
+ """Change any URLs that start with ../ to work properly when output from
+ /cgi-bin/private"""
+ # Escape any % signs not followed by (
+ page = re.sub('%([^(])', r'%%\1', page)
+
+ # Convert references like HREF="../doc" to just /doc.
+ page = re.sub('([\'="])../', r'\1/', page)
+
+ return page
+
+def main():
+ print 'Content-type: text/html\n'
+ path = os.environ.get('PATH_INFO', "/index.html")
+ true_filename = os.path.join(ROOT, true_path(path) )
+ list_name = getListName(path)
+
+ if os.path.isdir(true_filename):
+ true_filename = true_filename + '/index.html'
+
+ if not isAuthenticated(list_name):
+ # Output the password form
+ page = processPage( PAGE )
+
+ listobj = GetListobj(list_name)
+ if login_attempted:
+ message = ("Your email address or password were incorrect."
+ " Please try again.")
+ else:
+ message = ("Please enter your %s subscription email address"
+ " and password." % listobj.real_name)
+ while path and path[0] == '/': path=path[1:] # Remove leading /'s
+ basepath = os.path.split(listobj.GetBaseArchiveURL())[0]
+ listname = listobj.real_name
+ print '\n\n', page % vars()
+ sys.exit(0)
+
+ print '\n\n'
+ # Authorization confirmed... output the desired file
+ try:
+ f = open(true_filename, 'r')
+ except IOError:
+ print "<H3>Archive File Not Found</H3>"
+ print "No file", path
+ else:
+ while (1):
+ data = f.read(16384)
+ if data == "": break
+ sys.stdout.write(data)
+ f.close()
+
+
diff --git a/modules/Cgi/roster.py b/modules/Cgi/roster.py
new file mode 100755
index 000000000..b150df68d
--- /dev/null
+++ b/modules/Cgi/roster.py
@@ -0,0 +1,110 @@
+#! /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.
+
+"""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 sys
+import os, string
+import cgi
+import mm_utils, maillist, htmlformat, mm_cfg, mm_err
+
+def main():
+ doc = htmlformat.HeadlessDocument()
+ form = cgi.FieldStorage()
+ list = get_list()
+
+ bad = ""
+ # These nested conditionals constituted a cascading authentication
+ # check, yielding a
+ if not list.private_roster:
+ # No privacy.
+ bad = ""
+ else:
+ auth_req = ("%s subscriber list requires authentication."
+ % list.real_name)
+ if not form.has_key("roster-pw"):
+ bad = auth_req
+ else:
+ pw = form['roster-pw'].value
+ # Just the admin password is sufficient - check it early.
+ if not list.ValidAdminPassword(pw):
+ if not form.has_key('roster-email'):
+ # No admin password and no user id, nogo.
+ bad = auth_req
+ else:
+ id = form['roster-email'].value
+ if list.private_roster == 1:
+ # Private list - members visible.
+ try:
+ list.ConfirmUserPassword(id, pw)
+ except (mm_err.MMBadUserError,
+ mm_err.MMBadPasswordError):
+ bad = ("%s subscriber authentication failed."
+ % list.real_name)
+ else:
+ # Anonymous list - admin-only visible
+ # - and we already tried admin password, above.
+ bad = ("%s admin authentication failed."
+ % list.real_name)
+ if bad:
+ doc = error_page_doc(bad)
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+
+ replacements = list.GetStandardReplacements()
+
+ doc.AddItem(list.ParseTags('roster.html', replacements))
+ print doc.Format()
+
+def get_list():
+ "Return list or bail out with error page."
+
+ list_info = mm_utils.GetPathPieces(os.environ['PATH_INFO'])
+ if len(list_info) != 1:
+ error_page("Invalid options to CGI script.")
+ sys.exit(0)
+ list_name = string.lower(list_info[0])
+ try:
+ list = maillist.MailList(list_name, lock = 0)
+ except mm_err.MMUnknownListError:
+ error_page("%s: No such list.", list_name)
+ sys.exit(0)
+ if not list._ready:
+ error_page("%s: No such list.", list_name)
+ sys.exit(0)
+ return list
+
+def error_page(errmsg, *args):
+ print apply(error_page_doc, (errmsg,) + args).Format()
+
+def error_page_doc(errmsg, *args):
+ """Produce a simple error-message page on stdout and exit.
+
+ Optional arg justreturn means just return the doc, don't print it."""
+ doc = htmlformat.Document()
+ doc.SetTitle("Error")
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold(errmsg % args))
+ return doc
diff --git a/modules/Cgi/subscribe.py b/modules/Cgi/subscribe.py
new file mode 100755
index 000000000..aa69b959f
--- /dev/null
+++ b/modules/Cgi/subscribe.py
@@ -0,0 +1,188 @@
+#! /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 listinfo form submission, ie subscriptions or roster requests."""
+
+import sys
+import os, cgi, string
+from regsub import gsub
+import mm_utils, maillist, mm_err, mm_message, mm_cfg, mm_pending, htmlformat
+
+doc = htmlformat.Document()
+
+path = os.environ['PATH_INFO']
+list_info = mm_utils.GetPathPieces(path)
+list_name = string.lower(list_info[0])
+
+if len(list_info) < 1:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("Invalid options to CGI script."))
+ print doc.Format()
+ sys.exit(0)
+
+try:
+ list = maillist.MailList(list_name)
+except:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name ))
+ print doc.Format()
+ sys.exit(0)
+
+
+if not list._ready:
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s: No such list." % list_name ))
+ print doc.Format()
+ list.Unlock()
+ sys.exit(0)
+
+form = cgi.FieldStorage()
+
+error = 0
+results = ''
+
+def call_script(which, pathinfo):
+ "A little bit of a hack to call one of the scripts..."
+ os.environ['PATH_INFO'] = string.join(pathinfo, '/')
+ file = os.path.join(mm_cfg.SCRIPTS_DIR, which)
+ list.Unlock()
+ execfile(file)
+ sys.exit(0)
+
+#######
+# Preliminaries done, actual processing of the form input below.
+
+if (form.has_key("UserOptions")
+ or (form.has_key("info") and not form.has_key("email"))):
+ # Go to user options section.
+ if not form.has_key("info"):
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("You must supply your email address."))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ list.Unlock()
+ sys.exit(0)
+ addr = form['info'].value
+ member = list.FindUser(addr)
+ if not list.FindUser(addr):
+ doc.AddItem(htmlformat.Header(2, "Error"))
+ doc.AddItem(htmlformat.Bold("%s has no subscribed addr <i>%s</i>."
+ % (list.real_name, addr)))
+ doc.AddItem(list.GetMailmanFooter())
+ print doc.Format()
+ list.Unlock()
+ sys.exit(0)
+ call_script('options', [list._internal_name, member])
+if not form.has_key("email"):
+ error = 1
+ results = results + "You must supply a valid email address.<br>"
+else:
+ email = form["email"].value
+if not form.has_key("pw") or not form.has_key("pw-conf"):
+ error = 1
+ results = results + "You must supply a valid password, and confirm it.<br>"
+else:
+ pw = form["pw"].value
+ pwc = form["pw-conf"].value
+
+if not error and (pw <> pwc):
+ error = 1
+ results = results + "Your passwords did not match.<br>"
+
+if form.has_key("digest"):
+ digest = eval(form["digest"].value)
+
+if not list.digestable:
+ digest = 0
+elif not list.nondigestable:
+ digest = 1
+
+
+def PrintResults():
+ replacements = list.GetStandardReplacements()
+ replacements['<mm-results>'] = results
+ output = list.ParseTags('subscribe.html', replacements)
+
+ doc.AddItem(output)
+ print doc.Format()
+ list.Unlock()
+ sys.exit(0)
+
+if error:
+ PrintResults()
+
+else:
+ try:
+ results = results + ("Confirmation from your email address is "
+ "required, to prevent anyone from covertly "
+ "subscribing you. Instructions are being "
+ "sent to you at %s." % email)
+ if os.environ.has_key('REMOTE_HOST'):
+ remote = os.environ['REMOTE_HOST']
+ elif os.environ.has_key('REMOTE_ADDR'):
+ remote = os.environ['REMOTE_ADDR']
+ else:
+ remote = "."
+ if digest:
+ digesting = " digest"
+ else:
+ digesting = ""
+ cookie = mm_pending.gencookie()
+ mm_pending.add2pending(email, pw, digest, cookie)
+ list.SendTextToUser(subject = "%s -- confirmation of subscription -- request %d" % \
+ (list.real_name, cookie),
+ recipient = email,
+ sender = list.GetRequestEmail(),
+ text = mm_pending.VERIFY_FMT % ({"email": email,
+ "listaddress": list.GetListEmail(),
+ "listname": list.real_name,
+ "cookie": cookie,
+ "requestor": remote,
+ "request_addr": list.GetRequestEmail()}),
+
+ add_headers = ["Reply-to: %s"
+ % list.GetRequestEmail(),
+ "Errors-To: %s"
+ % list.GetAdminEmail()])
+ except mm_err.MMBadEmailError:
+ results = results + ("Mailman won't accept the given email "
+ "address as a valid address. (Does it "
+ "have an @ in it???)<p>")
+ except mm_err.MMListNotReady:
+ results = results + ("The list is not fully functional, and "
+ "can not accept subscription requests.<p>")
+#
+# deprecating this, it might be useful if we decide to
+# allow approved based subscriptions without confirmation
+#
+## except mm_err.MMNeedApproval, x:
+## results = results + ("Subscription was <em>deferred</em> "
+## "because:<br> %s<p>Your request must "
+## "be approved by the list admin. "
+## "You will receive email informing you "
+## "of the moderator's descision when they "
+## "get to your request.<p>" % x)
+ except mm_err.MMHostileAddress:
+ results = results + ("Your subscription is not allowed because "
+ "the email address you gave is insecure.<p>")
+ except mm_err.MMAlreadyAMember:
+ results = results + "You are already subscribed!<p>"
+
+
+PrintResults()
+list.Unlock() \ No newline at end of file