Commit f7cbf566 authored by Barry Warsaw's avatar Barry Warsaw

* Fix a test based on updated output.

* Add a stub for the -confirm email command
* Add stubs for -leave and -unsubscribe
* Remove the crufty (and broken) 'tojoin' 'toleave' and 'toconfirm' metadata
  keys for synchronizing between lmtp and the command runner.  Replace this by
  putting the subaddress recognized by lmtp into the metadata and having the
  command runner look at the subaddress.
parent af33fabf
......@@ -63,7 +63,7 @@ Starting the daemons prints a useful message and starts the master qrunner
watcher process in the background.
>>> start.process(args)
Starting Mailman's master qrunner
Starting Mailman's master queue runner
>>> import errno, os, time
>>> from datetime import timedelta, datetime
......@@ -104,7 +104,7 @@ stops all the child processes too.
>>> from mailman.commands.cli_control import Stop
>>> stop = Stop()
>>> stop.process(args)
Shutting down Mailman's master qrunner
Shutting down Mailman's master queue runner
>>> def bury_master():
... until = timedelta(seconds=10) + datetime.now()
......
# Copyright (C) 2009 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman 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 3 of the License, or (at your option)
# any later version.
#
# GNU Mailman 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
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
"""Module stuff."""
from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
'Confirm',
]
from zope.interface import implements
from mailman.core.i18n import _
from mailman.interfaces.command import ContinueProcessing, IEmailCommand
class Confirm:
"""The email 'confirm' command."""
implements(IEmailCommand)
name = 'confirm'
argument_description = ''
description = ''
def process(self, mlist, msg, msgdata, arguments, results):
"""See `IEmailCommand`."""
print >> results, _('Confirmed')
return ContinueProcessing.yes
......@@ -17,10 +17,14 @@
"""The email commands 'join' and 'subscribe'."""
from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
'Join',
'Subscribe',
'Leave',
'Unsubscribe',
]
......@@ -38,6 +42,7 @@ from mailman.interfaces.registrar import IRegistrar
class Join:
"""The email 'join' command."""
implements(IEmailCommand)
name = 'join'
......@@ -125,3 +130,26 @@ class Subscribe(Join):
"""The email 'subscribe' command (an alias for 'join')."""
name = 'subscribe'
class Leave:
"""The email 'leave' command."""
implements(IEmailCommand)
name = 'leave'
argument_description = ''
description = ''
def process(self, mlist, msg, msgdata, arguments, results):
"""See `IEmailCommand`."""
person = msg['from']
print >> results, _('$person left $mlist.fqdn_listname')
return ContinueProcessing.yes
class Unsubscribe(Leave):
"""The email 'unsubscribe' command (an alias for 'leave')."""
name = 'unsubscribe'
......@@ -60,11 +60,12 @@ class CommandFinder:
# commands. For example, if this was sent to the -join or -leave
# addresses, it's the same as if 'join' or 'leave' commands were sent
# to the -request address.
if msgdata.get('tojoin'):
subaddress = msgdata.get('subaddress')
if subaddress == 'join':
self.command_lines.append('join')
elif msgdata.get('toleave'):
elif subaddress == 'leave':
self.command_lines.append('leave')
elif msgdata.get('toconfirm'):
elif subaddress == 'confirm':
mo = re.match(config.mta.verp_confirm_regexp, msg.get('to', ''))
if mo:
self.command_lines.append('confirm ' + mo.group('cookie'))
......
......@@ -108,6 +108,105 @@ message is plain text.
<BLANKLINE>
Implicit commands
=================
For some commands, specifically for joining and leaving a mailing list, there
are email aliases that act like commands, even when there's nothing else in
the Subject or body. For example, to join a mailing list, a user need only
email the -join address or -subscribe address (the latter is deprecated).
Because Dirk has never registered with Mailman before, he gets two responses.
The first is a confirmation message so that Dirk can validate his email
address, and the other is the results of his email command.
>>> msg = message_from_string("""\
... From: Dirk Person <[email protected]>
... To: [email protected]
...
... """)
>>> inject_message(mlist, msg, switchboard='command', subaddress='join')
>>> command.run()
>>> len(virgin_queue.files)
2
>>> def sortkey(item):
... return item.msg['subject']
>>> messages = sorted(get_queue_messages('virgin'), key=sortkey)
>>> for item in messages:
... print 'Subject:', item.msg['subject']
Subject: confirm ...
Subject: The results of your email commands
Similarly, to leave a mailing list, the user need only email the -leave or
-unsubscribe address (the latter is deprecated).
>>> msg = message_from_string("""\
... From: [email protected]
... To: [email protected]
...
... """)
>>> inject_message(mlist, msg, switchboard='command', subaddress='leave')
>>> command.run()
>>> len(virgin_queue.files)
1
>>> item = get_queue_messages('virgin')[0]
>>> print item.msg.as_string()
Subject: The results of your email commands
From: [email protected]
To: [email protected]
...
<BLANKLINE>
The results of your email command are provided below.
<BLANKLINE>
- Original message details:
From: [email protected]
Subject: n/a
Date: ...
Message-ID: ...
<BLANKLINE>
- Results:
[email protected] left [email protected]
<BLANKLINE>
- Done.
<BLANKLINE>
The -confirm address is also available as an implicit command.
>>> msg = message_from_string("""\
... From: [email protected]
... To: [email protected]
...
... """)
>>> inject_message(mlist, msg, switchboard='command', subaddress='confirm')
>>> command.run()
>>> len(virgin_queue.files)
1
>>> item = get_queue_messages('virgin')[0]
>>> print item.msg.as_string()
Subject: The results of your email commands
From: [email protected]
To: [email protected]
...
<BLANKLINE>
The results of your email command are provided below.
<BLANKLINE>
- Original message details:
From: [email protected]
Subject: n/a
Date: ...
Message-ID: ...
<BLANKLINE>
- Results:
Confirmed
<BLANKLINE>
- Done.
<BLANKLINE>
Stopping command processing
===========================
......
......@@ -61,6 +61,25 @@ Once the mailing list is created, the posting address is valid.
... """)
{}
>>> from mailman.testing.helpers import get_queue_messages
>>> messages = get_queue_messages('in')
>>> len(messages)
1
>>> print messages[0].msg.as_string()
From: [email protected]
To: [email protected]
Subject: An interesting message
Message-ID: <badger>
X-MailFrom: [email protected]
<BLANKLINE>
This is an interesting message.
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : [email protected]
original_size: ...
to_list : True
version : ...
Sub-addresses
=============
......@@ -98,6 +117,193 @@ But the message is accepted if posted to a valid sub-address.
{}
Request subaddress
------------------
Depending on the subaddress, there is a message in the appropriate queue for
later processing. For example, all -request messages are put into the command
queue for processing.
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> print messages[0].msg.as_string()
From: [email protected]
To: [email protected]
Subject: Help
Message-ID: <dog>
X-MailFrom: [email protected]
<BLANKLINE>
Please help me.
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : [email protected]
original_size: ...
subaddress : request
version : ...
Bounce processor
----------------
A message to the -bounces address goes to the bounce processor.
>>> lmtp.sendmail(
... '[email protected]',
... ['[email protected]'], """\
... From: [email protected]
... To: [email protected]
... Subject: A bounce
... Message-ID: <elephant>
...
... Bouncy bouncy.
... """)
{}
>>> messages = get_queue_messages('bounces')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : [email protected]
original_size: ...
subaddress : bounces
version : ...
Command processor
-----------------
Confirmation messages go to the command processor...
>>> lmtp.sendmail(
... '[email protected]',
... ['[email protected]'], """\
... From: [email protected]
... To: [email protected]
... Subject: A bounce
... Message-ID: <falcon>
...
... confirm 123
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : [email protected]
original_size: ...
subaddress : confirm
version : ...
...as do join messages...
>>> lmtp.sendmail(
... '[email protected]',
... ['[email protected]'], """\
... From: [email protected]
... To: [email protected]
... Message-ID: <giraffe>
...
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : [email protected]
original_size: ...
subaddress : join
version : ...
>>> lmtp.sendmail(
... '[email protected]',
... ['[email protected]'], """\
... From: [email protected]
... To: [email protected]
... Message-ID: <hippopotamus>
...
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : [email protected]
original_size: ...
subaddress : join
version : ...
...and leave messages.
>>> lmtp.sendmail(
... '[email protected]',
... ['[email protected]'], """\
... From: [email protected]
... To: [email protected]
... Message-ID: <iguana>
...
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : [email protected]
original_size: ...
subaddress : leave
version : ...
>>> lmtp.sendmail(
... '[email protected]',
... ['[email protected]'], """\
... From: [email protected]
... To: [email protected]
... Message-ID: <jackal>
...
... """)
{}
>>> messages = get_queue_messages('command')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
listname : [email protected]
original_size: ...
subaddress : leave
version : ...
Incoming processor
------------------
Messages to the -owner address go to the incoming processor.
>>> lmtp.sendmail(
... '[email protected]',
... ['[email protected]'], """\
... From: [email protected]
... To: [email protected]
... Message-ID: <kangaroo>
...
... """)
{}
>>> messages = get_queue_messages('in')
>>> len(messages)
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
envsender : [email protected]
listname : [email protected]
original_size: ...
subaddress : owner
to_owner : True
version : ...
Clean up
========
......
......@@ -50,10 +50,29 @@ qlog = logging.getLogger('mailman.qrunner')
# We only care about the listname and the sub-addresses as in [email protected] or
# [email protected]
SUBADDRESS_NAMES = (
'bounces', 'confirm', 'join', ' leave',
'owner', 'request', 'subscribe', 'unsubscribe',
# [email protected] This maps user visible subaddress names (which may
# include aliases) to the internal canonical subaddress name.
SUBADDRESS_NAMES = dict(
admin='bounces',
bounces='bounces',
confirm='confirm',
join='join',
leave='leave',
owner='owner',
request='request',
subscribe='join',
unsubscribe='leave',
)
# This maps subaddress canonical name to the destination queue that handles
# messages sent to that subaddress.
SUBADDRESS_QUEUES = dict(
bounces='bounces',
confirm='command',
join='command',
leave='command',
owner='in',
request='command',
)
DASH = '-'
......@@ -136,7 +155,6 @@ class LMTPRunner(Runner, smtpd.SMTPServer):
# Refresh the list of list names every time we process a message
# since the set of mailing lists could have changed.
listnames = set(getUtility(IListManager).names)
qlog.debug('listnames: %s', listnames)
# Parse the message data. If there are any defects in the
# message, reject it right away; it's probably spam.
msg = email.message_from_string(data, Message)
......@@ -144,6 +162,7 @@ class LMTPRunner(Runner, smtpd.SMTPServer):
if msg.defects:
return ERR_501
msg['X-MailFrom'] = mailfrom
message_id = msg['message-id']
except Exception, e:
elog.exception('LMTP message parsing')
config.db.abort()
......@@ -158,48 +177,45 @@ class LMTPRunner(Runner, smtpd.SMTPServer):
try:
to = parseaddr(to)[1].lower()
listname, subaddress, domain = split_recipient(to)
qlog.debug('to: %s, list: %s, sub: %s, dom: %s',
to, listname, subaddress, domain)
qlog.debug('%s to: %s, list: %s, sub: %s, dom: %s',
message_id, to, listname, subaddress, domain)
listname += '@' + domain
if listname not in listnames:
status.append(ERR_550)
continue
# The recipient is a valid mailing list; see if it's a valid
# sub-address, and if so, enqueue it.
# The recipient is a valid mailing list. Find the subaddress
# if there is one, and set things up to enqueue to the proper
# queue runner.
queue = None
msgdata = dict(listname=listname,
original_size=msg.original_size)
if subaddress in ('bounces', 'admin'):
queue = 'bounce'
elif subaddress == 'confirm':
msgdata['to_confirm'] = True
queue = 'command'
elif subaddress in ('join', 'subscribe'):
msgdata['to_join'] = True
queue = 'command'
elif subaddress in ('leave', 'unsubscribe'):
msgdata['to_leave'] = True
queue = 'command'
elif subaddress == 'owner':
msgdata.update(dict(
to_owner=True,
envsender=config.mailman.site_owner,
))
queue = 'in'
elif subaddress is None:
canonical_subaddress = SUBADDRESS_NAMES.get(subaddress)
queue = SUBADDRESS_QUEUES.get(canonical_subaddress)
if subaddress is None:
# The message is destined for the mailing list.
msgdata['to_list'] = True
queue = 'in'
elif subaddress == 'request':
msgdata['to_request'] = True
queue = 'command'
else:
elog.error('Unknown sub-address: %s', subaddress)
elif canonical_subaddress is None:
# The subaddress was bogus.
elog.error('%s unknown sub-address: %s',
message_id, subaddress)
status.append(ERR_550)
continue
# If we found a valid subaddress, enqueue the message and add
else:
# A valid subaddress.
msgdata['subaddress'] = canonical_subaddress
if canonical_subaddress == 'owner':
msgdata.update(dict(
to_owner=True,
envsender=config.mailman.site_owner,
))
queue = 'in'
# If we found a valid destination, enqueue the message and add
# a success status for this recipient.
if queue is not None:
config.switchboards[queue].enqueue(msg, msgdata)
qlog.debug('%s subaddress: %s, queue: %s',
message_id, canonical_subaddress, queue)
status.append('250 Ok')
except Exception, e:
elog.exception('Queue detection: %s', msg['message-id'])
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment