From ab911fd55eeecaa7c4c7168fe4bed1ea208c5673 Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Tue, 25 Aug 2020 12:51:05 +0200 Subject: [PATCH] do not forward cycle email: email sent to alias from its mailbox --- app/config.py | 3 ++ email_handler.py | 45 +++++++++++++++++++ .../emails/transactional/cycle-email.html | 30 +++++++++++++ .../emails/transactional/cycle-email.txt | 16 +++++++ 4 files changed, 94 insertions(+) create mode 100644 templates/emails/transactional/cycle-email.html create mode 100644 templates/emails/transactional/cycle-email.txt diff --git a/app/config.py b/app/config.py index 75499b25..79636057 100644 --- a/app/config.py +++ b/app/config.py @@ -289,6 +289,9 @@ ALERT_BOUNCE_EMAIL = "bounce" # When a forwarding email is detected as 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" # <<<<< END ALERT EMAIL >>>> diff --git a/email_handler.py b/email_handler.py index 500e4065..f5ebc5ef 100644 --- a/email_handler.py +++ b/email_handler.py @@ -71,6 +71,7 @@ from app.config import ( SPAMASSASSIN_HOST, MAX_SPAM_SCORE, MAX_REPLY_PHASE_SPAM_SCORE, + ALERT_SEND_EMAIL_CYCLE, ) from app.email_utils import ( send_email, @@ -116,6 +117,7 @@ _MAILBOX_ID_HEADER = "X-SimpleLogin-Mailbox-ID" _EMAIL_LOG_ID_HEADER = "X-SimpleLogin-EmailLog-ID" _MESSAGE_ID = "Message-ID" + # fix the database connection leak issue # use this method instead of create_app def new_app(): @@ -374,6 +376,41 @@ def prepare_pgp_message(orig_msg: Message, pgp_fingerprint: str): 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( envelope, smtp: SMTP, msg: Message, rcpt_to: 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) 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) email_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id) db.session.commit() diff --git a/templates/emails/transactional/cycle-email.html b/templates/emails/transactional/cycle-email.html new file mode 100644 index 00000000..3ac3183b --- /dev/null +++ b/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 {{ alias.email }} from its mailbox + {{ mailbox.email }}: + the email would be forwarded to the same mailbox {{ mailbox.email }}. + + {% endcall %} + + {% call text() %} + This creates a cycle 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,
SimpleLogin Team.') }} +{% endblock %} diff --git a/templates/emails/transactional/cycle-email.txt b/templates/emails/transactional/cycle-email.txt new file mode 100644 index 00000000..0860c051 --- /dev/null +++ b/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. \ No newline at end of file