From 4cf754ca57ef2e904082e105fcd4b78f97ed00a9 Mon Sep 17 00:00:00 2001 From: viega Date: Sat, 30 May 1998 06:06:53 +0000 Subject: Integrated Scott's cookie code into the distribution. Note that it does have one problem... If you have cookies off, you have to log in every time, plus your changes don't take effect! That definitely needs to be fixed. --- cgi/admin | 392 ++++++++++++++++++++++++++++++++++++++++++------------------ cgi/private | 22 ---- 2 files changed, 274 insertions(+), 140 deletions(-) (limited to 'cgi') diff --git a/cgi/admin b/cgi/admin index 69f3d9b5d..b0c7457db 100755 --- a/cgi/admin +++ b/cgi/admin @@ -21,12 +21,12 @@ To run stand-alone for debugging, set env var PATH_INFO to name of list and, optionally, options category.""" -__version__ = "$Revision: 564 $" +__version__ = "$Revision: 635 $" import sys -import os, cgi, string, crypt, types +import os, cgi, string, crypt, types, time import paths # path hacking -import mm_utils, maillist, mm_cfg, mm_err +import mm_utils, maillist, mm_cfg, mm_err, mm_mailcmd, Cookie from htmlformat import * try: @@ -44,12 +44,63 @@ CATEGORIES = [('general', "General Options"), ('archive', "Archival Options")] +LOGIN_PAGE = """ + + + %(listname)s Administrative Authentication + + +
+%(message)s + + + + + + + + + + + +
+ %(listname)s Administrative + Authentication +
List Administrative Password:
+
+
+""" + +# " <- icky emacs font-lock bug workaround + +def isAuthenticated(list, password=None): + if password is not None: # explicit login + try: + list.ConfirmAdminPassword(password) + except mm_err.MMBadPasswordError: + AddErrorMessage(doc, 'Error: Incorrect admin password.') + return 0 + else: + c = Cookie.Cookie() + c[list_name] = list.password # its crypted so this should be ok + c[list_name]['expires'] = mm_cfg.ADMIN_COOKIE_LIFE + c[list_name]["path"] = "/mailman/" + list.GetScriptURL("admin") + 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): + return (c[list_name].value == list.password) + 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 + global list_name, list_info, doc doc = Document() try: @@ -81,26 +132,36 @@ def main(): if category not in map(lambda x: x[0], CATEGORIES): category = 'general' - + global cgi_data cgi_data = cgi.FieldStorage() - if len(cgi_data.keys()): - if cgi_data.has_key('VARHELP'): - FormatOptionHelp(doc, cgi_data['VARHELP'].value, lst) - print doc.Format(bgcolor="#ffffff") + 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 not cgi_data.has_key('adminpw'): - AddErrorMessage(doc, - 'Error: You must supply the admin password to' - ' change options.') - else: + if (cgi_data.has_key('bounce_matching_headers')): try: - lst.ConfirmAdminPassword(cgi_data['adminpw'].value) - ChangeOptions(lst, category, cgi_data, doc) - # Yuck. This shouldn't need to be here. - if not lst.digestable and not lst.nondigestable: - lst.nondigestable = 1 - except mm_err.MMBadPasswordError: - AddErrorMessage(doc, 'Error: Incorrect admin password.') + pairs = lst.parse_matching_header_opt() + except mm_err.MMBadConfigError, line: + AddErrorMessage(doc, + 'Warning: bad matching-header line' + ' (does it have the colon?)', + line) if not lst.digestable and len(lst.digest_members): AddErrorMessage(doc, @@ -113,17 +174,8 @@ def main(): ' 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 = lst.parse_matching_header_opt() - except mm_err.MMBadConfigError, line: - AddErrorMessage(doc, - 'Warning: bad matching-header line' - ' (does it have the colon?)', - line) - + if len(cgi_data.keys()): + ChangeOptions(lst, category, cgi_data, doc) FormatConfiguration(doc, lst, category, category_suffix) print doc.Format(bgcolor="#ffffff") @@ -143,27 +195,13 @@ def FormatAdminOverview(error=None): table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2, bgcolor="#99ccff") - # 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 - advertised = [] names = mm_utils.list_names() names.sort() for n in names: l = maillist.MailList(n) l.Unlock() - 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 l.advertised: advertised.append(l) if error: greeting = FontAttr(error, color="ff5060", size="+1") @@ -247,12 +285,12 @@ def FormatConfiguration(doc, lst, category, category_suffix): other_links.AddItem(link) these_links = UnorderedList() - url = lst.GetScriptURL('admin') for k, v in CATEGORIES: if k == category: these_links.AddItem(" => " + v + " <= ") else: - these_links.AddItem(Link(os.path.join(url, k), v)) + these_links.AddItem(Link("%s/%s" % (lst.GetScriptURL('admin'),k), + v)) links_table.AddRow([these_links, other_links]) links_table.AddRowInfo(max(links_table.GetCurrentRowIndex(), 0), @@ -261,7 +299,8 @@ def FormatConfiguration(doc, lst, category, category_suffix): doc.AddItem(links_table) doc.AddItem('
') if category_suffix: - form = Form(os.path.join(lst.GetScriptURL('admin'), category)) + form = Form("%s/%s" % (lst.GetScriptURL('admin'), + category_suffix)) else: form = Form(lst.GetScriptURL('admin')) doc.AddItem(form) @@ -452,53 +491,91 @@ def FormatMembershipOptions(lst): header.AddRow([Center(Header(2, "Membership Management"))]) header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0, colspan=2, bgcolor="#99ccff") - header.AddRow([Bold("Subscribe and Unsubscribe Members")]) - header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0, - colspan=2, bgcolor ="#FFF0D0") container.AddItem(header) - - container.AddItem('

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. -

(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.)

""" - % lst.GetScriptURL('roster')) + 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 = ("

To View other sections, " + "click on the appropriate range listed below") + 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(" from %s to %s " % \ + ( pi, ci, start, end)) + buttons = apply(UnorderedList, tuple(buttons)) + footer = footer + buttons.Format() + "

" + else: + all.sort() + footer = "

" + for member in all: + cells = [member + "" % (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(" Enter One address per line

")) return container def FormatPasswordStuff(): - password_submit = Table(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(['
And...
', - Bold(SubmitButton('submit', 'Submit Changes'))]) - change_pw_table = Table(bgcolor="#cccccc", border=0, - cellspacing=0, cellpadding=2) - change_pw_table.AddRow([Bold(Center('To Change The Administrator' - ' Password'))]) - change_pw_table.AddCellInfo(change_pw_table.GetCurrentRowIndex(), 0, - colspan=2) - change_pw_table.AddRow(['
Enter the new ' - 'password:
', - PasswordBox('newpw')]) - change_pw_table.AddRow(['
And also confirm it:
', - PasswordBox('confirmpw')]) - - password_stuff = Table(bgcolor="#99cccc") - password_stuff.AddRow([password_submit, change_pw_table]) + 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')), + '
Enter the new password:
', + PasswordBox('newpw'),]) + change_pw_table.AddCellInfo(0, 0, align="middle", colspan=2) + change_pw_table.AddRow(['
Enter the current password
', + PasswordBox('adminpw'), + '
Again to confirm it:
', + PasswordBox('confirmpw')]) + password_stuff = Container() + password_stuff.AddItem(change_pw_table) + password_stuff.AddItem("

