move spf_pass(), sl_sendmail() to email_utils.py
This commit is contained in:
parent
d1d81e6a6d
commit
11789559f1
|
@ -5,15 +5,17 @@ import os
|
|||
import quopri
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
from email.header import decode_header
|
||||
from email.message import Message
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import make_msgid, formatdate, parseaddr
|
||||
from smtplib import SMTP
|
||||
from smtplib import SMTP, SMTPServerDisconnected
|
||||
|
||||
import arrow
|
||||
import dkim
|
||||
import spf
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from validate_email import validate_email
|
||||
|
||||
|
@ -38,6 +40,8 @@ from app.config import (
|
|||
ALERT_DIRECTORY_DISABLED_ALIAS_CREATION,
|
||||
TRANSACTIONAL_BOUNCE_EMAIL,
|
||||
REDDIT_URL,
|
||||
ALERT_SPF,
|
||||
POSTFIX_PORT_FORWARD,
|
||||
)
|
||||
from app.dns_utils import get_mx_domains
|
||||
from app.extensions import db
|
||||
|
@ -59,6 +63,7 @@ from app.utils import (
|
|||
convert_to_alphanumeric,
|
||||
sanitize_email,
|
||||
)
|
||||
from email_handler import _IP_HEADER
|
||||
|
||||
|
||||
def render(template_name, **kwargs) -> str:
|
||||
|
@ -990,3 +995,120 @@ def should_disable(alias: Alias) -> bool:
|
|||
|
||||
def parse_id_from_bounce(email_address: str) -> int:
|
||||
return int(email_address[email_address.find("+") : email_address.rfind("+")])
|
||||
|
||||
|
||||
def spf_pass(
|
||||
ip: str,
|
||||
envelope,
|
||||
mailbox: Mailbox,
|
||||
user: User,
|
||||
alias: Alias,
|
||||
contact_email: str,
|
||||
msg: Message,
|
||||
) -> bool:
|
||||
if ip:
|
||||
LOG.d("Enforce SPF on %s %s", ip, envelope.mail_from)
|
||||
try:
|
||||
r = spf.check2(i=ip, s=envelope.mail_from, h=None)
|
||||
except Exception:
|
||||
LOG.exception("SPF error, mailbox %s, ip %s", mailbox.email, ip)
|
||||
else:
|
||||
# TODO: Handle temperr case (e.g. dns timeout)
|
||||
# only an absolute pass, or no SPF policy at all is 'valid'
|
||||
if r[0] not in ["pass", "none"]:
|
||||
LOG.w(
|
||||
"SPF fail for mailbox %s, reason %s, failed IP %s",
|
||||
mailbox.email,
|
||||
r[0],
|
||||
ip,
|
||||
)
|
||||
subject = get_header_unicode(msg["Subject"])
|
||||
send_email_with_rate_control(
|
||||
user,
|
||||
ALERT_SPF,
|
||||
mailbox.email,
|
||||
f"SimpleLogin Alert: attempt to send emails from your alias {alias.email} from unknown IP Address",
|
||||
render(
|
||||
"transactional/spf-fail.txt",
|
||||
alias=alias.email,
|
||||
ip=ip,
|
||||
mailbox_url=URL + f"/dashboard/mailbox/{mailbox.id}#spf",
|
||||
to_email=contact_email,
|
||||
subject=subject,
|
||||
time=arrow.now(),
|
||||
),
|
||||
render(
|
||||
"transactional/spf-fail.html",
|
||||
ip=ip,
|
||||
mailbox_url=URL + f"/dashboard/mailbox/{mailbox.id}#spf",
|
||||
to_email=contact_email,
|
||||
subject=subject,
|
||||
time=arrow.now(),
|
||||
),
|
||||
)
|
||||
return False
|
||||
|
||||
else:
|
||||
LOG.w(
|
||||
"Could not find %s header %s -> %s",
|
||||
_IP_HEADER,
|
||||
mailbox.email,
|
||||
contact_email,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def sl_sendmail(
|
||||
from_addr,
|
||||
to_addr,
|
||||
msg: Message,
|
||||
mail_options,
|
||||
rcpt_options,
|
||||
is_forward: bool,
|
||||
can_retry=True,
|
||||
):
|
||||
"""replace smtp.sendmail"""
|
||||
if NOT_SEND_EMAIL:
|
||||
LOG.d(
|
||||
"send email with subject '%s', from '%s' to '%s'",
|
||||
msg["Subject"],
|
||||
msg["From"],
|
||||
msg["To"],
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
if POSTFIX_SUBMISSION_TLS:
|
||||
smtp = SMTP(POSTFIX_SERVER, 587)
|
||||
smtp.starttls()
|
||||
else:
|
||||
if is_forward:
|
||||
smtp = SMTP(POSTFIX_SERVER, POSTFIX_PORT_FORWARD)
|
||||
else:
|
||||
smtp = SMTP(POSTFIX_SERVER, POSTFIX_PORT)
|
||||
|
||||
# smtp.send_message has UnicodeEncodeError
|
||||
# encode message raw directly instead
|
||||
smtp.sendmail(
|
||||
from_addr,
|
||||
to_addr,
|
||||
to_bytes(msg),
|
||||
mail_options,
|
||||
rcpt_options,
|
||||
)
|
||||
except SMTPServerDisconnected:
|
||||
if can_retry:
|
||||
LOG.w("SMTPServerDisconnected error, retry")
|
||||
time.sleep(3)
|
||||
sl_sendmail(
|
||||
from_addr,
|
||||
to_addr,
|
||||
msg,
|
||||
mail_options,
|
||||
rcpt_options,
|
||||
is_forward,
|
||||
can_retry=False,
|
||||
)
|
||||
else:
|
||||
raise
|
||||
|
|
129
email_handler.py
129
email_handler.py
|
@ -41,11 +41,9 @@ from email.mime.application import MIMEApplication
|
|||
from email.mime.multipart import MIMEMultipart
|
||||
from email.utils import formataddr, make_msgid, formatdate, getaddresses
|
||||
from io import BytesIO
|
||||
from smtplib import SMTP, SMTPRecipientsRefused, SMTPServerDisconnected
|
||||
from smtplib import SMTPRecipientsRefused
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
import arrow
|
||||
import spf
|
||||
from aiosmtpd.controller import Controller
|
||||
from aiosmtpd.smtp import Envelope
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
@ -54,17 +52,13 @@ from app import pgp_utils, s3
|
|||
from app.alias_utils import try_auto_create
|
||||
from app.config import (
|
||||
EMAIL_DOMAIN,
|
||||
POSTFIX_SERVER,
|
||||
URL,
|
||||
POSTFIX_SUBMISSION_TLS,
|
||||
UNSUBSCRIBER,
|
||||
LOAD_PGP_EMAIL_HANDLER,
|
||||
ENFORCE_SPF,
|
||||
ALERT_REVERSE_ALIAS_UNKNOWN_MAILBOX,
|
||||
ALERT_BOUNCE_EMAIL,
|
||||
ALERT_SPAM_EMAIL,
|
||||
ALERT_SPF,
|
||||
POSTFIX_PORT,
|
||||
SPAMASSASSIN_HOST,
|
||||
MAX_SPAM_SCORE,
|
||||
MAX_REPLY_PHASE_SPAM_SCORE,
|
||||
|
@ -78,8 +72,6 @@ from app.config import (
|
|||
BOUNCE_SUFFIX,
|
||||
TRANSACTIONAL_BOUNCE_PREFIX,
|
||||
TRANSACTIONAL_BOUNCE_SUFFIX,
|
||||
POSTFIX_PORT_FORWARD,
|
||||
NOT_SEND_EMAIL,
|
||||
)
|
||||
from app.email.spam import get_spam_score
|
||||
from app.email_utils import (
|
||||
|
@ -109,6 +101,8 @@ from app.email_utils import (
|
|||
replace,
|
||||
should_disable,
|
||||
parse_id_from_bounce,
|
||||
spf_pass,
|
||||
sl_sendmail,
|
||||
)
|
||||
from app.extensions import db
|
||||
from app.greylisting import greylisting_needed
|
||||
|
@ -1000,68 +994,6 @@ def get_mailbox_from_mail_from(mail_from: str, alias) -> Optional[Mailbox]:
|
|||
return None
|
||||
|
||||
|
||||
def spf_pass(
|
||||
ip: str,
|
||||
envelope,
|
||||
mailbox: Mailbox,
|
||||
user: User,
|
||||
alias: Alias,
|
||||
contact_email: str,
|
||||
msg: Message,
|
||||
) -> bool:
|
||||
if ip:
|
||||
LOG.d("Enforce SPF on %s %s", ip, envelope.mail_from)
|
||||
try:
|
||||
r = spf.check2(i=ip, s=envelope.mail_from, h=None)
|
||||
except Exception:
|
||||
LOG.exception("SPF error, mailbox %s, ip %s", mailbox.email, ip)
|
||||
else:
|
||||
# TODO: Handle temperr case (e.g. dns timeout)
|
||||
# only an absolute pass, or no SPF policy at all is 'valid'
|
||||
if r[0] not in ["pass", "none"]:
|
||||
LOG.w(
|
||||
"SPF fail for mailbox %s, reason %s, failed IP %s",
|
||||
mailbox.email,
|
||||
r[0],
|
||||
ip,
|
||||
)
|
||||
subject = get_header_unicode(msg["Subject"])
|
||||
send_email_with_rate_control(
|
||||
user,
|
||||
ALERT_SPF,
|
||||
mailbox.email,
|
||||
f"SimpleLogin Alert: attempt to send emails from your alias {alias.email} from unknown IP Address",
|
||||
render(
|
||||
"transactional/spf-fail.txt",
|
||||
alias=alias.email,
|
||||
ip=ip,
|
||||
mailbox_url=URL + f"/dashboard/mailbox/{mailbox.id}#spf",
|
||||
to_email=contact_email,
|
||||
subject=subject,
|
||||
time=arrow.now(),
|
||||
),
|
||||
render(
|
||||
"transactional/spf-fail.html",
|
||||
ip=ip,
|
||||
mailbox_url=URL + f"/dashboard/mailbox/{mailbox.id}#spf",
|
||||
to_email=contact_email,
|
||||
subject=subject,
|
||||
time=arrow.now(),
|
||||
),
|
||||
)
|
||||
return False
|
||||
|
||||
else:
|
||||
LOG.w(
|
||||
"Could not find %s header %s -> %s",
|
||||
_IP_HEADER,
|
||||
mailbox.email,
|
||||
contact_email,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def handle_unknown_mailbox(
|
||||
envelope, msg, reply_email: str, user: User, alias: Alias, contact: Contact
|
||||
):
|
||||
|
@ -1670,61 +1602,6 @@ def handle_bounce(envelope, rcpt_to) -> str:
|
|||
return "550 SL E26 Email cannot be forwarded to mailbox"
|
||||
|
||||
|
||||
def sl_sendmail(
|
||||
from_addr,
|
||||
to_addr,
|
||||
msg: Message,
|
||||
mail_options,
|
||||
rcpt_options,
|
||||
is_forward: bool,
|
||||
can_retry=True,
|
||||
):
|
||||
"""replace smtp.sendmail"""
|
||||
if NOT_SEND_EMAIL:
|
||||
LOG.d(
|
||||
"send email with subject '%s', from '%s' to '%s'",
|
||||
msg["Subject"],
|
||||
msg["From"],
|
||||
msg["To"],
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
if POSTFIX_SUBMISSION_TLS:
|
||||
smtp = SMTP(POSTFIX_SERVER, 587)
|
||||
smtp.starttls()
|
||||
else:
|
||||
if is_forward:
|
||||
smtp = SMTP(POSTFIX_SERVER, POSTFIX_PORT_FORWARD)
|
||||
else:
|
||||
smtp = SMTP(POSTFIX_SERVER, POSTFIX_PORT)
|
||||
|
||||
# smtp.send_message has UnicodeEncodeError
|
||||
# encode message raw directly instead
|
||||
smtp.sendmail(
|
||||
from_addr,
|
||||
to_addr,
|
||||
to_bytes(msg),
|
||||
mail_options,
|
||||
rcpt_options,
|
||||
)
|
||||
except SMTPServerDisconnected:
|
||||
if can_retry:
|
||||
LOG.w("SMTPServerDisconnected error, retry")
|
||||
time.sleep(3)
|
||||
sl_sendmail(
|
||||
from_addr,
|
||||
to_addr,
|
||||
msg,
|
||||
mail_options,
|
||||
rcpt_options,
|
||||
is_forward,
|
||||
can_retry=False,
|
||||
)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class MailHandler:
|
||||
async def handle_DATA(self, server, session, envelope: Envelope):
|
||||
try:
|
||||
|
|
Loading…
Reference in New Issue