python底层socket库实现smpt邮件客户端,使用TTL/SSL加密传输,带附件发送

发布时间 2023-10-10 14:45:05作者: zenglihan

这个是《计算机网络:自顶向下方法》(Computer Network: A Top Down Approach)第二章的Assignment 3: Mail Client,通过自己动手实现,有助于理解SMPT协议和MIME邮件格式,实验当中的建议是不要使用python里的stmplib库,因为其隐藏了很多细节,下面的代码完成了实验当中的两个附加要求:TTL/SSL加密和发送多媒体文件。

几个关键点

  1. 是验证身份时使用base64编码,发送消息给邮件服务器也使用了base64
  2. 发送STARTTLS命令后,服务器要求加密连接后,客户端这里使用SSL模块对socket进行封装,实现加密传输数据。
  3. 邮件头和邮件体之间要有空行,邮件体内的段头和段文之间也要有空行。
  4. 分隔用boundary时前面要加两横杆(--boundary),最后消息结尾是boundary前后都要加两横杆(--boundary--)

实验可能需要用的参考资料:

书籍配套网站(实验下载地址及实验说明)

腾讯云的SMPT文档(简述了邮件MIME格式)

RFC2821(附录D,SMTP对话示例)

RFC2822(附录A,邮件头、简单文本邮件体格式示例)

RFC2912(4.1 MIME邮件体示例)

qq邮箱里,通过显示邮件原文,可以查看邮件MIME格式

代码如下:

  1 import socket
  2 import ssl
  3 import base64
  4 
  5 msg = '\r\nI love computer networks!\r\n'
  6 endmsg = '\r\n.\r\n'
  7 
  8 # Base64
  9 username = 'MTxxxxxxxc1OA==\r\n'
 10 passwd = 'ZWxxxxxxmNpYQ==\r\n'
 11 
 12 # Choose a mail server (e.g Google mail server) and call it mailserver
 13 mailServer = ('smtp.qq.com', 587)
 14 # Create socket called clientsocket to establish TCP connection with mailserver
 15 clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 16 clientsocket.connect(mailServer)
 17 recv = clientsocket.recv(1024).decode()
 18 print(recv)
 19 if recv[:3] != '220':
 20     print('220 reply not received from server.')
 21 
 22 # Send EHLO command and print server response.
 23 helocommand = 'EHLO gmail.com\r\n'
 24 clientsocket.send(helocommand.encode())
 25 recv1 = clientsocket.recv(1024).decode()
 26 print(recv1)
 27 if recv1[:3] != '250':
 28     print('250 reply not received from server.')
 29 
 30 # Send STARTTLS command
 31 startcommand = 'STARTTLS\r\n'
 32 clientsocket.send(startcommand.encode())
 33 recvTemp = clientsocket.recv(1024).decode()
 34 print(recvTemp)
 35 
 36 # create TLS/SSL security socket
 37 ssl_socket = ssl.wrap_socket(clientsocket)
 38 
 39 # Send again EHLO command and print server response.
 40 helocommand = 'EHLO gmail.com\r\n'
 41 ssl_socket.send(helocommand.encode())
 42 recv1 = ssl_socket.recv(1024).decode()
 43 print(recv1)
 44 if recv1[:3] != '250':
 45     print('250 reply not received from server.')
 46 
 47 # auth-login
 48 temp = 'AUTH LOGIN\r\n'
 49 ssl_socket.send(temp.encode())
 50 recvTemp = ssl_socket.recv(1024).decode()
 51 print(recvTemp)
 52 
 53 ssl_socket.send(username.encode())
 54 recvTemp = ssl_socket.recv(1024).decode()
 55 print(recvTemp)
 56 
 57 ssl_socket.send(passwd.encode())
 58 recvTemp = ssl_socket.recv(1024).decode()
 59 print(recvTemp)
 60 
 61 # Send MAIL FROM command and print server response.
 62 MailFrcommand = 'MAIL FROM: <10xxxxx58@qq.com>\r\n'
 63 ssl_socket.send(MailFrcommand.encode())
 64 recv2 = ssl_socket.recv(1024).decode()
 65 print(recv2)
 66 if recv2[:3] != '250':
 67     print('250 reply not received from server.')
 68 
 69 # Send RCPT TO command and print server response.
 70 RCPTcommand = 'RCPT TO: <16xxxxxx01@qq.com>\r\n'
 71 ssl_socket.send(RCPTcommand.encode())
 72 recv3 = ssl_socket.recv(1024).decode()
 73 print(recv3)
 74 if recv3[:3] != '250':
 75     print('250 reply not received from server.')
 76 
 77 # Send DATA command and print server response.
 78 DATAcommand = 'DATA\r\n'
 79 ssl_socket.send(DATAcommand.encode())
 80 recv4 = ssl_socket.recv(1024).decode()
 81 print(recv4)
 82 if recv4[:3] != '354':
 83     print('354 reply not received from server.')
 84 
 85 # Send header data
 86 header = 'From: <106xxxxxx89@qq.com>\r\n'
 87 header += 'To: <16xxxxx01@qq.com>\r\n'
 88 header += 'Subject: Nothing\r\n'
 89 header += 'MIME-Version: 1.0\r\n'
 90 header += 'Content-Type: multipart/mixed;boundary=boundary123\r\n'
 91 ssl_socket.send(header.encode())
 92 
 93 # Send message data.
 94 messageBody = '\r\n--boundary123\r\n'
 95 messageBody += 'Content-Type: text/plain\r\n'
 96 messageBody += msg
 97 
 98 with open('output1.png', 'rb') as img:
 99     imgData = img.read()
100     img64 = base64.b64encode(imgData).decode()
101     messageBody += '\r\n--boundary123\r\n'
102     messageBody += 'Content-Type: image/png\r\n'
103     messageBody += 'Content-Transfer-Encoding: base64\r\n'
104     messageBody += 'Content-Disposition: attachment; filename=output1.png\r\n'
105     messageBody += '\r\n' + img64 + '\r\n'
106 
107 messageBody += '\r\n--boundayr123--'
108 ssl_socket.send(messageBody.encode())
109 
110 # Message ends with a single period.
111 ssl_socket.send(endmsg.encode())
112 recv5 = ssl_socket.recv(1024).decode()
113 print(recv5)
114 if recv5[:3] != '250':
115     print('250 reply not received from server.')
116 
117 # Send QUIT command and print server response.
118 qcommand = 'QUIT\r\n'
119 ssl_socket.send(qcommand.encode())
120 recv6 = ssl_socket.recv(1024).decode()
121 print(recv6)
122 if recv6[:3] != '221':
123     print('221 reply not received from server.')