mirror of
https://github.com/simple-login/app.git
synced 2025-02-25 00:03:03 +08:00
refactor: put all SMTP statuses into status.py
This commit is contained in:
parent
58a1d6e783
commit
6fa267e92b
2 changed files with 80 additions and 40 deletions
39
app/email/status.py
Normal file
39
app/email/status.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
# 2** status
|
||||
E200 = "250 Message accepted for delivery"
|
||||
E201 = "250 SL E201"
|
||||
E202 = "250 Unsubscribe request accepted"
|
||||
E203 = "250 SL E203 email can't be sent from a reverse-alias"
|
||||
E204 = "250 SL E204 ignore"
|
||||
E205 = "250 SL E205 bounce handled"
|
||||
# out of office status
|
||||
E206 = "250 SL E206 Out of office"
|
||||
|
||||
# 4** errors
|
||||
E401 = "421 SL E401 Retry later"
|
||||
E402 = "421 SL E402 Retry later"
|
||||
E403 = "421 SL E403 Retry later"
|
||||
E404 = "421 SL E404 Retry later"
|
||||
E405 = "421 SL E405 Retry later"
|
||||
E406 = "421 SL E406 Retry later"
|
||||
|
||||
# 5** errors
|
||||
E501 = "550 SL E501"
|
||||
E502 = "550 SL E502 Email not exist"
|
||||
E503 = "550 SL E503"
|
||||
E504 = "550 SL E504 Account disabled"
|
||||
E505 = "550 SL E505"
|
||||
E506 = "550 SL E506 Email detected as spam"
|
||||
E507 = "550 SL E507 Wrongly formatted subject"
|
||||
E508 = "550 SL E508 Email not exist"
|
||||
E509 = "550 SL E509 unauthorized"
|
||||
E510 = "550 SL E510 so such user"
|
||||
E511 = "550 SL E511 unsubscribe error"
|
||||
E512 = "550 SL E512 No such email log"
|
||||
E513 = "550 SL E513 Email cannot be forwarded to mailbox"
|
||||
E514 = "550 SL E514 Email sent to noreply address"
|
||||
E515 = "550 SL E515 Email not exist"
|
||||
E516 = "550 SL E516 invalid mailbox"
|
||||
E517 = "550 SL E517 unverified mailbox"
|
||||
E518 = "550 SL E518 Disabled mailbox"
|
||||
E519 = "550 SL E519 Email detected as spam"
|
||||
E520 = "550 SL E24 Email cannot be sent to contact"
|
|
@ -75,6 +75,7 @@ from app.config import (
|
|||
ENABLE_SPAM_ASSASSIN,
|
||||
BOUNCE_PREFIX_FOR_REPLY_PHASE,
|
||||
)
|
||||
from app.email import status
|
||||
from app.email.spam import get_spam_score
|
||||
from app.email_utils import (
|
||||
send_email,
|
||||
|
@ -485,13 +486,13 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str
|
|||
alias = try_auto_create(address)
|
||||
if not alias:
|
||||
LOG.d("alias %s cannot be created on-the-fly, return 550", address)
|
||||
return [(False, "550 SL E3 Email not exist")]
|
||||
return [(False, status.E515)]
|
||||
|
||||
user = alias.user
|
||||
|
||||
if user.disabled:
|
||||
LOG.w("User %s disabled, disable forwarding emails for %s", user, alias)
|
||||
return [(False, "550 SL E20 Account disabled")]
|
||||
return [(False, status.E504)]
|
||||
|
||||
# mail_from = envelope.mail_from
|
||||
# for mb in alias.mailboxes:
|
||||
|
@ -518,20 +519,20 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str
|
|||
)
|
||||
db.session.commit()
|
||||
# do not return 5** to allow user to receive emails later when alias is enabled
|
||||
return [(True, "250 Message accepted for delivery")]
|
||||
return [(True, status.E200)]
|
||||
|
||||
ret = []
|
||||
mailboxes = alias.mailboxes
|
||||
|
||||
# no valid mailbox
|
||||
if not mailboxes:
|
||||
return [(False, "550 SL E16 invalid mailbox")]
|
||||
return [(False, status.E516)]
|
||||
|
||||
# no need to create a copy of message
|
||||
for mailbox in mailboxes:
|
||||
if not mailbox.verified:
|
||||
LOG.d("%s unverified, do not forward", mailbox)
|
||||
ret.append((False, "550 SL E19 unverified mailbox"))
|
||||
ret.append((False, status.E517))
|
||||
else:
|
||||
# create a copy of message for each forward
|
||||
ret.append(
|
||||
|
@ -560,7 +561,7 @@ def forward_email_to_mailbox(
|
|||
|
||||
if mailbox.disabled:
|
||||
LOG.debug("%s disabled, do not forward")
|
||||
return False, "550 SL E21 Disabled mailbox"
|
||||
return False, status.E518
|
||||
|
||||
# sanity check: make sure mailbox is not actually an alias
|
||||
if get_email_domain_part(alias.email) == get_email_domain_part(mailbox.email):
|
||||
|
@ -593,7 +594,7 @@ def forward_email_to_mailbox(
|
|||
|
||||
# retry later
|
||||
# so when user fixes the mailbox, the email can be delivered
|
||||
return False, "421 SL E14"
|
||||
return False, status.E405
|
||||
|
||||
email_log = EmailLog.create(
|
||||
contact_id=contact.id, user_id=user.id, mailbox_id=mailbox.id, commit=True
|
||||
|
@ -641,7 +642,7 @@ def forward_email_to_mailbox(
|
|||
db.session.commit()
|
||||
|
||||
handle_spam(contact, alias, msg, user, mailbox, email_log)
|
||||
return False, "550 SL E1 Email detected as spam"
|
||||
return False, status.E519
|
||||
|
||||
if contact.invalid_email:
|
||||
LOG.d("add noreply information %s %s", alias, mailbox)
|
||||
|
@ -684,11 +685,11 @@ def forward_email_to_mailbox(
|
|||
msg, mailbox.pgp_finger_print, mailbox.pgp_public_key, can_sign=True
|
||||
)
|
||||
except PGPException:
|
||||
LOG.exception(
|
||||
LOG.e(
|
||||
"Cannot encrypt message %s -> %s. %s %s", contact, alias, mailbox, user
|
||||
)
|
||||
# so the client can retry later
|
||||
return False, "421 SL E12 Retry later"
|
||||
return False, status.E406
|
||||
|
||||
# add custom header
|
||||
add_or_replace_header(msg, _DIRECTION, "Forward")
|
||||
|
@ -750,10 +751,10 @@ def forward_email_to_mailbox(
|
|||
alias,
|
||||
mailbox,
|
||||
)
|
||||
return False, "421 SL E17 Retry later"
|
||||
return False, status.E401
|
||||
else:
|
||||
db.session.commit()
|
||||
return True, "250 Message accepted for delivery"
|
||||
return True, status.E200
|
||||
|
||||
|
||||
def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
||||
|
@ -766,7 +767,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
|||
# reply_email must end with EMAIL_DOMAIN
|
||||
if not reply_email.endswith(EMAIL_DOMAIN):
|
||||
LOG.w(f"Reply email {reply_email} has wrong domain")
|
||||
return False, "550 SL E2"
|
||||
return False, status.E501
|
||||
|
||||
# handle case where reply email is generated with non-allowed char
|
||||
reply_email = normalize_reply_email(reply_email)
|
||||
|
@ -774,7 +775,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
|||
contact = Contact.get_by(reply_email=reply_email)
|
||||
if not contact:
|
||||
LOG.w(f"No such forward-email with {reply_email} as reply-email")
|
||||
return False, "550 SL E4 Email not exist"
|
||||
return False, status.E502
|
||||
|
||||
alias = contact.alias
|
||||
address: str = contact.alias.email
|
||||
|
@ -784,7 +785,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
|||
# scenario: a user have removed a domain but due to a bug, the aliases are still there
|
||||
if not is_valid_alias_address_domain(alias.email):
|
||||
LOG.exception("%s domain isn't known", alias)
|
||||
return False, "550 SL E5"
|
||||
return False, status.E503
|
||||
|
||||
user = alias.user
|
||||
mail_from = envelope.mail_from
|
||||
|
@ -796,7 +797,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
|||
alias,
|
||||
contact,
|
||||
)
|
||||
return [(False, "550 SL E20 Account disabled")]
|
||||
return [(False, status.E504)]
|
||||
|
||||
# Anti-spoofing
|
||||
mailbox = get_mailbox_from_mail_from(mail_from, alias)
|
||||
|
@ -813,12 +814,13 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
|||
else:
|
||||
# only mailbox can send email to the reply-email
|
||||
handle_unknown_mailbox(envelope, msg, reply_email, user, alias, contact)
|
||||
return False, "550 SL E7"
|
||||
return False, status.E505
|
||||
|
||||
if ENFORCE_SPF and mailbox.force_spf and not alias.disable_email_spoofing_check:
|
||||
if not spf_pass(envelope, mailbox, user, alias, contact.website_email, msg):
|
||||
# cannot use 4** here as sender will retry. 5** because that generates bounce report
|
||||
return True, "250 SL E11"
|
||||
# cannot use 4** here as sender will retry.
|
||||
# cannot use 5** because that generates bounce report
|
||||
return True, status.E201
|
||||
|
||||
email_log = EmailLog.create(
|
||||
contact_id=contact.id,
|
||||
|
@ -870,7 +872,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
|||
db.session.commit()
|
||||
|
||||
handle_spam(contact, alias, msg, user, mailbox, email_log, is_reply=True)
|
||||
return False, "550 SL E15 Email detected as spam"
|
||||
return False, status.E506
|
||||
|
||||
delete_all_headers_except(
|
||||
msg,
|
||||
|
@ -907,7 +909,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
|||
EmailLog.delete(email_log.id)
|
||||
db.session.commit()
|
||||
# return 421 so the client can retry later
|
||||
return False, "421 SL E13 Retry later"
|
||||
return False, status.E402
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
@ -991,8 +993,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
|
|||
)
|
||||
|
||||
# return 250 even if error as user is already informed of the incident and can retry sending the email
|
||||
|
||||
return True, "250 Message accepted for delivery"
|
||||
return True, status.E200
|
||||
|
||||
|
||||
def get_mailbox_from_mail_from(mail_from: str, alias) -> Optional[Mailbox]:
|
||||
|
@ -1262,7 +1263,7 @@ def handle_bounce_reply_phase(envelope, msg: Message, email_log: EmailLog):
|
|||
refused_email_url=refused_email_url,
|
||||
),
|
||||
)
|
||||
return "550 SL E24 Email cannot be sent to contact"
|
||||
return status.E520
|
||||
|
||||
|
||||
def handle_spam(
|
||||
|
@ -1381,11 +1382,11 @@ def handle_unsubscribe(envelope: Envelope, msg: Message) -> str:
|
|||
alias = Alias.get(alias_id)
|
||||
except Exception:
|
||||
LOG.w("Cannot parse alias from subject %s", msg["Subject"])
|
||||
return "550 SL E8 Wrongly formatted subject"
|
||||
return status.E507
|
||||
|
||||
if not alias:
|
||||
LOG.w("No such alias %s", alias_id)
|
||||
return "550 SL E9 Email not exist"
|
||||
return status.E508
|
||||
|
||||
# This sender cannot unsubscribe
|
||||
mail_from = envelope.mail_from
|
||||
|
@ -1393,7 +1394,7 @@ def handle_unsubscribe(envelope: Envelope, msg: Message) -> str:
|
|||
mailbox = get_mailbox_from_mail_from(mail_from, alias)
|
||||
if not mailbox:
|
||||
LOG.d("%s cannot disable alias %s", envelope.mail_from, alias)
|
||||
return "550 SL E10 unauthorized"
|
||||
return status.E509
|
||||
|
||||
# Sender is owner of this alias
|
||||
alias.enabled = False
|
||||
|
@ -1419,7 +1420,7 @@ def handle_unsubscribe(envelope: Envelope, msg: Message) -> str:
|
|||
),
|
||||
)
|
||||
|
||||
return "250 Unsubscribe request accepted"
|
||||
return status.E202
|
||||
|
||||
|
||||
def handle_unsubscribe_user(user_id: int, mail_from: str) -> str:
|
||||
|
@ -1427,11 +1428,11 @@ def handle_unsubscribe_user(user_id: int, mail_from: str) -> str:
|
|||
user = User.get(user_id)
|
||||
if not user:
|
||||
LOG.exception("No such user %s %s", user_id, mail_from)
|
||||
return "550 SL E22 so such user"
|
||||
return status.E510
|
||||
|
||||
if mail_from != user.email:
|
||||
LOG.exception("Unauthorized mail_from %s %s", user, mail_from)
|
||||
return "550 SL E23 unsubscribe error"
|
||||
return status.E511
|
||||
|
||||
user.notification = False
|
||||
db.session.commit()
|
||||
|
@ -1449,7 +1450,7 @@ def handle_unsubscribe_user(user_id: int, mail_from: str) -> str:
|
|||
),
|
||||
)
|
||||
|
||||
return "250 Unsubscribe request accepted"
|
||||
return status.E202
|
||||
|
||||
|
||||
def handle_transactional_bounce(envelope: Envelope, rcpt_to):
|
||||
|
@ -1472,7 +1473,7 @@ def handle_bounce(envelope, email_log: EmailLog, msg: Message) -> str:
|
|||
|
||||
if not email_log:
|
||||
LOG.w("No such email log")
|
||||
return "550 SL E27 No such email log"
|
||||
return status.E512
|
||||
|
||||
contact: Contact = email_log.contact
|
||||
alias = contact.alias
|
||||
|
@ -1524,7 +1525,7 @@ def handle_bounce(envelope, email_log: EmailLog, msg: Message) -> str:
|
|||
return handle_bounce_reply_phase(envelope, msg, email_log)
|
||||
else: # forward phase
|
||||
handle_bounce_forward_phase(msg, email_log)
|
||||
return "550 SL E26 Email cannot be forwarded to mailbox"
|
||||
return status.E513
|
||||
|
||||
|
||||
def should_ignore(mail_from: str, rcpt_tos: List[str]) -> bool:
|
||||
|
@ -1556,7 +1557,7 @@ def handle(envelope: Envelope) -> str:
|
|||
|
||||
if should_ignore(mail_from, rcpt_tos):
|
||||
LOG.e("Ignore email mail_from=%s rcpt_to=%s", mail_from, rcpt_tos)
|
||||
return "250 email can't be sent from a reverse-alias"
|
||||
return status.E204
|
||||
|
||||
# sanitize email headers
|
||||
sanitize_header(msg, "from")
|
||||
|
@ -1584,7 +1585,7 @@ def handle(envelope: Envelope) -> str:
|
|||
contact.alias,
|
||||
contact.website_email,
|
||||
)
|
||||
return "250 email can't be sent from a reverse-alias"
|
||||
return status.E203
|
||||
|
||||
# unsubscribe request
|
||||
if UNSUBSCRIBER and rcpt_tos == [UNSUBSCRIBER]:
|
||||
|
@ -1599,7 +1600,7 @@ def handle(envelope: Envelope) -> str:
|
|||
):
|
||||
LOG.d("Handle email sent to sender from %s", mail_from)
|
||||
handle_transactional_bounce(envelope, rcpt_tos[0])
|
||||
return "250 bounce handled"
|
||||
return status.E205
|
||||
|
||||
# Handle bounce
|
||||
if (
|
||||
|
@ -1638,7 +1639,7 @@ def handle(envelope: Envelope) -> str:
|
|||
# Whether it's necessary to apply greylisting
|
||||
if greylisting_needed(mail_from, rcpt_tos):
|
||||
LOG.w("Grey listing applied for mail_from:%s rcpt_tos:%s", mail_from, rcpt_tos)
|
||||
return "421 SL Retry later"
|
||||
return status.E403
|
||||
|
||||
# Handle "out of office" auto notice. An automatic response is sent for every forwarded email
|
||||
# todo: remove logging
|
||||
|
@ -1646,7 +1647,7 @@ def handle(envelope: Envelope) -> str:
|
|||
LOG.w(
|
||||
"out-of-office email to reverse alias %s. %s", rcpt_tos[0], msg.as_string()
|
||||
)
|
||||
return "250 SL E28"
|
||||
return status.E206
|
||||
|
||||
# result of all deliveries
|
||||
# each element is a couple of whether the delivery is successful and the smtp status
|
||||
|
@ -1656,7 +1657,7 @@ def handle(envelope: Envelope) -> str:
|
|||
for rcpt_index, rcpt_to in enumerate(rcpt_tos):
|
||||
if rcpt_to == NOREPLY:
|
||||
LOG.e("email sent to noreply address from %s", mail_from)
|
||||
return "550 SL E25 Email sent to noreply address"
|
||||
return status.E514
|
||||
|
||||
# create a copy of msg for each recipient except the last one
|
||||
# as copy() is a slow function
|
||||
|
@ -1704,7 +1705,7 @@ class MailHandler:
|
|||
envelope.mail_from,
|
||||
envelope.rcpt_tos,
|
||||
)
|
||||
return "421 SL Retry later"
|
||||
return status.E404
|
||||
|
||||
def _handle(self, envelope: Envelope):
|
||||
start = time.time()
|
||||
|
|
Loading…
Reference in a new issue