Commit ea909c05 authored by bwarsaw's avatar bwarsaw

New architecture for email commands. Instead of the monolithic (and

unmaintainable) MailCommandHandler.py file, we've now got a framework
where each command is implemented in a separate file.  This means it's
both more extensible and more flexible:

- you can easily add new commands for things I haven't thought of
  <wink>, and the `help' command will automatically adjust

- you can disable commands entirely by removing the appropriate file

- you can disable, change, or add commands on a per-list (or even
  per-message or per-sender) basis

CommandRunner.py is the module that calls into this framework.  Each
command is implemented as a cmd_<command>.py file.  The `set' command
is the most complicated.  The help text is currently implemented as
module docstrings (for most commands), so the i18n catalogs must be
updated.  Also the help.txt files will be updated.
parent 8550439a
# Copyright (C) 2002 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.
# NOTE: Makefile.in is converted into Makefile by the configure script
# in the parent directory. Once configure has run, you can recreate
# the Makefile by running just config.status.
# Variables set by configure
VPATH= @[email protected]
srcdir= @[email protected]
bindir= @[email protected]
prefix= @[email protected]
exec_prefix= @[email protected]
CC= @[email protected]
CHMOD= @[email protected]
INSTALL= @[email protected]
DEFS= @[email protected]
# Customizable but not set by configure
OPT= @[email protected]
CFLAGS= $(OPT) $(DEFS)
PACKAGEDIR= $(prefix)/Mailman/Commands
SHELL= /bin/sh
MODULES= *.py
# Modes for directories and executables created by the install
# process. Default to group-writable directories but
# user-only-writable for executables.
DIRMODE= 775
EXEMODE= 755
FILEMODE= 644
INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
# Rules
all:
install:
for f in $(MODULES); \
do \
$(INSTALL) -m $(FILEMODE) $$f $(PACKAGEDIR); \
done
finish:
clean:
distclean:
-rm *.pyc
-rm Makefile
# Copyright (C) 2001 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.
# Copyright (C) 2002 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.
"""
confirm <confirmation-string>
Confirm an action. The confirmation-string is required and should be
supplied with in mailback confirmation notice.
"""
from Mailman import mm_cfg
from Mailman import Errors
from Mailman.i18n import _
STOP = 1
def gethelp(mlist):
return _(__doc__)
def process(res, args):
mlist = res.mlist
if len(args) <> 1:
res.results.append(_('Usage:'))
res.results.append(gethelp(mlist))
return STOP
cookie = args[0]
try:
results = mlist.ProcessConfirmation(cookie, res.msg)
except Errors.MMBadConfirmation, e:
# Express in approximate days
days = int(mm_cfg.PENDING_REQUEST_LIFE / mm_cfg.days(1) + 0.5)
res.results.append(_("""\
Invalid confirmation string. Note that confirmation strings expire
approximately %(days)s days after the initial subscription request. If your
confirmation has expired, please try to re-submit your original request or
message."""))
return STOP
except Errors.MMNeedApproval:
res.results.append(_("""\
Your request has been forwarded to the list moderator for approval."""))
except Errors.MMAlreadyAMember:
# Some other subscription request for this address has
# already succeeded.
res.results.append(_('You are already subscribed.'))
return STOP
except Errors.MMNoSuchUserError:
# They've already been unsubscribed
res.results.append(_("""\
You are not a member. Have you already unsubscribed?"""))
return STOP
else:
res.results.append(_('Confirmation succeeded'))
# Copyright (C) 2002 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.
"""
echo [args]
Simply echo an acknowledgement. Args are echoed back unchanged.
"""
SPACE = ' '
def process(res, args):
res.results.append('echo %s' % SPACE.join(args))
return 1
# Copyright (C) 2002 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.
"""
end
Stop processing commands. Use this if your mail program automatically
adds a signature file.
"""
from Mailman.i18n import _
def gethelp(mlist):
return _(__doc__)
def process(res, args):
return 1 # STOP
# Copyright (C) 2002 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.
"""
help
Print this help message.
"""
import sys
import os
from Mailman import mm_cfg
from Mailman import Utils
from Mailman.i18n import _
EMPTYSTRING = ''
def gethelp(mlist):
return _(__doc__)
def process(res, args):
# Get the help text introduction
mlist = res.mlist
# build the specific command helps from the module docstrings
modhelps = {}
import Mailman.Commands
path = os.path.dirname(os.path.abspath(Mailman.Commands.__file__))
for file in os.listdir(path):
if not file.startswith('cmd_') or not file.endswith('.py'):
continue
module = os.path.splitext(file)[0]
modname = 'Mailman.Commands.' + module
try:
__import__(modname)
except ImportError:
continue
cmdname = module[4:]
help = None
if hasattr(sys.modules[modname], 'gethelp'):
help = sys.modules[modname].gethelp(mlist)
if help:
modhelps[cmdname] = help
# Now sort the command helps
helptext = []
keys = modhelps.keys()
keys.sort()
for cmd in keys:
helptext.append(modhelps[cmd])
commands = EMPTYSTRING.join(helptext)
# Now craft the response
helptext = Utils.maketext(
'help.txt',
{'listname' : mlist.real_name,
'version' : mm_cfg.VERSION,
'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
'requestaddr' : mlist.GetRequestEmail(),
'adminaddr' : mlist.GetOwnerEmail(),
'commands' : commands,
}, mlist=mlist, raw=1)
# Now add to the response
res.results.append('help')
res.results.append(helptext)
# Copyright (C) 2002 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.
"""
info
Get information about this mailing list.
"""
from Mailman.i18n import _
STOP = 1
def gethelp(mlist):
return _(__doc__)
def process(res, args):
mlist = res.mlist
if args:
res.results.append(gethelp(mlist))
return STOP
listname = mlist.real_name
description = mlist.description or _('n/a')
postaddr = mlist.GetListEmail()
requestaddr = mlist.GetRequestEmail()
owneraddr = mlist.GetOwnerEmail()
listurl = mlist.GetScriptURL('listinfo', absolute=1)
res.results.append(_('List name: %(listname)s'))
res.results.append(_('Description: %(description)s'))
res.results.append(_('Postings to: %(postaddr)s'))
res.results.append(_('List Helpbot: %(requestaddr)s'))
res.results.append(_('List Owners: %(owneraddr)s'))
res.results.append(_('More information: %(listurl)s'))
# Copyright (C) 2002 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.
"""The `join' command is synonymous with `subscribe'.
"""
from Mailman.Commands.cmd_subscribe import process
# Copyright (C) 2002 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.
"""The `leave' command is synonymous with `unsubscribe'.
"""
from Mailman.Commands.cmd_unsubscribe import process
# Copyright (C) 2002 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.
"""
lists
See a list of the public mailing lists on this GNU Mailman server.
"""
from Mailman import Utils
from Mailman.MailList import MailList
from Mailman.i18n import _
STOP = 1
def gethelp(mlist):
return _(__doc__)
def process(res, args):
mlist = res.mlist
if args:
res.results.append(_('Usage:'))
res.results.append(gethelp(mlist))
return STOP
hostname = mlist.host_name
res.results.append(_('Public mailing lists at %(hostname)s:'))
lists = Utils.list_names()
lists.sort()
i = 1
for listname in lists:
if listname == mlist.internal_name():
xlist = mlist
else:
xlist = MailList(listname, lock=0)
# We can mention this list if you already know about it
if not xlist.advertised and xlist is not mlist:
continue
realname = xlist.real_name
description = xlist.description or _('n/a')
requestaddr = xlist.GetRequestEmail()
if i > 1:
res.results.append('')
res.results.append(_('%(i)3d. List name: %(realname)s'))
res.results.append(_(' Description: %(description)s'))
res.results.append(_(' Requests to: %(requestaddr)s'))
i += 1
# Copyright (C) 2002 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.
"""
password [<oldpassword> <newpassword>] [address=<address>]
Retrieve or change your password. With no arguments, this returns
your current password. With arguments <oldpassword> and <newpassword>
you can change your password.
If you're posting from an address other than your membership address,
specify your membership address with `address=<address>' (no brackets
around the email address, and no quotes!). Note that in this case the
response is always sent to the subscribed address.
"""
from email.Utils import parseaddr
from Mailman import mm_cfg
from Mailman.i18n import _
STOP = 1
def gethelp(mlist):
return _(__doc__)
def process(res, args):
mlist = res.mlist
address = None
if not args:
# They just want to get their existing password
realname, address = parseaddr(res.msg['from'])
if mlist.isMember(address):
password = mlist.getMemberPassword(address)
res.results.append(_('Your password is: %(password)s'))
else:
listname = mlist.real_name
res.results.append(
_('You are not a member of the %(listname)s mailing list'))
return STOP
elif len(args) == 1 and args[0].startswith('address='):
# They want their password, but they're posting from a different
# address. We /must/ return the password to the subscribed address.
address = args[0][8:]
res.returnaddr = address
if mlist.isMember(address):
password = mlist.getMemberPassword(address)
res.results.append(_('Your password is: %(password)s'))
else:
listname = mlist.real_name
res.results.append(
_('You are not a member of the %(listname)s mailing list'))
return STOP
elif len(args) == 2:
# They are changing their password
oldpasswd = args[0]
newpasswd = args[1]
realname, address = parseaddr(res.msg['from'])
if mlist.isMember(address):
if mlist.Authenticate((mm_cfg.AuthUser, mm_cfg.AuthListAdmin),
oldpasswd, address):
mlist.setMemberPassword(address, newpasswd)
res.results.append(_('Password successfully changed.'))
else:
res.results.append(_("""\
You did not give the correct old password, so your password has not been
changed. Use the no argument version of the password command to retrieve your
current password, then try again."""))
res.results.append(_('\nUsage:'))
res.results.append(gethelp(mlist))
return STOP
else:
listname = mlist.real_name
res.results.append(
_('You are not a member of the %(listname)s mailing list'))
return STOP
elif len(args) == 3 and args[2].startswith('address='):
# They want to change their password, and they're sending this from a
# different address than what they're subscribed with. Be sure the
# response goes to the subscribed address.
oldpasswd = args[0]
newpasswd = args[1]
address = args[2][8:]
res.returnaddr = address
if mlist.isMember(address):
if mlist.Authenticate((mm_cfg.AuthUser, mm_cfg.AuthListAdmin),
oldpasswd, address):
mlist.setMemberPassword(address, newpasswd)
res.results.append(_('Password successfully changed.'))
else:
res.results.append(_("""\
You did not give the correct old password, so your password has not been
changed. Use the no argument version of the password command to retrieve your
current password, then try again."""))
res.results.append(_('\nUsage:'))
res.results.append(gethelp(mlist))
return STOP
else:
listname = mlist.real_name
res.results.append(
_('You are not a member of the %(listname)s mailing list'))
return STOP
# Copyright (C) 2002 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.
"""The `remove' command is synonymous with `unsubscribe'.
"""
from Mailman.Commands.cmd_unsubscribe import process
This diff is collapsed.
# Copyright (C) 2002 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.
"""stop is synonymous with the end command.
"""
from Mailman.Commands.cmd_end import process
# Copyright (C) 2002 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.
"""
subscribe [password] [digest|nodigest] [address=<address>]
Subscribe to this mailing list. Your password must be given to
unsubscribe or change your options, but if you omit the password, one
will be generated for you. You may be periodically reminded of your
password.
The next argument may be either: `nodigest' or `digest' (no quotes!).
If you wish to subscribe an address other than the address you sent
this request from, you may specify `address=<address>' (no brackets
around the email address, and no quotes!)
"""
from email.Utils import parseaddr
from Mailman import Utils
from Mailman import Errors
from Mailman.UserDesc import UserDesc
from Mailman.i18n import _
STOP = 1
def gethelp(mlist):
return _(__doc__)
def process(res, args):
mlist = res.mlist
digest = None
password = None
address = None
realname = None
# Parse the args
argnum = 0
for arg in args:
if arg.startswith('address='):
address = arg[8:]
elif argnum == 0:
password = arg
elif argnum == 1:
if arg.lower() not in ('digest', 'nodigest'):
res.results.append(_('Bad digest specifier: %(arg)s'))
return STOP
if arg.lower() == 'digest':
digest = 1
else:
digest = 0
else:
res.results.append(_('Usage:'))
res.results.append(gethelp(mlist))
return STOP
argnum += 1
# Fill in empty defaults
if digest is None:
digest = mlist.digest_is_default
if password is None:
password = Utils.MakeRandomPassword()
if address is None:
realname, address = parseaddr(res.msg['from'])
if not address:
# Fall back to the sender address
address = res.msg.get_sender()
if not address:
res.results.append(_('No valid address found to subscribe'))
return STOP
# Create the UserDesc record and do a non-approved subscription
listowner = mlist.GetOwnerEmail()
userdesc = UserDesc(address, realname, password, digest)
remote = res.msg.get_sender()
try:
mlist.AddMember(userdesc, remote)
except Errors.MembershipIsBanned:
res.results.append(_("""\
The email address you supplied is banned from this mailing list.
If you think this restriction is erroneous, please contact the list
owners at %(listowner)s."""))
return STOP
except Errors.MMBadEmailError:
res.results.append(_("""\
Mailman won't accept the given email address as a valid address.
(E.g. it must have an @ in it.)"""))
return STOP
except Errors.MMHostileAddress:
res.results.append(_("""\
Your subscription is not allowed because
the email address you gave is insecure."""))
return STOP
except Errors.MMAlreadyAMember:
res.results.append(_('You are already subscribed!'))
return STOP