summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mailman/app/membership.py96
-rw-r--r--mailman/app/moderator.py17
-rw-r--r--mailman/app/notifications.py134
-rw-r--r--mailman/bin/set_members.py6
-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.txt80
-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.py193
-rw-r--r--mailman/interfaces/command.py12
-rw-r--r--mailman/queue/command.py9
-rw-r--r--mailman/queue/docs/command.txt65
-rw-r--r--mailman/queue/docs/outgoing.txt9
17 files changed, 629 insertions, 257 deletions
diff --git a/mailman/app/membership.py b/mailman/app/membership.py
index 8a6e571a1..227a85003 100644
--- a/mailman/app/membership.py
+++ b/mailman/app/membership.py
@@ -23,10 +23,8 @@ __metaclass__ = type
__all__ = [
'add_member',
'delete_member',
- 'send_goodbye_message',
- 'send_welcome_message',
]
-
+
from email.utils import formataddr
@@ -34,6 +32,7 @@ from mailman import Errors
from mailman import Message
from mailman import Utils
from mailman import i18n
+from mailman.app.notifications import send_goodbye_message
from mailman.configuration import config
from mailman.interfaces import AlreadySubscribedError, DeliveryMode, MemberRole
@@ -41,31 +40,30 @@ _ = i18n._
-def add_member(mlist, address, realname, password, delivery_mode, language,
- ack=None, admin_notif=None, text=''):
+def add_member(mlist, address, realname, password, delivery_mode, language):
"""Add a member right now.
The member's subscription must be approved by whatever policy the list
enforces.
- ack is a flag that specifies whether the user should get an
- acknowledgement of their being subscribed. Default is to use the
- list's default flag value.
-
- admin_notif is a flag that specifies whether the list owner should get
- an acknowledgement of this subscription. Default is to use the list's
- default flag value.
+ :param mlist: the mailing list to add the member to
+ :type mlist: IMailingList
+ :param address: the address to subscribe
+ :type address: string
+ :param realname: the subscriber's full name
+ :type realname: string
+ :param password: the subscriber's password
+ :type password: string
+ :param delivery_mode: the delivery mode the subscriber has chosen
+ :type delivery_mode: DeliveryMode
+ :param language: the language that the subscriber is going to use
+ :type language: string
"""
- # Set up default flag values
- if ack is None:
- ack = mlist.send_welcome_msg
- if admin_notif is None:
- admin_notif = mlist.admin_notify_mchanges
# Let's be extra cautious.
Utils.ValidateEmail(address)
if mlist.members.get_member(address) is not None:
- raise AlreadySubscribedError(mlist.fqdn_listname, address,
- MemberRole.member)
+ raise AlreadySubscribedError(
+ mlist.fqdn_listname, address, MemberRole.member)
# Check for banned address here too for admin mass subscribes and
# confirmations.
pattern = Utils.get_pattern(address, mlist.ban_list)
@@ -110,53 +108,6 @@ def add_member(mlist, address, realname, password, delivery_mode, language,
member.preferences.delivery_mode = delivery_mode
## mlist.setMemberOption(email, config.Moderate,
## mlist.default_member_moderation)
- # Send notifications.
- if ack:
- send_welcome_message(mlist, address, language, delivery_mode, text)
- if admin_notif:
- with i18n.using_language(mlist.preferred_language):
- subject = _('$mlist.real_name subscription notification')
- if isinstance(realname, unicode):
- realname = realname.encode(Utils.GetCharSet(language), 'replace')
- text = Utils.maketext(
- 'adminsubscribeack.txt',
- {'listname' : mlist.real_name,
- 'member' : formataddr((realname, address)),
- }, mlist=mlist)
- msg = Message.OwnerNotification(mlist, subject, text)
- msg.send(mlist)
-
-
-
-def send_welcome_message(mlist, address, language, delivery_mode, text=''):
- if mlist.welcome_msg:
- welcome = Utils.wrap(mlist.welcome_msg) + '\n'
- else:
- welcome = ''
- # Find the IMember object which is subscribed to the mailing list, because
- # from there, we can get the member's options url.
- member = mlist.members.get_member(address)
- options_url = member.options_url
- # Get the text from the template.
- text += Utils.maketext(
- 'subscribeack.txt', {
- 'real_name' : mlist.real_name,
- 'posting_address' : mlist.fqdn_listname,
- 'listinfo_url' : mlist.script_url('listinfo'),
- 'optionsurl' : options_url,
- 'request_address' : mlist.request_address,
- 'welcome' : welcome,
- }, lang=language, mlist=mlist)
- if delivery_mode is not DeliveryMode.regular:
- digmode = _(' (Digest mode)')
- else:
- digmode = ''
- msg = Message.UserNotification(
- address, mlist.request_address,
- _('Welcome to the "$mlist.real_name" mailing list${digmode}'),
- text, language)
- msg['X-No-Archive'] = 'yes'
- msg.send(mlist, verp=config.VERP_PERSONALIZED_DELIVERIES)
@@ -184,16 +135,3 @@ def delete_member(mlist, address, admin_notif=None, userack=None):
}, mlist=mlist)
msg = Message.OwnerNotification(mlist, subject, text)
msg.send(mlist)
-
-
-
-def send_goodbye_message(mlist, address, language):
- if mlist.goodbye_msg:
- goodbye = Utils.wrap(mlist.goodbye_msg) + '\n'
- else:
- goodbye = ''
- msg = Message.UserNotification(
- address, mlist.bounces_address,
- _('You have been unsubscribed from the $mlist.real_name mailing list'),
- goodbye, language)
- msg.send(mlist, verp=config.VERP_PERSONALIZED_DELIVERIES)
diff --git a/mailman/app/moderator.py b/mailman/app/moderator.py
index 8b2db9ef7..6cd32c562 100644
--- a/mailman/app/moderator.py
+++ b/mailman/app/moderator.py
@@ -38,8 +38,11 @@ from mailman import Message
from mailman import Utils
from mailman import i18n
from mailman.app.membership import add_member, delete_member
+from mailman.app.notifications import (
+ send_admin_subscription_notice, send_welcome_message)
from mailman.configuration import config
from mailman.interfaces import Action, DeliveryMode, RequestType
+from mailman.interfaces.member import AlreadySubscribedError
from mailman.queue import Switchboard
_ = i18n._
@@ -237,13 +240,21 @@ def handle_subscription(mlist, id, action, comment=None):
delivery_mode = DeliveryMode(enum_value)
address = data['address']
realname = data['realname']
+ language = data['language']
+ password = data['password']
try:
- add_member(mlist, address, realname, data['password'],
- delivery_mode, data['language'])
- except Errors.AlreadySubscribedError:
+ add_member(mlist, address, realname, password,
+ delivery_mode, language)
+ except AlreadySubscribedError:
# The address got subscribed in some other way after the original
# request was made and accepted.
pass
+ else:
+ if mlist.send_welcome_msg:
+ send_welcome_message(mlist, address, language, delivery_mode)
+ if mlist.admin_notify_mchanges:
+ send_admin_subscription_notice(
+ mlist, address, realname, language)
slog.info('%s: new %s, %s %s', mlist.fqdn_listname,
delivery_mode, formataddr((realname, address)),
'via admin approval')
diff --git a/mailman/app/notifications.py b/mailman/app/notifications.py
new file mode 100644
index 000000000..74068c0c2
--- /dev/null
+++ b/mailman/app/notifications.py
@@ -0,0 +1,134 @@
+# Copyright (C) 2007-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.
+
+"""Sending notifications."""
+
+from __future__ import with_statement
+
+__metaclass__ = type
+__all__ = [
+ 'send_admin_subscription_notice',
+ 'send_goodbye_message',
+ 'send_welcome_message',
+ ]
+
+
+from email.utils import formataddr
+
+from mailman import Message
+from mailman import Utils
+from mailman import i18n
+from mailman.configuration import config
+from mailman.interfaces.member import DeliveryMode
+
+_ = i18n._
+
+
+
+def send_welcome_message(mlist, address, language, delivery_mode, text=''):
+ """Send a welcome message to a subscriber.
+
+ Prepending to the standard welcome message template is the mailing list's
+ welcome message, if there is one.
+
+ :param mlist: the mailing list
+ :type mlist: IMailingList
+ :param address: The address to respond to
+ :type address: string
+ :param language: the language of the response
+ :type language: string
+ :param delivery_mode: the type of delivery the subscriber is getting
+ :type delivery_mode: DeliveryMode
+ """
+ if mlist.welcome_msg:
+ welcome = Utils.wrap(mlist.welcome_msg) + '\n'
+ else:
+ welcome = ''
+ # Find the IMember object which is subscribed to the mailing list, because
+ # from there, we can get the member's options url.
+ member = mlist.members.get_member(address)
+ options_url = member.options_url
+ # Get the text from the template.
+ text += Utils.maketext(
+ 'subscribeack.txt', {
+ 'real_name' : mlist.real_name,
+ 'posting_address' : mlist.fqdn_listname,
+ 'listinfo_url' : mlist.script_url('listinfo'),
+ 'optionsurl' : options_url,
+ 'request_address' : mlist.request_address,
+ 'welcome' : welcome,
+ }, lang=language, mlist=mlist)
+ if delivery_mode is not DeliveryMode.regular:
+ digmode = _(' (Digest mode)')
+ else:
+ digmode = ''
+ msg = Message.UserNotification(
+ address, mlist.request_address,
+ _('Welcome to the "$mlist.real_name" mailing list${digmode}'),
+ text, language)
+ msg['X-No-Archive'] = 'yes'
+ msg.send(mlist, verp=config.VERP_PERSONALIZED_DELIVERIES)
+
+
+
+def send_goodbye_message(mlist, address, language):
+ """Send a goodbye message to a subscriber.
+
+ Prepending to the standard goodbye message template is the mailing list's
+ goodbye message, if there is one.
+
+ :param mlist: the mailing list
+ :type mlist: IMailingList
+ :param address: The address to respond to
+ :type address: string
+ :param language: the language of the response
+ :type language: string
+ """
+ if mlist.goodbye_msg:
+ goodbye = Utils.wrap(mlist.goodbye_msg) + '\n'
+ else:
+ goodbye = ''
+ msg = Message.UserNotification(
+ address, mlist.bounces_address,
+ _('You have been unsubscribed from the $mlist.real_name mailing list'),
+ goodbye, language)
+ msg.send(mlist, verp=config.VERP_PERSONALIZED_DELIVERIES)
+
+
+
+def send_admin_subscription_notice(mlist, address, full_name, language):
+ """Send the list administrators a subscription notice.
+
+ :param mlist: the mailing list
+ :type mlist: IMailingList
+ :param address: the address being subscribed
+ :type address: string
+ :param full_name: the name of the subscriber
+ :type full_name: string
+ :param language: the language of the address's realname
+ :type language: string
+ """
+ with i18n.using_language(mlist.preferred_language):
+ subject = _('$mlist.real_name subscription notification')
+ full_name = full_name.encode(Utils.GetCharSet(language), 'replace')
+ text = Utils.maketext(
+ 'adminsubscribeack.txt',
+ {'listname' : mlist.real_name,
+ 'member' : formataddr((full_name, address)),
+ }, mlist=mlist)
+ msg = Message.OwnerNotification(mlist, subject, text)
+ msg.send(mlist)
diff --git a/mailman/bin/set_members.py b/mailman/bin/set_members.py
index a97b13df8..4071627a7 100644
--- a/mailman/bin/set_members.py
+++ b/mailman/bin/set_members.py
@@ -25,6 +25,8 @@ from mailman import Utils
from mailman import i18n
from mailman import passwords
from mailman.app.membership import add_member
+from mailman.app.notifications import (
+ send_admin_subscription_notice, send_welcome_message)
from mailman.configuration import config
from mailman.initialize import initialize
from mailman.interfaces import DeliveryMode
@@ -176,6 +178,10 @@ def main():
add_member(mlist, address, real_name, password, delivery_mode,
mlist.preferred_language, send_welcome_msg,
admin_notify)
+ if send_welcome_msg:
+ send_welcome_message(mlist, address, language, delivery_mode)
+ if admin_notify:
+ send_admin_subscription_notice(mlist, address, real_name)
config.db.flush()
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..d9163fbb6
--- /dev/null
+++ b/mailman/commands/docs/join.txt
@@ -0,0 +1,80 @@
+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'test@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)
+ XXX
+
+Anne is not yet a member because she must confirm her subscription request
+first. Mailman has sent her the confirmation message.
+
+ >>> for message in smtpd.messages:
+ ... print message.as_string()
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..bfc3435e7
--- /dev/null
+++ b/mailman/commands/join.py
@@ -0,0 +1,193 @@
+# 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 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)
+
+
+
+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_mmode = self._parse_arguments(arguments)
+ if address is None:
+ realname, 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
+ password = MakeRandomPassword()
+ try:
+ validate_subscription(mlist, address)
+ confirm_subscription(mlist, address, realname, password,
+ delivery_mode, mlist_preferred_language)
+ except XXX:
+ pass
+ print >> results, self.name, address, \
+ (_('digest delivery') if digest else _('regular delivery'))
+ 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
+
+def ignore():
+ # 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.'))
+
+
+
+class Subscribe(Join):
+ """The email 'subscribe' command (an alias for 'join')."""
+
+ name = 'subscribe'
diff --git a/mailman/interfaces/command.py b/mailman/interfaces/command.py
index 553dcb0e3..5e294fe08 100644
--- a/mailman/interfaces/command.py
+++ b/mailman/interfaces/command.py
@@ -17,10 +17,18 @@
"""Interfaces defining email commands."""
+from munepy import Enum
from zope.interface import Interface, Attribute
+class ContinueProcessing(Enum):
+ """Should `IEmailCommand.process()` continue or not."""
+ no = 0
+ yes = 1
+
+
+
class IEmailResults(Interface):
"""The email command results object."""
@@ -45,6 +53,6 @@ class IEmailCommand(Interface):
:param msgdata: The message metadata.
:param arguments: The command arguments tuple.
:param results: An IEmailResults object for these commands.
- :return: True if further processing should be taken of the email
- commands in this message.
+ :return: A `ContinueProcessing` enum specifying whether to continue
+ processing or not.
"""
diff --git a/mailman/queue/command.py b/mailman/queue/command.py
index e9009809e..9c547184c 100644
--- a/mailman/queue/command.py
+++ b/mailman/queue/command.py
@@ -44,7 +44,7 @@ from mailman import Utils
from mailman.app.replybot import autorespond_to_sender
from mailman.configuration import config
from mailman.i18n import _
-from mailman.interfaces import IEmailResults
+from mailman.interfaces import ContinueProcessing, IEmailResults
from mailman.queue import Runner
NL = '\n'
@@ -179,7 +179,12 @@ class CommandRunner(Runner):
if command is None:
print >> results, _('No such command: $command_name')
else:
- command.process(mlist, msg, msgdata, arguments, results)
+ status = command.process(
+ mlist, msg, msgdata, arguments, results)
+ assert status in ContinueProcessing, (
+ 'Invalid status: %s' % status)
+ if status == ContinueProcessing.no:
+ break
# All done, send the response.
if len(finder.command_lines) > 0:
print >> results, _('\n- Unprocessed:')
diff --git a/mailman/queue/docs/command.txt b/mailman/queue/docs/command.txt
index c18e7a34c..470a632b7 100644
--- a/mailman/queue/docs/command.txt
+++ b/mailman/queue/docs/command.txt
@@ -104,3 +104,68 @@ message is plain text.
<BLANKLINE>
- Done.
<BLANKLINE>
+
+
+Stopping command processing
+---------------------------
+
+The 'end' command stops email processing, so that nothing following is looked
+at by the command queue.
+
+ >>> msg = message_from_string("""\
+ ... From: cperson@example.com
+ ... To: test-request@example.com
+ ... Message-ID: <caribou>
+ ...
+ ... echo foo bar
+ ... end ignored
+ ... echo baz qux
+ ... """)
+
+ >>> inject_message(mlist, msg, qdir=config.CMDQUEUE_DIR)
+ >>> command.run()
+ >>> len(virgin_queue.files)
+ 1
+ >>> item = get_queue_messages(virgin_queue)[0]
+ >>> print item.msg.as_string()
+ Subject: The results of your email commands
+ ...
+ <BLANKLINE>
+ - Results:
+ echo foo bar
+ <BLANKLINE>
+ - Unprocessed:
+ echo baz qux
+ <BLANKLINE>
+ - Done.
+ <BLANKLINE>
+
+The 'stop' command is an alias for 'end'.
+
+ >>> msg = message_from_string("""\
+ ... From: cperson@example.com
+ ... To: test-request@example.com
+ ... Message-ID: <caribou>
+ ...
+ ... echo foo bar
+ ... stop ignored
+ ... echo baz qux
+ ... """)
+
+ >>> inject_message(mlist, msg, qdir=config.CMDQUEUE_DIR)
+ >>> command.run()
+ >>> len(virgin_queue.files)
+ 1
+ >>> item = get_queue_messages(virgin_queue)[0]
+ >>> print item.msg.as_string()
+ Subject: The results of your email commands
+ ...
+ <BLANKLINE>
+ - Results:
+ echo foo bar
+ <BLANKLINE>
+ - Unprocessed:
+ echo baz qux
+ <BLANKLINE>
+ - Done.
+ <BLANKLINE>
diff --git a/mailman/queue/docs/outgoing.txt b/mailman/queue/docs/outgoing.txt
index cfb6c6988..edc806d47 100644
--- a/mailman/queue/docs/outgoing.txt
+++ b/mailman/queue/docs/outgoing.txt
@@ -17,14 +17,11 @@ move messages to the 'retry queue' for handling delivery failures.
>>> from mailman.app.membership import add_member
>>> from mailman.interfaces import DeliveryMode
>>> add_member(mlist, u'aperson@example.com', u'Anne Person',
- ... u'password', DeliveryMode.regular, u'en',
- ... ack=False, admin_notif=False)
+ ... u'password', DeliveryMode.regular, u'en')
>>> add_member(mlist, u'bperson@example.com', u'Bart Person',
- ... u'password', DeliveryMode.regular, u'en',
- ... ack=False, admin_notif=False)
+ ... u'password', DeliveryMode.regular, u'en')
>>> add_member(mlist, u'cperson@example.com', u'Cris Person',
- ... u'password', DeliveryMode.regular, u'en',
- ... ack=False, admin_notif=False)
+ ... u'password', DeliveryMode.regular, u'en')
By setting the mailing list to personalize messages, each recipient will get a
unique copy of the message, with certain headers tailored for that recipient.