summaryrefslogtreecommitdiff
path: root/mailman/commands
diff options
context:
space:
mode:
Diffstat (limited to 'mailman/commands')
-rw-r--r--mailman/commands/__init__.py20
-rw-r--r--mailman/commands/cmd_confirm.py98
-rw-r--r--mailman/commands/cmd_end.py33
-rw-r--r--mailman/commands/cmd_help.py92
-rw-r--r--mailman/commands/cmd_info.py49
-rw-r--r--mailman/commands/cmd_join.py20
-rw-r--r--mailman/commands/cmd_leave.py20
-rw-r--r--mailman/commands/cmd_lists.py65
-rw-r--r--mailman/commands/cmd_password.py122
-rw-r--r--mailman/commands/cmd_remove.py20
-rw-r--r--mailman/commands/cmd_set.py360
-rw-r--r--mailman/commands/cmd_stop.py20
-rw-r--r--mailman/commands/cmd_subscribe.py133
-rw-r--r--mailman/commands/cmd_unsubscribe.py87
-rw-r--r--mailman/commands/cmd_who.py152
-rw-r--r--mailman/commands/echo.py47
16 files changed, 1338 insertions, 0 deletions
diff --git a/mailman/commands/__init__.py b/mailman/commands/__init__.py
new file mode 100644
index 000000000..81035e44a
--- /dev/null
+++ b/mailman/commands/__init__.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+__all__ = [
+ 'echo',
+ ]
diff --git a/mailman/commands/cmd_confirm.py b/mailman/commands/cmd_confirm.py
new file mode 100644
index 000000000..47d88a704
--- /dev/null
+++ b/mailman/commands/cmd_confirm.py
@@ -0,0 +1,98 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""
+ confirm <confirmation-string>
+ Confirm an action. The confirmation-string is required and should be
+ supplied by a mailback confirmation notice.
+"""
+
+from mailman import Errors
+from mailman import Pending
+from mailman.configuration import config
+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(config.PENDING_REQUEST_LIFE / config.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."""))
+ 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.'))
+ except Errors.NotAMemberError:
+ # They've already been unsubscribed
+ res.results.append(_("""\
+You are not currently a member. Have you already unsubscribed or changed
+your email address?"""))
+ except Errors.MembershipIsBanned:
+ owneraddr = mlist.GetOwnerEmail()
+ res.results.append(_("""\
+You are currently banned from subscribing to this list. If you think this
+restriction is erroneous, please contact the list owners at
+%(owneraddr)s."""))
+ except Errors.HostileSubscriptionError:
+ res.results.append(_("""\
+You were not invited to this mailing list. The invitation has been discarded,
+and both list administrators have been alerted."""))
+ except Errors.MMBadPasswordError:
+ res.results.append(_("""\
+Bad approval password given. Held message is still being held."""))
+ else:
+ if ((results[0] == Pending.SUBSCRIPTION and mlist.send_welcome_msg)
+ or
+ (results[0] == Pending.UNSUBSCRIPTION and mlist.send_goodbye_msg)):
+ # We don't also need to send a confirmation succeeded message
+ res.respond = 0
+ else:
+ res.results.append(_('Confirmation succeeded'))
+ # Consume any other confirmation strings with the same cookie so
+ # the user doesn't get a misleading "unprocessed" message.
+ match = 'confirm ' + cookie
+ unprocessed = []
+ for line in res.commands:
+ if line.lstrip() == match:
+ continue
+ unprocessed.append(line)
+ res.commands = unprocessed
+ # Process just one confirmation string per message
+ return STOP
diff --git a/mailman/commands/cmd_end.py b/mailman/commands/cmd_end.py
new file mode 100644
index 000000000..81cb15a4a
--- /dev/null
+++ b/mailman/commands/cmd_end.py
@@ -0,0 +1,33 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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
diff --git a/mailman/commands/cmd_help.py b/mailman/commands/cmd_help.py
new file mode 100644
index 000000000..94951ba02
--- /dev/null
+++ b/mailman/commands/cmd_help.py
@@ -0,0 +1,92 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""
+ help
+ Print this help message.
+"""
+
+import os
+import sys
+
+from mailman import Utils
+from mailman.configuration import config
+from mailman.i18n import _
+
+EMPTYSTRING = ''
+
+
+
+def gethelp(mlist):
+ return _(__doc__)
+
+
+
+def process(res, args):
+ # Get the help text introduction
+ mlist = res.mlist
+ # Since this message is personalized, add some useful information if the
+ # address requesting help is a member of the list.
+ msg = res.msg
+ for sender in msg.get_senders():
+ if mlist.isMember(sender):
+ memberurl = mlist.GetOptionsURL(sender, absolute=1)
+ urlhelp = _(
+ 'You can access your personal options via the following url:')
+ res.results.append(urlhelp)
+ res.results.append(memberurl)
+ # Get a blank line in the output.
+ res.results.append('')
+ break
+ # 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' : config.VERSION,
+ 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
+ 'requestaddr' : mlist.GetRequestEmail(),
+ 'adminaddr' : mlist.GetOwnerEmail(),
+ 'commands' : commands,
+ }, mlist=mlist, lang=res.msgdata['lang'], raw=1)
+ # Now add to the response
+ res.results.append('help')
+ res.results.append(helptext)
diff --git a/mailman/commands/cmd_info.py b/mailman/commands/cmd_info.py
new file mode 100644
index 000000000..449c73ad4
--- /dev/null
+++ b/mailman/commands/cmd_info.py
@@ -0,0 +1,49 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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.posting_address
+ requestaddr = mlist.request_address
+ owneraddr = mlist.owner_address
+ listurl = mlist.script_url('listinfo')
+ 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'))
diff --git a/mailman/commands/cmd_join.py b/mailman/commands/cmd_join.py
new file mode 100644
index 000000000..7a80cd72b
--- /dev/null
+++ b/mailman/commands/cmd_join.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""The `join' command is synonymous with `subscribe'.
+"""
+
+from mailman.Commands.cmd_subscribe import process
diff --git a/mailman/commands/cmd_leave.py b/mailman/commands/cmd_leave.py
new file mode 100644
index 000000000..8cb3b60a8
--- /dev/null
+++ b/mailman/commands/cmd_leave.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""The `leave' command is synonymous with `unsubscribe'.
+"""
+
+from mailman.Commands.cmd_unsubscribe import process
diff --git a/mailman/commands/cmd_lists.py b/mailman/commands/cmd_lists.py
new file mode 100644
index 000000000..7087ff488
--- /dev/null
+++ b/mailman/commands/cmd_lists.py
@@ -0,0 +1,65 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""
+ lists
+ See a list of the public mailing lists on this GNU Mailman server.
+"""
+
+from mailman.MailList import MailList
+from mailman.configuration import config
+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:'))
+ i = 1
+ for listname in sorted(config.list_manager.names):
+ 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
+ # Skip the list if it isn't in the same virtual domain.
+ if xlist.host_name <> mlist.host_name:
+ 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
diff --git a/mailman/commands/cmd_password.py b/mailman/commands/cmd_password.py
new file mode 100644
index 000000000..4e64ad78e
--- /dev/null
+++ b/mailman/commands/cmd_password.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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.configuration import config
+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'))
+ # Prohibit multiple password retrievals.
+ 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) == 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'))
+ # Prohibit multiple password retrievals.
+ 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) == 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((config.AuthUser, config.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((config.AuthUser, config.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
diff --git a/mailman/commands/cmd_remove.py b/mailman/commands/cmd_remove.py
new file mode 100644
index 000000000..c68f28800
--- /dev/null
+++ b/mailman/commands/cmd_remove.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""The `remove' command is synonymous with `unsubscribe'.
+"""
+
+from mailman.Commands.cmd_unsubscribe import process
diff --git a/mailman/commands/cmd_set.py b/mailman/commands/cmd_set.py
new file mode 100644
index 000000000..77d5d2ac4
--- /dev/null
+++ b/mailman/commands/cmd_set.py
@@ -0,0 +1,360 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+from email.Utils import parseaddr, formatdate
+
+from mailman import Errors
+from mailman import MemberAdaptor
+from mailman import i18n
+from mailman.configuration import config
+
+def _(s): return s
+
+OVERVIEW = _("""
+ set ...
+ Set or view your membership options.
+
+ Use `set help' (without the quotes) to get a more detailed list of the
+ options you can change.
+
+ Use `set show' (without the quotes) to view your current option
+ settings.
+""")
+
+DETAILS = _("""
+ set help
+ Show this detailed help.
+
+ set show [address=<address>]
+ View your current option settings. 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!).
+
+ set authenticate <password> [address=<address>]
+ To set any of your options, you must include this command first, along
+ with your membership 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!).
+
+ set ack on
+ set ack off
+ When the `ack' option is turned on, you will receive an
+ acknowledgement message whenever you post a message to the list.
+
+ set digest plain
+ set digest mime
+ set digest off
+ When the `digest' option is turned off, you will receive postings
+ immediately when they are posted. Use `set digest plain' if instead
+ you want to receive postings bundled into a plain text digest
+ (i.e. RFC 1153 digest). Use `set digest mime' if instead you want to
+ receive postings bundled together into a MIME digest.
+
+ set delivery on
+ set delivery off
+ Turn delivery on or off. This does not unsubscribe you, but instead
+ tells Mailman not to deliver messages to you for now. This is useful
+ if you're going on vacation. Be sure to use `set delivery on' when
+ you return from vacation!
+
+ set myposts on
+ set myposts off
+ Use `set myposts off' to not receive copies of messages you post to
+ the list. This has no effect if you're receiving digests.
+
+ set hide on
+ set hide off
+ Use `set hide on' to conceal your email address when people request
+ the membership list.
+
+ set duplicates on
+ set duplicates off
+ Use `set duplicates off' if you want Mailman to not send you messages
+ if your address is explicitly mentioned in the To: or Cc: fields of
+ the message. This can reduce the number of duplicate postings you
+ will receive.
+
+ set reminders on
+ set reminders off
+ Use `set reminders off' if you want to disable the monthly password
+ reminder for this mailing list.
+""")
+
+_ = i18n._
+
+STOP = 1
+
+
+
+def gethelp(mlist):
+ return _(OVERVIEW)
+
+
+
+class SetCommands:
+ def __init__(self):
+ self.__address = None
+ self.__authok = 0
+
+ def process(self, res, args):
+ if not args:
+ res.results.append(_(DETAILS))
+ return STOP
+ subcmd = args.pop(0)
+ methname = 'set_' + subcmd
+ method = getattr(self, methname, None)
+ if method is None:
+ res.results.append(_('Bad set command: %(subcmd)s'))
+ res.results.append(_(DETAILS))
+ return STOP
+ return method(res, args)
+
+ def set_help(self, res, args=1):
+ res.results.append(_(DETAILS))
+ if args:
+ return STOP
+
+ def _usage(self, res):
+ res.results.append(_('Usage:'))
+ return self.set_help(res)
+
+ def set_show(self, res, args):
+ mlist = res.mlist
+ if not args:
+ realname, address = parseaddr(res.msg['from'])
+ elif len(args) == 1 and args[0].startswith('address='):
+ # Send the results to the address, not the From: dude
+ address = args[0][8:]
+ res.returnaddr = address
+ else:
+ return self._usage(res)
+ if not mlist.isMember(address):
+ listname = mlist.real_name
+ res.results.append(
+ _('You are not a member of the %(listname)s mailing list'))
+ return STOP
+ res.results.append(_('Your current option settings:'))
+ opt = mlist.getMemberOption(address, config.AcknowledgePosts)
+ onoff = opt and _('on') or _('off')
+ res.results.append(_(' ack %(onoff)s'))
+ # Digests are a special ternary value
+ digestsp = mlist.getMemberOption(address, config.Digests)
+ if digestsp:
+ plainp = mlist.getMemberOption(address, config.DisableMime)
+ if plainp:
+ res.results.append(_(' digest plain'))
+ else:
+ res.results.append(_(' digest mime'))
+ else:
+ res.results.append(_(' digest off'))
+ # If their membership is disabled, let them know why
+ status = mlist.getDeliveryStatus(address)
+ how = None
+ if status == MemberAdaptor.ENABLED:
+ status = _('delivery on')
+ elif status == MemberAdaptor.BYUSER:
+ status = _('delivery off')
+ how = _('by you')
+ elif status == MemberAdaptor.BYADMIN:
+ status = _('delivery off')
+ how = _('by the admin')
+ elif status == MemberAdaptor.BYBOUNCE:
+ status = _('delivery off')
+ how = _('due to bounces')
+ else:
+ assert status == MemberAdaptor.UNKNOWN
+ status = _('delivery off')
+ how = _('for unknown reasons')
+ changetime = mlist.getDeliveryStatusChangeTime(address)
+ if how and changetime > 0:
+ date = formatdate(changetime)
+ res.results.append(_(' %(status)s (%(how)s on %(date)s)'))
+ else:
+ res.results.append(' ' + status)
+ opt = mlist.getMemberOption(address, config.DontReceiveOwnPosts)
+ # sense is reversed
+ onoff = (not opt) and _('on') or _('off')
+ res.results.append(_(' myposts %(onoff)s'))
+ opt = mlist.getMemberOption(address, config.ConcealSubscription)
+ onoff = opt and _('on') or _('off')
+ res.results.append(_(' hide %(onoff)s'))
+ opt = mlist.getMemberOption(address, config.DontReceiveDuplicates)
+ # sense is reversed
+ onoff = (not opt) and _('on') or _('off')
+ res.results.append(_(' duplicates %(onoff)s'))
+ opt = mlist.getMemberOption(address, config.SuppressPasswordReminder)
+ # sense is reversed
+ onoff = (not opt) and _('on') or _('off')
+ res.results.append(_(' reminders %(onoff)s'))
+
+ def set_authenticate(self, res, args):
+ mlist = res.mlist
+ if len(args) == 1:
+ realname, address = parseaddr(res.msg['from'])
+ password = args[0]
+ elif len(args) == 2 and args[1].startswith('address='):
+ password = args[0]
+ address = args[1][8:]
+ else:
+ return self._usage(res)
+ # See if the password matches
+ if not mlist.isMember(address):
+ listname = mlist.real_name
+ res.results.append(
+ _('You are not a member of the %(listname)s mailing list'))
+ return STOP
+ if not mlist.Authenticate((config.AuthUser,
+ config.AuthListAdmin),
+ password, address):
+ res.results.append(_('You did not give the correct password'))
+ return STOP
+ self.__authok = 1
+ self.__address = address
+
+ def _status(self, res, arg):
+ status = arg.lower()
+ if status == 'on':
+ flag = 1
+ elif status == 'off':
+ flag = 0
+ else:
+ res.results.append(_('Bad argument: %(arg)s'))
+ self._usage(res)
+ return -1
+ # See if we're authenticated
+ if not self.__authok:
+ res.results.append(_('Not authenticated'))
+ self._usage(res)
+ return -1
+ return flag
+
+ def set_ack(self, res, args):
+ mlist = res.mlist
+ if len(args) <> 1:
+ return self._usage(res)
+ status = self._status(res, args[0])
+ if status < 0:
+ return STOP
+ mlist.setMemberOption(self.__address, config.AcknowledgePosts, status)
+ res.results.append(_('ack option set'))
+
+ def set_digest(self, res, args):
+ mlist = res.mlist
+ if len(args) <> 1:
+ return self._usage(res)
+ if not self.__authok:
+ res.results.append(_('Not authenticated'))
+ self._usage(res)
+ return STOP
+ arg = args[0].lower()
+ if arg == 'off':
+ try:
+ mlist.setMemberOption(self.__address, config.Digests, 0)
+ except Errors.AlreadyReceivingRegularDeliveries:
+ pass
+ elif arg == 'plain':
+ try:
+ mlist.setMemberOption(self.__address, config.Digests, 1)
+ except Errors.AlreadyReceivingDigests:
+ pass
+ mlist.setMemberOption(self.__address, config.DisableMime, 1)
+ elif arg == 'mime':
+ try:
+ mlist.setMemberOption(self.__address, config.Digests, 1)
+ except Errors.AlreadyReceivingDigests:
+ pass
+ mlist.setMemberOption(self.__address, config.DisableMime, 0)
+ else:
+ res.results.append(_('Bad argument: %(arg)s'))
+ self._usage(res)
+ return STOP
+ res.results.append(_('digest option set'))
+
+ def set_delivery(self, res, args):
+ mlist = res.mlist
+ if len(args) <> 1:
+ return self._usage(res)
+ status = self._status(res, args[0])
+ if status < 0:
+ return STOP
+ # Delivery status is handled differently than other options. If
+ # status is true (set delivery on), then we enable delivery.
+ # Otherwise, we have to use the setDeliveryStatus() interface to
+ # specify that delivery was disabled by the user.
+ if status:
+ mlist.setDeliveryStatus(self.__address, MemberAdaptor.ENABLED)
+ res.results.append(_('delivery enabled'))
+ else:
+ mlist.setDeliveryStatus(self.__address, MemberAdaptor.BYUSER)
+ res.results.append(_('delivery disabled by user'))
+
+ def set_myposts(self, res, args):
+ mlist = res.mlist
+ if len(args) <> 1:
+ return self._usage(res)
+ status = self._status(res, args[0])
+ if status < 0:
+ return STOP
+ # sense is reversed
+ mlist.setMemberOption(self.__address, config.DontReceiveOwnPosts,
+ not status)
+ res.results.append(_('myposts option set'))
+
+ def set_hide(self, res, args):
+ mlist = res.mlist
+ if len(args) <> 1:
+ return self._usage(res)
+ status = self._status(res, args[0])
+ if status < 0:
+ return STOP
+ mlist.setMemberOption(self.__address, config.ConcealSubscription,
+ status)
+ res.results.append(_('hide option set'))
+
+ def set_duplicates(self, res, args):
+ mlist = res.mlist
+ if len(args) <> 1:
+ return self._usage(res)
+ status = self._status(res, args[0])
+ if status < 0:
+ return STOP
+ # sense is reversed
+ mlist.setMemberOption(self.__address, config.DontReceiveDuplicates,
+ not status)
+ res.results.append(_('duplicates option set'))
+
+ def set_reminders(self, res, args):
+ mlist = res.mlist
+ if len(args) <> 1:
+ return self._usage(res)
+ status = self._status(res, args[0])
+ if status < 0:
+ return STOP
+ # sense is reversed
+ mlist.setMemberOption(self.__address, config.SuppressPasswordReminder,
+ not status)
+ res.results.append(_('reminder option set'))
+
+
+
+def process(res, args):
+ # We need to keep some state between set commands
+ if not getattr(res, 'setstate', None):
+ res.setstate = SetCommands()
+ res.setstate.process(res, args)
diff --git a/mailman/commands/cmd_stop.py b/mailman/commands/cmd_stop.py
new file mode 100644
index 000000000..ecbd5bd88
--- /dev/null
+++ b/mailman/commands/cmd_stop.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""stop is synonymous with the end command.
+"""
+
+from mailman.Commands.cmd_end import process
diff --git a/mailman/commands/cmd_subscribe.py b/mailman/commands/cmd_subscribe.py
new file mode 100644
index 000000000..e1f7e6721
--- /dev/null
+++ b/mailman/commands/cmd_subscribe.py
@@ -0,0 +1,133 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 email.Header import decode_header, make_header
+
+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
+ # Watch for encoded names
+ try:
+ h = make_header(decode_header(realname))
+ # BAW: in Python 2.2, use just unicode(h)
+ realname = h.__unicode__()
+ except UnicodeError:
+ realname = u''
+ # Coerce to byte string if uh contains only ascii
+ try:
+ realname = realname.encode('us-ascii')
+ except UnicodeError:
+ pass
+ # 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.InvalidEmailAddress:
+ res.results.append(_("""\
+Mailman won't accept the given email address as a valid address."""))
+ return STOP
+ except Errors.MMAlreadyAMember:
+ res.results.append(_('You are already subscribed!'))
+ return STOP
+ except Errors.MMCantDigestError:
+ res.results.append(
+ _('No one can subscribe to the digest of this list!'))
+ return STOP
+ except Errors.MMMustDigestError:
+ res.results.append(_('This list only supports digest subscriptions!'))
+ return STOP
+ except Errors.MMSubscribeNeedsConfirmation:
+ # We don't need to respond /and/ send a confirmation message.
+ res.respond = 0
+ except Errors.MMNeedApproval:
+ res.results.append(_("""\
+Your subscription request has been forwarded to the list administrator
+at %(listowner)s for review."""))
+ else:
+ # Everything is a-ok
+ res.results.append(_('Subscription request succeeded.'))
diff --git a/mailman/commands/cmd_unsubscribe.py b/mailman/commands/cmd_unsubscribe.py
new file mode 100644
index 000000000..ea2b96f03
--- /dev/null
+++ b/mailman/commands/cmd_unsubscribe.py
@@ -0,0 +1,87 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""
+ unsubscribe [password] [address=<address>]
+ Unsubscribe from the mailing list. If given, your password must match
+ your current password. If omitted, a confirmation email will be sent
+ to the unsubscribing address. If you wish to unsubscribe 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 Errors
+from mailman.i18n import _
+
+STOP = 1
+
+
+
+def gethelp(mlist):
+ return _(__doc__)
+
+
+
+def process(res, args):
+ mlist = res.mlist
+ password = None
+ address = None
+ argnum = 0
+ for arg in args:
+ if arg.startswith('address='):
+ address = arg[8:]
+ elif argnum == 0:
+ password = arg
+ else:
+ res.results.append(_('Usage:'))
+ res.results.append(gethelp(mlist))
+ return STOP
+ argnum += 1
+ # Fill in empty defaults
+ if address is None:
+ realname, address = parseaddr(res.msg['from'])
+ if not mlist.isMember(address):
+ listname = mlist.real_name
+ res.results.append(
+ _('%(address)s is not a member of the %(listname)s mailing list'))
+ return STOP
+ # If we're doing admin-approved unsubs, don't worry about the password
+ if mlist.unsubscribe_policy:
+ try:
+ mlist.DeleteMember(address, 'mailcmd')
+ except Errors.MMNeedApproval:
+ res.results.append(_("""\
+Your unsubscription request has been forwarded to the list administrator for
+approval."""))
+ elif password is None:
+ # No password was given, so we need to do a mailback confirmation
+ # instead of unsubscribing them here.
+ cpaddr = mlist.getMemberCPAddress(address)
+ mlist.ConfirmUnsubscription(cpaddr)
+ # We don't also need to send a confirmation to this command
+ res.respond = 0
+ else:
+ # No admin approval is necessary, so we can just delete them if the
+ # passwords match.
+ oldpw = mlist.getMemberPassword(address)
+ if oldpw <> password:
+ res.results.append(_('You gave the wrong password'))
+ return STOP
+ mlist.ApprovedDeleteMember(address, 'mailcmd')
+ res.results.append(_('Unsubscription request succeeded.'))
diff --git a/mailman/commands/cmd_who.py b/mailman/commands/cmd_who.py
new file mode 100644
index 000000000..02d500043
--- /dev/null
+++ b/mailman/commands/cmd_who.py
@@ -0,0 +1,152 @@
+# Copyright (C) 2002-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+from email.Utils import parseaddr
+
+from mailman import i18n
+from mailman.configuration import config
+
+STOP = 1
+
+def _(s): return s
+
+PUBLICHELP = _("""
+ who
+ See the non-hidden members of this mailing list.
+ who password
+ See everyone who is on this mailing list. The password is the
+ list's admin or moderator password.
+""")
+
+MEMBERSONLYHELP = _("""
+ who password [address=<address>]
+ See the non-hidden members of this mailing list. The roster is
+ limited to list members only, and you must supply your membership
+ password to retrieve it. 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!). If you provide the list's admin or moderator password,
+ hidden members will be included.
+""")
+
+ADMINONLYHELP = _("""
+ who password
+ See everyone who is on this mailing list. The roster is limited to
+ list administrators and moderators only; you must supply the list
+ admin or moderator password to retrieve the roster.
+""")
+
+_ = i18n._
+
+
+
+def gethelp(mlist):
+ if mlist.private_roster == 0:
+ return _(PUBLICHELP)
+ elif mlist.private_roster == 1:
+ return _(MEMBERSONLYHELP)
+ elif mlist.private_roster == 2:
+ return _(ADMINONLYHELP)
+
+
+def usage(res):
+ res.results.append(_('Usage:'))
+ res.results.append(gethelp(res.mlist))
+
+
+
+def process(res, args):
+ mlist = res.mlist
+ address = None
+ password = None
+ ok = False
+ full = False
+ if mlist.private_roster == 0:
+ # Public rosters
+ if args:
+ if len(args) == 1:
+ if mlist.Authenticate((config.AuthListModerator,
+ config.AuthListAdmin),
+ args[0]):
+ full = True
+ else:
+ usage(res)
+ return STOP
+ else:
+ usage(res)
+ return STOP
+ ok = True
+ elif mlist.private_roster == 1:
+ # List members only
+ if len(args) == 1:
+ password = args[0]
+ realname, address = parseaddr(res.msg['from'])
+ elif len(args) == 2 and args[1].startswith('address='):
+ password = args[0]
+ address = args[1][8:]
+ else:
+ usage(res)
+ return STOP
+ if mlist.isMember(address) and mlist.Authenticate(
+ (config.AuthUser,
+ config.AuthListModerator,
+ config.AuthListAdmin),
+ password, address):
+ # Then
+ ok = True
+ if mlist.Authenticate(
+ (config.AuthListModerator,
+ config.AuthListAdmin),
+ password):
+ # Then
+ ok = full = True
+ else:
+ # Admin only
+ if len(args) <> 1:
+ usage(res)
+ return STOP
+ if mlist.Authenticate((config.AuthListModerator,
+ config.AuthListAdmin),
+ args[0]):
+ ok = full = True
+ if not ok:
+ res.results.append(
+ _('You are not allowed to retrieve the list membership.'))
+ return STOP
+ # It's okay for this person to see the list membership
+ dmembers = mlist.getDigestMemberKeys()
+ rmembers = mlist.getRegularMemberKeys()
+ if not dmembers and not rmembers:
+ res.results.append(_('This list has no members.'))
+ return
+ # Convenience function
+ def addmembers(members):
+ for member in members:
+ if not full and mlist.getMemberOption(member,
+ config.ConcealSubscription):
+ continue
+ realname = mlist.getMemberName(member)
+ if realname:
+ res.results.append(' %s (%s)' % (member, realname))
+ else:
+ res.results.append(' %s' % member)
+ if rmembers:
+ res.results.append(_('Non-digest (regular) members:'))
+ addmembers(rmembers)
+ if dmembers:
+ res.results.append(_('Digest members:'))
+ addmembers(dmembers)
diff --git a/mailman/commands/echo.py b/mailman/commands/echo.py
new file mode 100644
index 000000000..d95e72aa1
--- /dev/null
+++ b/mailman/commands/echo.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2002-2008 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., 51
+# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""The email command 'echo'."""
+
+__metaclass__ = type
+__all__ = [
+ 'Echo',
+ ]
+
+
+from zope.interface import implements
+
+from mailman.i18n import _
+from mailman.interfaces import IEmailCommand
+
+
+SPACE = ' '
+
+
+
+class Echo:
+ """The email 'echo' command."""
+ implements(IEmailCommand)
+
+ name = 'echo'
+ argument_description = '[args]'
+ description = _(
+ 'Echo an acknowledgement. Arguments are return unchanged.')
+
+ def process(self, mlist, msg, msgdata, arguments, results):
+ """See `IEmailCommand`."""
+ print >> results, 'echo', SPACE.join(arguments)
+ return True