Flask邮件安全性不符合Microsoft Outlook的安全性要求吗? [英] Flask mail security is not meeting Microsoft Outlook's security requirements?

查看:111
本文介绍了Flask邮件安全性不符合Microsoft Outlook的安全性要求吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个Web应用程序,可以将电子邮件发送给客户端,并且该Web应用程序正在使用Flask邮件框架进行处理.大约2周前,我们的Web应用程序未能将电子邮件发送给客户和我们自己的一群人.我们使用Office 365的Outlook作为发件人.

We have a web application that sends emails to clients and the web application is using Flask mail framework to handle that. About 2 weeks ago, our web application failed to send emails to clients and our own group of people. We use Office 365's Outlook as our sender.

远程服务器返回'554 5.6.0损坏的消息内容; STOREDRV.Deliver.Exception:ConversionFailedException;由于消息内容转换的永久性异常而无法处理消息:TNEF摘要内容已损坏. ConversionFailedException:内容转换:概要TNEF内容已损坏. [阶段:PromoteCreateReplay] 原始邮件标题:

Remote Server returned '554 5.6.0 Corrupt message content; STOREDRV.Deliver.Exception:ConversionFailedException; Failed to process message due to a permanent exception with message Content conversion: Corrupt summary TNEF content. ConversionFailedException: Content conversion: Corrupt summary TNEF content. [Stage: PromoteCreateReplay]' Original message headers:

这是发件人被指示发送电子邮件后收到的错误消息.我们联系了Office 365管理员,Microsoft告诉他我们的Web应用程序具有的安全性不符合Microsoft的要求/协议.

This is the error message that the sender receives after being instructed to send email out. We contacted our Office 365 admin and Microsoft told him that the security that our web application has does not meet Microsoft's requirement / protocol.

问题是Flask邮件使用的较旧的安全协议或配置不能与Microsoft Outlook很好地配合吗?

The question is is Flask mail using the older security protocol or configuration that is not working well with Microsoft Outlook?

推荐答案

Outlook.com/Office365错误消息没有多大用处,因为它可以指示许多问题.这表明Microsoft邮件服务器对电子邮件包装的某些方面(标头,附件等)不满意,并且其解析器在某处出错.否则,它们的错误消息在提供的细节上几乎是无用的.我认为这是一个安全问题的说法是胡说八道; Flask-Mail使用经过良好测试的Python标准库emailsmtplib包通过TLS加密连接发送电子邮件.

The Outlook.com / Office365 error message is less than helpful, as it can indicate any number of problems. It indicates that the Microsoft mail servers were unhappy about some aspect of the email packaging (headers, attachments, etc) and their parsers errorred out somewhere. Their error message is otherwise next to useless in what detail it provides. I find the assertion that this is a security problem to be nonsense; Flask-Mail uses the well-tested Python standard library email and smtplib packages to send email over a TLS encrypted connection.

对于Heroku上的Flask-Mail,我将问题追溯到在Heroku Dyno机器上生成的Message-ID标头.该问题不仅限于Heroku,但是,您会在任何 long主机名的主机上看到此问题.您典型的Heroku dyno主机名以完整的UUID开头,外加另外5个组件,例如aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com.

For Flask-Mail on Heroku, I instead traced the issue to the Message-ID header that is generated on Heroku Dyno machines. The problem is not limited to Heroku, however, you'd see this on any host with a long hostname. Your typical Heroku dyno hostname starts with a full UUID, plus another 5 components or so, e.g. aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com.

此主机名用于为每封电子邮件生成的Message-ID标头中. Flask-Mail软件包使用标准的 email.utils.make_msgid()函数生成头,并且默认情况下使用当前主机名.然后,产生一个Message-ID标头,如:

This hostname is used in the Message-ID header that is generated for each email. The Flask-Mail package uses the standard email.utils.make_msgid() function to generate the header, and that uses the current hostname by default. This then results in a Message-ID header like:

Message-ID: <154810422972.4.16142961424846318784@aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com>

这是一个110个字符长的字符串.对于电子邮件标题来说,这是一个小问题,因为电子邮件RFC规定标题 应该限制为 78 个字符.但是,有多种方法可以解决此问题.对于超过77个字符的标头值,您可以使用 RFC 5322 中的规定 fold 标头.折叠可以在多行上使用多个 RFC 2047 编码字.这就是这里发生的情况,上面的电子邮件标题变为

That's a string 110 characters long. That's a slight problem for email headers, because the email RFCs state that headers should be limited to 78 characters. There are ways around this, however; for header values longer than 77 characters you can use the provisions in RFC 5322 to fold headers. Folding can use multiple RFC 2047 encoded words on multiple lines. This is is what happens here, the email header above becomes

Message-ID: =?utf-8?q?=3C154810422972=2E4=2E16142961424846318784=40aaf39fce-?=
 =?utf-8?q?569e-473a-9453-6862595bd8da=2Eprvt=2Edyno=2Ert=2Eheroku=2Ecom=3E?=

现在可以容纳78个字符和77个字符,符合电子邮件MIME标准.

