Explorar el Código

do not forward cycle email: email sent to alias from its mailbox

Son NK hace 4 años
padre
commit
ab911fd55e

+ 3 - 0
app/config.py

@@ -289,6 +289,9 @@ ALERT_BOUNCE_EMAIL = "bounce"
 # When a forwarding email is detected as spam
 # When a forwarding email is detected as spam
 ALERT_SPAM_EMAIL = "spam"
 ALERT_SPAM_EMAIL = "spam"
 
 
+# When an email is sent from a mailbox to an alias - a cycle
+ALERT_SEND_EMAIL_CYCLE = "cycle"
+
 ALERT_SPF = "spf"
 ALERT_SPF = "spf"
 
 
 # <<<<< END ALERT EMAIL >>>>
 # <<<<< END ALERT EMAIL >>>>

+ 45 - 0
email_handler.py

@@ -71,6 +71,7 @@ from app.config import (
     SPAMASSASSIN_HOST,
     SPAMASSASSIN_HOST,
     MAX_SPAM_SCORE,
     MAX_SPAM_SCORE,
     MAX_REPLY_PHASE_SPAM_SCORE,
     MAX_REPLY_PHASE_SPAM_SCORE,
+    ALERT_SEND_EMAIL_CYCLE,
 )
 )
 from app.email_utils import (
 from app.email_utils import (
     send_email,
     send_email,
@@ -116,6 +117,7 @@ _MAILBOX_ID_HEADER = "X-SimpleLogin-Mailbox-ID"
 _EMAIL_LOG_ID_HEADER = "X-SimpleLogin-EmailLog-ID"
 _EMAIL_LOG_ID_HEADER = "X-SimpleLogin-EmailLog-ID"
 _MESSAGE_ID = "Message-ID"
 _MESSAGE_ID = "Message-ID"
 
 
+
 # fix the database connection leak issue
 # fix the database connection leak issue
 # use this method instead of create_app
 # use this method instead of create_app
 def new_app():
 def new_app():
@@ -374,6 +376,41 @@ def prepare_pgp_message(orig_msg: Message, pgp_fingerprint: str):
     return msg
     return msg
 
 
 
 
+def handle_email_sent_to_ourself(alias, mailbox, msg: Message, user):
+    # store the refused email
+    random_name = str(uuid.uuid4())
+    full_report_path = f"refused-emails/cycle-{random_name}.eml"
+    s3.upload_email_from_bytesio(full_report_path, BytesIO(msg.as_bytes()), random_name)
+    refused_email = RefusedEmail.create(
+        path=None, full_report_path=full_report_path, user_id=alias.user_id
+    )
+    db.session.commit()
+    LOG.d("Create refused email %s", refused_email)
+    # link available for 6 days as it gets deleted in 7 days
+    refused_email_url = refused_email.get_url(expires_in=518400)
+
+    send_email_with_rate_control(
+        user,
+        ALERT_SEND_EMAIL_CYCLE,
+        mailbox.email,
+        f"Warning: email sent from {mailbox.email} to {alias.email} forms a cycle",
+        render(
+            "transactional/cycle-email.txt",
+            name=user.name or "",
+            alias=alias,
+            mailbox=mailbox,
+            refused_email_url=refused_email_url,
+        ),
+        render(
+            "transactional/cycle-email.html",
+            name=user.name or "",
+            alias=alias,
+            mailbox=mailbox,
+            refused_email_url=refused_email_url,
+        ),
+    )
+
+
 async def handle_forward(
 async def handle_forward(
     envelope, smtp: SMTP, msg: Message, rcpt_to: str
     envelope, smtp: SMTP, msg: Message, rcpt_to: str
 ) -> List[Tuple[bool, str]]:
 ) -> List[Tuple[bool, str]]:
@@ -390,6 +427,14 @@ async def handle_forward(
             LOG.d("alias %s cannot be created on-the-fly, return 550", address)
             LOG.d("alias %s cannot be created on-the-fly, return 550", address)
             return [(False, "550 SL E3 Email not exist")]
             return [(False, "550 SL E3 Email not exist")]
 
 
+    mail_from = envelope.mail_from.lower().strip()
+    for mb in alias.mailboxes:
+        # email send from a mailbox to alias
+        if mb.email.lower().strip() == mail_from:
+            LOG.exception("cycle email sent from %s to %s", mb, alias)
+            handle_email_sent_to_ourself(alias, mb, msg, alias.user)
+            return [(True, "250 Message accepted for delivery")]
+
     contact = get_or_create_contact(msg["From"], envelope.mail_from, alias)
     contact = get_or_create_contact(msg["From"], envelope.mail_from, alias)
     email_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id)
     email_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id)
     db.session.commit()
     db.session.commit()

+ 30 - 0
templates/emails/transactional/cycle-email.html

@@ -0,0 +1,30 @@
+{% extends "base.html" %}
+
+{% block content %}
+  {% call text() %}
+    Hi {{ name }}
+  {% endcall %}
+
+  {% call text() %}
+    SimpleLogin has blocked an email that was sent to your alias <b>{{ alias.email }}</b> from its mailbox
+    <b>{{ mailbox.email }}</b>:
+    the email would be forwarded to the same mailbox <b>{{ mailbox.email }}</b>.
+
+  {% endcall %}
+
+  {% call text() %}
+    This creates a <b>cycle</b> and the email will probably be refused or hidden by your email service anyway.
+  {% endcall %}
+
+  {{ render_button("View the refused email", refused_email_url) }}
+
+  {% call text() %}
+    The email is automatically deleted in 7 days.
+  {% endcall %}
+
+  {% call text() %}
+    Please let us know if you have any question.
+  {% endcall %}
+
+  {{ render_text('Thanks, <br />SimpleLogin Team.') }}
+{% endblock %}

+ 16 - 0
templates/emails/transactional/cycle-email.txt

@@ -0,0 +1,16 @@
+Hi {{name}}
+
+SimpleLogin has blocked an email that was sent to your alias {{alias.email}} from its mailbox {{ mailbox.email }}:
+    the email would be forwarded to the same mailbox {{ mailbox.email }}.
+
+This creates a *cycle* and the email will probably be refused or hidden by your email service anyway.
+
+You can view this email here:
+{{ refused_email_url }}
+
+The email is automatically deleted in 7 days.
+
+Please let us know if you have any question.
+
+Best,
+SimpleLogin team.