Python实战之socket实现邮件发送

发布于 2017-07-17  4421 次阅读


python有很多强大的module,邮箱相关的就有smtplib,sendmail等,但是今天ichram想使用socket来实现发送邮件的功能,也是为了学习python,练手为主。

SMTP

smtp是simple mail tranfer protocal的简写,是定义在应用层上的协议,通信的过程双方使用SMTP命令码交换信息,详细资料可以参考 http://www.cnpaf.net/rfc/rfc821.txt 。

源码

下面代码托管于github: https://github.com/icharm/pyTools/blob/master/sendmail.py

#this is a simple demo to send mail by socket module

import socket
import time
import base64
import md5
import random

PATH = "log/sendmail.log"

#getTime according special format
def getTime(format):
    #return the time format like 2017-07-17 15:34:23
    if(format == 1):
        ISOTIMEFORMAT = '%Y-%m-%d %X'
        return time.strftime(ISOTIMEFORMAT, time.localtime());
    #return the time format like Sun Jun 15:34:23 2017
    elif(format == 2):
        return time.asctime( time.localtime(time.time()) )

def printErr(msg):
    print "[-]Error "+ getTime(1) + ' ' + msg +'\n'

def saveLog(msg, flag):
    if flag == 1:
        log = '[-]Error '+ getTime(1) + ' ' + msg +'\n'
    elif flag == 2:
        log = '[-]Warning '+ getTime(1) + ' ' + msg + '\n'
    elif flag == 3:
        log = '[*]Debug '+ getTime(1) + ' '+ msg + '\n'
    else:
        log = '[+]Normal '+ getTime(1) + ' '+ msg + '\n'
    
    try:
        f = open(PATH, 'a')
        f.write(log)
        f.close()
    except Exception, e:
        printErr(str(e))
    
#使用smtp命令码与服务器通信
#fr: mail form
#to: mail to
#data: mail content
def sendMailCore(fr, to, data):
    s = socket.socket()
    #make sure to connect server
    try:
        while True:
            s.connect(("10.204.148.47", 25))
            banner = s.recv(1024)
            if banner[:3] == '220':
                break
            else:
                saveLog("Connect return: "+ banner, 3)
        while True:
            s.send("EHLO kali.mei.com\r\n")
            ret = s.recv(1024)
            if ret[:3] == '250':
                break
            else:
                saveLog("EHLO return: "+ banner, 3 )
        while True:
            s.send("MAIL FROM:<"+ fr +">\r\n")
            ret = s.recv(1024)
            if ret[:3] == '250':
                break
            else:
                saveLog("FROM return: "+ banner, 3)
        while True:
            s.send("RCPT TO:<"+ to +">\r\n")
            ret = s.recv(1024)
            if ret[:3] == '250':
                break
            else:
                saveLog("RCPT return: "+ banner, 3)
        while True:
            s.send("DATA\r\n")
            ret = s.recv(1024)
            if ret[:3] == '354':
                break
            else:
                saveLog("DATA return:"+ banner, 3)
        while True:
            s.send(data.encode('utf-8'))
            s.send("\r\n.\r\n")
            ret = s.recv(1024)
            if ret[:3] == '250':
                break
            else:
                saveLog("DATA OVER return: "+ banner, 3)
        while True:
            s.send("QUIT\r\n")
            ret = s.recv(1024)
            if ret[:3] == '221':
                break
            else:
                saveLog("Line 79 Error", 3)
        
        s.close()
        return True
    except Exception, e:
        saveLog(str(e), 1)
        return False
    
