python smtplib模块发送SSL/TLS安全邮件实例


Posted in Python onApril 08, 2015

python的smtplib提供了一种很方便的途径发送电子邮件。它对smtp协议进行了简单的封装。

smtp协议的基本命令包括:

HELO 向服务器标识用户身份
MAIL 初始化邮件传输 mail from:
RCPT 标识单个的邮件接收人;常在MAIL命令后面,可有多个rcpt to:
DATA 在单个或多个RCPT命令后,表示所有的邮件接收人已标识,并初始化数据传输,以.结束
VRFY 用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常禁止此命令
EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用
HELP 查询服务器支持什么命令
NOOP 无操作,服务器应响应OK
QUIT 结束会话
RSET 重置会话,当前传输被取消
MAIL FROM 指定发送者地址
RCPT TO 指明的接收者地址

一般smtp会话有两种方式,一种是邮件直接投递,就是说,比如你要发邮件?zzz@163.com,那就直接连接163.com的邮件服务器,把信投?zzz@163.com; 另一种是验证过后的发信,它的过程是,比如你要发邮件?zzz@163.com,你不是直接投到163.com,而是通过自己在sina.com的另一个邮箱来发。这样就要先连接sina.com的smtp服务器,然后认证,之后在把要发到163.com的信件投到sina.com上,sina.com会帮你把信投递到163.com。

第一种方式的命令流程基本是这样:
1. helo
2. mail from
3. rcpt to
4. data
5. quit

但是第一种发送方式一般有限制的,就是rcpt to指定的这个邮件接收者必须在这个服务器上存在,否则是不会接收的。 先看看代码:

#-*- encoding: gb2312 -*-

import os, sys, string

import smtplib
# 邮件服务器地址

mailserver = "smtp.163.com"

# smtp会话过程中的mail from地址

from_addr = "asfgysg@zxsdf.com"

# smtp会话过程中的rcpt to地址

to_addr = "zhaoweikid@163.com"

# 信件内容

msg = "test mail"
svr = smtplib.SMTP(mailserver)

# 设置为调试模式,就是在会话过程中会有输出信息

svr.set_debuglevel(1)

# helo命令,docmd方法包括了获取对方服务器返回信息

svr.docmd("HELO server")

# mail from, 发送邮件发送者

svr.docmd("MAIL FROM: <%s>" % from_addr)

# rcpt to, 邮件接收者

svr.docmd("RCPT TO: <%s>" % to_addr)

# data命令,开始发送数据

svr.docmd("DATA")

# 发送正文数据

svr.send(msg)

# 比如以 . 作为正文发送结束的标记,用send发送的,所以要用getreply获取返回信息

svr.send(" . ")

svr.getreply()

# 发送结束,退出

svr.quit()

注意的是,163.com是有反垃圾邮件功能的,想上面的这种投递邮件的方法不一定能通过反垃圾邮件系统的检测的。所以一般不推荐个人这样发送。

 第二种有点不一样:

1.ehlo
2.auth login
3.mail from
4.rcpt to
5.data
6.quit

相对于第一种来说,多了一个认证过程,就是auth login这个过程。

#-*- encoding: gb2312 -*-

import os, sys, string

import smtplib

import base64
# 邮件服务器地址

mailserver = "smtp.163.com"

# 邮件用户名

username = "xxxxxx@163.com"

# 密码

password = "xxxxxxx"

# smtp会话过程中的mail from地址

from_addr = "xxxxxx@163.com"

# smtp会话过程中的rcpt to地址

to_addr = "yyyyyy@163.com"

# 信件内容

msg = "my test mail"
svr = smtplib.SMTP(mailserver)

# 设置为调试模式,就是在会话过程中会有输出信息

svr.set_debuglevel(1)

# ehlo命令,docmd方法包括了获取对方服务器返回信息

svr.docmd("EHLO server")

# auth login 命令

svr.docmd("AUTH LOGIN")

# 发送用户名,是base64编码过的,用send发送的,所以要用getreply获取返回信息

svr.send(base64.encodestring(username))

svr.getreply()

# 发送密码

svr.send(base64.encodestring(password))

svr.getreply()

# mail from, 发送邮件发送者

svr.docmd("MAIL FROM: <%s>" % from_addr)

# rcpt to, 邮件接收者

svr.docmd("RCPT TO: <%s>" % to_addr)

# data命令,开始发送数据

svr.docmd("DATA")

