你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

你是否需要每天使用电子邮件服务?

电子邮件(email)是互联网上历史悠久又常用的消息收发形式。对于大多数办公室一族,每天到班上的第一件事恐怕就是要查一下新的邮件。虽然即时通信工具在飞速占领着通信市场,但是在商业或者学术圈里,email依然占据着主流地位。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

当你点击“发送”之后,你的邮箱做了那些操作?今天的实验带你亲自看一看。

引言:互联网协议和SMTP协议

在之前的一篇文章(点击这里查看)中,笔者讨论了互联网协议的几个层,并且构建实验探讨了提供网页服务的HTTP服务器如何工作。如果你也喜欢探索原理,并且还没有做过这个实验,那么强烈建议你点开连接,跟着文章中设计的实验探索一下HTTP协议。这里,我们会做一个类似的实验来窥探email收发使用的SMTP协议。

简单概括原理:我们的互联网分为四个层,每一层的正常工作建立在下面层的基础上。工作在最上层的“应用层”,有提供网页服务的HTTP协议,提供邮件收发的SMTP协议,提供文件传输的FTP协议等等。这些协议想要正常工作,都要基于下面“传输层”的支持。传输层比较常用的是TCP协议。今天的实验里,我们将在SMTP层和TCP层两个层面上观察SMTP协议,并且在TCP层上构造一个简单的,需要手动控制的“SMTP服务器”。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

这张图诠释了SMTP连接。当我们说SMTP通信时,它其实是一种虚拟的,抽象的说法。真正建立连接的,是在下面的层上

实验0(准备工作):查看email服务器地址

在真正开始实验之前,我们先看一下如何从一个email地址出发,查询它对应的SMTP服务器域名。比如 [email protected] ,我们知道它的email域名是 126.com 。但是我们需要知道, 126.com 背后的SMTP服务器地址是多少。为了便于区分,通常管 126.com 叫做 email域名 ,而其背后的SMTP服务器地址,叫做 mx域名 。(mx是mail exchange的缩写。)

这里我们使用工具 nslookup 查询mx域名。无论你使用的是Windows系统,还是Mac OS,还是Linux, nslookup 都已经存在于你的电脑里了。使用它的步骤如下:

  1. 打开命令行。Windows系统:打开“开始”菜单,输入"cmd",搜索到“命令提示行”工具。打开后界面如下。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

windows中的命令提示行

Mac OS在应用程序中找到"Terminal"。Linux我就不说命令行在哪里了。

  1. 在命令行中输入 nslookup 按回车,进入 nslookup 工具中。输入 set q=mx ,指定查询mx域名。输入 126.com ,按下回车,你就会得到查询结果。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

nslookup查询126.com的mx域名

图中可以看到, 126.com email域名背后有4个mx服务器。后面的讨论中,使用任何一个mx服务器(比如“126mx01.mxmail.netease.com”)都可以。友情提示:复制mx域名时,注意不要把最后的句号复制上了。

实验1(应用层):使用Python发送email

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

当然,很多时候这样的邮件会被对方SMTP服务器拒收。即时接收了,也有可能因为来源不明而被放到垃圾邮件里。所以,并不建议读者用这种收发日常邮件。但是为了弄懂SMTP的协议,这样做一两次还是值得的。

话不多说,先上一个完整的邮件发送的截图。注意,我作为发件人,并没有登录任何自己的邮箱。另外,注意变量 s_body 的格式。大部分邮件服务器对这个格式很看重。不符合这个格式的邮件经常会被拒绝。最后,注意在 server.connect 那一行运行之后,后面手速一定要快。隔一会再执行下一行的话,对方服务器通常会断开。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

Python中使用smtplib发送邮件。要先把收件人地址、发件人地址和邮件内容都实现编辑好,存在变量中。避免连接到服务器之后,由于超时没有响应而导致服务器断开连接

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

实验2 (传输层):使用TCP socket假装自己是个SMTP服务器

