Merge lp:~mbp/launchpad/881237-dkim-error into lp:launchpad

Proposed by Martin Pool
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 14199
Proposed branch: lp:~mbp/launchpad/881237-dkim-error
Merge into: lp:launchpad
Diff against target: 68 lines (+27/-7)
2 files modified
lib/lp/services/mail/incoming.py (+11/-7)
lib/lp/services/mail/tests/test_dkim.py (+16/-0)
To merge this branch: bzr merge lp:~mbp/launchpad/881237-dkim-error
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+80413@code.launchpad.net

Commit message

[r=wgrant][bug=881237] Unexpected errors from dkim.verify log a warning but don't drop the mail

Description of the change

pydkim can sometimes raise eg a KeyError when given badly formatted data from the network.

This puts a firewall in incoming.py so that those errors are treated as an operational warning, but they don't cause the mail to be dropped.

We should also probably land an update to pydkim that will handle these things more gracefully within itself.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/services/mail/incoming.py'
2--- lib/lp/services/mail/incoming.py 2011-10-25 05:10:28 +0000
3+++ lib/lp/services/mail/incoming.py 2011-10-26 02:36:24 +0000
4@@ -131,26 +131,30 @@
5 signed_message['From'],
6 signed_message['Sender']))
7 signing_details = []
8+ dkim_result = False
9 try:
10- # NB: if this fails with a keyword argument error, you need the
11- # python-dkim 0.3-3.2 that adds it
12 dkim_result = dkim.verify(
13 signed_message.parsed_string, dkim_log, details=signing_details)
14 except dkim.DKIMException, e:
15 log.warning('DKIM error: %r' % (e,))
16- dkim_result = False
17 except dns.resolver.NXDOMAIN, e:
18 # This can easily happen just through bad input data, ie claiming to
19 # be signed by a domain with no visible key of that name. It's not an
20 # operational error.
21 log.info('DNS exception: %r' % (e,))
22- dkim_result = False
23 except dns.exception.DNSException, e:
24 # many of them have lame messages, thus %r
25 log.warning('DNS exception: %r' % (e,))
26- dkim_result = False
27- else:
28- log.info('DKIM verification result=%s' % (dkim_result,))
29+ except Exception, e:
30+ # DKIM leaks some errors when it gets bad input, as in bug 881237. We
31+ # don't generally want them to cause the mail to be dropped entirely
32+ # though. It probably is reasonable to treat them as potential
33+ # operational errors, at least until they're handled properly, by
34+ # making pydkim itself more defensive.
35+ log.warning(
36+ 'unexpected error in DKIM verification, treating as unsigned: %r'
37+ % (e,))
38+ log.info('DKIM verification result: trusted=%s' % (dkim_result,))
39 log.debug('DKIM debug log: %s' % (dkim_log.getvalue(),))
40 if not dkim_result:
41 return None
42
43=== modified file 'lib/lp/services/mail/tests/test_dkim.py'
44--- lib/lp/services/mail/tests/test_dkim.py 2011-09-26 06:30:07 +0000
45+++ lib/lp/services/mail/tests/test_dkim.py 2011-10-26 02:36:24 +0000
46@@ -119,6 +119,22 @@
47 if l.find(substring) == -1:
48 self.fail("didn't find %r in log: %s" % (substring, l))
49
50+ def test_dkim_broken_pubkey(self):
51+ """Handle a subtly-broken pubkey like qq.com, see bug 881237.
52+
53+ The message is not trusted but inbound message processing does not
54+ abort either.
55+ """
56+ signed_message = self.fake_signing(plain_content)
57+ self._dns_responses['example._domainkey.canonical.com.'] = \
58+ sample_dns.replace(';', '')
59+ principal = authenticateEmail(
60+ signed_message_from_string(signed_message))
61+ self.assertWeaklyAuthenticated(principal, signed_message)
62+ self.assertEqual(principal.person.preferredemail.email,
63+ 'foo.bar@canonical.com')
64+ self.assertDkimLogContains('unexpected error in DKIM verification')
65+
66 def test_dkim_garbage_pubkey(self):
67 signed_message = self.fake_signing(plain_content)
68 self._dns_responses['example._domainkey.canonical.com.'] = \