# 发送正文数据

svr.send(msg)

# 比如以 . 作为正文发送结束的标记

svr.send(" . ")

svr.getreply()

# 发送结束,退出

svr.quit()
   
上面说的是最普通的情况,但是不能忽略的是现在好多企业邮件是支持安全邮件的,就是通过SSL发送的邮件,这个怎么发呢?SMTP对SSL安全邮件的支持有两种方案,一种老的是专门开启一个465端口来接收ssl邮件,另一种更新的做法是在标准的25端口的smtp上增加一个starttls的命令来支持。

看看第一种怎么办:

#-*- encoding: gb2312 -*-

import os, sys, string, socket

import smtplib


class SMTP_SSL (smtplib.SMTP):

    def __init__(self, host='', port=465, local_hostname=None, key=None, cert=None):

        self.cert = cert

        self.key = key

        smtplib.SMTP.__init__(self, host, port, local_hostname)

        

    def connect(self, host='localhost', port=465):

        if not port and (host.find(':') == host.rfind(':')):

            i = host.rfind(':')

            if i >= 0:

                host, port = host[:i], host[i+1:]

                try: port = int(port)

                except ValueError:

                    raise socket.error, "nonnumeric port"

        if not port: port = 654

        if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)

        msg = "getaddrinfo returns an empty list"

        self.sock = None

        for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):

            af, socktype, proto, canonname, sa = res

            try:

                self.sock = socket.socket(af, socktype, proto)

                if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)

                self.sock.connect(sa)

                # 新增加的创建ssl连接

                sslobj = socket.ssl(self.sock, self.key, self.cert)

            except socket.error, msg:

                if self.debuglevel > 0: 

                    print>>stderr, 'connect fail:', (host, port)

                if self.sock:

                    self.sock.close()

                self.sock = None

                continue

            break

        if not self.sock:

            raise socket.error, msg
        # 设置ssl

        self.sock = smtplib.SSLFakeSocket(self.sock, sslobj)

        self.file = smtplib.SSLFakeFile(sslobj);
        (code, msg) = self.getreply()

        if self.debuglevel > 0: print>>stderr, "connect:", msg

        return (code, msg)

        

if __name__ == '__main__':

    smtp = SMTP_SSL('192.168.2.10')

    smtp.set_debuglevel(1)

    smtp.sendmail("zzz@xxx.com", "zhaowei@zhaowei.com", "xxxxxxxxxxxxxxxxx")

    smtp.quit()
   

这里我是从原来的smtplib.SMTP派生出了新的SMTP_SSL类,它专门来处理ssl连接。我这里测试的192.168.2.10是我自己的测试服务器.

第二种是新增加了starttls的命令,这个很简单,smtplib里就有这个方法,叫smtplib.starttls()。当然,不是所有的邮件系统都支持安全邮件的,这个需要从ehlo的返回值里来确认,如果里面有starttls,才表示支持。相对于发送普通邮件的第二种方法来说,只需要新增加一行代码就可以了:

#-*- encoding: gb2312 -*-

import os, sys, string

import smtplib

import base64
# 邮件服务器地址

mailserver = "smtp.163.com"

# 邮件用户名

username = "xxxxxx@163.com"

# 密码

password = "xxxxxxx"

# smtp会话过程中的mail from地址

from_addr = "xxxxxx@163.com"

# smtp会话过程中的rcpt to地址

to_addr = "yyyyyy@163.com"

# 信件内容

msg = "my test mail"
svr = smtplib.SMTP(mailserver)

# 设置为调试模式,就是在会话过程中会有输出信息

svr.set_debuglevel(1)

# ehlo命令,docmd方法包括了获取对方服务器返回信息,如果支持安全邮件,返回值里会有starttls提示

svr.docmd("EHLO server")

svr.starttls()  # <------ 这行就是新加的支持安全邮件的代码!

# auth login 命令

svr.docmd("AUTH LOGIN")

# 发送用户名,是base64编码过的,用send发送的,所以要用getreply获取返回信息

svr.send(base64.encodestring(username))

svr.getreply()

# 发送密码

svr.send(base64.encodestring(password))

svr.getreply()

# mail from, 发送邮件发送者

svr.docmd("MAIL FROM: <%s>" % from_addr)

# rcpt to, 邮件接收者

svr.docmd("RCPT TO: <%s>" % to_addr)

# data命令,开始发送数据

svr.docmd("DATA")

# 发送正文数据

