公司要求搭建一个smtp server 拦截邮箱发送的内容,但是还要让邮箱不报错, 使用python自带的SMTPD 搭建一个简单的smtp server
SMTPD
代码如下
#-*- coding: UTF-8 -*-
import threading
import asyncore
import datetime
from email.utils import parseaddr
from email.header import decode_header
from smtpd import SMTPServer
from email.parser import Parser
#SMTPD 服务
class SSServer(SMTPServer):
def guess_charset(self, msg):
charset = msg.get_charset()
if charset is None:
content_type = msg.get('Content-Type', '').lower()
pos = content_type.find('charset=')
if pos >= 0:
charset = content_type[pos + 8:].strip()
return charset
def decode_str(self,s):
value, charset = decode_header(s)[0]
if charset:
value = value.decode(charset)
return value
def print_info(self,msg, indent=0):
value_dict = {}
if indent == 0:
for header in ['From', 'To', 'Subject']:
value = msg.get(header, '')
if value:
if header == 'Subject':
value = self.decode_str(value)
else:
hdr, addr = parseaddr(value)
value = self.decode_str(addr)
value_dict[header] = value
# print('%s%s: %s' % (' ' * indent, header, value))
if (msg.is_multipart()):
parts = msg.get_payload()
for n, part in enumerate(parts):
print('%s part %s' % (' ' * indent, n))
print('%s--------------------' % (' ' * indent))
self.print_info(part, indent + 1)
else:
content_type = msg.get_content_type()
if content_type == 'text/plain':
content = msg.get_payload(decode=True)
charset = self.guess_charset(msg)
if charset:
content = content.decode(charset)
value_dict['Text'] = content
# print('%sText: %s' % (' ' * indent, content))
elif content_type == 'text/html':
content = msg.get_payload(decode=True)
charset = self.guess_charset(msg)
if charset:
content = content.decode(charset)
value_dict['Html'] = content
#print('%sHtml: %s' % (' ' * indent, content))
else:
print('%sAttachment: %s' % (' ' * indent, content_type))
return value_dict
# API for "doing something useful with the message"
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
try:
peer = peer
mailfrom = mailfrom
rcpttos = rcpttos
data = data
# peer('127.0.0.1', 44612)
# mailfrom hezhaoning@foxmail.com
# rcpttos['hezhaoning333@163.com']
# data = b'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: base64\nFrom: =?utf-8?b?6I+c6bif5pWZ56iL?=\nTo: =?utf-8?b?5rWL6K+V?=\nSubject: =?utf-8?b?UHl0aG9uIFNNVFAg6YKu5Lu25rWL6K+V?=\n\nUHl0aG9uIOmCruS7tuWPkemAgea1i+ivlS4uLg=='
data = data.decode()
# print(data)
#print(type(data))
# msg = Parser().parsestr(data)
# print(peer)
# print(mailfrom)
# print(rcpttos)
# print(msg)
now_time = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d_%H%M%S')
with open('./emailData-{}.txt'.format(now_time), 'w', encoding='utf-8') as f: # 使用with open()新建对象f
f.write( 'peer : '.format(str(peer)))
f.write('mailfrom : '.format(mailfrom))
f.write('rcpttos : '.format(', '.format(rcpttos)))
f.write(data)
#解析邮件内容
# value = self.print_info(msg)
except Exception as e:
print(str(e))
return
#多线程的方式启动SMTPD服务器
class SMTPServerw():
def __init__(self):
#服务器端口
self.port = 1025
def start(self):
'''Start listening on self.port'''
# create an instance of the SMTP server, derived from asyncore.dispatcher
#创建一个SMTP服务器的实例,它派生自asyncore.dispatche
self.smtp = SSServer(('0.0.0.0', self.port),None)
# self.smtp = SSSsever(('127.0.0.1', self.port), ( 'smtp.qq.com', 25))
print(self.smtp)
# start the asyncore loop, listening for SMTP connection, within a thread
# timeout parameter is important, otherwise code will block 30 seconds
# after the smtp channel has been closed
kwargs = {'timeout':1, 'use_poll': True}
self.thread = threading.Thread(target=asyncore.loop, kwargs=kwargs)
self.thread.start()
def stop(self):
'''Stop listening to self.port'''
# close the SMTPserver to ensure no channels connect to asyncore
self.smtp.close()
# now it is safe to wait for asyncore.loop() to exit
self.thread.join()
smtpd个人理解
该模块提供了几个类来实现SMTP服务器。一种是通用的无所作为的实现,可以被覆盖,另外两种提供特定的邮件发送策略
1. SMTPServer对象
class SMTPServer(localaddr, remoteaddr)
在前文的代码种我们引用的也是这个类,是源码中提供的示例服务器的基类,该对象继承自asyncore.dispatcher,并将自身插入asyncore实例化的事件循环中。它处理与客户端的通信,接收数据,并提供了一个方便的钩子函数,可在消息完全可用后覆盖它以处理消息。localaddr:传入两个参数(主机,端口)元组,用于侦听连接发送到服务器的邮箱信息,如果只侦听本地则是(‘127.0.0.1’,1025)如果是侦听所有连接则是(‘0.0.0.0’, 1025),我暂时remoteaddr 还没有用到过可以填 None
该对象有个方法process_message(peer, mailfrom, rcpttos, data)当完全接收到邮箱消息时,将调用该方法:
peer :客户端的地址,一个包含IP和传入端口的元组。
mailfrom :邮箱中的的“From”信息,在消息传递时由客户机提供给服务器。这并不一定在所有情况下都与From头匹配。
rcpttos :邮箱中的收件人列表。同样,这并不总是与To头匹配,特别是当某人是盲目复制的时候。
data :邮箱内容数据,完整的RFC 2822消息正文
可以在这个方法中打印出这些邮箱内容来 并对他们进行下一步操作做,具体参考代码
2. 调试服务器对象
class smtpd.DebuggingServer(localaddr, remoteaddr)
创建一个新的调试服务器。参数是按照SMTPServer。消息将被丢弃,并打印在stdout上。
3. PureProxy对象
class smtpd.PureProxy(localaddr, remoteaddr)
创建一个新的纯代理服务器。参数是按照SMTPServer。一切都会被传送到remoteaddr。请注意,运行这是一个很好的机会让你成为一个开放的继电器,所以请小心。
4. MailmanProxy对象
class smtpd.MailmanProxy(localaddr, remoteaddr)
创建一个新的纯代理服务器。参数是按照SMTPServer。除非本地邮递员配置知道地址,否则一切都会被转发到remoteaddr,在这种情况下,邮件将通过邮递员处理。请注意,运行这是一个很好的机会让你成为一个开放的继电器,所以请小心。
更多SMTPD的介绍可以看这个网站
https://pymotw.com/2/smtpd/
SMTPD 服务器运行
启动服务器
我使用的是 1025端口,我的服务器ip为192.168.99.204
邮箱我使用的foxmail, 修改邮箱的发件服务器的为 刚才启动的smtp 服务器的 ip跟端口
发送一封邮件到任意服务器
显示邮件发送成功
smtp 服务器收到打印出的邮件内容