首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SMTP STARTTLS格式

SMTP STARTTLS格式
EN

Stack Overflow用户
提问于 2021-12-15 11:47:28
回答 1查看 391关注 0票数 0

在建立TLS连接之后是否需要EHLO消息?我使用的是橡子ltl-6511M野生动物相机,它在建立TLS连接后似乎没有发送EHLO消息,导致基于aiosmtpd的SMTP服务器出现503个错误。不过,它适用于gmail SMTP。相机是否遵循协议,还是我的服务器不够健壮?

我使用的代码是:

代码语言:javascript
复制
import email
from email.header import decode_header
from email import message_from_bytes
from email.policy import default
from aiosmtpd.controller import Controller
from aiosmtpd.smtp import LoginPassword, AuthResult
import os
import sys
import time
import signal 
import logging
import ssl

##setting timezone
os.environ['TZ'] = "Europe/London"
time.tzset()

def onExit( sig, func=None):
    print("*************Stopping program*****************")
    controller.stop()
    exit()
 
signal.signal(signal.SIGTERM, onExit)

# removes the spaces and replaces with _ so they're valid folder names
def clean(text):
    return "".join(c if c.isalnum() else "_" for c in text)


log = logging.getLogger('mail.log')

auth_db = {
    b"TestCamera1@gmail.com": b"password1",
    b"user2": b"password2",
    b"TestCamera1": b"password1",
}

def authenticator_func(server, session, envelope, mechanism, auth_data):
    #this deliberately lets everything through
    assert isinstance(auth_data, LoginPassword)
    username = auth_data.login
    password = auth_data.password
    return AuthResult(success=True)


def configure_logging():
    file_handler = logging.FileHandler("aiosmtpd.log", "a")
    stderr_handler = logging.StreamHandler(sys.stderr)
    logger = logging.getLogger("mail.log")
    fmt = "[%(asctime)s %(levelname)s] %(message)s"
    datefmt = None
    formatter = logging.Formatter(fmt, datefmt, "%")
    stderr_handler.setFormatter(formatter)
    logger.addHandler(stderr_handler)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)
    logger.setLevel(logging.DEBUG)

class CustomHandler:
    def handle_exception(self, error):
        print("exception occured")
        print(error)
        return '542 Internal Server Error'

    async def handle_DATA(self, server, session, envelope):
        peer = session.peer
        data = envelope.content         # type: bytes
        msg = message_from_bytes(envelope.content, policy=default)
        # decode the email subject
        print("Msg:{}".format(msg))
        print("Data:{}".format(data))
        print("All of the relevant data has been extracted from the email")
        return '250 OK'


if __name__ == '__main__':
    configure_logging()
    handler = CustomHandler()
    #update hostname to your IP
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain('cert.pem', 'key.pem')    
    controller = Controller(handler, hostname='0.0.0.0', port=587, authenticator=authenticator_func, auth_required=True,auth_require_tls=True,tls_context=context)    
    # Run the event loop in a separate thread.
    controller.start()
    while True:
        time.sleep(10)

在尝试集成之后的代码是:

代码语言:javascript
复制
import email
from email.header import decode_header
from email import message_from_bytes
from email.policy import default
from aiosmtpd.controller import Controller
from aiosmtpd.smtp import LoginPassword, AuthResult, SMTP
import os
import json
import re
import sys
import time
import signal 
import logging
import ssl

from datetime import datetime
import configparser

##setting timezone
os.environ['TZ'] = "Europe/London"
time.tzset()

spacer = "*"*100

def onExit( sig, func=None):
    print("*************Stopping program*****************",3)
    controller.stop()
    exit()
 
signal.signal(signal.SIGTERM, onExit)

# removes the spaces and replaces with _ so they're valid folder names
def clean(text):
    return "".join(c if c.isalnum() else "_" for c in text)

log = logging.getLogger('mail.log')

auth_db = {
    b"TestCamera1@gmail.com": b"password1",
    b"user2": b"password2",
    b"TestCamera1": b"password1",
}

def authenticator_func(server, session, envelope, mechanism, auth_data):
    # Simple auth - is only being used because of the reolink cam
    assert isinstance(auth_data, LoginPassword)
    username = auth_data.login
    password = auth_data.password
    log.warning("Authenticator is being used")
    return AuthResult(success=True)

def configure_logging():
    file_handler = logging.FileHandler("aiosmtpd.log", "a")
    stderr_handler = logging.StreamHandler(sys.stderr)
    logger = logging.getLogger("mail.log")
    fmt = "[%(asctime)s %(levelname)s] %(message)s"
    datefmt = None
    formatter = logging.Formatter(fmt, datefmt, "%")
    stderr_handler.setFormatter(formatter)
    logger.addHandler(stderr_handler)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)
    logger.setLevel(logging.DEBUG)

