Skip to content

Instantly share code, notes, and snippets.

@yszou
Created December 9, 2013 11:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yszou/7871107 to your computer and use it in GitHub Desktop.
Save yszou/7871107 to your computer and use it in GitHub Desktop.
Tornado实现的SMTP客户端
# -*- coding: utf-8 -*-
'参考smtplib,以tornado的IOStream实现的SMTP客户端'
import socket
import re
import email.utils
import tornado.ioloop
import tornado.iostream
import tornado.gen
IL = tornado.ioloop.IOLoop.instance()
CRLF = email.utils.CRLF
class SMTPClient(object):
#helo/ehlo 后面的主机参数
_fqdn = socket.getfqdn()
if '.' in _fqdn:
HOST_NAME = _fqdn
else:
# We can't find an fqdn hostname, so use a domain literal
_addr = '127.0.0.1'
try:
_addr = socket.gethostbyname(socket.gethostname())
except socket.gaierror:
pass
HOST_NAME = '[%s]' % _addr
def __init__(self, host, port=25, user='', password='', ssl=False):
self.host = host
self.port = port
self.user = user
self.password = password
self.ssl = ssl
def send_mail(self, sender, to, msg, callback, errback):
self.msg = msg
self.callback = callback
self.errback = errback
self.sender = sender
self.to = [to] if isinstance(to, basestring) else to
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
self.stream = tornado.iostream.IOStream(s) if not self.ssl else tornado.iostream.SSLIOStream(s)
self.stream.connect((self.host, self.port), self.session)
def quoteaddr(self, addr):
"""Quote a subset of the email addresses defined by RFC 821.
Should be able to handle anything rfc822.parseaddr can handle.
"""
m = (None, None)
try:
m = email.utils.parseaddr(addr)[1]
except AttributeError:
pass
if m == (None, None): # Indicates parse failure or AttributeError
# something weird here.. punt -ddm
return "<%s>" % addr
elif m is None:
# the sender wants an empty return address
return "<>"
else:
return "<%s>" % m
def quotedata(self, data):
"""Quote data for email.
Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
Internet CRLF end-of-line.
"""
return re.sub(r'(?m)^\.', '..',
re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
def process(self, data):
'解析返回'
if data == '':
return -1, ''
try:
msg = data[4:].strip()
code = data[:3]
except IndexError:
return -1, data
try:
code = int(code)
except ValueError:
code = -1
return -1, data
return code, msg
@tornado.gen.engine
def session(self):
response = []
#连接
while 1:
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
if code != 220:
self.stream.close()
self.errback(code, msg, response)
raise StopIteration
if data[3] != '-':
break
#EHLO / HELO
self.stream.write('ehlo %s%s' % (self.__class__.HOST_NAME, CRLF))
response.append(('ehlo', '%s' % self.__class__.HOST_NAME))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
if not (200 <= code <= 299):
self.stream.write('helo %s%s' % (self.__class__.HOST_NAME, CRLF))
response.append(('helo', '%s' % self.__class__.HOST_NAME))
while 1:
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
if not (200 <= code <= 299):
self.stream.write('rset%s' % CRLF);
response.append(('rset', ''))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
self.stream.close()
self.errback(code, msg, response)
raise StopIteration
if data[3] != '-':
break
#密码
if self.user:
self.stream.write('auth login%s' % CRLF)
response.append(('auth login', ''))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
self.stream.write('%s%s' % (self.user.encode('base64').rstrip(), CRLF))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
self.stream.write('%s%s' % (self.password.encode('base64').rstrip(), CRLF))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
if code != 235:
self.stream.write('rset%s' % CRLF);
response.append(('rset', ''))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
self.stream.close()
self.errback(code, msg, response)
raise StopIteration
#MAIL
self.stream.write('mail FROM:%s%s' % (self.quoteaddr(self.sender), CRLF))
response.append(('mail', 'FROM:%s' % self.quoteaddr(self.sender)))
while 1:
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
if code != 250:
self.stream.write('rset%s' % CRLF);
response.append(('rset', ''))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
self.stream.close()
self.errback(code, msg, response)
raise StopIteration
if data[3] != '-':
break
#RCPT
error = {}
for rcpt in self.to:
self.stream.write('rcpt TO:%s%s' % (self.quoteaddr(rcpt), CRLF))
response.append(('rcpt', 'TO:%s' % self.quoteaddr(rcpt)))
while 1:
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
if (code != 250) and (code != 251):
error[rcpt] = (code, msg)
if data[3] != '-':
break
if len(error) == len(self.to):
#全部失败
self.stream.write('rset%s' % CRLF);
response.append(('rset', ''))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
self.stream.close()
self.errback(code, msg, response)
raise StopIteration
#DATA
self.stream.write('data%s' % CRLF);
response.append(('data', ''))
while 1:
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
if code != 354:
self.stream.write('rset%s' % CRLF);
response.append(('rset', ''))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
self.stream.close()
self.errback(code, msg, response)
raise StopIteration
if data[3] != '-':
break
if isinstance(self.msg, basestring):
data = self.msg
elif isinstance(self.msg, unicode):
data = self.msg.encode('utf8')
else:
data = self.msg.as_string()
q = self.quotedata(data)
if q[-2:] != CRLF:
q = q + CRLF
q = q + "." + CRLF
self.stream.write(q);
response.append(('(mail data)', ''))
while 1:
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
if code != 250:
self.stream.write('rset%s' % CRLF);
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
self.stream.close()
self.errback(code, msg, response)
raise StopIteration
if data[3] != '-':
break
self.stream.write('quit%s' % CRLF);
response.append(('quit', ''))
data = yield tornado.gen.Task(self.stream.read_until, CRLF)
code, msg = self.process(data)
response.append((code, msg))
self.stream.close()
self.callback(code, msg, response)
raise StopIteration
if __name__ == '__main__':
from email.mime.text import MIMEText
from email.header import make_header
msg = MIMEText('中文', _subtype='html', _charset='utf8')
msg['Subject'] = make_header([('标题', 'utf8')])
msg['From'] = make_header([('xxx@sohu-inc.com>', None)])
msg['To'] = make_header([('邹业盛', 'utf8'), ('<xxx@sohu-inc.com>', None)])
msg['Message-ID'] = email.utils.make_msgid()
msg['Date'] = email.utils.formatdate()
#print msg.as_string()
def callback(code, msg, response):
print code, msg
print response
def errback(code, msg, response):
print code, msg
print response
#client = SMTPClient('gmail-smtp-in-v4v6.l.google.com')
#client.send_mail('no-reply@sohu.net', ['xxx@gmail.com', 'xxx@gmail.com'], msg,
# callback, errback)
client = SMTPClient('smtp.163.com', 465, 'xxx@163.com', '***', True)
client = SMTPClient('127.0.0.1', 25, '', '', False)
client.send_mail('xxx@163.com', ['xxx@163.com'], msg, callback, errback)
IL.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment