summaryrefslogtreecommitdiff
path: root/mailman/commands
diff options
context:
space:
mode:
authorBarry Warsaw2008-09-24 22:59:05 -0400
committerBarry Warsaw2008-09-24 22:59:05 -0400
commit12e5731cb50716590c86bdd55a1dae442b8d256c (patch)
treee63e9d6ffcf552472de3076c0739d4871400e586 /mailman/commands
parent9be793725d86c2bbd0bb0791bce4aecd80838e48 (diff)
parentc129b48a91d11fd3fb69666b8109c3a9372b2f8f (diff)
downloadmailman-12e5731cb50716590c86bdd55a1dae442b8d256c.tar.gz
mailman-12e5731cb50716590c86bdd55a1dae442b8d256c.tar.zst
mailman-12e5731cb50716590c86bdd55a1dae442b8d256c.zip
thread merge
Diffstat (limited to '')
-rw-r--r--mailman/commands/__init__.py2
-rw-r--r--mailman/commands/cmd_join.py20
-rw-r--r--mailman/commands/cmd_subscribe.py133
-rw-r--r--mailman/commands/docs/echo.txt31
-rw-r--r--mailman/commands/docs/end.txt38
-rw-r--r--mailman/commands/docs/join.txt172
-rw-r--r--mailman/commands/echo.py4
-rw-r--r--mailman/commands/end.py (renamed from mailman/commands/cmd_end.py)37
-rw-r--r--mailman/commands/join.py127
9 files changed, 399 insertions, 165 deletions
diff --git a/mailman/commands/__init__.py b/mailman/commands/__init__.py
index 81035e44a..044ffd6a5 100644
--- a/mailman/commands/__init__.py
+++ b/mailman/commands/__init__.py
@@ -17,4 +17,6 @@
__all__ = [
'echo',
+ 'end',
+ 'join',
]
diff --git a/mailman/commands/cmd_join.py b/mailman/commands/cmd_join.py
deleted file mode 100644
index 7a80cd72b..000000000
--- a/mailman/commands/cmd_join.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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_subscribe.py b/mailman/commands/cmd_subscribe.py
deleted file mode 100644
index e1f7e6721..000000000
--- a/mailman/commands/cmd_subscribe.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# 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/docs/echo.txt b/mailman/commands/docs/echo.txt
new file mode 100644
index 000000000..d2781d330
--- /dev/null
+++ b/mailman/commands/docs/echo.txt
@@ -0,0 +1,31 @@
+The 'echo' command
+==================
+
+The mail command 'echo' simply replies with the original command and arguments
+to the sender.
+
+ >>> from mailman.configuration import config
+ >>> command = config.commands['echo']
+ >>> command.name
+ 'echo'
+ >>> command.argument_description
+ '[args]'
+ >>> command.description
+ u'Echo an acknowledgement. Arguments are return unchanged.'
+
+The original message is ignored, but the results receive the echoed command.
+
+ >>> from mailman.app.lifecycle import create_list
+ >>> mlist = create_list(u'test@example.com')
+
+ >>> from mailman.queue.command import Results
+ >>> results = Results()
+
+ >>> from mailman.Message import Message
+ >>> print command.process(mlist, Message(), {}, ('foo', 'bar'), results)
+ ContinueProcessing.yes
+ >>> print unicode(results)
+ The results of your email command are provided below.
+ <BLANKLINE>
+ echo foo bar
+ <BLANKLINE>
diff --git a/mailman/commands/docs/end.txt b/mailman/commands/docs/end.txt
new file mode 100644
index 000000000..bd632de48
--- /dev/null
+++ b/mailman/commands/docs/end.txt
@@ -0,0 +1,38 @@
+The 'end' command
+=================
+
+The mail command processor recognized an 'end' command which tells it to stop
+processing email messages.
+
+ >>> from mailman.configuration import config
+ >>> command = config.commands['end']
+ >>> command.name
+ 'end'
+ >>> command.description
+ u'Stop processing commands.'
+
+The 'end' command takes no arguments.
+
+ >>> command.argument_description
+ ''
+
+The command itself is fairly simple; it just stops command processing, and the
+message isn't even looked at.
+
+ >>> from mailman.app.lifecycle import create_list
+ >>> mlist = create_list(u'test@example.com')
+ >>> from mailman.Message import Message
+ >>> print command.process(mlist, Message(), {}, (), None)
+ ContinueProcessing.no
+
+The 'stop' command is a synonym for 'end'.
+
+ >>> command = config.commands['stop']
+ >>> command.name
+ 'stop'
+ >>> command.description
+ u'Stop processing commands.'
+ >>> command.argument_description
+ ''
+ >>> print command.process(mlist, Message(), {}, (), None)
+ ContinueProcessing.no
diff --git a/mailman/commands/docs/join.txt b/mailman/commands/docs/join.txt
new file mode 100644
index 000000000..492297787
--- /dev/null
+++ b/mailman/commands/docs/join.txt
@@ -0,0 +1,172 @@
+The 'join' command
+==================
+
+The mail command 'join' subscribes an email address to the mailing list.
+'subscribe' is an alias for 'join'.
+
+ >>> from mailman.configuration import config
+ >>> command = config.commands['join']
+ >>> print command.name
+ join
+ >>> print command.description
+ Join this mailing list. You will be asked to confirm your subscription
+ request and you may be issued a provisional password.
+ <BLANKLINE>
+ By using the 'digest' option, you can specify whether you want digest
+ delivery or not. If not specified, the mailing list's default will be
+ used. You can also subscribe an alternative address by using the
+ 'address' option. For example:
+ <BLANKLINE>
+ join address=myotheraddress@example.com
+ <BLANKLINE>
+ >>> print command.argument_description
+ [digest=<yes|no>] [address=<address>]
+
+
+No address to join
+------------------
+
+ >>> from mailman.Message import Message
+ >>> from mailman.app.lifecycle import create_list
+ >>> from mailman.queue.command import Results
+ >>> mlist = create_list(u'alpha@example.com')
+
+When no address argument is given, the message's From address will be used.
+If that's missing though, then an error is returned.
+
+ >>> results = Results()
+ >>> print command.process(mlist, Message(), {}, (), results)
+ ContinueProcessing.no
+ >>> print unicode(results)
+ The results of your email command are provided below.
+ <BLANKLINE>
+ join: No valid address found to subscribe
+ <BLANKLINE>
+
+The 'subscribe' command is an alias.
+
+ >>> subscribe = config.commands['subscribe']
+ >>> print subscribe.name
+ subscribe
+ >>> results = Results()
+ >>> print subscribe.process(mlist, Message(), {}, (), results)
+ ContinueProcessing.no
+ >>> print unicode(results)
+ The results of your email command are provided below.
+ <BLANKLINE>
+ subscribe: No valid address found to subscribe
+ <BLANKLINE>
+
+
+Joining the sender
+------------------
+
+When the message has a From field, that address will be subscribed.
+
+ >>> msg = message_from_string("""\
+ ... From: Anne Person <anne@example.com>
+ ...
+ ... """)
+ >>> results = Results()
+ >>> print command.process(mlist, msg, {}, (), results)
+ ContinueProcessing.yes
+ >>> print unicode(results)
+ The results of your email command are provided below.
+ <BLANKLINE>
+ Confirmation email sent to Anne Person <anne@example.com>
+ <BLANKLINE>
+
+Anne is not yet a member because she must confirm her subscription request
+first.
+
+ >>> print config.db.user_manager.get_user(u'anne@example.com')
+ None
+
+Mailman has sent her the confirmation message.
+
+ >>> from mailman.queue import Switchboard
+ >>> virginq = Switchboard(config.VIRGINQUEUE_DIR)
+ >>> qmsg, qdata = virginq.dequeue(virginq.files[0])
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ ...
+ Subject: confirm ...
+ From: confirm-...@example.com
+ To: anne@example.com
+ ...
+ <BLANKLINE>
+ Email Address Registration Confirmation
+ <BLANKLINE>
+ Hello, this is the GNU Mailman server at example.com.
+ <BLANKLINE>
+ We have received a registration request for the email address
+ <BLANKLINE>
+ anne@example.com
+ <BLANKLINE>
+ Before you can start using GNU Mailman at this site, you must first
+ confirm that this is your email address. You can do this by replying to
+ this message, keeping the Subject header intact. Or you can visit this
+ web page
+ <BLANKLINE>
+ http://www.example.com/confirm/...
+ <BLANKLINE>
+ If you do not wish to register this email address simply disregard this
+ message. If you think you are being maliciously subscribed to the list, or
+ have any other questions, you may contact
+ <BLANKLINE>
+ postmaster@example.com
+ <BLANKLINE>
+
+Once Anne confirms her registration, she will be made a member of the mailing
+list.
+
+ >>> token = str(qmsg['subject']).split()[1].strip()
+ >>> from mailman.interfaces.registrar import IRegistrar
+ >>> registrar = IRegistrar(config.domains['example.com'])
+ >>> registrar.confirm(token)
+ True
+
+ >>> user = config.db.user_manager.get_user(u'anne@example.com')
+ >>> print user.real_name
+ Anne Person
+ >>> list(user.addresses)
+ [<Address: Anne Person <anne@example.com> [verified] at ...>]
+
+Anne is also now a member of the mailing list.
+
+ >>> mlist.members.get_member(u'anne@example.com')
+ <Member: Anne Person <anne@example.com>
+ on alpha@example.com as MemberRole.member>
+
+
+Joining a second list
+---------------------
+
+ >>> mlist_2 = create_list(u'baker@example.com')
+ >>> msg = message_from_string("""\
+ ... From: Anne Person <anne@example.com>
+ ...
+ ... """)
+ >>> print command.process(mlist_2, msg, {}, (), Results())
+ ContinueProcessing.yes
+
+Anne of course, is still registered.
+
+ >>> print config.db.user_manager.get_user(u'anne@example.com')
+ <User "Anne Person" at ...>
+
+But she is not a member of the mailing list.
+
+ >>> print mlist_2.members.get_member(u'anne@example.com')
+ None
+
+One Anne confirms this subscription, she becomes a member of the mailing list.
+
+ >>> qmsg, qdata = virginq.dequeue(virginq.files[0])
+ >>> token = str(qmsg['subject']).split()[1].strip()
+ >>> registrar.confirm(token)
+ True
+
+ >>> print mlist_2.members.get_member(u'anne@example.com')
+ <Member: Anne Person <anne@example.com>
+ on baker@example.com as MemberRole.member>
diff --git a/mailman/commands/echo.py b/mailman/commands/echo.py
index d95e72aa1..547f6a9b2 100644
--- a/mailman/commands/echo.py
+++ b/mailman/commands/echo.py
@@ -25,7 +25,7 @@ __all__ = [
from zope.interface import implements
from mailman.i18n import _
-from mailman.interfaces import IEmailCommand
+from mailman.interfaces import ContinueProcessing, IEmailCommand
SPACE = ' '
@@ -44,4 +44,4 @@ class Echo:
def process(self, mlist, msg, msgdata, arguments, results):
"""See `IEmailCommand`."""
print >> results, 'echo', SPACE.join(arguments)
- return True
+ return ContinueProcessing.yes
diff --git a/mailman/commands/cmd_end.py b/mailman/commands/end.py
index 81cb15a4a..6e76e1eb2 100644
--- a/mailman/commands/cmd_end.py
+++ b/mailman/commands/end.py
@@ -14,20 +14,37 @@
# 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.
-"""
+"""The email commands 'end' and 'stop'."""
+
+__metaclass__ = type
+__all__ = [
+ 'End',
+ 'Stop',
+ ]
+
+
+from zope.interface import implements
from mailman.i18n import _
+from mailman.interfaces import ContinueProcessing, IEmailCommand
-def gethelp(mlist):
- return _(__doc__)
+class End:
+ """The email 'end' command."""
+ implements(IEmailCommand)
+ name = 'end'
+ argument_description = ''
+ description = _('Stop processing commands.')
-
-def process(res, args):
- return 1 # STOP
+ def process(self, mlist, msg, msgdata, arguments, results):
+ """See `IEmailCommand`."""
+ # Ignore all arguments.
+ return ContinueProcessing.no
+
+
+class Stop(End):
+ """The email 'stop' command (an alias for 'end')."""
+
+ name = 'stop'
diff --git a/mailman/commands/join.py b/mailman/commands/join.py
new file mode 100644
index 000000000..639a74a99
--- /dev/null
+++ b/mailman/commands/join.py
@@ -0,0 +1,127 @@
+# 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 commands 'join' and 'subscribe'."""
+
+__metaclass__ = type
+__all__ = [
+ 'Join',
+ 'Subscribe',
+ ]
+
+
+from email.header import decode_header, make_header
+from email.utils import formataddr, parseaddr
+from zope.interface import implements
+
+from mailman.Utils import MakeRandomPassword
+from mailman.configuration import config
+from mailman.i18n import _
+from mailman.interfaces import (
+ ContinueProcessing, DeliveryMode, IEmailCommand)
+from mailman.interfaces.registrar import IRegistrar
+
+
+
+class Join:
+ """The email 'join' command."""
+ implements(IEmailCommand)
+
+ name = 'join'
+ argument_description = '[digest=<yes|no>] [address=<address>]'
+ description = _("""\
+Join this mailing list. You will be asked to confirm your subscription
+request and you may be issued a provisional password.
+
+By using the 'digest' option, you can specify whether you want digest delivery
+or not. If not specified, the mailing list's default will be used. You can
+also subscribe an alternative address by using the 'address' option. For
+example:
+
+ join address=myotheraddress@example.com
+""")
+
+ def process(self, mlist, msg, msgdata, arguments, results):
+ """See `IEmailCommand`."""
+ # Parse the arguments.
+ address, delivery_mode = self._parse_arguments(arguments)
+ if address is None:
+ real_name, address = parseaddr(msg['from'])
+ # Address could be None or the empty string.
+ if not address:
+ address = msg.get_sender()
+ if not address:
+ print >> results, _(
+ '$self.name: No valid address found to subscribe')
+ return ContinueProcessing.no
+ domain = config.domains[mlist.host_name]
+ registrar = IRegistrar(domain)
+ registrar.register(address, real_name, mlist)
+ person = formataddr((real_name, address))
+ print >> results, _('Confirmation email sent to $person')
+ return ContinueProcessing.yes
+
+ def _parse_arguments(self, arguments):
+ """Parse command arguments.
+
+ :param arguments: The sequences of arguments as given to the
+ `process()` method.
+ :return: address, delivery_mode
+ """
+ address = None
+ delivery_mode = None
+ for argument in arguments:
+ parts = argument.split('=', 1)
+ if parts[0].lower() == 'digest':
+ if digest is not None:
+ print >> results, self.name, \
+ _('duplicate argument: $argument')
+ return ContinueProcessing.no
+ if len(parts) == 0:
+ # We treat just plain 'digest' as 'digest=yes'. We don't
+ # yet support the other types of digest delivery.
+ delivery_mode = DeliveryMode.mime_digests
+ else:
+ if parts[1].lower() == 'yes':
+ delivery_mode = DeliveryMode.mime_digests
+ elif parts[1].lower() == 'no':
+ delivery_mode = DeliveryMode.regular
+ else:
+ print >> results, self.name, \
+ _('bad argument: $argument')
+ return ContinueProcessing.no
+ elif parts[0].lower() == 'address':
+ if address is not None:
+ print >> results, self.name, \
+ _('duplicate argument $argument')
+ return ContinueProcessing.no
+ if len(parts) == 0:
+ print >> results, self.name, \
+ _('missing argument value: $argument')
+ return ContinueProcessing.no
+ if len(parts) > 1:
+ print >> results, self.name, \
+ _('too many argument values: $argument')
+ return ContinueProcessing.no
+ address = parts[1]
+ return address, delivery_mode
+
+
+
+class Subscribe(Join):
+ """The email 'subscribe' command (an alias for 'join')."""
+
+ name = 'subscribe'