Take into account spamassassin spam report
This commit is contained in:
parent
33a80236d3
commit
9500cc6cee
|
@ -365,6 +365,21 @@ def get_orig_message_from_bounce(msg: Message) -> Message:
|
||||||
return part
|
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:
|
def new_addr(old_addr, new_email, user: User) -> str:
|
||||||
"""replace First Last <first@example.com> by
|
"""replace First Last <first@example.com> by
|
||||||
first@example.com by SimpleLogin <new_email>
|
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
|
# do not return empty string
|
||||||
return [r for r in ret if r]
|
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
|
||||||
|
|
|
@ -68,6 +68,8 @@ from app.email_utils import (
|
||||||
delete_all_headers_except,
|
delete_all_headers_except,
|
||||||
new_addr,
|
new_addr,
|
||||||
get_addrs_from_header,
|
get_addrs_from_header,
|
||||||
|
get_spam_info,
|
||||||
|
get_orig_message_from_spamassassin_report,
|
||||||
)
|
)
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
from app.log import LOG
|
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)
|
LOG.d("Forward from %s to %s, nothing to do", envelope.mail_from, mailbox_email)
|
||||||
return False, "550 SL ignored"
|
return False, "550 SL ignored"
|
||||||
|
|
||||||
|
contact = get_or_create_contact(msg["From"], alias)
|
||||||
|
|
||||||
|
spam_check = True
|
||||||
|
|
||||||
# create PGP email if needed
|
# create PGP email if needed
|
||||||
if mailbox.pgp_finger_print and user.is_premium():
|
if mailbox.pgp_finger_print and user.is_premium():
|
||||||
LOG.d("Encrypt message using mailbox %s", mailbox)
|
LOG.d("Encrypt message using mailbox %s", mailbox)
|
||||||
msg = prepare_pgp_message(msg, mailbox.pgp_finger_print)
|
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)
|
forward_log = EmailLog.create(contact_id=contact.id, user_id=contact.user_id)
|
||||||
|
|
||||||
if alias.enabled:
|
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):
|
def handle_unsubscribe(envelope):
|
||||||
message_data = envelope.content.decode("utf8", errors="replace")
|
message_data = envelope.content.decode("utf8", errors="replace")
|
||||||
msg = Parser(policy=SMTPUTF8).parsestr(message_data)
|
msg = Parser(policy=SMTPUTF8).parsestr(message_data)
|
||||||
|
|
|
@ -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 %}
|
|
@ -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.
|
Loading…
Reference in New Issue