Take into account spamassassin spam report

This commit is contained in:
Son NK 2020-03-30 22:05:31 +02:00
parent 33a80236d3
commit 9500cc6cee
4 changed files with 164 additions and 1 deletions

View File

@ -365,6 +365,21 @@ def get_orig_message_from_bounce(msg: Message) -> Message:
return part
def get_orig_message_from_spamassassin_report(msg: Message) -> Message:
"""parse the original email from Spamassassin report"""
i = 0
for part in msg.walk():
i += 1
# the original message is the 4th part
# 1st part is the root part, multipart/report
# 2nd is text/plain, SpamAssassin part
# 3rd is the original message in message/rfc822 content type
# 4th is original message
if i == 4:
return part
def new_addr(old_addr, new_email, user: User) -> str:
"""replace First Last <first@example.com> by
first@example.com by SimpleLogin <new_email>
@ -399,3 +414,21 @@ def get_addrs_from_header(msg: Message, header) -> [str]:
# do not return empty string
return [r for r in ret if r]
def get_spam_info(msg: Message) -> (bool, str):
"""parse SpamAssassin header to detect whether a message is classified as spam.
Return (is spam, spam status detail)
The header format is
```X-Spam-Status: No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,
DKIM_VALID_AU,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_H2,SPF_PASS,
URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2```
"""
spamassassin_status = msg["X-Spam-Status"]
if not spamassassin_status:
return False, ""
# yes or no
spamassassin_answer = spamassassin_status[: spamassassin_status.find(",")]
return spamassassin_answer.lower() == "yes", spamassassin_status

View File

@ -68,6 +68,8 @@ from app.email_utils import (
delete_all_headers_except,
new_addr,
get_addrs_from_header,
get_spam_info,
get_orig_message_from_spamassassin_report,
)
from app.extensions import db
from app.log import LOG
@ -424,12 +426,25 @@ def handle_forward(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, s
LOG.d("Forward from %s to %s, nothing to do", envelope.mail_from, mailbox_email)
return False, "550 SL ignored"
contact = get_or_create_contact(msg["From"], alias)
spam_check = True
# create PGP email if needed
if mailbox.pgp_finger_print and user.is_premium():
LOG.d("Encrypt message using mailbox %s", mailbox)
msg = prepare_pgp_message(msg, mailbox.pgp_finger_print)
contact = get_or_create_contact(msg["From"], alias)
# no need to spam check for encrypted message
spam_check = False
if spam_check:
is_spam, spam_status = get_spam_info(msg)
if is_spam:
LOG.warning("Email detected as spam. Alias: %s, from: %s", alias, contact)
handle_spam(contact, alias, msg, user, mailbox_email, spam_status)
return False, "550 SL ignored"
forward_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id)
if alias.enabled:
@ -750,6 +765,81 @@ def handle_bounce(
)
def handle_spam(
contact: Contact,
alias: Alias,
msg: Message,
user: User,
mailbox_email: str,
spam_status: str,
):
email_log: EmailLog = EmailLog.create(
contact_id=contact.id,
user_id=contact.user_id,
is_spam=True,
spam_status=spam_status,
)
db.session.commit()
# Store the report & original email
orig_msg = get_orig_message_from_spamassassin_report(msg)
# generate a name for the email
random_name = str(uuid.uuid4())
full_report_path = f"refused-emails/full-{random_name}.eml"
s3.upload_email_from_bytesio(full_report_path, BytesIO(msg.as_bytes()), random_name)
file_path = None
if orig_msg:
file_path = f"refused-emails/{random_name}.eml"
s3.upload_email_from_bytesio(
file_path, BytesIO(orig_msg.as_bytes()), random_name
)
refused_email = RefusedEmail.create(
path=file_path, full_report_path=full_report_path, user_id=user.id
)
db.session.flush()
email_log.refused_email_id = refused_email.id
db.session.commit()
LOG.d("Create spam email %s", refused_email)
refused_email_url = URL + f"/dashboard/refused_email?highlight_id=" + str(email_log.id)
disable_alias_link = f"{URL}/dashboard/unsubscribe/{alias.id}"
# inform user
LOG.d(
"Inform user %s about spam email sent by %s to alias %s",
user,
contact.website_from,
alias.email,
)
send_email(
mailbox_email,
f"Email from {contact.website_from} to {alias.email} is detected as spam",
render(
"transactional/spam-email.txt",
name=user.name,
alias=alias,
website_from=contact.website_from,
website_email=contact.website_email,
disable_alias_link=disable_alias_link,
refused_email_url=refused_email_url,
),
render(
"transactional/spam-email.html",
name=user.name,
alias=alias,
website_from=contact.website_from,
website_email=contact.website_email,
disable_alias_link=disable_alias_link,
refused_email_url=refused_email_url,
),
)
def handle_unsubscribe(envelope):
message_data = envelope.content.decode("utf8", errors="replace")
msg = Parser(policy=SMTPUTF8).parsestr(message_data)

View File

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block content %}
{{ render_text("Hi " + name) }}
{{ render_text("An email sent to your alias <b>" + alias.email + "</b> from <b>" + website_email + "</b> is detected as spam by our Spam Detection Engine (SpamAssassin).") }}
{{ render_text('In most of the cases, the email will be refused by your email provider.') }}
{{ render_button("View the email", refused_email_url) }}
{{ render_text('The email is automatically deleted in 7 days.') }}
{{ render_text('Your alias <b>' + alias.email + '</b> is probably in the hands of a spammer now. In this case, you should disable or delete the alias immediately.') }}
{{ render_button("Disable alias", disable_alias_link) }}
{{ render_text('Please let us know if you have any question by replying to this email.') }}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
{{ raw_url(disable_alias_link) }}
{% endblock %}

View File

@ -0,0 +1,19 @@
Hi {{name}}
An email sent to your alias {{alias.email}} from {{website_from}} is detected as spam by our Spam Detection Engine (SpamAssassin).
In most of the cases, the email will be refused by your email provider.
You can view this email here:
{{ refused_email_url }}
The email is automatically deleted in 7 days.
Your alias {{alias}} is probably in the hands of a spammer now. In this case, you should disable or delete the alias immediately.
{{disable_alias_link}}
Please let us know if you have any question by replying to this email.
Best,
SimpleLogin team.