diff options
| author | Barry Warsaw | 2011-05-27 19:34:44 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2011-05-27 19:34:44 -0400 |
| commit | 5f93d80364aea9535c14f9f22c2fd7d02b8dd78d (patch) | |
| tree | b60e0c8dc70c195c9f0f97ea900d69065741d579 /src/mailman/bouncers/dsn.py | |
| parent | 7b7b63c34324efe4055c285106b3d7bf92dd322b (diff) | |
| download | mailman-5f93d80364aea9535c14f9f22c2fd7d02b8dd78d.tar.gz mailman-5f93d80364aea9535c14f9f22c2fd7d02b8dd78d.tar.zst mailman-5f93d80364aea9535c14f9f22c2fd7d02b8dd78d.zip | |
Diffstat (limited to 'src/mailman/bouncers/dsn.py')
| -rw-r--r-- | src/mailman/bouncers/dsn.py | 132 |
1 files changed, 57 insertions, 75 deletions
diff --git a/src/mailman/bouncers/dsn.py b/src/mailman/bouncers/dsn.py index 49803ef88..c9d7803ee 100644 --- a/src/mailman/bouncers/dsn.py +++ b/src/mailman/bouncers/dsn.py @@ -37,84 +37,66 @@ from mailman.interfaces.bounce import IBounceDetector, Stop -def check(msg): - # Iterate over each message/delivery-status subpart. - failed_addresses = [] - delayed_addresses = [] - for part in typed_subpart_iterator(msg, 'message', 'delivery-status'): - if not part.is_multipart(): - # Huh? - continue - # Each message/delivery-status contains a list of Message objects - # which are the header blocks. Iterate over those too. - for msgblock in part.get_payload(): - address_set = None - # We try to dig out the Original-Recipient (which is optional) and - # Final-Recipient (which is mandatory, but may not exactly match - # an address on our list). Some MTA's also use X-Actual-Recipient - # as a synonym for Original-Recipient, but some apparently use - # that for other purposes :( - # - # Also grok out Action so we can do something with that too. - action = msgblock.get('action', '').lower() - # Some MTAs have been observed that put comments on the action. - if action.startswith('delayed'): - address_set = delayed_addresses - elif action.startswith('fail'): - address_set = failed_addresses - else: - # Some non-permanent failure, so ignore this block. - continue - params = [] - foundp = False - for header in ('original-recipient', 'final-recipient'): - for k, v in msgblock.get_params([], header): - if k.lower() == 'rfc822': - foundp = True - else: - params.append(k) - if foundp: - # Note that params should already be unquoted. - address_set.extend(params) - break - else: - # MAS: This is a kludge, but SMTP-GATEWAY01.intra.home.dk - # has a final-recipient with an angle-addr and no - # address-type parameter at all. Non-compliant, but ... - for param in params: - if param.startswith('<') and param.endswith('>'): - address_set.append(param[1:-1]) - # There may be both delayed and failed addresses. If there are any failed - # addresses, return those, otherwise just stop processing. - if len(failed_addresses) == 0: - if len(delayed_addresses) == 0: - return set() - else: - return Stop - return set(parseaddr(address)[1] for address in failed_addresses - if address is not None) - - - class DSN: """Parse RFC 3464 (i.e. DSN) bounce formats.""" implements(IBounceDetector) def process(self, msg): - return check(msg) - ## # A DSN has been seen wrapped with a "legal disclaimer" by an outgoing - ## # MTA in a multipart/mixed outer part. - ## if msg.is_multipart() and msg.get_content_subtype() == 'mixed': - ## msg = msg.get_payload()[0] - ## # The above will suffice if the original message 'parts' were wrapped - ## # with the disclaimer added, but the original DSN can be wrapped as a - ## # message/rfc822 part. We need to test that too. - ## if msg.is_multipart() and msg.get_content_type() == 'message/rfc822': - ## msg = msg.get_payload()[0] - ## # The report-type parameter should be "delivery-status", but it seems - ## # that some DSN generating MTAs don't include this on the - ## # Content-Type: header, so let's relax the test a bit. - ## if not msg.is_multipart() or msg.get_content_subtype() <> 'report': - ## return set() - ## return check(msg) + """See `IBounceDetector`.""" + # Iterate over each message/delivery-status subpart. + failed_addresses = [] + delayed_addresses = [] + for part in typed_subpart_iterator(msg, 'message', 'delivery-status'): + if not part.is_multipart(): + # Huh? + continue + # Each message/delivery-status contains a list of Message objects + # which are the header blocks. Iterate over those too. + for msgblock in part.get_payload(): + address_set = None + # We try to dig out the Original-Recipient (which is optional) + # and Final-Recipient (which is mandatory, but may not exactly + # match an address on our list). Some MTA's also use + # X-Actual-Recipient as a synonym for Original-Recipient, but + # some apparently use that for other purposes :( + # + # Also grok out Action so we can do something with that too. + action = msgblock.get('action', '').lower() + # Some MTAs have been observed that put comments on the action. + if action.startswith('delayed'): + address_set = delayed_addresses + elif action.startswith('fail'): + address_set = failed_addresses + else: + # Some non-permanent failure, so ignore this block. + continue + params = [] + foundp = False + for header in ('original-recipient', 'final-recipient'): + for k, v in msgblock.get_params([], header): + if k.lower() == 'rfc822': + foundp = True + else: + params.append(k) + if foundp: + # Note that params should already be unquoted. + address_set.extend(params) + break + else: + # MAS: This is a kludge, but + # SMTP-GATEWAY01.intra.home.dk has a final-recipient + # with an angle-addr and no address-type parameter at + # all. Non-compliant, but ... + for param in params: + if param.startswith('<') and param.endswith('>'): + address_set.append(param[1:-1]) + # There may be both delayed and failed addresses. If there are any + # failed addresses, return those, otherwise just stop processing. + if len(failed_addresses) == 0: + if len(delayed_addresses) == 0: + return set() + else: + return Stop + return set(parseaddr(address)[1] for address in failed_addresses + if address is not None) |
