python利用smtplib發郵件

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=""))
相關文章
相關標籤/搜索