def makeContent(fr, to, subject, data, isHtml=False, Path=None):
    #构造邮件头 和 MIMEtext 分段
    #需要注意的就是boundary="-----=_Part_{random}_=-----" 这行定义了一个邮件的分段标志,这里假设分段标志为A,即boundary="A"。则每一个内容分段开头一行必须是--A,而邮件的结尾必须有A--
    mail_template = '''Date: {date}\r\nFrom: <{from}>\r\nTo: <{to}>\r\nSubject: {subject}\r\nX-priority: {priority}\r\nX-Mailer: Charmail 1.0.0[cn]\r\nMIME-Version: 1.0\r\nContent-Type: multipart/alternative;\r\n\tboundary="-----=_Part_{random}_=-----"\r\n{content}\r\n-------=_Part_{random}_=-------'''
    
    #构造邮件的一个内容分段(该分段的内容类型为text/plain)
    #需要注意的是内容的分段必须以上面定义的分段标志开始,且在分段标志的最前面加上两个短横杠--
    text_content_template = '''-------=_Part_{random}_=-----\r\nContent-Type: text/plain;\n\tcharset="UTF-8"\r\nContent-Transfer-Encoding: base64\r\n\r\n{content}\r\n'''
    
    #构造邮件的一个内容分段(该分段的内容类型为text/html)
    html_content_template = '''-------=_Part_{random}_=-----\r\nContent-Type: text/html;\r\n\tcharset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n{content}\r\n'''
    
    #构造邮件的一个内容分段(该分段的内容类型为zip附件)
    attachment_content_template='''-------=_Part_{random}_=-----\r\nContent-Type: {type};\r\n\tname={name}\r\nContent-Transfer-Encoding: base6\r\nContent-Disposition: attachment;\r\n\tfilename={name}\r\n{content}\r\n'''

    #MIME 支持的文件类型有很多,详细的可以参考http://www.w3school.com.cn/media/media_mimeref.asp
    attachment_type = ['text/plain', 'application/zip', 'image/jpeg']

    #生成一个随机码来表示邮件分段
    #这里icharm是使用随机的截取forn,to,data中的任意长度的字符重新结合并进行md5加密,得到的随机码,理论上重复的可能性很小,但是写的有点麻烦,不推荐
    fr_len = len(fr)
    fr_len_2 = int(fr_len/2)
    to_len = len(to)
    to_len_2 = int(to_len/2)
    sub_len = len(subject)
    sub_len_2 = int(sub_len/2)
    rand = fr[random.randint(0,fr_len_2) : random.randint(fr_len_2,fr_len)] + to[ random.randint(0,to_len_2) : random.randint(to_len_2, to_len)] + subject[ random.randint(0,sub_len_2) : random.randint(sub_len_2, sub_len)]
    md = md5.new()
    md.update(rand)
    rand = md.hexdigest()

    if isHtml == True:
        content = html_content_template
        content = content.replace('{content}', data)
        content = content.replace('{random}', rand)
    else:
        content = text_content_template
        content = content.replace('{content}', base64.b64encode(data))
        content = content.replace('{random}', rand)
    #make attachment content later

    #make the total mail content
    mail_template =  mail_template.replace('{date}', getTime(2))
    mail_template =  mail_template.replace('{from}', fr)
    mail_template =  mail_template.replace('{to}', to)
    mail_template =  mail_template.replace('{subject}', subject)
    mail_template =  mail_template.replace('{priority}', '3')
    mail_template =  mail_template.replace('{random}', rand, 5)
    mail_template =  mail_template.replace('{content}', content)
    
    return mail_template
    
def sendMail(fr, to, subject, data, isHtml=False, Path=None ):
    return sendMailCore(fr, to, makeContent(fr, to, subject, data, isHtml, Path) )

def main():
    result =  sendMail('a@icharm.me', 'b@icharm.me', 'Charmail test' ,'''
            Mail come form Charmail By icharm.me
            ''')
    if result == True:
        print "Successed"
        saveLog("Mail Send Success", 5)
    else:
        print "Failed"
        saveLog("Send Mail Failed", 3)

if __name__ == '__main__':
    main()

参考

http://www.cnpaf.net/rfc/rfc821.txt

http://www.w3school.com.cn/media/media_mimeref.asp


风雨兼程路,雨雪初霁时