Commit a26ed5b0 authored by Barry Warsaw's avatar Barry Warsaw

Add a development mode setting which changes the basic behavior of mailman.

The only thing it does currently is force the recipients in the low level
connection code to a hard-coded address.

Also:
* Fix the inject command's --filename/-f argument
* Make inject's LISTNAME required
* When inject reads from stdin, capture C-c and print a nicer message
* Extend the members command so that blank lines and lines starting with # are
  ignored.
* members command should not fail when an address is already subscribed.  Just
  warn and continue.
parent 79d9e36a
......@@ -60,13 +60,12 @@ class Inject:
help=_('Show a list of all available queue names and exit.'))
command_parser.add_argument(
'-f', '--filename',
type='string', help=_("""
type=unicode, help=_("""
Name of file containing the message to inject. If not given, or
'-' (without the quotes) standard input is used.
"""))
'-' (without the quotes) standard input is used."""))
# Required positional argument.
command_parser.add_argument(
'listname', metavar='LISTNAME', nargs='?',
'listname', metavar='LISTNAME', nargs=1,
help=_("""\
The 'fully qualified list name', i.e. the posting address of the
mailing list to inject the message into."""))
......@@ -97,7 +96,11 @@ class Inject:
self.parser.error(_('No such queue: $queue'))
return
if args.filename in (None, '-'):
message_text = sys.stdin.read()
try:
message_text = sys.stdin.read()
except KeyboardInterrupt:
print 'Interrupted'
sys.exit(1)
else:
with open(args.filename) as fp:
message_text = fp.read()
......
......@@ -37,7 +37,7 @@ from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.command import ICLISubCommand
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.member import DeliveryMode
from mailman.interfaces.member import AlreadySubscribedError, DeliveryMode
......@@ -53,8 +53,10 @@ class Members:
command_parser.add_argument(
'-a', '--add',
dest='filename',
help=_('Add all member addresses in FILENAME. FILENAME can be '
"'-' to indicate standard input."))
help=_("""\
Add all member addresses in FILENAME. FILENAME can be '-' to
indicate standard input. Blank lines and lines That start with a
'#' are ignored."""))
# Required positional argument.
command_parser.add_argument(
'listname', metavar='LISTNAME', nargs=1,
......@@ -78,14 +80,23 @@ class Members:
fp = codecs.open(args.filename, 'r', 'utf-8')
try:
for line in fp:
# Ignore blank lines and lines that start with a '#'.
if line.startswith('#') or len(line.strip()) == 0:
continue
real_name, email = parseaddr(line)
# If not given in the input data, parseaddr() will return the
# empty string, as opposed to the empty unicode. We need a
# unicode real name here.
if real_name == '':
real_name = u''
add_member(mlist, email, real_name, None,
DeliveryMode.regular, mlist.preferred_language.code)
try:
add_member(mlist, email, real_name, None,
DeliveryMode.regular,
mlist.preferred_language.code)
except AlreadySubscribedError:
# It's okay if the address is already subscribed, just
# print a warning and continue.
print 'Already subscribed (skipping):', email, real_name
finally:
if fp is not sys.stdin:
fp.close()
......
......@@ -57,3 +57,40 @@ taken from standard input.
>>> sorted(address.address for address in mlist.members.addresses)
[u'aperson@example.com', u'bperson@example.com', u'cperson@example.com',
u'dperson@example.com', u'eperson@example.com', u'fperson@example.com']
Blank lines and lines that begin with '#' are ignored.
>>> with open(path, 'w') as fp:
... for address in ('gperson@example.com',
... '# hperson@example.com',
... ' ',
... '',
... 'iperson@example.com',
... ):
... print >> fp, address
>>> args.filename = path
>>> command.process(args)
>>> sorted(address.address for address in mlist.members.addresses)
[u'aperson@example.com', u'bperson@example.com', u'cperson@example.com',
u'dperson@example.com', u'eperson@example.com', u'fperson@example.com',
u'gperson@example.com', u'iperson@example.com']
Addresses which are already subscribed are ignored, although a warning is
printed.
>>> with open(path, 'w') as fp:
... for address in ('gperson@example.com',
... 'aperson@example.com',
... 'jperson@example.com',
... ):
... print >> fp, address
>>> command.process(args)
Already subscribed (skipping): gperson@example.com
Already subscribed (skipping): aperson@example.com
>>> sorted(address.address for address in mlist.members.addresses)
[u'aperson@example.com', u'bperson@example.com', u'cperson@example.com',
u'dperson@example.com', u'eperson@example.com', u'fperson@example.com',
u'gperson@example.com', u'iperson@example.com', u'jperson@example.com']
......@@ -21,6 +21,13 @@
# options. See <https://launchpad.net/lazr.config> for details.
[mailman]
# Setting devmode to true enables certain safeguards and other behavior
# changes that make developing Mailman easier. For example, it forces the
# SMTP RCPT TO recipients to be a test address so that no messages are
# accidentally sent to real addresses. All devmode variables in other
# sections begin with 'devmode_' for easy searching.
devmode: false
# This address is the "site owner" address. Certain messages which must be
# delivered to a human, but which can't be delivered to a list owner (e.g. a
# bounce from a list owner), will be sent to this address. It should point to
......@@ -261,10 +268,16 @@ chain: hold
[mta]
# Set this to an address to force the SMTP RCPT TO recipents when devmode is
# enabled. This way messages can't be accidentally sent to real addresses.
devmode_recipient:
# The class defining the interface to the incoming mail transport agent.
incoming: mailman.mta.postfix.LMTP
# The class defining the interface to the outgoing mail transport agent.
# The callable implementing delivery to the outgoing mail transport agent.
# This must accept three arguments, the mailing list, the message, and the
# message metadata dictionary.
outgoing: mailman.mta.deliver.deliver
# How to connect to the outgoing MTA.
......
......@@ -28,6 +28,7 @@ __all__ = [
import logging
import smtplib
from lazr.config import as_boolean
from mailman.config import config
......@@ -66,6 +67,10 @@ class Connection:
def sendmail(self, envsender, recips, msgtext):
"""Mimic `smtplib.SMTP.sendmail`."""
if as_boolean(config.mailman.devmode):
# Force the recipients to the specified address, but still deliver
# to the same number of recipients.
recips = [config.mta.devmode_recipient] * len(recips)
if self._connection is None:
self._connect()
try:
......
......@@ -155,3 +155,44 @@ the server.
>>> smtpd.get_connection_count()
1
Development mode
================
By putting Mailman into development mode, you can force the recipients to a
given hard-coded address. This allows you to test Mailman without worrying
about accidental deliveries to unintended recipients.
>>> config.push('devmode', """
... [mailman]
... devmode: true
... [mta]
... devmode_recipient: zperson@example.com
... """)
>>> smtpd.clear()
>>> connection.sendmail(
... 'anne@example.com',
... ['bart@example.com', 'cate@example.com'], """\
... From: anne@example.com
... To: bart@example.com
... Subject: aardvarks
...
... """)
{}
>>> messages = list(smtpd.messages)
>>> len(messages)
1
>>> print messages[0].as_string()
From: anne@example.com
To: bart@example.com
Subject: aardvarks
X-Peer: ...
X-MailFrom: anne@example.com
X-RcptTo: zperson@example.com, zperson@example.com
<BLANKLINE>
<BLANKLINE>
>>> config.pop('devmode')
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