怎么才能搞清楚 server.sendmail 背着你跟服务器干了什么事情呢?好吧,换个问题:假设你怀疑你对象在网上见了漂亮妹子/帅气男生就勾搭,怎么才能抓住他/她的把柄呢?一个方法就是,自己注册个上网账号,把自己伪装成漂亮妹子/帅气男生,跟他/她聊。

我们知道,像 requests 一样, smtplib 要想进行SMTP通信,一定会使用下面传输层的TCP协议,跟对方的SMTP服务器建立TCP连接。所以,我们就像上一篇文章那样,准备一个TCP连接,把对方发过来的数据都显示到屏幕上,具体哪些消息,什么格式,就一目了然了。

跟http请求不同的是, server.sendmail 不是一个 单次 的请求/响应,而是要求双方使用协议规定的格式 反复提问回答几次 才可以完成邮件发送。因此,我们的服务器里也要仔细设置响应的内容,确保返回的东西符合格式,使得对话能继续进行。(也就是说,想假装自己是SMTP服务器,比假装自己是HTTP服务器要难一点,穿帮的可能性也更大一点。)为了增强体验感,我们在每次收到信息时,让我们手动填写返回内容。

我们先来看代码。

"""
这是一个虚拟服务器。当任何程序简介到它时,它先发送一条欢迎信息WELCOME_MSG,
然后等待对方发送信息。对方每发送一条信息,它就会把信息显示到屏幕上,然后提示
我们输入应答内容。紧接着,它会把我们输入的内容后面加上换行符
,发送回去。
"""
SERVER_IP = "localhost"
SERVER_PORT = 25 #默认的SMTP之一
MAX_LENGTH = 1023 #规定每条信息长度上限。
WELCOME_MSG = "220 Virtual Server At Your Service!
" #欢迎信息
socket_list = []
import socket
def close_sockets(): #再程序出现异常退出时关闭所有端口,避免端口占用
 for sock in socket_list:
 sock.close()
