From 0f0a9845fce00d4f194b4f3ae3b745c883e42df7 Mon Sep 17 00:00:00 2001 From: Florent de Labarre Date: Fri, 27 Oct 2023 14:25:15 +0200 Subject: [PATCH] [FIX] mail: bounce incoming email with catchall email and other unroutable emails How to reproduce - Send an email to catchall@exemple.com, random@exemple.com (Note : random@exemple.com is an email witch does not exist) --> Issue the email is not bounced To justify the change, this was actually the behavior a while ago, prior to odoo-dev@68a457e The intent of that commit was to consider other possible routes, so this commit doesn't contradict the change. Task-3714565 closes odoo/odoo#161782 X-original-commit: df0c05afbf81ce41c9b1b4cfe93476d4ca397749 Signed-off-by: Thibault Delavallee (tde) --- addons/mail/models/mail_thread.py | 28 +++++++++++++++++++-- addons/test_mail/tests/test_mail_gateway.py | 16 ++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index ff6a0c89da2..b34bdc7aa2b 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1023,12 +1023,21 @@ class MailThread(models.AbstractModel): @api.model def _detect_write_to_catchall(self, msg_dict): """Return True if directly contacts catchall.""" - catchall_aliases = self.env['mail.alias.domain'].search([]).mapped('catchall_email') + # Note: tweaked in stable to avoid doing two times same search due to bugfix + # (see odoo/odoo#161782), to clean when reaching master + if self.env.context.get("mail_catchall_aliases"): + catchall_aliases = self.env.context["mail_catchall_aliases"] + else: + catchall_aliases = self.env['mail.alias.domain'].search([]).mapped('catchall_email') + email_to_list = [ tools.email_normalize(e) or e for e in (tools.email_split(msg_dict['to']) or ['']) ] - # check it does not directly contact catchall + # check it does not directly contact catchall; either (legacy) strict aka + # all TOs belong are catchall, either (optional) any catchall in all TOs + if self.env.context.get("mail_catchall_write_any_to"): + return catchall_aliases and any(email_to in catchall_aliases for email_to in email_to_list) return ( catchall_aliases and email_to_list and all(email_to in catchall_aliases for email_to in email_to_list) @@ -1166,6 +1175,9 @@ class MailThread(models.AbstractModel): return [] # 2. Handle new incoming email by checking aliases and applying their settings + # prefetch catchall aliases as they are used several times + catchall_aliases = self.env['mail.alias.domain'].search([]).mapped('catchall_email') + self = self.with_context(mail_catchall_aliases=catchall_aliases) if rcpt_tos_list: # no route found for a matching reference (or reply), so parent is invalid message_dict.pop('parent_id', None) @@ -1213,6 +1225,18 @@ class MailThread(models.AbstractModel): email_from, message_dict['to'], message_id, fallback_model, thread_id, custom_values, user_id) return [route] + # 4. Recipients contain catchall and unroutable emails -> bounce + if rcpt_tos_list and self.with_context(mail_catchall_write_any_to=True)._detect_write_to_catchall(message_dict): + _logger.info( + 'Routing mail from %s to %s with Message-Id %s: write to catchall + other unroutable emails, bounce', + email_from, message_dict['to'], message_id + ) + body = self.env['ir.qweb']._render('mail.mail_bounce_catchall', { + 'message': message, + }) + self._routing_create_bounce_email(email_from, body, message, references=message_id, reply_to=self.env.company.email) + return [] + # ValueError if no routes found and if no bounce occurred raise ValueError( 'No possible route found for incoming message from %s to %s (Message-Id %s:). ' diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index bc16c9296fe..18db9e02af6 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -1076,6 +1076,22 @@ class TestMailgateway(MailCommon): # No bounce email self.assertNotSentEmail() + @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') + def test_message_route_write_to_catchall_other_recipients_invalid(self): + """ Writing to catchall and other unroutable recipients should bounce. """ + # Test: no group created, email bounced + with self.mock_mail_gateway(): + record = self.format_and_process( + MAIL_TEMPLATE, self.partner_1.email_formatted, + f'"My Super Catchall" <{self.alias_catchall}@{self.alias_domain}>, Unroutable ', + subject='Should Bounce') + self.assertFalse(record) + self.assertSentEmail( + self.mailer_daemon_email, + ['whatever-2a840@postmaster.twitter.com'], + subject='Re: Should Bounce' + ) + @mute_logger('odoo.addons.mail.models.mail_thread') def test_message_process_bounce_alias(self): """ Writing to bounce alias is considered as a bounce even if not multipart/report bounce structure """