") + password_stuff.AddItem(submit) return password_stuff # XXX klm - looks like turn_on_moderation is orphaned. @@ -582,7 +659,38 @@ def GetValidValue(lst, prop, my_type, val, dependant): def ChangeOptions(lst, category, cgi_info, document): dirty = 0 - if category != 'members': + 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"): opt_list = GetConfigOptions(lst, category) for item in opt_list: if len(item) < 5: @@ -600,37 +708,85 @@ def ChangeOptions(lst, category, cgi_info, document): val = cgi_info[property].value value = GetValidValue(lst, property, kind, val, deps) if getattr(lst, property) != value: + print "property: ", property, "value: ", 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 - names = string.split(name_text, '\r\n') - for new_name in names: + 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: -#FIXME: The admin needs to be able to specify subscribe options - lst.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: - lst.password = crypt.crypt(new, mm_utils.GetRandomSeed()) + 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("

") + if subscribe_errors: + document.AddItem(Header(5, "Error Subscribing:")) + items = map(lambda x: "%s -- %s" % (x[0], x[1]), subscribe_errors) + document.AddItem(apply(UnorderedList, tuple((items)))) + document.AddItem("

") + # + # do the user options for members category + # + if cgi_info.has_key('user'): + user = cgi_info["user"] + if type(user) is type([]): + users = [] + for ui in range(len(user)): + users.append(user[ui].value) + else: + users = [user.value] + for user in users: + if not cgi_info.has_key('%s_subscribed' % (user)): + lst.DeleteMember(user) dirty = 1 - else: - m = 'Error: Passwords did not match.' - document.AddItem( - Header(3, Italic(FontAttr(m, color="ff5060")))) + 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 - else: - m = 'Error: You must type in your new password twice.' - document.AddItem( - Header(3, Italic(FontAttr(m, color="ff5060")))) if dirty: lst.Save() diff --git a/cgi/private b/cgi/private index 72d5c6031..facca81bc 100755 --- a/cgi/private +++ b/cgi/private @@ -74,7 +74,6 @@ PAGE = ''' login_attempted = 0 _list = None -# XXX: This looks broken, should investigate name_pat = re.compile(r""" (?: / (?: \d{4} q \d\. )? # Match "/", and, optionally, 1998q1." ( [^/]* ) /? # The SIG name @@ -88,20 +87,6 @@ name_pat = re.compile(r""" ) """, re.VERBOSE) -# " XXX: Emacs turd -# the following is a potentially better rewrite - -## name_pat = re.compile( -## r'(?: / (?: \d{4} q \d\. )?' # Match "/", and, optionally, 1998q1." -## r'( [^/]* ) /?' # The SIG name -## r'/[^/]*$' # The trailing 12345.html portion -## r') | (?:' -## r'/ ( [^/.]* )' # Match matrix-sig -## r'(?:\.html)?' # Optionally match .html -## r'/?' # Optionally match a trailing slash -## r'$)' # Must match to end of string -## , re.VERBOSE) - def getListName(path): match = name_pat.search(path) if match is None: return @@ -109,13 +94,6 @@ def getListName(path): if match.group(2): return match.group(2) raise ValueError, "Can't identify SIG name" -#for i in ['/matrix-sig.html', '/1998q1.c++-sig/index.html', -# '/1998q1.string-sig/foobar.html', -# '/psa-members.html']: -# print i, `getListName(i)` -#sys.exit(0) - -## sys.exit(0) def GetListobj(list_name): """Return an unlocked instance of the named maillist, if found.""" -- cgit v1.2.3-70-g09d2