def main():
 
 sock_listen = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 socket_list.append(sock_listen)
 sock_listen.bind((SERVER_IP, SERVER_PORT))
 sock_listen.listen(1)
 conn, addr = sock_listen.accept()
 socket_list.append(conn)
 # Once connection is built: send welcome message and print connection
 print("Connection established. From: " + str(addr))
 if WELCOME_MSG is not None and WELCOME_MSG != '': # 如果WELCOME_MSG为''或者None,
 # 则不发送欢迎信息,链接建立后
 # 直接进入接收信息状态
 conn.send(WELCOME_MSG.encode())
 print("欢迎信息发出!")
 
 while True:
 print("等待对方应答... 信息长度上限: " + str(MAX_LENGTH))
 data = conn.recv(MAX_LENGTH)
 print("收到信息:
", data)
 if len(data) == 0:
 break
 reply_msg = input("您的回复: ")
 reply_msg += '
'
 conn.send(reply_msg.encode())
 print("回复消息发出!")
if __name__ == "__main__":
 try:
 main()
 except Exception as err:
 print(str(err))
 close_sockets()
 exit()

这段代码比较直接。代码里的注释或者 print 的提示字都描述着每一部分代码的功能。

有了这个人工服务器,我们就可以拿它接收 smtplib 发来的请求了。前面的演示用了Windows和Mac OS的电脑。为了不偏心,这里就用Linux的电脑做演示了。(我才不会告诉你,其实是因为Windows电脑老婆在用,而Mac电脑落在办公室里了T_T)

  1. 服务器开启。注意由于使用了25端口(SMTP协议的默认端口之一),为系统预留端口,因此程序需要管理员权限。第一幅图中,第一次尝试由于没有用管理员权限 sudo 而被拒绝执行了。加了 sudo 程序得以启动,并开启端口,等待连接。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

开启服务器。第一次由于没有使用管理员权限而被拒绝。第二次成功开启,进入等待连接状态

  1. 使用Python3的 smtplib 连接SMTP服务器。这一步跟上面实验是一样的。注意 server.connect 连接的是 'localhost' 。此时,右边图中服务器也显示收到了连接,并发送了欢迎消息'220 Virtual Server At Your Service!' 这时从 smtplib 接收到的信息来看,它识别了这种 状态码<空格>回复信息 的格式,返回了一个二元素的数组。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

进群:548377875  即可获取数十套PDF以及大量学习资料!

python的smtp连接,服务器发送来欢迎信息

接下来,我们就重复上面实验中的做法,定义 s_from , s_to 和 s_msg ,然后交给 server.sendmail 函数来以邮件形式发送出去。见下图。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

使用smtplib的sendmail发送邮件

  1. 接下来的图 很重要! 它显示了sendmail函数执行后,服务器上收到的 一连串信息 。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

sendmail函数给服务器发送的一系列SMTP消息。从`helo`开始

注意,这里为了把换行符也都显示出来,特意没有将Bytes型字符串解码成普通的Python3字符串(即,没有调用 decode 方法)。所以图里每条信息前面都有个 b 。

这段对话是这样的:

sendmail: helo [自己地址]

我: 250 Nice to meet you # 注意格式是“状态代码<空格>响应信息”。

sendmail: mail FROM:<发件人地址>

我: 250 Ok

sendmail: rcpt TO: <收件人地址>

我: 250 Ok

sendmail: data # 这个 data 单词是告诉对方,注意,我后面要开始发送正文了!

我: 354 Go ahead # 这里状态码也不再是250,而是354,表示“我等你发信息”

sendmail: <邮件正文> # 注意:这段文字最后的 . 是SMTP协议定义的data结束符号。

我: 250 Received

到这里, sendmail 函数就完成了它的任务,发出一封邮件。其实, sendmail 正常工作,分析的是每一个请求对方发来的状态码(250, 354这些)。比如 sendmail 发送 data 字符的时候,如果你还是回复250而不是354的话, sendmail 会认为你这个服务器有问题,就不再理你了。与之相比,后面的响应信息的具体内容,SMTP协议是没有具体要求的。所以才会有五花八门的回复。比如,gmail的服务器响应 helo 的内容是"at your service",而我这里写的是"Nice to meet you"。

一切发送完毕后, sendmail 返回了熟悉的 {} ,即空字典,表示信息发送成功了。后面我又调用了 server.quit() 结束对话。从下图可以看到,这个函数在断开连接之前,先给服务器发送了 quit 信息。我响应了 221 bye 之后,它才关闭了TCP连接。

你发邮件出去的时候!你的电脑泄露了这些东西?你还不知道吧?

sendmail返回`{}`之后,调用quit函数发送“结束通信”的消息

总结

这一篇实验有点长。在准备阶段(实验0),我们了解了如何使用 nslookup 查询一个邮件域名对应的MX服务器地址。实验1中,我们使用Python的 smtplib 包发送邮件,观察了在应用层(SMTP层)上的情况,并且掌握了 smtplib 的使用方法。实验2中,我们下潜到TCP层,开启了一个简单的人工SMTP服务器,接收 smtplib 发来的邮件发送请求。看到了 helo , mail FROM , rcpt TO , data , quit 这些标准的SMTP请求报文,也了解了服务器响应信息的"状态代码<空格>响应消息"格式。顺便说一下,其实 smtplib 里也是提供了 server.helo , server.mail , server.rcpt , server.data 这些函数的。有兴趣的读者可以自己尝试一下。

通过亲手进行这个实验,相信读者会对SMTP协议有一个更直观的了解,以后再发邮件的时候,脑子里会不会自动浮现出你的邮件管理程序在背后发送的这一连串请求呢?

感谢您的支持,如果您有任何疑问,或者建议,或者还想看什么简单的探索计算机的小实验,欢迎给我留言!

猜你喜欢

转载自blog.csdn.net/qq_42156420/article/details/83958108