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 相关文章推荐
解决谷歌搜索技术文章时打不开网页问题的python脚本
Feb 10 Python
python用ConfigObj读写配置文件的实现代码
Mar 04 Python
python列表与元组详解实例
Nov 01 Python
python发腾讯微博代码分享
Jan 10 Python
Python中的模块和包概念介绍
Apr 13 Python
python装饰器初探(推荐)
Jul 21 Python
Python自然语言处理之词干,词形与最大匹配算法代码详解
Nov 16 Python
python DataFrame 修改列的顺序实例
Apr 10 Python
Python之列表实现栈的工作功能
Jan 28 Python
python django框架中使用FastDFS分布式文件系统的安装方法
Jun 10 Python
Python QQBot库的QQ聊天机器人
Jun 19 Python
下载官网python并安装的步骤详解
Oct 12 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
使用MaxMind 根据IP地址对访问者定位
2006/10/09 PHP
php Ubb代码编辑器函数代码
2012/07/05 PHP
一个经典的PHP文件上传类分享
2014/11/18 PHP
详解PHP的Yii框架中日志的相关配置及使用
2015/12/08 PHP
TP5框架实现上传多张图片的方法分析
2020/03/29 PHP
Alliance vs AM BO3 第二场2.13
2021/03/10 DOTA
Javascript UrlDecode函数代码
2010/01/09 Javascript
JS获取鼠标坐标的实例方法
2013/07/18 Javascript
通过jquery 获取URL参数并进行转码
2014/08/18 Javascript
js实现a标签超链接提交form表单的方法
2015/06/24 Javascript
jQuery如何使用自动触发事件trigger
2015/11/29 Javascript
JS延时器提示框的应用实例代码解析
2016/04/27 Javascript
在Docker快速部署Node.js应用的详细步骤
2016/09/02 Javascript
vue通过watch对input做字数限定的方法
2017/07/13 Javascript
js用类封装pop弹窗组件
2017/10/08 Javascript
VUE 组件转换为微信小程序组件的方法
2019/11/06 Javascript
vue中watch和computed为什么能监听到数据的改变以及不同之处
2019/12/27 Javascript
Vue通过配置WebSocket并实现群聊功能
2019/12/31 Javascript
微信小程序实现带放大效果的轮播图
2020/05/26 Javascript
javascript如何使用函数random来实现课堂随机点名方法详解
2020/07/28 Javascript
python实现在windows服务中新建进程的方法
2015/06/30 Python
利用pyinstaller或virtualenv将python程序打包详解
2017/03/22 Python
Python使用asyncio包处理并发详解
2017/09/09 Python
对python字典过滤条件的实例详解
2019/01/22 Python
python实现微信防撤回神器
2019/04/29 Python
Django项目之Elasticsearch搜索引擎的实例
2019/08/21 Python
Django对接支付宝实现支付宝充值金币功能示例
2019/12/17 Python
Python中pyecharts安装及安装失败的解决方法
2020/02/18 Python
Python Tkinter Entry和Text的添加与使用详解
2020/03/04 Python
如何利用python检测图片是否包含二维码
2020/10/15 Python
Django Admin后台模型列表页面如何添加自定义操作按钮
2020/11/11 Python
小学生家长寄语
2014/04/02 职场文书
电大奖学金获奖感言
2014/08/14 职场文书
毕业论文致谢范文
2015/05/14 职场文书
Keras多线程机制与flask多线程冲突的解决方案
2021/05/28 Python
Nginx开源可视化配置工具NginxConfig使用教程
2022/06/21 Servers