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 相关文章推荐
使用Node.js和Socket.IO扩展Django的实时处理功能
Apr 20 Python
Python多线程爬虫简单示例
Mar 04 Python
Python中字典的浅拷贝与深拷贝用法实例分析
Jan 02 Python
Python3.5 创建文件的简单实例
Apr 26 Python
python conda操作方法
Sep 11 Python
pytorch中torch.max和Tensor.view函数用法详解
Jan 03 Python
python高阶函数map()和reduce()实例解析
Mar 16 Python
Python读取配置文件(config.ini)以及写入配置文件
Apr 08 Python
django日志默认打印request请求信息的方法示例
May 17 Python
mac系统下安装pycharm、永久激活、中文汉化详细教程
Nov 24 Python
python 实现控制鼠标键盘
Nov 27 Python
使用pytorch实现线性回归
Apr 11 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
dedecms防止FCK乱格式化你的代码的修改方法
2007/03/17 PHP
Gambit vs CL BO3 第一场 2.13
2021/03/10 DOTA
javascript 模拟JQuery的Ready方法实现并出现的问题
2009/12/06 Javascript
JavaScript学习笔记记录我的旅程
2012/05/23 Javascript
JavaScript 学习笔记之操作符
2015/01/14 Javascript
EasyUI实现二级页面的内容勾选的方法
2015/03/01 Javascript
了解Javascript的模块化开发
2015/03/02 Javascript
javascript清空table表格的方法
2015/05/14 Javascript
使用Javascript监控前端相关数据的代码
2016/10/27 Javascript
js实现下拉框效果(select)
2017/03/28 Javascript
如何让Nodejs支持H5 History模式(connect-history-api-fallback源码分析)
2019/05/30 NodeJs
vue 取出v-for循环中的index值实例
2019/11/09 Javascript
Node.js 深度调试方法解析
2020/07/28 Javascript
[03:17]DOTA2英雄基础教程 剧毒术士
2013/12/12 DOTA
Python中正则表达式的用法实例汇总
2014/08/18 Python
Python中 Lambda表达式全面解析
2016/11/28 Python
使用python调用zxing库生成二维码图片详解
2017/01/10 Python
python 对象和json互相转换方法
2018/03/22 Python
python实现搜索文本文件内容脚本
2018/06/22 Python
浅谈Python的list中的选取范围
2018/11/12 Python
Python3.5运算符操作实例详解
2019/04/25 Python
tensorflow如何继续训练之前保存的模型实例
2020/01/21 Python
python3 字符串知识点学习笔记
2020/02/08 Python
python 利用toapi库自动生成api
2020/10/19 Python
CSS3近阶段篇之酷炫的3D旋转透视
2016/04/28 HTML / CSS
纯css3使用vw和vh实现自适应的方法
2018/02/09 HTML / CSS
Artist Guitars新西兰:乐器在线商店
2017/09/17 全球购物
Lands’ End英国官方网站:高质量男女服装
2017/10/07 全球购物
卡西欧G-SHOCK英国官网: 防水防震手表
2018/01/08 全球购物
Noon埃及:埃及在线购物
2019/11/26 全球购物
商务日语毕业生自荐信范文
2013/11/14 职场文书
小学生环保倡议书
2014/05/15 职场文书
大学生翘课检讨书范文
2014/10/06 职场文书
护士求职简历自我评价
2015/03/10 职场文书
2015年仓库管理工作总结
2015/05/25 职场文书
医者仁心观后感
2015/06/17 职场文书