Commit a02c5203 authored by Abhilash Raj's avatar Abhilash Raj

Merge branch 'mr-upstream-515' into 'master'

ARC Support for Mailman

See merge request !518
parents c4b6f5d1 5940e817
Pipeline #66095405 passed with stages
in 19 minutes and 36 seconds
......@@ -107,7 +107,9 @@ case second 'm'. Any other spelling is incorrect.""",
'aiosmtpd>=1.1',
'alembic',
'atpublic',
'click>=7.0',
'authheaders>=0.9.2',
'authres>=1.0.1',
'click>=7.0.0',
'dnspython>=1.14.0',
'falcon>1.0.0',
'flufl.bounce',
......
......@@ -16,7 +16,7 @@ To get a list of all key-value pairs of any section, you need to call the
command without any options.
>>> command('mailman conf')
[antispam] header_checks:
[ARC] authserv_id:
...
[logging.bounce] level: info
...
......
......@@ -71,6 +71,22 @@ MAILMAN_CFG_TEMPLATE = """\
# recipient: your.address@your.domain"""
@public
class ARC:
"""ARC represents the parameters required for validation of arc."""
# This is a perfect candidate for dataclass when we are ready to support
# Python 3.7+. This class is a namespace to store the ARC related params in
# a single namespace.
authserv_id = None
trusted_authserv_ids = None
private_key = None
arc_headers = None
selector = None
domain = None
dkim_enabled = False
dmarc_enabled = False
@public
@implementer(IConfiguration)
class Configuration:
......@@ -93,6 +109,9 @@ class Configuration:
self.plugins = {}
self.password_context = None
self.db = None
# ARC parameters.
self.arc_enabled = False
self.arc = ARC()
def _clear(self):
"""Clear the cached configuration variables."""
......@@ -139,6 +158,7 @@ class Configuration:
# Expand and set up all directories.
self._expand_paths()
self.ensure_directories_exist()
self._update_arc_parameters()
notify(ConfigurationUpdatedEvent(self))
def _expand_paths(self):
......@@ -295,6 +315,55 @@ class Configuration:
"""Iterate over all the language configuration sections."""
yield from self._config.getByCategory('language', [])
def _update_arc_parameters(self):
"""Update ARC related parameters by loading them from new config."""
self.arc_enabled = self.ARC.enabled.strip().lower() == 'yes'
# Load the private key to sign the messages.
if self.arc_enabled:
ids = self.ARC.trusted_authserv_ids.split(',')
ids.append(self.ARC.authserv_id)
self.arc.trusted_authserv_ids = [x.strip() for x in ids]
# The list of headers we need to sign for ARC.
self.arc.headers = [header.strip().lower().encode()
for header in self.ARC.sig_headers.split(',')]
# Make sure the "From" header is included in arc_headers because we
# are going to fail when trying to sign a message with a
# DKIM-exception. It is better to fail early.
if b'from' not in self.arc.headers:
print('[ARC] "sig_headers" do not include "From" header.',
file=sys.stderr)
sys.exit(1)
# Make sure the private signing key is configured if ARC is
# enabled.
if self.ARC.privkey.strip() == '':
print('[ARC] "enabled" but "privkey" is not configured.',
file=sys.stderr)
sys.exit(1)
try:
# Open the private key in ascii encoding to make sure it
# doesn't include any non-ascii characters.
with open(self.ARC.privkey, encoding='ascii') as fd:
arc_private_key = fd.read()
except OSError as e:
print('[ARC] "privkey" is unreadable: ', str(e),
file=sys.stderr)
sys.exit(1)
except UnicodeDecodeError:
print('[ARC] "privkey" contains non-ascii characters.',
file=sys.stderr)
sys.exit(1)
self.arc.private_key = arc_private_key.encode()
self.arc.domain = self.ARC.domain.encode()
self.arc.selector = self.ARC.selector.encode()
self.arc.authserv_id = self.ARC.authserv_id
self.arc.dkim_enabled = self.ARC.dkim == 'yes'
self.arc.dmarc_enabled = self.ARC.dmarc == 'yes'
else:
# Reset arc params.
self.arc = ARC()
@public
def load_external(path):
......
......@@ -175,6 +175,8 @@ data_dir: $var_dir/data
cache_dir: $var_dir/cache
# Directory for configuration files and such.
etc_dir: $var_dir/etc
# Directory containing Mailman plugins.
ext_dir: $var_dir/ext
# Directory where the default IMessageStore puts its messages.
messages_dir: $var_dir/messages
# Directory for archive backends to store their messages in. Archivers should
......@@ -602,6 +604,84 @@ description: Chinese (Taiwan)
charset: utf-8
enabled: yes
[ARC]
# This section defines email authetication parameters, in particular, with
# respect to the ARC(Authenticated-Recieved-Chain) protocol. See
# http://arc-spec.org/ for reference.
#
# The DMARC protocol is the industry standard for cryptographically validating
# both the content and originating source of email. However it is regularly
# the case that mailing lists break this source of authentication via modifying
# the From, and possibly other headers, and altering the contents of
# emails by, say, adding a common footer to outgoing mail.
# The ARC protocol is the industry standard for rectify this.
# ARC cryptographically seals the outgoing emails by adding a collection
# of headers. These headers act quite analagously to a chain of DKIM
# signatures, where each intermediary validates the ARC signature(if one exists)
# of the incomming message, and then appends its own collection of header fields.
# Enabling this protocol makes it possible for email service providers
# to validate the content & originator of an email, even if it has taken multiple
# steps from the originator to the recipient.
#
# The general implementation of ARC within Mailman is addition of two
# additional handlers to the pipeline. One, ate the very beginning of the
# pipeline cryptographically validaties the incomming ARC headers before the
# message has been modified, and appends its results to the
# Authentication-Results header.
#
# The second handler is at the end of the pipeline. It cryptographically
# signs the message, with all modifications that have been made, along
# with the analysis of the validation handler, and adds its output as
# a new set of ARC header fields.
# This flag globally enables ARC signing & validation. To enable, set this to
# yes.
enabled: no
# DKIM & DMARC authentication checks on incoming email is critical to using ARC
# successfully. Mailman can do these check on its own, but if you already perform
# these checks earlier in your pipeline, say via a milter previous to Mailman,
# they can be used instead, as long as you specify your domain as a trusted
# domain below. If those checks are not placed in an Authentication-Results
# header from a trusted domain they will be ignored.
dmarc: yes
dkim: yes
# TRUSTED DOMAINS
#
# This is the domain name of your mailserver. Necessary to set correctly.
# authserv_id: your_domain.com
authserv_id:
# This list should include all additional domains
# that you manage that may be handling your incoming mail
# Only necessary to update if there are local domains or subdomains
# that are performing DKIM, DMARC, or SPF checks.
# trusted_authserv_ids: subdomain.your_domain.com, trusted_other_domain.com
trusted_authserv_ids:
# KEY MANAGEMENT
#
# In order for your server to be able to cryptographical sign its messages
# a DKIM public/private key pair will need to be created.
# See: http://www.gettingemaildelivered.com/dkim-explained-how-to-set-up-and-use-domainkeys-identified-mail-effectively
# for reference. The following parameters let the software find your
# private & public keys
# privkey: /some/location/yourprivatekey.pem
# selector: test
# domain: your_domain.com
privkey:
selector:
domain:
# This configures the headers that will be cryptographically signed
# This list is what is recommended by the DKIM & ARC specifications.
# Inclusion of the From header is mandatory.
sig_headers: From, Sender, Reply-To, Subject, Date, Message-ID, To, Cc, MIME-Version, Content-Type, Content-Transfer-Encoding, Content-ID, Content-Description, Resent-Date, Resent-From, Resent-Sender, Resent-To, Resent-Cc, Resent-Message-ID, In-Reply-To, References, List-Id, List-Help, List-Unsubscribe, List-Subscribe, List-Post, List-Owner, List-Archive
[antispam]
# This section defines basic antispam detection settings.
......
......@@ -18,6 +18,7 @@
"""Test the system-wide global configuration."""
import os
import tempfile
import unittest
from contextlib import ExitStack
......@@ -142,3 +143,131 @@ log_dir: $nopath/log_dir
cm = resources.enter_context(self.assertRaises(SystemExit))
config.load(fp.name)
self.assertEqual(cm.exception.args, (1,))
class TestARCParameterValidation(unittest.TestCase):
"""Test ARCSigning Exceptions."""
layer = ConfigLayer
def setUp(self):
privkey = b"""-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQi
Y/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqM
KrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB
AoGAH0cxOhFZDgzXWhDhnAJDw5s4roOXN4OhjiXa8W7Y3rhX3FJqmJSPuC8N9vQm
6SVbaLAE4SG5mLMueHlh4KXffEpuLEiNp9Ss3O4YfLiQpbRqE7Tm5SxKjvvQoZZe
zHorimOaChRL2it47iuWxzxSiRMv4c+j70GiWdxXnxe4UoECQQDzJB/0U58W7RZy
6enGVj2kWF732CoWFZWzi1FicudrBFoy63QwcowpoCazKtvZGMNlPWnC7x/6o8Gc
uSe0ga2xAkEA8C7PipPm1/1fTRQvj1o/dDmZp243044ZNyxjg+/OPN0oWCbXIGxy
WvmZbXriOWoSALJTjExEgraHEgnXssuk7QJBALl5ICsYMu6hMxO73gnfNayNgPxd
WFV6Z7ULnKyV7HSVYF0hgYOHjeYe9gaMtiJYoo0zGN+L3AAtNP9huqkWlzECQE1a
licIeVlo1e+qJ6Mgqr0Q7Aa7falZ448ccbSFYEPD6oFxiOl9Y9se9iYHZKKfIcst
o7DUw1/hz2Ck4N5JrgUCQQCyKveNvjzkkd8HjYs0SwM0fPjK16//5qDZ2UiDGnOe
uEzxBDAr518Z8VFbR41in3W4Y3yCDgQlLlcETrS+zYcL
-----END RSA PRIVATE KEY-----
"""
self.keyfile = tempfile.NamedTemporaryFile(delete=True)
self.keyfile.write(privkey)
self.keyfile.flush()
def tearDown(self):
self.keyfile.close()
def test_arc_enabled_but_missing_privkey(self):
# Missing private key when ARC is enabled.
config = Configuration()
with ExitStack() as resources:
fp = resources.enter_context(
NamedTemporaryFile('w', encoding='utf-8'))
print("""\
[ARC]
enabled: yes
authserv_id: lists.example.org
selector: dummy
domain: example.org
sig_headers: mime-version, date, from, to, subject
privkey:
""", file=fp)
fp.flush()
# Suppress warning messages in the test output. Also, make sure
# that the config.load() call doesn't break global state.
resources.enter_context(mock.patch('sys.stderr'))
resources.enter_context(mock.patch.object(config, '_clear'))
resources.enter_context(self.assertRaises(SystemExit))
config.load(fp.name)
def test_arc_enabled_but_wrong_file(self):
# Unreadable private key when ARC is enabled.
config = Configuration()
with ExitStack() as resources:
fp = resources.enter_context(
NamedTemporaryFile('w', encoding='utf-8'))
print("""\
[ARC]
enabled: yes
authserv_id: lists.example.org
selector: dummy
domain: example.org
sig_headers: mime-version, date, from, to, subject
privkey: /missing/location.pem
""", file=fp)
fp.flush()
# Suppress warning messages in the test output. Also, make sure
# that the config.load() call doesn't break global state.
resources.enter_context(mock.patch('sys.stderr'))
resources.enter_context(mock.patch.object(config, '_clear'))
resources.enter_context(self.assertRaises(SystemExit))
config.load(fp.name)
def test_arc_sign_non_ascii_privkey(self):
# Private Key contains non-ascii characters.
config = Configuration()
uni_keyfile = tempfile.NamedTemporaryFile(delete=True)
uni_keyfile.write("¢¢¢¢¢¢¢".encode('utf-8'))
uni_keyfile.flush()
with ExitStack() as resources:
fp = resources.enter_context(
NamedTemporaryFile('w', encoding='utf-8'))
print("""\
[ARC]
enabled: yes
authserv_id: lists.example.org
selector: dummy
domain: example.org
sig_headers: mime-version, date, from, to, subject
privkey: {}
""".format(uni_keyfile.name), file=fp)
fp.flush()
# Suppress warning messages in the test output. Also, make sure
# that the config.load() call doesn't break global state.
resources.enter_context(mock.patch('sys.stderr'))
resources.enter_context(mock.patch.object(config, '_clear'))
resources.enter_context(self.assertRaises(SystemExit))
config.load(fp.name)
def test_arc_missing_from_in_headers(self):
# List of sig_headers should always include "From" header, otherwise,
# exception is raised when signing a message.
config = Configuration()
with ExitStack() as resources:
fp = resources.enter_context(
NamedTemporaryFile('w', encoding='utf-8'))
print("""\
[ARC]
enabled: yes
authserv_id: lists.example.org
selector: dummy
domain: example.org
sig_headers: to, subject, date
privkey: {}
""".format(self.keyfile.name), file=fp)
fp.flush()
# Suppress warning messages in the test output. Also, make sure
# that the config.load() call doesn't break global state.
resources.enter_context(mock.patch('sys.stderr'))
resources.enter_context(mock.patch.object(config, '_clear'))
resources.enter_context(self.assertRaises(SystemExit))
config.load(fp.name)
# Copyright (C) 2017 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/>.
"""Perform authentication checks and adds headers to outgoing message"""
import logging
from authheaders import sign_message
from dkim import DKIMException
from mailman.config import config
from mailman.core.i18n import _
from mailman.handlers.validate_authenticity import prepend_headers
from mailman.interfaces.handler import IHandler
from public import public
from zope.interface import implementer
# A manual override used by the test suite.
timestamp = None
log = logging.getLogger('mailman.error')
def sign(msg, msgdata):
"""ARC sign a message, and prepend the signature headers to the message."""
try:
# Since the underlying `sign_message` expects bytes for all the fields,
# we will encode all the parameters.
sig = sign_message(msg.as_bytes(),
config.arc.selector,
config.arc.domain,
config.arc.private_key,
config.arc.headers,
'ARC',
config.arc.authserv_id.encode(),
timestamp=timestamp,
standardize=('ARC-Standardize' in msgdata))
except DKIMException:
log.exception('Failed to sign message: %s', msg['Message-ID'])
raise
headers = [x.decode('utf-8').split(': ', 1) for x in sig]
prepend_headers(msg, headers)
@public
@implementer(IHandler)
class ARCSign:
"""Sign message and attach result headers."""
name = 'arc-sign'
description = _('Perform ARC auth checks and attach resulting headers')
def process(self, mlist, msg, msgdata):
"""See `IHandler`."""
if config.arc_enabled:
sign(msg, msgdata)
===========
ARC Signing
===========
It is highly recommended that Mailman maintainers configure ARC siging of their
outgoing email. ARC is the standard protocol for authenticating the content
and authenticity of indirect email flows. These are systems that are more
complex than a basic sender -> reciever flow. Mailing lists are a primary
example of this.
Configuration is handled in the [ARC] section of ``mailman.cfg``, and is mostly
a question of cryptographic key management. A public/private key pair should
be generated, and the various options configured. See
http://www.gettingemaildelivered.com/dkim-explained-how-to-set-up-and-use-domainkeys-identified-mail-effectively
for reference, as well as the additional documentaion about ARC configuration
in general in schema.cfg.
The private key should be secured locally and made readable to Mailman, and the
can be specified in ``mailman.cfg``::
[ARC]
privkey: /path/to/private.key
The public key should be put into a DNS TXT record, and located at:
#{config.ARC.selector}._domainkeys.#{config.ARC.domain}
--ex.--
test._domainkeys.example.com
The following is an example TXT record:
::
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyBwu6PiaDN87t3DVZ84zIrEhCoxtFuv7g52oCwAUXTDnXZ+0XHM/rhkm8XSGr1yLsDc1zLGX8IfITY1dL2CzptdgyiX7vgYjzZqG368C8BtGB5m6nj26NyhSKEdlV7MS9KbASd359ggCeGTT5QjRKEMSauVyVSeapq6ZcpZ9JwQIDAQAB"
The value of the above p= tag should be the public key from your pair.
Enabling signing will result in the addition of three ARC header fields to the
outgoing email, which will be evaluated by the receiver.
=====================
Validate Authenticity
=====================
Incoming messages have the ability to be authenticated via the DKIM and DMARC
protocols, which are used for ARC signing of messages. The results of these
authentication checks are added to an Authentication-Results header which is
added to the top of the message. If the most recent Authentication-Results
header is from a trusted domain, as specified in the configuration file, the
results of both sets of authentication checks are merged. ARC authentication is
enable and configured via the ``[ARC]`` section of ``mailman.cfg``. Further
documentation is included there.
# Copyright (C) 2011-2017 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/>.
"""Test the outgoing runner."""
import tempfile
import unittest
import mailman.handlers.arc_sign
from dkim import DKIMException
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.handlers.arc_sign import ARCSign
from mailman.testing.helpers import (
specialized_message_from_string as message_from_string)
from mailman.testing.layers import ConfigLayer
from unittest.mock import patch
class TestARCSignMessage(unittest.TestCase):
"""Test Authentication-Results generation."""
layer = ConfigLayer
def setUp(self):
privkey = b"""-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQi
Y/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqM
KrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB
AoGAH0cxOhFZDgzXWhDhnAJDw5s4roOXN4OhjiXa8W7Y3rhX3FJqmJSPuC8N9vQm
6SVbaLAE4SG5mLMueHlh4KXffEpuLEiNp9Ss3O4YfLiQpbRqE7Tm5SxKjvvQoZZe
zHorimOaChRL2it47iuWxzxSiRMv4c+j70GiWdxXnxe4UoECQQDzJB/0U58W7RZy
6enGVj2kWF732CoWFZWzi1FicudrBFoy63QwcowpoCazKtvZGMNlPWnC7x/6o8Gc
uSe0ga2xAkEA8C7PipPm1/1fTRQvj1o/dDmZp243044ZNyxjg+/OPN0oWCbXIGxy
WvmZbXriOWoSALJTjExEgraHEgnXssuk7QJBALl5ICsYMu6hMxO73gnfNayNgPxd
WFV6Z7ULnKyV7HSVYF0hgYOHjeYe9gaMtiJYoo0zGN+L3AAtNP9huqkWlzECQE1a
licIeVlo1e+qJ6Mgqr0Q7Aa7falZ448ccbSFYEPD6oFxiOl9Y9se9iYHZKKfIcst
o7DUw1/hz2Ck4N5JrgUCQQCyKveNvjzkkd8HjYs0SwM0fPjK16//5qDZ2UiDGnOe
uEzxBDAr518Z8VFbR41in3W4Y3yCDgQlLlcETrS+zYcL
-----END RSA PRIVATE KEY-----
"""
self.keyfile = tempfile.NamedTemporaryFile(delete=True)
self.keyfile.write(privkey)
self.keyfile.flush()
mailman.handlers.arc_sign.timestamp = "12345"
def tearDown(self):
self.keyfile.close()
def test_arc_sign_message(self):
config.push('arc_sign', """
[ARC]
enabled: yes
authserv_id: lists.example.org
selector: dummy
domain: example.org
sig_headers: mime-version, date, from, to, subject
privkey: %s
""" % (self.keyfile.name))
self.addCleanup(config.pop, 'arc_sign')
lst = create_list('test@example.com')
msgdata = {'ARC-Standardize': True}
msg = """Authentication-Results: lists.example.org; arc=none;
spf=pass smtp.mfrom=jqd@d1.example;
dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass
MIME-Version: 1.0
Return-Path: <jqd@d1.example.org>
Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)
Message-ID: <54B84785.1060301@d1.example.org>
Date: Thu, 14 Jan 2015 15:00:01 -0800
From: John Q Doe <jqd@d1.example.org>
To: arc@dmarc.org
Subject: Example 1
Hey gang,
This is a test message.
--J."""
msg = message_from_string(msg)
ARCSign().process(lst, msg, msgdata)
res = ["i=1;lists.example.org;arc=none;spf=passsmtp.mfrom=jqd@d1"
".example;dkim=pass(1024-bitkey)header.i=@d1.example;dmar"
"c=pass"]
self.assertEqual("".join(msg["ARC-Authentication-Results"].split()),
"".join(res))
sig = """a=rsa-sha256;
b=XWeK9DxQ8MUm+Me5GLZ5lQ3L49RdoFv7m7VlrAkKb3/C7jjw33TrTY0KYI5lkowvEGnAtm
5lAqLz67FxA/VrJc2JiYFQR/mBoJLLz/hh9y77byYmSO9tLfIDe2A83+6QsXHO3K6PxTz7+v
rCB4wHD9GADeUKVfHzmpZhFuYOa88=;
bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;
d=example.org; h=mime-version:date:from:to:subject;
i=1; s=dummy; t=12345"""
sig = set("".join(sig.split()).split(";"))
expected = "".join(msg["ARC-Message-Signature"].split()).split(";")
expected = set(expected)
self.assertEqual(sig, expected)
seal = "".join(["a=rsa-sha256;b=Pg8Yyk1AgYy2l+kb6iy+mY106AXm5EdgDwJ"
"hLP7+XyT6yaS38ZUho+bmgSDorV+LyARH4A967A/oWMX3coyC7"
"pAGyI+hA3+JifL7P3/aIVP4ooRJ/WUgT79snPuulxE15jg6FgQ"
"E68ObA1/hy77BxdbD9EQxFGNcr/wCKQoeKJ8=; cv=none; d="
"example.org; i=1; s=dummy; t=12345"])
seal = set("".join(seal.split()).split(";"))
expected = set("".join(msg["ARC-Seal"].split()).split(";"))
self.assertEqual(seal, expected)
# I *believe* that this test from Gene Shuman's PR is incorrect. As I
# read the currect draft
# https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-23#section-5.2
# the ARC Validator SHOULD have added an arc=none clause to the field,
# but its absence doesn't invalidate the Authentication-Results field.
# As far as I can see the draft says nothing about a missing arc method
# in the Authentication-Results field(s) used by the Signer.
# I suspect that the Signer might want to add a "missing arc" warning,
# but it's unclear what the syntax should be.
# Differences from previous test: no sig_headers in config, no arc=none
# in Authentication-Results.
@unittest.expectedFailure
def test_arc_sign_message_no_chain_validation(self):
config.push('arc_sign', """
[ARC]
enabled: yes
authserv_id: lists.example.org
selector: dummy
domain: example.org
privkey: %s
""" % (self.keyfile.name))
self.addCleanup(config.pop, 'arc_sign')
lst = create_list('test@example.com')
msgdata = {}
msg = """Authentication-Results: lists.example.org;
spf=pass smtp.mfrom=jqd@d1.example;
dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass
MIME-Version: 1.0
Return-Path: <jqd@d1.example.org>
Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)
Message-ID: <54B84785.1060301@d1.example.org>
Date: Thu, 14 Jan 2015 15:00:01 -0800
From: John Q Doe <jqd@d1.example.org>
To: arc@dmarc.org
Subject: Example 1
Hey gang,
This is a test message.
--J."""
msg = message_from_string(msg)
ARCSign().process(lst, msg, msgdata)
self.assertEqual("ARC-Authentication-Results" in msg, False)
self.assertEqual("ARC-Message-Signature" in msg, False)
self.assertEqual("ARC-Seal" in msg, False)
@patch('mailman.handlers.arc_sign.sign_message', side_effect=DKIMException)
def test_arc_sign_raises_exception(self, mocked_sign_message):
config.push('arc_sign', """
[ARC]
enabled: yes
authserv_id: test.example.org
selector: dummy
domain: example.org
privkey: %s
""" % (self.keyfile.name))
self.addCleanup(config.pop, 'arc_sign')
lst = create_list('test@example.com')
msgdata = {}
msg = """\
Message-ID: <54B84785.1060301@d1.example.org>
Date: Thu, 14 Jan 2015 15:00:01 -0800
From: John Q Doe <jqd@d1.example.org>
To: test@example.com
Subject: Example 1
Hey gang,
This is a test message.
--J."""
msg = message_from_string(msg)
with self.assertRaises(DKIMException):
ARCSign().process(lst, msg, msgdata)
self.assertTrue(mocked_sign_message.called)
This diff is collapsed.
# Copyright (C) 2017 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/>.
"""Perform origination & content authentication checks and add
an Authentication-Results header to the outgoing message"""
import logging
from authheaders import authenticate_message
from authres import AuthenticationResultsHeader
from dns.resolver import Timeout
from itertools import chain
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
from mailman.utilities.retry import retry
from public import public
from zope.interface import implementer
log = logging.getLogger('mailman.debug')