python之因此強大,是由於它支持的庫不少,光標準庫就知足了大部分的經常使用需求,工做中遇到收發郵件過防火牆的場景,一端發一個病毒穿過防火牆到達另外一端,檢測是否可以被防火牆攔住,這個功能是很是普通的,但又很是重要,基本上每次必測以內容html
SMTP通常收發流程python
發信人在用戶代理如foxmail中編輯郵件,包括填寫發信人,收信人,標題等 用戶代理提取發信人編輯的信息,生成一封符合郵件標準格式的(RFC822)的郵件 用戶代理用SMTP將郵件發送到發送郵件服務器上(發信人郵箱所對應的sendmail server) 發送端郵件服務器用SMTP將郵件發送到接收郵件服務器上(pop3 server) 收信人再調用用戶代理,用pop3協議取回郵件 用戶代理解解析收到的郵件 ,以適當的形式呈如今收信人面前
測試流程:服務器
在LAN端搭一個SMTP server 在WAN端用foxmail客戶端鏈接SMTP server發送一個病毒給LAN端的PC 此時病毒以附件的形式發送到LAN Server, 穿過防火牆,防火牆能夠檢測的到
python實現自動發送郵件思路session
smtplib模塊負責發送郵件 email模塊負責發送的內容 發送郵件分三種形式 正文 附件 圖片
smtplib模塊介紹less
server = smtplib.SMTP(smtp server) 實例化SMTP server.docmd(cmd) 仿telenet 郵箱服務器中直接輸入命令 server.login(username,password) SMTP服務器須要驗證用戶才能發郵件 server.sendmail(from_mail, to_mail,msg) 核心轉發程序,把msg從from_mail發送到to_mail郵箱中
email模塊介紹函數
MIMEMultipart類負責多種類消息載體 MIMEImage類負責圖片載體 MIMEText類負責文字載體 MIMEBase類負責通常文件附件載體 MIMEImage __init__(self, _imagedata, _subtype=None, _encoder=<function encode_base64>, **_params) MIMEImage(file(filename,'rb').read()) MIMEText __init__(self, _text, _subtype='plain', _charset='us-ascii') MIMEText(body_text,'html','utf-8') MIMEBase __init__(self, _maintype, _subtype, **_params) MIMEBase('text',filename).set_payload(fp.read())
SMTP 發送郵件命令模式,理論上均可以用smtplib.SMTP(host).docmd()完成測試
[root@hding qa]# telnet 10.8.116.6 25 Trying 10.8.116.6... Connected to 10.8.116.6 (10.8.116.6). Escape character is '^]'. 220 hding.com ESMTP Sendmail 8.13.8/8.13.8; Tue, 16 Aug 2016 20:00:57 +0800 ehlo 10.8.116.6 #服務器支持信息 250-hding.com Hello [10.8.116.6], pleased to meet you 250-ENHANCEDSTATUSCODES 250-PIPELINING 250-8BITMIME 250-SIZE 250-DSN 250-ETRN 250-AUTH DIGEST-MD5 CRAM-MD5 LOGIN PLAIN #驗證方式 250-STARTTLS #支持STARTTLS 250-DELIVERBY 250 HELP auth login 334 VXNlcm5hbWU6 aGRpbmc= #只接受base64編碼 334 UGFzc3dvcmQ6 aGRpbmc= #只接受base64編碼 235 2.0.0 OK Authenticated mail from: hding@hding.com 250 2.1.0 hding@hding.com... Sender ok rcpt to : qa@ding.com 250 2.1.5 qa@ding.com... Recipient ok data 354 Enter mail, end with "." on a line by itself "hello world" I am terry please welcome me . #結束正文 250 2.0.0 u7GC0v9x027721 Message accepted for delivery quit 221 2.0.0 hding.com closing connection Connection closed by foreign host.
內部郵件服務器測試腳本ui
#!/usr/bin/env python #coding:utf-8 from smtplib import SMTP from smtplib import SMTP_SSL from email import encoders from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email.mime.image import MIMEImage class DPI_SSL_SMTPS(object): def __init__(self,from_mail, to_mail,subject,username='username',password='password', port=465,host='192.168.10.3'): self.from_mail = from_mail self.to_mail = to_mail self.server = SMTP_SSL(host,port) #採起SMTP_SSL的方式,端口465 #self.server.starttls() self.server.docmd('ehlo','192.168.10.3') #與服務器'ehlo' self.server.login(username,password) #登陸SMTP服務器 self.msg = MIMEMultipart() #定義消息載體 self.msg['From']= from_mail #消息from,to,subject字段不可省 self.msg['To']= to_mail self.msg['Subject']=subject def send_text(self,filename): "send text as a body mail" fp = open(filename,'r') #這邊的正文內容是讀取了一個文件中的內容寫在正文中 content = fp.read() fp.close() con = MIMEText(content,'plain','utf-8') self.msg.attach(con) #把正文內容加到消息載體中 def send_image(self,filename): "send mail with image" img = MIMEImage(file(filename,'rb').read()) #發送圖片附件 img.add_header('Content-ID','1') #增長頭信息cid,是爲了之後若是要在正文中引用"<image src='cid:1'>"' img.add_header("Content-Disposition","attachment",filename=filename) #增長附件 self.msg.attach(img) #圖片附件進入消息載體 def send_attachment(self,filename): "send mail with attachment" fp = open(filename,'rb') mime = MIMEBase('text',filename) mime.add_header('Content-Disposition','attachment',fiename=filename) mime.set_payload(fp.read()) #把文件中的內容寫進payload做爲附件 encoders.encode_base64(mime) fp.close() self.msg.attach(mime) def send_mail(self): self.server.sendmail(self.from_mail,self.to_mail,self.msg.as_string()) self.server.quit() if __name__ == '__main__': server = DPI_SSL_SMTPS('hding@hding.com','qa@ding.com','test_mail') server.send_attachment("test_file") server.send_image('2.jpg') server.send_text("test_file") server.send_mail()
遇到的問題this
smtplib.SMTPAuthenticationError: (535, 'Error: authentication failed')
這個問題是認證的問題,可是用命令行已經驗證了用戶口令正確,但執行時就會提示錯誤,緣由在於認證的方式不一樣,命令行用的是AUTH_LOGIN,而smtplib時的login函數用的倒是MD5,查看smtplib.py文件編碼
def login(self, user, password): """Log in on an SMTP server that requires authentication. The arguments are: - user: The user name to authenticate with. - password: The password for the authentication. If there has been no previous EHLO or HELO command this session, this method tries ESMTP EHLO first. This method will return normally if the authentication was successful. This method may raise the following exceptions: SMTPHeloError The server didn't reply properly to the helo greeting. SMTPAuthenticationError The server didn't accept the username/ password combination. SMTPException No suitable authentication method was found. """ def encode_cram_md5(challenge, user, password): challenge = base64.decodestring(challenge) response = user + " " + hmac.HMAC(password, challenge).hexdigest() return encode_base64(response, eol="") def encode_plain(user, password): return encode_base64("\0%s\0%s" % (user, password), eol="") AUTH_PLAIN = "PLAIN" AUTH_CRAM_MD5 = "CRAM-MD5" AUTH_LOGIN = "LOGIN" self.ehlo_or_helo_if_needed() if not self.has_extn("auth"): raise SMTPException("SMTP AUTH extension not supported by server.") # Authentication methods the server supports: authlist = self.esmtp_features["auth"].split() # List of authentication methods we support: from preferred to # less preferred methods. Except for the purpose of testing the weaker # ones, we prefer stronger methods like CRAM-MD5: preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN] # Determine the authentication method we'll use authmethod = None for method in preferred_auths: if method in authlist: authmethod = method break if authmethod == AUTH_CRAM_MD5: (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5) if code == 503: # 503 == 'Error: already authenticated' return (code, resp) (code, resp) = self.docmd(encode_cram_md5(resp, user, password)) elif authmethod == AUTH_PLAIN: (code, resp) = self.docmd("AUTH", AUTH_PLAIN + " " + encode_plain(user, password)) elif authmethod == AUTH_LOGIN: (code, resp) = self.docmd("AUTH", "%s %s" % (AUTH_LOGIN, encode_base64(user, eol=""))) if code != 334: raise SMTPAuthenticationError(code, resp) (code, resp) = self.docmd(encode_base64(password, eol="")) elif authmethod is None: raise SMTPException("No suitable authentication method found.") if code not in (235, 503): # 235 == 'Authentication successful' # 503 == 'Error: already authenticated' raise SMTPAuthenticationError(code, resp) return (code, resp)
preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]列表第一個是MD5,所以匹配到MD5就直接MD5認證了,若是想LOGIN認證的話,只須要把列表的順序改一下就好
preferred_auths = [AUTH_LOGIN,AUTH_CRAM_MD5, AUTH_PLAIN] 便可
固然還容易遇到這個問題的緣由是由於smtp的用戶名和密碼必需是通過base64編碼過的,若是用c或其它語方純編的話,這邊就要當心,須要編碼,而smtplib沒有這個顧慮,由於已經寫好了
elif authmethod == AUTH_LOGIN: (code, resp) = self.docmd("AUTH", "%s %s" % (AUTH_LOGIN, encode_base64(user, eol=""))) if code != 334: raise SMTPAuthenticationError(code, resp) (code, resp) = self.docmd(encode_base64(password, eol=""))