Index: Mailman/Defaults.py.in =================================================================== RCS file: /sources/mailman-xmlrpc/mailman-xmlrpc/Mailman/Defaults.py.in,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 Defaults.py.in --- Mailman/Defaults.py.in 1 Feb 2006 21:57:04 -0000 1.1.1.1 +++ Mailman/Defaults.py.in 4 Apr 2006 09:03:02 -0000 @@ -1348,3 +1348,20 @@ add_language('zh_TW', _('Chinese (Taiwan)'), 'utf-8') del _ + + +# XMLRPC settings +# +# Location of standalone XMLPRC communication endpoint. Not applicable when +# /cgi-bin/RPC2 endpoing is used. + +XMLRPC_SERVER_NAME = '@URLHOST@' +XMLRPC_SERVER_PORT = 8888 + +# Authentication scheme for methods that operate on a specific mailing list. +# when LIST_AUTHENTICATION is enabled (the default) list administrator's +# password is required. Otherwise site adminstrator's password is required. + +SITE_AUTHENTICATION = 0 +LIST_AUTHENTICATION = 1 +XMLRPC_AUTHENTICATION = LIST_AUTHENTICATION Index: Mailman/ListAdmin.py =================================================================== RCS file: /sources/mailman-xmlrpc/mailman-xmlrpc/Mailman/ListAdmin.py,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 ListAdmin.py --- Mailman/ListAdmin.py 1 Feb 2006 21:57:04 -0000 1.1.1.1 +++ Mailman/ListAdmin.py 4 Apr 2006 09:03:03 -0000 @@ -139,6 +139,12 @@ ids.sort() return ids + def GetPendingTaskIds(self, lastId): + self.__opendb() + ids = [k for k, (op, data) in self.__db.items() if k > lastId] + ids.sort() + return ids + def GetHeldMessageIds(self): return self.__getmsgids(HELDMSG) Index: Mailman/XMLRPC.py =================================================================== RCS file: /sources/mailman-xmlrpc/mailman-xmlrpc/Mailman/XMLRPC.py,v retrieving revision 1.1 diff -u -r1.1 XMLRPC.py --- Mailman/XMLRPC.py 3 Feb 2006 19:32:27 -0000 1.1 +++ Mailman/XMLRPC.py 4 Apr 2006 09:03:03 -0000 @@ -22,410 +22,15 @@ software ''' -from Mailman import MailList -from Mailman import mm_cfg -from Mailman import i18n -from Mailman import Errors -from Mailman import Utils -from Mailman import Message -from Mailman import UserDesc -from Mailman.Logging.Syslog import syslog -from Mailman.Cgi import create, rmlist -import os -import signal -import sys -import xmlrpclib -import traceback -import cgi -import re -import sha from DocXMLRPCServer import DocCGIXMLRPCRequestHandler +from Mailman import i18n +from Mailman import mm_cfg +from Mailman import XMLRPCHandler # Set up i18n _ = i18n._ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) -def __authenhandler__(username, password, siteadmin): - '''__authenhandler__(username, password, siteadmin) - - A helper script designed to verify the basic authentication data - - username -- the username provided - password -- the password provided - siteadmin -- If true, the username must be None and the password must - be the site administrator password. If false, the username must be the - list to work with, and the password must be either the list admin - password or the site admin password. - - Raises MMAuthenticationError if the authentication fails. Returns True - otherwise. - ''' - if siteadmin and username is not None: - raise Errors.MMBadUserError, _('This method requires site admin access.') - if not siteadmin and username is None: - raise Errors.MMBadUserError, \ - _('This method requires a list as a username.') - if username is None: - # if username is mailman, the only authentication that can happen is - # as site administrator - if Utils.check_global_password(password): - return True - else: - raise Errors.MMAuthenticationError, _('Invalid login.') - else: - try: - mlist = MailList.MailList(username, lock=0) - except Errors.MMListError, e: - # a missing list means a bad username - # plus, we don't want to confirm what lists do or don't exist by - # doing something different than if the u/p combination is bad - syslog('error', _('''Mailing list %s not found''' % (username,))) - raise Errors.MMAuthenticationError, _('Invalid login.') - if mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), - password): - return True - else: - raise Errors.MMAuthenticationError, _('Invalid login.') - -def __method_wrapper__(method_func): - '''__method_wrapper__(method_func): - - A Python decorator. - - Python's XMLRPC library will catch any exceptions that are not of the class - xmlrpclib.Fault and wrap then as xmlrpclib.Fault exceptions with an error - code of 1. However, for XMLRPC Fault Interoperability, the code needs to be - -32500. This function wraps method execution, catches Mailman errors, and - raises them as xmlrpclib.Fault errors with the appropriate Fault code. - ''' - def wrapper(*args): - try: - return method_func(*args) - except Exception, e: - tb_type, tb_value, tb = sys.exc_info() - # default application error code is -32500 - code = -32500 - if tb_type == Errors.MMSubscribeNeedsConfirmation: - code = -32501 - elif tb_type == Errors.MMNeedApproval: - code = -32502 - elif tb_type == Errors.MMAuthenticationError: - code = -32503 - error_message = str(e) + str(traceback.format_tb(tb)) - error_message = cgi.escape(error_message) - raise xmlrpclib.Fault(code, error_message) - wrapper.__name__ = method_func.__name__ - wrapper.__doc__ = method_func.__doc__ - wrapper.__dict__.update(method_func.__dict__) - return wrapper - -def __using_mlist__(method_func): - '''__using_mlist__(method_func): - - A Python decorator. - - Many methods will want to get the name of a list as form data. This - decorator will assist in the task of going from a string of a list name - to an mlist object. - ''' - def wrapper(listname, listpasswd, *args): - __authenhandler__(listname, listpasswd, False) - listname = listname.lower() - mlist = MailList.MailList(listname, lock=0) - i18n.set_language(mlist.preferred_language) - - def freak_out(signum, frame, mlist=mlist): - mlist.Unlock() - sys.exit(0) - - signal.signal(signal.SIGTERM, freak_out) - mlist.Lock() - try: - return method_func(mlist, *args) - finally: - mlist.Unlock() - wrapper.__name__ = method_func.__name__ - wrapper.__doc__ = method_func.__doc__ - wrapper.__dict__.update(method_func.__dict__) - return __method_wrapper__(wrapper) - -class XMLDoc(object): - ''' - An error reporting class must be passed into htmlformat.Document - This stub class serves that purpose - ''' - def __init__(self): - self.errors = [] - def addError(self, s, tag=None, *args): - if tag: - s = tag + " " + s - self.errors.append(s % args) - - def set_language(self, val): - pass - -@__using_mlist__ -def add_member(mlist, address, fullname, password, digest, now): - '''add_member(listname, admin_pw, address, fullname, password, digest, now): - - Subscribes the email address provided to the mailing list. - - listname -- the name of the list to configure - admin_pw -- the list or site administrator password - address -- the email address to add - fullname -- the user's full name - password -- the password the user would like to have, leave blank to - generate a random password - digest -- True or False implying if they wish to receive batched digest - delivery - now -- True or False implying whether to bypass normal mailing list rules - about confirmation/approval - - Returns True if everything succeeded; raises Fault -32501 if the user needs - to confirm their subscription; raises Fault -32502 if the list administrator - needs to approve their subscription - ''' - ip = os.environ['REMOTE_ADDR'] - victim = UserDesc.UserDesc() - for attr in ['address', 'fullname', 'password', 'digest']: - if locals()[attr] is not None: setattr(victim, attr, locals()[attr]) - if not victim.password: victim.password = Utils.MakeRandomPassword(mm_cfg.MEMBER_PASSWORD_LENGTH) - if now: - mlist.ApprovedAddMember(victim, whence='XMLRPC from %s' % (ip,)) - else: - try: - mlist.AddMember(victim, remote='XMLRPC from %s' % (ip,)) - except Errors.MMSubscribeNeedsConfirmation: - mlist.Save() - raise - mlist.Save() - return True - -@__using_mlist__ -def delete_member(mlist, email, now): - '''delete_member(listname, admin_pw, email, now): - - Unsubscribes a member from the list. - - listname -- the name of the list to configure - admin_pw -- the list or site administrator password - email -- The email address to unsubscribe - now -- True or False implying whether to bypass normal mailing list rules - about approval - - Returns True if everything succeeded - ''' - ip = os.environ['REMOTE_ADDR'] - email = email.lower() - if now: - mlist.ApprovedDeleteMember(email, whence='XMLRPC from %s' % (ip,)) - else: - mlist.DeleteMember(email, whence='XMLRPC from %s' % (ip,)) - mlist.Save() - return True - -@__using_mlist__ -def change_address(mlist, old, new, keepold): - '''change_address(listname, admin_pw, old, new, keepold): - - Changes an existing member's email address. - - listname -- the name of the list to configure - admin_pw -- the list or site administrator password - old -- the old address - new -- the new address - keepold -- True or False whether to keep the old address subscribed as well - - Returns True if everything succeeded - ''' - old = old.lower() - mlist.changeMemberAddress(old, new, not keepold) - mlist.Save() - return True - -@__method_wrapper__ -def create_list(site_pw, listname, emaildomain, moderated, listadmins, - listadmin_pw, notify, languages): - '''create_list(site_pw, listname, emaildomain, moderated, listadmins, - listadmin_pw, notify, languages): - - Creates a new mailing list. - - site_pw -- the site administrator password - listname -- the name of the list you wish to create - emaildomain -- the email domain this list will appear to come from; False or - an empty string will result in using the site default - moderated -- True or False implying whether new members of the list are - to be moderated by default - listadmins -- A Python list of the list administrators' email addresses - listadmin_pw -- The list administrator password; if blank, a random one will - be generated, and notify will be set to True - notify -- True or False indicating whether a list creation email should - be sent - languages -- a list of two-letter language codes for this list to support - - You must be logged in as the site administrator (username mailman, site admin - password) for this method to be invoked. - - Returns the newly created list's admin password if everything succeded - ''' - __authenhandler__(None, site_pw, True) - ip = os.environ['REMOTE_ADDR'] - - listname = listname.lower() - if listadmin_pw == '': - listadmin_pw = Utils.MakeRandomPassword(mm_cfg.ADMIN_PASSWORD_LENGTH) - notify = True - - if not isinstance(listadmins, list): - listadmins = [listadmins] - mlist = create.add_list(site_pw, listname, listadmins[0], listadmin_pw, notify, - moderated, languages) - options = {'owner': listadmins} - if emaildomain: options['host_name'] = emaildomain - set_options(listname, listadmin_pw, options) - return listadmin_pw - -@__method_wrapper__ -def delete_list(site_pw, listname, archives_too): - '''delete_list(site_pw, listname, archives_too): - - Delete a list and possibly its archives too. - - site_pw -- The site administrator password - listname -- The list to delete - archives_too -- True or False implying whether to remove the lists archives - - Returns True if everything succeded. - ''' - __authenhandler__(None, site_pw, True) - listname = listname.lower() - mlist = MailList.MailList(listname, lock=True) - rmlist.delete_list(site_pw, mlist, archives_too) - return True - -def __notifyOwner__(mlist, subject, text, **kwargs): - locals().update(kwargs) - otrans = i18n.get_translation() - i18n.set_language(mlist.preferred_language) - try: - hostname = mlist.host_name - adminurl = mlist.GetScriptURL('admin', absolute=1) - listname = mlist.real_name - msg = Message.UserNotification( - mlist.owner[:], Utils.get_site_email(), - _(subject), _(text), mlist.preferred_language) - finally: - i18n.set_translation(otrans) - msg.send(mlist) - -@__using_mlist__ -def reset_password(mlist, newpasswd): - """reset_password(listname, admin_pw, newpasswd): - - Resets the list administrator password. - - listname -- the name of the list to configure - admin_pw -- the list or site administrator password - newpasswd -- the new password; leave blank for a random password - """ - if newpasswd is not None and not newpasswd: - newpasswd = Utils.MakeRandomPassword(mm_cfg.ADMIN_PASSWORD_LENGTH) - mlist.password = sha.new(newpasswd).hexdigest() - mlist.Save() - # The below text is copied unceremoniously from change_pw - __notifyOwner__(mlist, 'Your new %(listname)s list password', - '''\ -The site administrator at %(hostname)s has changed the password for your -mailing list %(listname)s. It is now - -%(notifypassword)s - -Please be sure to use this for all future list administration. You may want -to log in now to your list and change the password to something more to your -liking. Visit your list admin page at - -%(adminurl)s -''', notifypassword = newpasswd) - return newpasswd - -@__method_wrapper__ -def list_advertised_lists(filter_re=''): - '''list_advertised_lists(filter_re=''): - - List all mailing lists publically advertised. - - filter_re -- An optional regular expression string useable as a search filter. - - Returns a list of list names. - ''' - listnames = filter(lambda listname: re.search(filter_re, listname) is not None, - Utils.list_names()) - return map(lambda mlist: (mlist.real_name, mlist.description), - filter(lambda mlist: mlist.advertised, - map(lambda listname: MailList.MailList(listname, lock=0), listnames))) - -@__method_wrapper__ -def list_all_lists(site_pw, filter_re=''): - '''list_all_lists(site_pw, filter_re=''): - - List all mailing lists. - - site_pw -- The site administrator's password - filter_re -- An optional regular expression string useable as a search filter. - - Returns a list of list names. - ''' - __authenhandler__(None, site_pw, True) - listnames = Utils.list_names() - return filter(lambda listname: re.search(filter_re, listname) is not None, - listnames) - -@__using_mlist__ -def set_options(mlist, options): - '''set_options(listname, admin_pw, options): - - Set configuration options on the given mailing list. - - listname -- the name of the list to configure - admin_pw -- the list or site administrator password - options -- a Python dictionary of key/value pairs for the options to set - - Returns True - ''' - errHandler = XMLDoc() - guibyprop = mlist.GetPropertyMap() - for key in options.keys(): - mlist.SetValue(guibyprop, key, options[key], errHandler) - if errHandler.errors != []: - raise Errors.MailmanError, '\n'.join(errHandler.errors) - mlist.Save() - return True - -@__using_mlist__ -def get_options(mlist, keys): - '''get_options(mlist, keys): - - Get configuration options on the given mailing list. - - mlist -- the MailList object (see __using_mlist__ decorator) - keys -- a Python list of the configuration keys to return - - Returns a dictionary of key:value pairs. - ''' - d = {} - for key in keys: - d[key] = getattr(mlist, key) - return d - -# Trivial function wrappers -@__using_mlist__ -def get_members(mlist): return mlist.getMembers() -@__using_mlist__ -def get_regular_members(mlist): return mlist.getRegularMemberKeys() -@__using_mlist__ -def get_digest_members(mlist): return mlist.getDigestMemberKeys() - # Set up the XMLRPC handler handler = DocCGIXMLRPCRequestHandler() handler.set_server_title(_('GNU/Mailman List Administration Interface')) @@ -434,19 +39,28 @@ handler.register_introspection_functions() __methodMap__ = {\ - 'Mailman.addMember': add_member, - 'Mailman.deleteMember': delete_member, - 'Mailman.changeMemberAddress': change_address, - 'Mailman.createList': create_list, - 'Mailman.deleteList': delete_list, - 'Mailman.resetListPassword': reset_password, - 'Mailman.listAdvertisedLists': list_advertised_lists, - 'Mailman.listAllLists': list_all_lists, - 'Mailman.setOptions': set_options, - 'Mailman.getOptions': get_options, - 'Mailman.getMembers': get_members, - 'Mailman.getRegularMembers': get_regular_members, - 'Mailman.getDigestMembers': get_digest_members} + 'Mailman.addMember': XMLRPCHandler.add_member, + 'Mailman.deleteMember': XMLRPCHandler.delete_member, + 'Mailman.changeMemberAddress': XMLRPCHandler.change_address, + 'Mailman.getLocales': XMLRPCHandler.get_locales, + 'Mailman.createList': XMLRPCHandler.create_list, + 'Mailman.deleteList': XMLRPCHandler.delete_list, + 'Mailman.resetListPassword': XMLRPCHandler.reset_password, + 'Mailman.listAdvertisedLists': XMLRPCHandler.list_advertised_lists, + 'Mailman.listAllLists': XMLRPCHandler.list_all_lists, + 'Mailman.setOptions': XMLRPCHandler.set_options, + 'Mailman.getOptions': XMLRPCHandler.get_options, + 'Mailman.getPendingMessages': XMLRPCHandler.get_pending_messages, + 'Mailman.getPendingSubscriptions': XMLRPCHandler.get_pending_subscriptions, + 'Mailman.getPendingUnsubscriptions': XMLRPCHandler.get_pending_unsubscriptions, + 'Mailman.handleModeratorRequest': XMLRPCHandler.handle_moderator_request, + 'Mailman.getNewPendingTasks': XMLRPCHandler.get_new_pending_tasks, + 'Mailman.getPendingTask': XMLRPCHandler.get_pending_task, + 'Mailman.getPendingTaskType': XMLRPCHandler.get_pending_task_type, + 'Mailman.postMessage': XMLRPCHandler.post_message, + 'Mailman.getMembers': XMLRPCHandler.get_members, + 'Mailman.getRegularMembers': XMLRPCHandler.get_regular_members, + 'Mailman.getDigestMembers': XMLRPCHandler.get_digest_members} for method in __methodMap__.keys(): func = __methodMap__[method] Index: Mailman/StandaloneXMLRPC.py =================================================================== RCS file: Mailman/StandaloneXMLRPC.py diff -N Mailman/StandaloneXMLRPC.py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Mailman/StandaloneXMLRPC.py 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,70 @@ +# Copyright (C) 2005 by the Free Software Foundation, Inc. +# Copyright (C) 2005 by Joshua Ginsberg +# Copyright (C) 2005 by Joseph Tate +# Copyright (C) 2006 by Pawel Potempski +# +# 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., 51 Franklin St., 5th Floor, Boston, MA 02111-1307, USA. + +'''StandalonXMLRPC.py + +This library implements an XML-RPC interface to the GNU/Mailman list manager +software +''' +import sys +# This should be removed +sys.path.append('/usr/local/mailman/') +from DocXMLRPCServer import DocXMLRPCServer +from Mailman import i18n +from Mailman import mm_cfg +from Mailman import XMLRPCHandler + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + +# Set up the XMLRPC server +server = DocXMLRPCServer((mm_cfg.XMLRPC_SERVER_NAME, mm_cfg.XMLRPC_SERVER_PORT)) +server.set_server_title(_('GNU/Mailman List Administration Interface')) +server.set_server_name(_('GNU/Mailman List Administration Interface')) + +__methodMap__ = {\ + 'Mailman.addMember': XMLRPCHandler.add_member, + 'Mailman.deleteMember': XMLRPCHandler.delete_member, + 'Mailman.changeMemberAddress': XMLRPCHandler.change_address, + 'Mailman.getLocales': XMLRPCHandler.get_locales, + 'Mailman.createList': XMLRPCHandler.create_list, + 'Mailman.deleteList': XMLRPCHandler.delete_list, + 'Mailman.resetListPassword': XMLRPCHandler.reset_password, + 'Mailman.listAdvertisedLists': XMLRPCHandler.list_advertised_lists, + 'Mailman.listAllLists': XMLRPCHandler.list_all_lists, + 'Mailman.setOptions': XMLRPCHandler.set_options, + 'Mailman.getOptions': XMLRPCHandler.get_options, + 'Mailman.getPendingMessages': XMLRPCHandler.get_pending_messages, + 'Mailman.getPendingSubscriptions': XMLRPCHandler.get_pending_subscriptions, + 'Mailman.getPendingUnsubscriptions': XMLRPCHandler.get_pending_unsubscriptions, + 'Mailman.handleModeratorRequest': XMLRPCHandler.handle_moderator_request, + 'Mailman.getNewPendingTasks': XMLRPCHandler.get_new_pending_tasks, + 'Mailman.getPendingTask': XMLRPCHandler.get_pending_task, + 'Mailman.getPendingTaskType': XMLRPCHandler.get_pending_task_type, + 'Mailman.postMessage': XMLRPCHandler.post_message, + 'Mailman.getMembers': XMLRPCHandler.get_members, + 'Mailman.getRegularMembers': XMLRPCHandler.get_regular_members, + 'Mailman.getDigestMembers': XMLRPCHandler.get_digest_members} + +for method in __methodMap__.keys(): + func = __methodMap__[method] + server.register_function(func, method) + +server.serve_forever() Index: Mailman/XMLRPCHandler.py =================================================================== RCS file: Mailman/XMLRPCHandler.py diff -N Mailman/XMLRPCHandler.py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Mailman/XMLRPCHandler.py 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,558 @@ +# Copyright (C) 2005 by the Free Software Foundation, Inc. +# Copyright (C) 2005 by Joshua Ginsberg +# Copyright (C) 2005 by Joseph Tate +# +# 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., 51 Franklin St., 5th Floor, Boston, MA 02111-1307, USA. + +'''XMLRPC.py + +This library implements an XML-RPC interface to the GNU/Mailman list manager +software +''' + +from Mailman import ListAdmin +from Mailman import MailList +from Mailman import mm_cfg +from Mailman import i18n +from Mailman import Errors +from Mailman import Utils +from Mailman import Message +from Mailman import UserDesc +from Mailman.Queue.sbcache import get_switchboard +from Mailman.Logging.Syslog import syslog +from Mailman.Cgi import create, rmlist +import os +import signal +import sys +import xmlrpclib +import traceback +import cgi +import re +import sha + +# Set up i18n +_ = i18n._ +i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) + + +def __authenhandler__(username, password, siteadmin): + '''__authenhandler__(username, password, siteadmin) + + A helper script designed to verify the basic authentication data + + username -- the username provided + password -- the password provided + siteadmin -- If true, the username must be None and the password must + be the site administrator password. If false, the username must be the + list to work with, and the password must be either the list admin + password or the site admin password. + + Raises MMAuthenticationError if the authentication fails. Returns True + otherwise. + ''' + if siteadmin and username is not None: + raise Errors.MMBadUserError, _('This method requires site admin access.') + if not siteadmin and username is None: + raise Errors.MMBadUserError, \ + _('This method requires a list as a username.') + if username is None or mm_cfg.XMLRPC_AUTHENTICATION == mm_cfg.SITE_AUTHENTICATION: + # if username is mailman or settings force it, the only authentication that can happen is + # as site administrator + if Utils.check_global_password(password): + return True + else: + raise Errors.MMAuthenticationError, _('Invalid login.') + else: + try: + mlist = MailList.MailList(username, lock=0) + except Errors.MMListError, e: + # a missing list means a bad username + # plus, we don't want to confirm what lists do or don't exist by + # doing something different than if the u/p combination is bad + syslog('error', _('''Mailing list %s not found''' % (username,))) + raise Errors.MMAuthenticationError, _('Invalid login.') + if mlist.WebAuthenticate((mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin), + password): + return True + else: + raise Errors.MMAuthenticationError, _('Invalid login.') + +def __method_wrapper__(method_func): + '''__method_wrapper__(method_func): + + A Python decorator. + + Python's XMLRPC library will catch any exceptions that are not of the class + xmlrpclib.Fault and wrap then as xmlrpclib.Fault exceptions with an error + code of 1. However, for XMLRPC Fault Interoperability, the code needs to be + -32500. This function wraps method execution, catches Mailman errors, and + raises them as xmlrpclib.Fault errors with the appropriate Fault code. + ''' + def wrapper(*args): + try: + return method_func(*args) + except Exception, e: + tb_type, tb_value, tb = sys.exc_info() + # default application error code is -32500 + code = -32500 + if tb_type == Errors.MMSubscribeNeedsConfirmation: + code = -32501 + elif tb_type == Errors.MMNeedApproval: + code = -32502 + elif tb_type == Errors.MMAuthenticationError: + code = -32503 + elif tb_type == Errors.BadListNameError: + code = -32504 + elif tb_type == Errors.MMListAlreadyExistsError: + code = -32505 + error_message = str(e) + str(traceback.format_tb(tb)) + error_message = cgi.escape(error_message) + raise xmlrpclib.Fault(code, error_message) + wrapper.__name__ = method_func.__name__ + wrapper.__doc__ = method_func.__doc__ + wrapper.__dict__.update(method_func.__dict__) + return wrapper + +def __using_mlist__(method_func): + '''__using_mlist__(method_func): + + A Python decorator. + + Many methods will want to get the name of a list as form data. This + decorator will assist in the task of going from a string of a list name + to an mlist object. + ''' + def wrapper(listname, listpasswd, *args): + __authenhandler__(listname, listpasswd, False) + listname = listname.lower() + mlist = MailList.MailList(listname, lock=0) + i18n.set_language(mlist.preferred_language) + + def freak_out(signum, frame, mlist=mlist): + mlist.Unlock() + sys.exit(0) + + signal.signal(signal.SIGTERM, freak_out) + mlist.Lock() + try: + return method_func(mlist, *args) + finally: + mlist.Unlock() + wrapper.__name__ = method_func.__name__ + wrapper.__doc__ = method_func.__doc__ + wrapper.__dict__.update(method_func.__dict__) + return __method_wrapper__(wrapper) + +class XMLDoc(object): + ''' + An error reporting class must be passed into htmlformat.Document + This stub class serves that purpose + ''' + def __init__(self): + self.errors = [] + def addError(self, s, tag=None, *args): + if tag: + s = tag + " " + s + self.errors.append(s % args) + + def set_language(self, val): + pass + +@__using_mlist__ +def add_member(mlist, address, fullname, password, digest, now): + '''add_member(listname, admin_pw, address, fullname, password, digest, now): + + Subscribes the email address provided to the mailing list. + + listname -- the name of the list to configure + admin_pw -- the list or site administrator password + address -- the email address to add + fullname -- the user's full name + password -- the password the user would like to have, leave blank to + generate a random password + digest -- True or False implying if they wish to receive batched digest + delivery + now -- True or False implying whether to bypass normal mailing list rules + about confirmation/approval + + Returns True if everything succeeded; raises Fault -32501 if the user needs + to confirm their subscription; raises Fault -32502 if the list administrator + needs to approve their subscription + ''' + ip = os.environ['REMOTE_ADDR'] + victim = UserDesc.UserDesc() + for attr in ['address', 'fullname', 'password', 'digest']: + if locals()[attr] is not None: setattr(victim, attr, locals()[attr]) + if not victim.password: victim.password = Utils.MakeRandomPassword(mm_cfg.MEMBER_PASSWORD_LENGTH) + if now: + mlist.ApprovedAddMember(victim, whence='XMLRPC from %s' % (ip,)) + else: + try: + mlist.AddMember(victim, remote='XMLRPC from %s' % (ip,)) + except Errors.MMSubscribeNeedsConfirmation: + mlist.Save() + raise + mlist.Save() + return True + +@__using_mlist__ +def delete_member(mlist, email, now): + '''delete_member(listname, admin_pw, email, now): + + Unsubscribes a member from the list. + + listname -- the name of the list to configure + admin_pw -- the list or site administrator password + email -- The email address to unsubscribe + now -- True or False implying whether to bypass normal mailing list rules + about approval + + Returns True if everything succeeded + ''' + ip = os.environ['REMOTE_ADDR'] + email = email.lower() + if now: + mlist.ApprovedDeleteMember(email, whence='XMLRPC from %s' % (ip,)) + else: + mlist.DeleteMember(email, whence='XMLRPC from %s' % (ip,)) + mlist.Save() + return True + +@__using_mlist__ +def change_address(mlist, old, new, keepold): + '''change_address(listname, admin_pw, old, new, keepold): + + Changes an existing member's email address. + + listname -- the name of the list to configure + admin_pw -- the list or site administrator password + old -- the old address + new -- the new address + keepold -- True or False whether to keep the old address subscribed as well + + Returns True if everything succeeded + ''' + old = old.lower() + mlist.changeMemberAddress(old, new, not keepold) + mlist.Save() + return True + +@__method_wrapper__ +def create_list(site_pw, listname, emaildomain, moderated, listadmins, + listadmin_pw, notify, languages): + '''create_list(site_pw, listname, emaildomain, moderated, listadmins, + listadmin_pw, notify, languages): + + Creates a new mailing list. + + site_pw -- the site administrator password + listname -- the name of the list you wish to create + emaildomain -- the email domain this list will appear to come from; False or + an empty string will result in using the site default + moderated -- True or False implying whether new members of the list are + to be moderated by default + listadmins -- A Python list of the list administrators' email addresses + listadmin_pw -- The list administrator password; if blank, a random one will + be generated, and notify will be set to True + notify -- True or False indicating whether a list creation email should + be sent + languages -- a list of two-letter language codes for this list to support + + You must be logged in as the site administrator (username mailman, site admin + password) for this method to be invoked. + + Returns the newly created list's admin password if everything succeded + ''' + __authenhandler__(None, site_pw, True) + ip = os.environ['REMOTE_ADDR'] + + listname = listname.lower() + if listadmin_pw == '': + listadmin_pw = Utils.MakeRandomPassword(mm_cfg.ADMIN_PASSWORD_LENGTH) + notify = True + + if not isinstance(listadmins, list): + listadmins = [listadmins] + mlist = create.add_list(site_pw, listname, listadmins[0], listadmin_pw, notify, + moderated, languages) + options = {'owner': listadmins} + if emaildomain: options['host_name'] = emaildomain + set_options(listname, listadmin_pw, options) + return listadmin_pw + +@__method_wrapper__ +def delete_list(site_pw, listname, archives_too): + '''delete_list(site_pw, listname, archives_too): + + Delete a list and possibly its archives too. + + site_pw -- The site administrator password + listname -- The list to delete + archives_too -- True or False implying whether to remove the lists archives + + Returns True if everything succeded. + ''' + __authenhandler__(None, site_pw, True) + listname = listname.lower() + mlist = MailList.MailList(listname, lock=True) + rmlist.delete_list(site_pw, mlist, archives_too) + return True + +def __notifyOwner__(mlist, subject, text, **kwargs): + locals().update(kwargs) + otrans = i18n.get_translation() + i18n.set_language(mlist.preferred_language) + try: + hostname = mlist.host_name + adminurl = mlist.GetScriptURL('admin', absolute=1) + listname = mlist.real_name + msg = Message.UserNotification( + mlist.owner[:], Utils.get_site_email(), + _(subject), _(text), mlist.preferred_language) + finally: + i18n.set_translation(otrans) + msg.send(mlist) + +@__using_mlist__ +def reset_password(mlist, newpasswd): + """reset_password(listname, admin_pw, newpasswd): + + Resets the list administrator password. + + listname -- the name of the list to configure + admin_pw -- the list or site administrator password + newpasswd -- the new password; leave blank for a random password + """ + if newpasswd is not None and not newpasswd: + newpasswd = Utils.MakeRandomPassword(mm_cfg.ADMIN_PASSWORD_LENGTH) + mlist.password = sha.new(newpasswd).hexdigest() + mlist.Save() + # The below text is copied unceremoniously from change_pw + __notifyOwner__(mlist, 'Your new %(listname)s list password', + '''\ +The site administrator at %(hostname)s has changed the password for your +mailing list %(listname)s. It is now + +%(notifypassword)s + +Please be sure to use this for all future list administration. You may want +to log in now to your list and change the password to something more to your +liking. Visit your list admin page at + +%(adminurl)s +''', notifypassword = newpasswd) + return newpasswd + +@__method_wrapper__ +def list_advertised_lists(filter_re=''): + '''list_advertised_lists(filter_re=''): + + List all mailing lists publically advertised. + + filter_re -- An optional regular expression string useable as a search filter. + + Returns a list of list names. + ''' + listnames = filter(lambda listname: re.search(filter_re, listname) is not None, + Utils.list_names()) + return map(lambda mlist: (mlist.real_name, mlist.description), + filter(lambda mlist: mlist.advertised, + map(lambda listname: MailList.MailList(listname, lock=0), listnames))) + +@__method_wrapper__ +def list_all_lists(site_pw, filter_re=''): + '''list_all_lists(site_pw, filter_re=''): + + List all mailing lists. + + site_pw -- The site administrator's password + filter_re -- An optional regular expression string useable as a search filter. + + Returns a list of list names. + ''' + __authenhandler__(None, site_pw, True) + listnames = Utils.list_names() + return filter(lambda listname: re.search(filter_re, listname) is not None, + listnames) + +@__using_mlist__ +def set_options(mlist, options): + '''set_options(listname, admin_pw, options): + + Set configuration options on the given mailing list. + + listname -- the name of the list to configure + admin_pw -- the list or site administrator password + options -- a Python dictionary of key/value pairs for the options to set + + Returns True + ''' + errHandler = XMLDoc() + guibyprop = mlist.GetPropertyMap() + for key in options.keys(): + mlist.SetValue(guibyprop, key, options[key], errHandler) + if errHandler.errors != []: + raise Errors.MailmanError, '\n'.join(errHandler.errors) + mlist.Save() + return True + +@__using_mlist__ +def get_options(mlist, keys): + '''get_options(mlist, keys): + + Get configuration options on the given mailing list. + + mlist -- the MailList object (see __using_mlist__ decorator) + keys -- a Python list of the configuration keys to return + + Returns a dictionary of key:value pairs. + ''' + d = {} + for key in keys: + d[key] = getattr(mlist, key) + return d + +@__using_mlist__ +def get_pending_messages(mlist): + '''get_pending_messages(mlist): + + Get pending messages from the given mailing list. + + mlist -- the MailList object (see __using_mlist__ decorator) + + Returns a list of messages ids. + ''' + return mlist.GetHeldMessageIds() + +@__using_mlist__ +def get_pending_subscriptions(mlist): + '''get_pending_subscriptions(mlist): + + Get pending subscriptions from the given mailing list. + + mlist -- the MailList object (see __using_mlist__ decorator) + + Returns a list of subscriptions ids. + ''' + return mlist.GetSubscriptionIds() + +@__using_mlist__ +def get_pending_unsubscriptions(mlist): + '''get_pending_unsubscriptions(mlist): + + Get pending unsubscriptions from the given mailing list. + + mlist -- the MailList object (see __using_mlist__ decorator) + + Returns a list of unsubscriptions ids. + ''' + return mlist.GetUnsubscriptionIds() + +@__using_mlist__ +def get_new_pending_tasks(mlist, lastId): + '''get_new_pendings(mlist): + + Get pending messages from the given mailing list. + + mlist -- the MailList object (see __using_mlist__ decorator) + lastId -- the last fetched identifier + + Returns a list of pending tasks identifiers. + ''' + return mlist.GetPendingTaskIds(lastId) + +@__using_mlist__ +def get_pending_task_type(mlist, id): + '''get_pending_task_type(mlist, id): + + Get pending task type. + + mlist -- the MailList object (see __using_mlist__ decorator) + id -- the task identifier + + Returns a list of pending tasks identifiers. + ''' + return mlist.GetRecordType(id) + +@__using_mlist__ +def handle_moderator_request(mlist, id, value): + '''handle_moderator_request(mlist, id, value): + + Handle moderator request. + + mlist -- the MailList object (see __using_mlist__ decorator) + id -- pending task identifier + value -- action to be performed + + Returns a True + ''' + mlist.HandleRequest(id, value) + return True + +@__using_mlist__ +def get_pending_task(mlist, id): + '''get_pending_task(mlist, id): + + Get pending task. + + mlist -- the MailList object (see __using_mlist__ decorator) + id -- pending task identifier + + Returns a Task + ''' + ##if mlist.NumRequestsPending() < id: + ## raise Errors.LostHeldMessage('Invalid message identifier %s', id) + record = mlist.GetRecord(id) + assert record <> None + ptime, sender, subject, reason, filename, msgdata = record + path = os.path.join(mm_cfg.DATA_DIR, filename) + msg = ListAdmin.readMessage(path) + assert msg <> None + return msg.as_string() + +@__using_mlist__ +def post_message(mlist, msg): + '''post_message(mlist, msg): + + Post message to the list + + mlist -- the MailList object (see __using_mlist__ decorator) + msg -- RFC822 message to post + + Returns a True + ''' + inq = get_switchboard(mm_cfg.INQUEUE_DIR) + inq.enqueue(msg, listname=mlist.internal_name(), tolist=1, _plaintext=1) + return True + +@__method_wrapper__ +def get_locales(site_pw): + '''get_locales(): + + Get all locales available in system + + site_pw -- The site administrator's password + + Returns a List of language codes + ''' + return mm_cfg.LC_DESCRIPTIONS.items() + +# Trivial function wrappers +@__using_mlist__ +def get_members(mlist): return mlist.getMembers() +@__using_mlist__ +def get_regular_members(mlist): return mlist.getRegularMemberKeys() +@__using_mlist__ +def get_digest_members(mlist): return mlist.getDigestMemberKeys()