#!/usr/local/bin/python """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: 383 $" import sys sys.path.append('/home/mailman/mailman/modules') import os, cgi, string, crypt, types import mm_utils, maillist, mm_cfg, mm_err 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")] 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 # For encompassing try/except. doc = Document() path = os.environ['PATH_INFO'] list_info = mm_utils.GetPathPieces(path) if len(list_info) < 1: doc.AddItem(Header(2, "Invalid options to CGI script.")) print doc.Format(bgcolor="#ffffff") sys.exit(0) list_name = string.lower(list_info[0]) list = maillist.MailList(list_name) try: if not (list and list._ready): doc.AddItem(Header(3, "%s: No such list" % list_name)) print doc.Format(bgcolor="#ffffff") sys.exit(0) 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' cgi_data = cgi.FieldStorage() if len(cgi_data.keys()): if cgi_data.has_key('VARHELP'): FormatOptionHelp(doc, cgi_data['VARHELP'].value, list) print doc.Format(bgcolor="#ffffff") return if not cgi_data.has_key('adminpw'): AddErrorMessage(doc, 'Error: You must supply the admin password to' ' change options.') else: try: list.ConfirmAdminPassword(cgi_data['adminpw'].value) ChangeOptions(list, category, cgi_data, doc) # Yuck. This shouldn't need to be here. if not list.digestable and not list.nondigestable: list.nondigestable = 1 except mm_err.MMBadPasswordError: AddErrorMessage(doc, 'Error: Incorrect admin password.') if not list.digestable and len(list.digest_members): AddErrorMessage(doc, 'Warning: you have digest members,' ' but digests are turned off.' ' Those people will not receive mail.') if not list.nondigestable and len(list.members): AddErrorMessage(doc, 'Warning: you have list members,' ' but non-digestified mail is turned' ' off. They will receive mail until' ' you fix this problem.') if len(cgi_data.keys()): if (cgi_data.has_key('bounce_matching_headers')): try: pairs = list.parse_matching_header_opt() except mm_err.MMBadConfigError, line: AddErrorMessage(doc, 'Warning: bad matching-header line' ' (does it have the colon?)
") form.AddItem(FormatOptionsSection(category, list)) form.AddItem(Center(FormatPasswordStuff())) form.AddItem(list.GetMailmanFooter()) def FormatOptionsSection(category, list): """Produce the category-specific options table.""" if category == 'members': # Special case for members section. return FormatMembershipOptions(list) options = GetConfigOptions(list, category) big_table = Table(cellspacing=3, cellpadding=4) # Get and portray the text label for the category. for k, v in CATEGORIES: if k == category: label = v big_table.AddRow([Center(Header(2, label))]) big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 0, colspan=2, bgcolor="#99ccff") def ColHeader(big_table = big_table): big_table.AddRow([Center(Bold('Description')), Center(Bold('Value'))]) big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 0, width="15%") big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 1, width="85%") did_col_header = 0 for item in options: if type(item) == types.StringType: # The very first banner option (string in an options list) is # treated as a general description, while any others are # treated as section headers - centered and italicized... if did_col_header: item = "
" % (varname, category, item[4])) doc.AddItem("%s
" % item[5])
doc.AddItem("Current value:")
valbox = Table(border=1, cellpadding=2)
val = getattr(list, varname)
if type(val) == types.StringType:
val = Preformatted(val)
valbox.AddRow([val])
doc.AddItem(Center(valbox))
def GetGuiItem(table_entry, category, list):
"""Return the contents for a table row representing an options item.
Elements of the table_entry list are:
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:
list.LogMsg("error",
"admin: Badly formed options entry:\n %s",
table_entry)
return Italic(" (Note that you can, alternately, set the subscriber's no-delivery
option to inhibit delivery of their messages, if you want to only
temporarily disable their delivery.) """
% list.GetScriptURL('roster'))
return container
def FormatPasswordStuff():
password_submit = Table(width="100%", bgcolor="#99cccc", border=0,
cellspacing=0, cellpadding=2)
password_submit.AddRow([Center(Bold('To Submit Your Changes'))])
password_submit.AddCellInfo(password_submit.GetCurrentRowIndex(), 0,
colspan=2)
password_submit.AddRow(["Enter the Administrator Password:",
PasswordBox('adminpw')])
password_submit.AddRow([Center("and..."),
Center(Bold(SubmitButton('submit',
'Submit Changes')))])
change_pw_table = Table(width="100%", bgcolor="#cccccc", border=0,
cellspacing=0, cellpadding=2)
change_pw_table.AddRow([Bold(Center('To Change Your Password'))])
change_pw_table.AddCellInfo(change_pw_table.GetCurrentRowIndex(), 0,
colspan=2)
change_pw_table.AddRow(['Enter your new password:',
PasswordBox('newpw')])
change_pw_table.AddRow(['And also confirm it:',
PasswordBox('confirmpw')])
change_pw_table.AddRow([Center('... and then submit your changes, above.')])
change_pw_table.AddCellInfo(change_pw_table.GetCurrentRowIndex(), 0,
colspan=2)
password_stuff = Table()
password_stuff.AddRow([password_submit])
password_stuff.AddRow([change_pw_table])
return password_stuff
# XXX klm - looks like turn_on_moderation is orphaned.
turn_on_moderation = 0
# Options processing
def GetValidValue(list, 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(list, 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(list, prop)
return num
except:
return getattr(list, prop)
else:
# Should never get here...
return val
def ChangeOptions(list, category, cgi_info, document):
dirty = 0
if category != 'members':
opt_list = GetConfigOptions(list, 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(list, property, kind, val, deps)
if getattr(list, property) != value:
setattr(list, property, value)
dirty = 1
if cgi_info.has_key('subscribees'):
name_text = cgi_info['subscribees'].value
names = string.split(name_text, '\r\n')
for new_name in names:
try:
#FIXME: The admin needs to be able to specify subscribe options
list.AddMember(new_name, (mm_utils.GetRandomSeed() +
mm_utils.GetRandomSeed()))
dirty = 1
#FIXME: Give some sort of an indication of which names didn't work,
# and why they didn't work...
except:
pass
if cgi_info.has_key('newpw'):
if cgi_info.has_key('confirmpw'):
new = cgi_info['newpw'].value
confirm = cgi_info['confirmpw'].value
if new == confirm:
list.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"))))
if dirty:
list.Save()
def AddErrorMessage(doc, errmsg, *args):
doc.AddItem(Header(3, Italic(FontAttr(errmsg % args,
color="#ff66cc"))))
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."""
doc = Document()
doc.AddItem(Header(2, "Error"))
doc.AddItem(Bold(errmsg % args))
return doc
_config_info = None
def GetConfigOptions(list, category):
global _config_info
if _config_info == None:
_config_info = list.GetConfigInfo()
return _config_info[category]
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print "Interrupted!"
raise SystemExit, 0
except mm_err.MMUnknownListError, msg:
error_page("%s: %s", list_name, msg)
raise SystemExit, 0
except:
print "Content-type: text/html\n"
print "Mass Subscribe Members
')
container.AddItem('Enter one email address per line
')
container.AddItem(TextArea(name='subscribees', rows=20,cols=60,wrap=None))
container.AddItem('To Unsubscribe Members...
')
container.AddItem("""
To unsubscribe members you must use your admin password in place of the
user's password on the user's edit-options page. Visit their
edit-options page (via the roster page) and do the
unsubscribe procedure, providing the admin password instead of the
user's password.
We're sorry, we hit a bug!
\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"
try:
import traceback
sys.stderr = sys.stdout
traceback.print_exc()
except:
print "[failed to get traceback]"
print "\n\n
"