which, being 78 and 77 characters, now fit the email MIME standard.

在我看来,所有这些似乎都符合标准和处理邮件标头的有效方法.或至少其他邮件提供商可以容忍和正确处理的内容,但是Microsoft的邮件服务器没有此功能.他们真的不喜欢上面的RFC2047编码的Message-ID标头,并尝试将正文包装在TNEF winmail.dat附件中.这并不总是有效,因此您最终会得到非常隐秘的 554 5.6.0损坏的消息内容错误消息.我认为这是Microsoft的错误;我不是100%肯定电子邮件RFC允许使用编码的单词来折叠Message-ID标头,但是MS通过向收件人发送无意义的错误而不是在接收时拒绝消息来解决该错误,这是很糟糕的.

All of this is appears to me to be standards compliant and valid method of handling mail headers. Or at least something that other mail providers tolerate and process properly, but Microsoft's mail servers are not having this. They really don't like the above RFC2047 encoded Message-ID header, and try to wrap the body in a TNEF winmail.dat attachment. That doesn't always work, so you end up with the very cryptic 554 5.6.0 Corrupt message content error message. I consider this a Microsoft bug; I am not 100% certain that the email RFCs allow the Message-ID header to be folded using encoded words, but MS’s handling of the error by sending the recipient a meaningless error rather than reject the message when receiving is just terrible.

您可以为Flask-Mail设置替代的电子邮件策略通过将flask_mail.message_policy模块设置为全局来使用,或者我们可以生成另一个message-ID.

You could set an alternative email policy for Flask-Mail to use by setting the flask_mail.message_policy module global, or we can generate a different message-ID.

仅当您使用Python 3.3或更高版本时,电子邮件策略才可用,但是它是处理折叠的策略对象,因此使我们能够更改如何处理Message-ID和其他RFC 5322标识符标头.这是一个不会折叠Message-ID标头的子类;该标准实际上允许一行上最多998个字符,并且该子类仅对此标头使用该限制:

Email policies are only available if you are using Python 3.3 or up, but it is the policy object that handles folding and so allows us to alter how Message-ID and other RFC 5322 identifier headers are handled. Here is a subclass that won't fold the Message-ID header; the standard actually allows up to 998 characters on a single line, and this subclass uses that limit just for this header:

import flask_mail
from email.policy import EmailPolicy, SMTP

# Headers that contain msg-id values, RFC5322
MSG_ID_HEADERS = {'message-id', 'in-reply-to', 'references', 'resent-msg-id'}

class MsgIdExcemptPolicy(EmailPolicy):
    def _fold(self, name, value, *args, **kwargs):
        if (name.lower() in MSG_ID_HEADERS and
            self.max_line_length < 998 and
            self.max_line_length - len(name) - 2 < len(value)
        ):
            # RFC 5322, section 2.1.1: "Each line of characters MUST be no
            # more than 998 characters, and SHOULD be no more than 78
            # characters, excluding the CRLF.". To avoid msg-id tokens from being folded
            # by means of RFC2047, fold identifier lines to the max length instead.
            return self.clone(max_line_length=998)._fold(name, value, *args, **kwargs)
        return super()._fold(name, value, *args, **kwargs)

flask_mail.message_policy = MsgIdExcemptPolicy() + SMTP

在Python 2.7或Python 3.2或更早版本上,您将不得不替换Message-Id标头,只需使用硬编码域名重新生成标头即可:

On Python 2.7 or Python 3.2 or older, you'll have to resort to replacing the Message-Id header, just re-generate the header with a hard-coded domain name:

from flask import current_app
from flask_mail import Message as _Message

# set this to your actual domain name
DOMAIN_NAME = 'example.com'

class Message(_Message):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # work around issues with Microsoft Office365 / Outlook.com email servers
        # and their inability to handle RFC2047 encoded Message-Id headers. The
        # Python email package only uses RFC2047 when encoding *long* message ids,
        # and those happen all the time on Heroku, where the hostname includes a
        # full UUID as well as 5 more components, e.g.
        # aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com
        # The work-around is to just use our own domain name, hard-coded, but only
        # when the message-id length exceeds 77 characters (MIME allows 78, but one
        # is used for a leading space)
        if len(self.msgId) > 77:
            domain = current_app.config.get('MESSAGE_ID_DOMAIN', DOMAIN_NAME)
            self.msgId = make_msgid(domain=domain)

然后您将使用上面的Message类而不是flask_mail.Message()类,它将生成一个较短的Message-ID标头,该标头不会与Microsoft有问题的标头解析器发生冲突.

You'd then use the above Message class instead of the flask_mail.Message() class, and it'll generate a shorter Message-ID header that won't clash with Microsoft's problematic header parsers.

我向Python项目提交了错误报告,以跟踪对msg-id令牌的处理,我怀疑这真的应该在那里解决.

I filed a bug report with the Python project to track the handling of msg-id tokens, as I suspect this should really be solved there.

这篇关于Flask邮件安全性不符合Microsoft Outlook的安全性要求吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