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=""))