diff --git a/lacre/config.py b/lacre/config.py index f31b6e2..6d85115 100644 --- a/lacre/config.py +++ b/lacre/config.py @@ -143,6 +143,11 @@ def strict_mode(): return ("default" in cfg and cfg["default"]["enc_keymap_only"] == "yes") +def should_log_headers() -> bool: + """Check if Lacre should log message headers.""" + return flag_enabled('daemon', 'log_headers') + + class FromStrMixin: """Additional operations for configuration enums.""" diff --git a/lacre/daemon.py b/lacre/daemon.py index 7b56dbf..0eebc92 100644 --- a/lacre/daemon.py +++ b/lacre/daemon.py @@ -42,9 +42,6 @@ class MailEncryptionProxy: if message.defects: LOG.warning("Issues found: %d; %s", len(message.defects), repr(message.defects)) - if conf.flag_enabled('daemon', 'log_headers'): - LOG.info('Message headers: %s', self._extract_headers(message)) - send = xport.SendFrom(envelope.mail_from) for operation in gate.delivery_plan(envelope.rcpt_tos, message, keys): LOG.debug(f"Sending mail via {operation!r}") @@ -55,21 +52,22 @@ class MailEncryptionProxy: # If the message can't be encrypted, deliver cleartext. LOG.error('Unable to encrypt message, delivering in cleartext: %s', e) if not isinstance(operation, KeepIntact): - self._send_unencrypted(operation, message, envelope, send) + self._send_unencrypted(operation, envelope, send) else: LOG.exception('Cannot perform: %s', operation) raise except: LOG.exception('Unexpected exception caught, bouncing message') - return xport.RESULT_ERROR + if conf.should_log_headers(): + LOG.info('Message headers: %s', self._extract_headers(message)) + return xport.RESULT_ERRORR return xport.RESULT_OK - def _send_unencrypted(self, operation, message, envelope, send: xport.SendFrom): - keep = KeepIntact(operation.recipients()) - new_message = keep.perform(message) - send(new_message, operation.recipients()) + def _send_unencrypted(self, operation, envelope, send: xport.SendFrom): + # Do not parse and re-generate the message, just send it as it is. + send(envelope.original_content, operation.recipients()) def _beginning(self, e: Envelope) -> bytes: double_eol_pos = e.original_content.find(DOUBLE_EOL_BYTES) diff --git a/test/modules/test_contracts.py b/test/modules/test_contracts.py index ac14e1a..1823faf 100644 --- a/test/modules/test_contracts.py +++ b/test/modules/test_contracts.py @@ -27,7 +27,8 @@ documentation. import email import email.mime.multipart from email.message import EmailMessage -from email.policy import SMTP +from email.policy import SMTP, SMTPUTF8 +from email.errors import HeaderParseError import unittest from configparser import RawConfigParser @@ -165,6 +166,26 @@ class EmailParsingTest(unittest.TestCase): self.assertIsInstance(payload, str) self.assertTrue(message_boundary in payload) + def test_fail_if_message_id_parsing_is_fixed(self): + # Unfortunately, Microsoft sends messages with Message-Id header values + # that email parser can't process. + # + # Bug: https://github.com/python/cpython/issues/105802 + # Fix: https://github.com/python/cpython/pull/108133 + + rawmsg = b"From: alice@lacre.io\r\n" \ + + b"To: bob@lacre.io\r\n" \ + + b"Subject: Test message\r\n" \ + + b"Content-Type: text/plain\r\n" \ + + b"Content-Transfer-Encoding: base64\r\n" \ + + b"Message-Id: <[yada-yada-yada@microsoft.com]>\r\n" \ + + b"\r\n" \ + + b"SGVsbG8sIFdvcmxkIQo=\r\n" + + msg = email.message_from_bytes(rawmsg, policy=SMTPUTF8) + self.assertEqual(len(msg.defects), 0) + self.assertRaises(IndexError, lambda: msg['Message-Id']) + class EmailTest(unittest.TestCase): def test_boundary_generated_after_as_string_call(self):