summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/commands/cli_inject.py13
-rw-r--r--src/mailman/commands/cli_members.py21
-rw-r--r--src/mailman/commands/docs/members.txt37
-rw-r--r--src/mailman/config/schema.cfg15
-rw-r--r--src/mailman/mta/connection.py5
-rw-r--r--src/mailman/mta/docs/connection.txt41
6 files changed, 121 insertions, 11 deletions
diff --git a/src/mailman/commands/cli_inject.py b/src/mailman/commands/cli_inject.py
index 025dcb036..8b7570e0a 100644
--- a/src/mailman/commands/cli_inject.py
+++ b/src/mailman/commands/cli_inject.py
@@ -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()
diff --git a/src/mailman/commands/cli_members.py b/src/mailman/commands/cli_members.py
index b26216785..213bb7a2c 100644
--- a/src/mailman/commands/cli_members.py
+++ b/src/mailman/commands/cli_members.py
@@ -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()
diff --git a/src/mailman/commands/docs/members.txt b/src/mailman/commands/docs/members.txt
index 9872b9ff0..aea76dad8 100644
--- a/src/mailman/commands/docs/members.txt
+++ b/src/mailman/commands/docs/members.txt
@@ -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']
diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg
index 00df3849b..36e736345 100644
--- a/src/mailman/config/schema.cfg
+++ b/src/mailman/config/schema.cfg
@@ -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.
diff --git a/src/mailman/mta/connection.py b/src/mailman/mta/connection.py
index 105d25afb..36af7a06e 100644
--- a/src/mailman/mta/connection.py
+++ b/src/mailman/mta/connection.py
@@ -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:
diff --git a/src/mailman/mta/docs/connection.txt b/src/mailman/mta/docs/connection.txt
index 8924826c8..c79120eba 100644
--- a/src/mailman/mta/docs/connection.txt
+++ b/src/mailman/mta/docs/connection.txt
@@ -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')