svr.send(msg)

# 比如以 . 作为正文发送结束的标记

svr.send(" . ")

svr.getreply()

# 发送结束,退出

svr.quit()

注意: 以上的代码为了方便我都没有判断返回值,严格说来,是应该判断一下返回的代码的,在smtp协议中,只有返回代码是2xx或者3xx才能继续下一步,返回4xx或5xx的,都是出错了。
Python 相关文章推荐
wxpython 最小化到托盘与欢迎图片的实现方法
Jun 09 Python
跟老齐学Python之编写类之二方法
Oct 11 Python
python中__call__方法示例分析
Oct 11 Python
使用PDB模式调试Python程序介绍
Apr 05 Python
利用python发送和接收邮件
Sep 27 Python
安装Python和pygame及相应的环境变量配置(图文教程)
Jun 04 Python
TensorFlow 合并/连接数组的方法
Jul 27 Python
python将一个英文语句以单词为单位逆序排放的方法
Dec 20 Python
python3 打印输出字典中特定的某个key的方法示例
Jul 06 Python
Python 词典(Dict) 加载与保存示例
Dec 06 Python
解决Pymongo insert时会自动添加_id的问题
Dec 05 Python
用Python仅20行代码编写一个简单的端口扫描器
Apr 08 Python
python复制与引用用法分析
Apr 08 #Python
Python导入txt数据到mysql的方法
Apr 08 #Python
python集合类型用法分析
Apr 08 #Python
在Python中使用Mako模版库的简单教程
Apr 08 #Python
python中requests模块的使用方法
Apr 08 #Python
介绍Python中几个常用的类方法
Apr 08 #Python
python自然语言编码转换模块codecs介绍
Apr 08 #Python
You might like
php下网站防IP攻击代码,超级实用
2010/10/24 PHP
PHP实现二维数组中的查找算法小结
2018/06/09 PHP
翻译整理的jQuery使用查询手册
2007/03/07 Javascript
JavaScript 判断指定字符串是否为有效数字
2010/05/11 Javascript
PHP 与 js的通信(via ajax,json)
2010/11/16 Javascript
jQuery中position()方法用法实例
2015/01/16 Javascript
解决jquery中动态新增的元素节点无法触发事件问题的两种方法
2015/10/30 Javascript
详解javascript中原始数据类型Null和Undefined
2015/12/17 Javascript
全面接触神奇的Bootstrap导航条实战篇
2016/08/01 Javascript
用js实现博客打赏功能
2016/10/24 Javascript
js 中文汉字转Unicode、Unicode转中文汉字、ASCII转换Unicode、Unicode转换ASCII、中文转换
2016/12/06 Javascript
JavaScript优化以及前段开发小技巧
2017/02/02 Javascript
jQuery动态移除和添加背景图片的方法详解
2017/03/07 Javascript
python端口扫描系统实现方法
2014/11/19 Python
Python3实现将文件归档到zip文件及从zip文件中读取数据的方法
2015/05/22 Python
Python实现Windows和Linux之间互相传输文件(文件夹)的方法
2017/05/08 Python
Scrapy框架使用的基本知识
2018/10/21 Python
python 实现语音聊天机器人的示例代码
2018/12/02 Python
python实现点击按钮修改数据的方法
2019/07/17 Python
Python字符串格式化输出代码实例
2019/11/22 Python
基于pytorch的lstm参数使用详解
2020/01/14 Python
django rest framework serializers序列化实例
2020/05/13 Python
浅谈Pycharm的项目文件名是红色的原因及解决方式
2020/06/01 Python
挪威手表购物网站:Klokker
2016/09/19 全球购物
为智能设备设计个性化保护套网站:caseable
2017/01/05 全球购物
在C#中如何实现多态
2014/07/02 面试题
总经理岗位职责
2013/11/09 职场文书
机械制造毕业生求职信
2014/03/03 职场文书
党的群众路线教育实践活动动员会主持词
2014/03/20 职场文书
就业协议书
2014/09/12 职场文书
党支部反对四风思想汇报
2014/10/10 职场文书
2014年办公室文秘工作总结
2014/12/09 职场文书
公司行政助理岗位职责
2015/04/11 职场文书
护士岗位竞聘书
2015/09/15 职场文书
node.js如何自定义实现一个EventEmitter
2021/07/16 Javascript
JS前端canvas交互实现拖拽旋转及缩放示例
2022/08/05 Javascript