From 5821294ae975b71f4f6d30383a990a69abf021ff Mon Sep 17 00:00:00 2001 From: Son Date: Mon, 11 Oct 2021 12:10:18 +0200 Subject: [PATCH] refactor: use headers.py --- app/email/headers.py | 18 +++++++++++++++ app/email_utils.py | 42 +++++++++++++++-------------------- email_handler.py | 52 +++++++++++++++++++++++--------------------- 3 files changed, 62 insertions(+), 50 deletions(-) diff --git a/app/email/headers.py b/app/email/headers.py index 82bce7ba..3615e8a5 100644 --- a/app/email/headers.py +++ b/app/email/headers.py @@ -3,3 +3,21 @@ MESSAGE_ID = "Message-ID" IN_REPLY_TO = "In-Reply-To" REFERENCES = "References" DATE = "date" +SUBJECT = "Subject" +FROM = "From" +TO = "To" +CONTENT_TYPE = "Content-Type" +MIME_VERSION = "Mime-Version" +REPLY_TO = "Reply-To" +RECEIVED = "Received" +CC = "Cc" +DKIM_SIGNATURE = "DKIM-Signature" +X_SPAM_STATUS = "X-Spam-Status" + +# headers used to DKIM sign in order of preference +DKIM_HEADERS = [ + [MESSAGE_ID.encode(), DATE.encode(), SUBJECT.encode(), FROM.encode(), TO.encode()], + [FROM.encode(), TO.encode()], + [MESSAGE_ID.encode(), DATE.encode()], + [FROM.encode()], +] diff --git a/app/email_utils.py b/app/email_utils.py index cdcf986e..42d87299 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -54,6 +54,7 @@ from app.config import ( ALIAS_AUTOMATIC_DISABLE, ) from app.dns_utils import get_mx_domains +from app.email import headers from app.extensions import db from app.log import LOG from app.models import ( @@ -275,15 +276,15 @@ def send_email( html = plaintext.replace("\n", "
") msg.attach(MIMEText(html, "html")) - msg["Subject"] = subject - msg["From"] = f"{SUPPORT_NAME} <{SUPPORT_EMAIL}>" - msg["To"] = to_email + msg[headers.SUBJECT] = subject + msg[headers.FROM] = f"{SUPPORT_NAME} <{SUPPORT_EMAIL}>" + msg[headers.TO] = to_email msg_id_header = make_msgid() - msg["Message-ID"] = msg_id_header + msg[headers.MESSAGE_ID] = msg_id_header date_header = formatdate() - msg["Date"] = date_header + msg[headers.DATE] = date_header if unsubscribe_link: add_or_replace_header(msg, "List-Unsubscribe", f"<{unsubscribe_link}>") @@ -408,17 +409,8 @@ def get_email_domain_part(address): return address[address.find("@") + 1 :] -# headers used to DKIM sign in order of preference -_DKIM_HEADERS = [ - [b"Message-ID", b"Date", b"Subject", b"From", b"To"], - [b"From", b"To"], - [b"Message-ID", b"Date"], - [b"From"], -] - - def add_dkim_signature(msg: Message, email_domain: str): - for dkim_headers in _DKIM_HEADERS: + for dkim_headers in headers.DKIM_HEADERS: try: add_dkim_signature_with_header(msg, email_domain, dkim_headers) return @@ -457,7 +449,7 @@ def add_dkim_signature_with_header( # remove linebreaks from sig sig = sig.replace("\n", " ").replace("\r", "") - msg["DKIM-Signature"] = sig[len("DKIM-Signature: ") :] + msg[headers.DKIM_SIGNATURE] = sig[len("DKIM-Signature: ") :] def add_or_replace_header(msg: Message, header: str, value: str): @@ -686,7 +678,7 @@ def get_spam_info(msg: Message, max_score=None) -> (bool, str): 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"] + spamassassin_status = msg[headers.X_SPAM_STATUS] if not spamassassin_status: return False, "" @@ -1164,7 +1156,7 @@ def spf_pass( r[0], ip, ) - subject = get_header_unicode(msg["Subject"]) + subject = get_header_unicode(msg[headers.SUBJECT]) send_email_with_rate_control( user, ALERT_SPF, @@ -1214,9 +1206,9 @@ def sl_sendmail( if NOT_SEND_EMAIL: LOG.d( "send email with subject '%s', from '%s' to '%s'", - msg["Subject"], - msg["From"], - msg["To"], + msg[headers.SUBJECT], + msg[headers.FROM], + msg[headers.TO], ) return @@ -1236,9 +1228,9 @@ def sl_sendmail( "Sendmail mail_from:%s, rcpt_to:%s, header_from:%s, header_to:%s, header_cc:%s", from_addr, to_addr, - msg["From"], - msg["To"], - msg["Cc"], + msg[headers.FROM], + msg[headers.TO], + msg[headers.CC], ) smtp.sendmail( from_addr, @@ -1266,7 +1258,7 @@ def sl_sendmail( def get_queue_id(msg: Message) -> Optional[str]: """Get the Postfix queue-id from a message""" - received_header = str(msg["Received"]) + received_header = str(msg[headers.RECEIVED]) if not received_header: return diff --git a/email_handler.py b/email_handler.py index 1d051161..b362d6d3 100644 --- a/email_handler.py +++ b/email_handler.py @@ -445,13 +445,13 @@ def prepare_pgp_message( _MIME_HEADERS, ) - if clone_msg["Content-Type"] is None: + if clone_msg[headers.CONTENT_TYPE] is None: LOG.d("Content-Type missing") - clone_msg["Content-Type"] = "text/plain" + clone_msg[headers.CONTENT_TYPE] = "text/plain" - if clone_msg["Mime-Version"] is None: + if clone_msg[headers.MIME_VERSION] is None: LOG.d("Mime-Version missing") - clone_msg["Mime-Version"] = "1.0" + clone_msg[headers.MIME_VERSION] = "1.0" first = MIMEApplication( _subtype="pgp-encrypted", _encoder=encoders.encode_7or8bit, _data="" @@ -581,13 +581,13 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str handle_email_sent_to_ourself(alias, mb, msg, user) return [(True, status.E209)] - from_header = get_header_unicode(msg["From"]) + from_header = get_header_unicode(msg[headers.FROM]) LOG.d("Create or get contact for from_header:%s", from_header) contact = get_or_create_contact(from_header, envelope.mail_from, alias) reply_to_contact = None - if msg["Reply-To"]: - reply_to = get_header_unicode(msg["Reply-To"]) + if msg[headers.REPLY_TO]: + reply_to = get_header_unicode(msg[headers.REPLY_TO]) LOG.d("Create or get contact for from_header:%s", reply_to) # ignore when reply-to = alias if reply_to == alias.email: @@ -766,7 +766,7 @@ def forward_email_to_mailbox( LOG.d("Encrypt message using mailbox %s", mailbox) if mailbox.generic_subject: LOG.d("Use a generic subject for %s", mailbox) - orig_subject = msg["Subject"] + orig_subject = msg[headers.SUBJECT] orig_subject = get_header_unicode(orig_subject) add_or_replace_header(msg, "Subject", mailbox.generic_subject) msg = add_header( @@ -806,13 +806,13 @@ def forward_email_to_mailbox( # change the from header so the sender comes from a reverse-alias # so it can pass DMARC check # replace the email part in from: header - contact_from_header = msg["From"] + contact_from_header = msg[headers.FROM] new_from_header = contact.new_addr() add_or_replace_header(msg, "From", new_from_header) LOG.d("From header, new:%s, old:%s", new_from_header, contact_from_header) if reply_to_contact: - reply_to_header = msg["Reply-To"] + reply_to_header = msg[headers.REPLY_TO] new_reply_to_header = reply_to_contact.new_addr() add_or_replace_header(msg, "Reply-To", new_reply_to_header) LOG.d("Reply-To header, new:%s, old:%s", new_reply_to_header, reply_to_header) @@ -1048,11 +1048,11 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str): LOG.d("make message id %s", message_id) add_or_replace_header( msg, - "Message-ID", + headers.MESSAGE_ID, message_id, ) date_header = formatdate() - msg["Date"] = date_header + msg[headers.DATE] = date_header msg[_DIRECTION] = "Reply" msg[_EMAIL_LOG_ID_HEADER] = str(email_log.id) @@ -1133,7 +1133,7 @@ def handle_unknown_mailbox( "Reply email can only be used by mailbox. " "Actual mail_from: %s. msg from header: %s, reverse-alias %s, %s %s %s", envelope.mail_from, - msg["From"], + msg[headers.FROM], reply_email, alias, user, @@ -1315,7 +1315,7 @@ def handle_hotmail_complaint(msg: Message) -> bool: Return True if the complaint can be handled, False otherwise """ orig_msg = get_orig_message_from_hotmail_complaint(msg) - to_header = orig_msg["To"] + to_header = orig_msg[headers.TO] if not to_header: LOG.e("cannot find the alias") return False @@ -1355,7 +1355,7 @@ def handle_yahoo_complaint(msg: Message) -> bool: Return True if the complaint can be handled, False otherwise """ orig_msg = get_orig_message_from_yahoo_complaint(msg) - to_header = orig_msg["To"] + to_header = orig_msg[headers.TO] if not to_header: LOG.e("cannot find the alias") return False @@ -1559,7 +1559,7 @@ def handle_spam( def handle_unsubscribe(envelope: Envelope, msg: Message) -> str: """return the SMTP status""" # format: alias_id: - subject = msg["Subject"] + subject = msg[headers.SUBJECT] try: # subject has the format {alias.id}= if subject.endswith("="): @@ -1574,7 +1574,7 @@ 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"]) + LOG.w("Cannot parse alias from subject %s", msg[headers.SUBJECT]) return status.E507 if not alias: @@ -1746,7 +1746,7 @@ def handle(envelope: Envelope) -> str: if postfix_queue_id: set_message_id(postfix_queue_id) else: - LOG.d("Cannot parse Postfix queue ID from %s", msg["Received"]) + LOG.d("Cannot parse Postfix queue ID from %s", msg[headers.RECEIVED]) if should_ignore(mail_from, rcpt_tos): LOG.w("Ignore email mail_from=%s rcpt_to=%s", mail_from, rcpt_tos) @@ -1763,10 +1763,10 @@ def handle(envelope: Envelope) -> str: "cc:%s, reply-to:%s, mail_options:%s, rcpt_options:%s", mail_from, rcpt_tos, - msg["From"], - msg["To"], - msg["Cc"], - msg["Reply-To"], + msg[headers.FROM], + msg[headers.TO], + msg[headers.CC], + msg[headers.REPLY_TO], envelope.mail_options, envelope.rcpt_options, ) @@ -1854,7 +1854,7 @@ def handle(envelope: Envelope) -> str: return handle_bounce(envelope, email_log, msg) # case where From: header is a reverse alias which should never happen - from_header = get_header_unicode(msg["From"]) + from_header = get_header_unicode(msg[headers.FROM]) if from_header: try: _, from_header_address = parse_full_address(from_header) @@ -1926,14 +1926,16 @@ def handle(envelope: Envelope) -> str: # Reply case # recipient starts with "reply+" or "ra+" (ra=reverse-alias) prefix if is_reply_email(rcpt_to): - LOG.d("Reply phase %s(%s) -> %s", mail_from, copy_msg["From"], rcpt_to) + LOG.d( + "Reply phase %s(%s) -> %s", mail_from, copy_msg[headers.FROM], rcpt_to + ) is_delivered, smtp_status = handle_reply(envelope, copy_msg, rcpt_to) res.append((is_delivered, smtp_status)) else: # Forward case LOG.d( "Forward phase %s(%s) -> %s", mail_from, - copy_msg["From"], + copy_msg[headers.FROM], rcpt_to, ) for is_delivered, smtp_status in handle_forward(