这个是《计算机网络:自顶向下方法》(Computer Network: A Top Down Approach)第二章的Assignment 3: Mail Client,通过自己动手实现,有助于理解SMPT协议和MIME邮件格式,实验当中的建议是不要使用python里的stmplib库,因为其隐藏了很多细节,下面的代码完成了实验当中的两个附加要求:TTL/SSL加密和发送多媒体文件。
几个关键点
- 是验证身份时使用base64编码,发送消息给邮件服务器也使用了base64
- 发送STARTTLS命令后,服务器要求加密连接后,客户端这里使用SSL模块对socket进行封装,实现加密传输数据。
- 邮件头和邮件体之间要有空行,邮件体内的段头和段文之间也要有空行。
- 分隔用boundary时前面要加两横杆(--boundary),最后消息结尾是boundary前后都要加两横杆(--boundary--)
实验可能需要用的参考资料:
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.')