class SMTPNoEhloAfterStarttls(SMTP):
    async def smtp_STARTTLS(self, arg: str):
        print(spacer)
        print("using starttls")
        host_name = self.session.host_name
        extended_smtp = self.session.extended_smtp
        await super().smtp_STARTTLS(arg)
        if host_name and extended_smtp and not self.session.host_name:
            # There was an EHLO before the STARTTLS.
            # RFC3207 says that we MUST reset the state
            # and forget the EHLO, but unfortunately
            # the client doesn't re-send the EHLO after STARTTLS,
            # so we need to pretend as if an EHLO has been sent.
            self.session.host_name = host_name
            self.session.extended_smtp = True

class ControllerNoEhloAfterStarttls(Controller):
    def factory(self):
        print(spacer)
        print("updating default settings")
        return SMTPNoEhloAfterStarttls(self.handler, **self.SMTP_kwargs)

class CustomHandler:
    def handle_exception(self, error):
        print("exception occured",3)
        print(error)
        return '542 Internal Server Error'

    async def handle_DATA(self, server, session, envelope):
        peer = session.peer
        data = envelope.content         # type: bytes
        msg = message_from_bytes(envelope.content, policy=default)
        # decode the email subject
        print("Msg:{}".format(msg),3)
        print("Data:{}".format(data),3)
        print("All of the relevant data has been extracted from the email",3)
        print(spacer,3)
        return '250 OK'

if __name__ == '__main__':
    configure_logging()
    handler = CustomHandler()
    # controller = Controller(handler, hostname='10.200.68.132', port=587)
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain('cert.pem', 'key.pem')
    controller = Controller(handler, hostname='10.200.68.133', port=587, authenticator=authenticator_func, auth_required=True,auth_require_tls=True,tls_context=context)    
    
    # Run the event loop in a separate thread.
    controller.start()
    #Confirmed that this is needed to keep the SMTP server running constantly
    while True:
        time.sleep(10)

但是,这并没有对错误日志产生任何影响。

EN

回答 1

Stack Overflow用户

发布于 2021-12-27 13:02:02

是的,在STARTTLS之后需要EHLO,请参阅RFC3207 4.2节 (其中特别提到忘记EHLO线-强调我):

完成TLS握手后,SMTP协议将重置为初始状态(在服务器发出220服务就绪问候语后的SMTP状态)。服务器必须丢弃从客户端()获得的任何知识,例如不是从TLS协商本身获得的EHLO命令的参数,

这意味着,不幸的是,您的相机没有遵循SMTP协议。同样不幸的是,GMail SMTP没有遵循该协议(它不需要在STARTTLS和AUTH登录之间使用EHLO )。

aiosmtpd非常坚持遵循SMTP协议,并在STARTTLS之前适当地忘记EHLO数据;EHLO主机名存储在self.session.host_name上的aiosmtpd.smtp.SMTP对象中,SMTP.connection_made()存储在STARTTLS之后调用。

可以使aiosmtpd违反SMTP规范,并以高度不符合的方式进行操作。显然,这是在生产中不能做的事情。使用下面定义的ControllerNoEhloAfterStarttls而不是标准的aiosmtpd Controller,然后它就可以工作了。

代码语言:javascript
复制
from aiosmtpd.smtp import SMTP
from aiosmtpd.controller import Controller

class SMTPNoEhloAfterStarttls(SMTP):
    async def smtp_STARTTLS(self, arg: str):
        host_name = self.session.host_name
        extended_smtp = self.session.extended_smtp
        await super().smtp_STARTTLS(arg)
        if host_name and extended_smtp and not self.session.host_name:
            # There was an EHLO before the STARTTLS.
            # RFC3207 says that we MUST reset the state
            # and forget the EHLO, but unfortunately
            # the client doesn't re-send the EHLO after STARTTLS,
            # so we need to pretend as if an EHLO has been sent.
            self.session.host_name = host_name
            self.session.extended_smtp = True

class ControllerNoEhloAfterStarttls(Controller):
    def factory(self):
        return SMTPNoEhloAfterStarttls(self.handler, **self.SMTP_kwargs)

然后,...and在if __name__ == "__main__":中实例化自定义控制器类,而不是默认控制器:

代码语言:javascript
复制
controller = ControllerNoEhloAfterStarttls(handler, hostname='10.200.68.133', port=587, ......)
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70363161

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档