You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

smtp.py 70KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247
  1. # -*- test-case-name: twisted.mail.test.test_smtp -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. #
  5. # pylint: disable=I0011,C0103,C9302
  6. """
  7. Simple Mail Transfer Protocol implementation.
  8. """
  9. from __future__ import absolute_import, division
  10. import time
  11. import re
  12. import base64
  13. import socket
  14. import os
  15. import random
  16. import binascii
  17. import warnings
  18. from email.utils import parseaddr
  19. from zope.interface import implementer
  20. from twisted import cred
  21. from twisted.copyright import longversion
  22. from twisted.protocols import basic
  23. from twisted.protocols import policies
  24. from twisted.internet import protocol
  25. from twisted.internet import defer
  26. from twisted.internet import error
  27. from twisted.internet import reactor
  28. from twisted.internet.interfaces import ITLSTransport, ISSLTransport
  29. from twisted.python import log
  30. from twisted.python import util
  31. from twisted.python.compat import (_PY3, range, long, unicode, networkString,
  32. nativeString, iteritems, _keys, _bytesChr,
  33. iterbytes)
  34. from twisted.python.runtime import platform
  35. from twisted.mail.interfaces import (IClientAuthentication,
  36. IMessageSMTP as IMessage,
  37. IMessageDeliveryFactory, IMessageDelivery)
  38. from twisted.mail._cred import (CramMD5ClientAuthenticator, LOGINAuthenticator,
  39. LOGINCredentials as _lcredentials)
  40. from twisted.mail._except import (
  41. AUTHDeclinedError, AUTHRequiredError, AddressError,
  42. AuthenticationError, EHLORequiredError, ESMTPClientError,
  43. SMTPAddressError, SMTPBadRcpt, SMTPBadSender, SMTPClientError,
  44. SMTPConnectError, SMTPDeliveryError, SMTPError, SMTPServerError,
  45. SMTPTimeoutError, SMTPTLSError as TLSError, TLSRequiredError,
  46. SMTPProtocolError)
  47. from io import BytesIO
  48. __all__ = [
  49. 'AUTHDeclinedError', 'AUTHRequiredError', 'AddressError',
  50. 'AuthenticationError', 'EHLORequiredError', 'ESMTPClientError',
  51. 'SMTPAddressError', 'SMTPBadRcpt', 'SMTPBadSender', 'SMTPClientError',
  52. 'SMTPConnectError', 'SMTPDeliveryError', 'SMTPError', 'SMTPServerError',
  53. 'SMTPTimeoutError', 'TLSError', 'TLSRequiredError', 'SMTPProtocolError',
  54. 'IClientAuthentication', 'IMessage', 'IMessageDelivery',
  55. 'IMessageDeliveryFactory',
  56. 'CramMD5ClientAuthenticator', 'LOGINAuthenticator', 'LOGINCredentials',
  57. 'PLAINAuthenticator',
  58. 'Address', 'User', 'sendmail', 'SenderMixin',
  59. 'ESMTP', 'ESMTPClient', 'ESMTPSender', 'ESMTPSenderFactory',
  60. 'SMTP', 'SMTPClient', 'SMTPFactory', 'SMTPSender', 'SMTPSenderFactory',
  61. 'idGenerator', 'messageid', 'quoteaddr', 'rfc822date', 'xtextStreamReader',
  62. 'xtextStreamWriter', 'xtext_codec', 'xtext_decode', 'xtext_encode'
  63. ]
  64. # Cache the hostname (XXX Yes - this is broken)
  65. if platform.isMacOSX():
  66. # On macOS, getfqdn() is ridiculously slow - use the
  67. # probably-identical-but-sometimes-not gethostname() there.
  68. DNSNAME = socket.gethostname()
  69. else:
  70. DNSNAME = socket.getfqdn()
  71. # Encode the DNS name into something we can send over the wire
  72. DNSNAME = DNSNAME.encode('ascii')
  73. # Used for fast success code lookup
  74. SUCCESS = dict.fromkeys(range(200, 300))
  75. def rfc822date(timeinfo=None, local=1):
  76. """
  77. Format an RFC-2822 compliant date string.
  78. @param timeinfo: (optional) A sequence as returned by C{time.localtime()}
  79. or C{time.gmtime()}. Default is now.
  80. @param local: (optional) Indicates if the supplied time is local or
  81. universal time, or if no time is given, whether now should be local or
  82. universal time. Default is local, as suggested (SHOULD) by rfc-2822.
  83. @returns: A L{bytes} representing the time and date in RFC-2822 format.
  84. """
  85. if not timeinfo:
  86. if local:
  87. timeinfo = time.localtime()
  88. else:
  89. timeinfo = time.gmtime()
  90. if local:
  91. if timeinfo[8]:
  92. # DST
  93. tz = -time.altzone
  94. else:
  95. tz = -time.timezone
  96. (tzhr, tzmin) = divmod(abs(tz), 3600)
  97. if tz:
  98. tzhr *= int(abs(tz)//tz)
  99. (tzmin, tzsec) = divmod(tzmin, 60)
  100. else:
  101. (tzhr, tzmin) = (0, 0)
  102. return networkString("%s, %02d %s %04d %02d:%02d:%02d %+03d%02d" % (
  103. ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][timeinfo[6]],
  104. timeinfo[2],
  105. ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
  106. 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][timeinfo[1] - 1],
  107. timeinfo[0], timeinfo[3], timeinfo[4], timeinfo[5],
  108. tzhr, tzmin))
  109. def idGenerator():
  110. i = 0
  111. while True:
  112. yield i
  113. i += 1
  114. _gen = idGenerator()
  115. def messageid(uniq=None, N=lambda: next(_gen)):
  116. """
  117. Return a globally unique random string in RFC 2822 Message-ID format
  118. <datetime.pid.random@host.dom.ain>
  119. Optional uniq string will be added to strengthen uniqueness if given.
  120. """
  121. datetime = time.strftime('%Y%m%d%H%M%S', time.gmtime())
  122. pid = os.getpid()
  123. rand = random.randrange(2**31-1)
  124. if uniq is None:
  125. uniq = ''
  126. else:
  127. uniq = '.' + uniq
  128. return '<%s.%s.%s%s.%s@%s>' % (datetime, pid, rand, uniq, N(), DNSNAME)
  129. def quoteaddr(addr):
  130. """
  131. Turn an email address, possibly with realname part etc, into
  132. a form suitable for and SMTP envelope.
  133. """
  134. if isinstance(addr, Address):
  135. return b'<' + bytes(addr) + b'>'
  136. if isinstance(addr, bytes):
  137. addr = addr.decode('ascii')
  138. res = parseaddr(addr)
  139. if res == (None, None):
  140. # It didn't parse, use it as-is
  141. return b'<' + bytes(addr) + b'>'
  142. else:
  143. return b'<' + res[1].encode('ascii') + b'>'
  144. COMMAND, DATA, AUTH = 'COMMAND', 'DATA', 'AUTH'
  145. # Character classes for parsing addresses
  146. atom = br"[-A-Za-z0-9!\#$%&'*+/=?^_`{|}~]"
  147. class Address:
  148. """Parse and hold an RFC 2821 address.
  149. Source routes are stipped and ignored, UUCP-style bang-paths
  150. and %-style routing are not parsed.
  151. @type domain: C{bytes}
  152. @ivar domain: The domain within which this address resides.
  153. @type local: C{bytes}
  154. @ivar local: The local (\"user\") portion of this address.
  155. """
  156. tstring = re.compile(br'''( # A string of
  157. (?:"[^"]*" # quoted string
  158. |\\. # backslash-escaped characted
  159. |''' + atom + br''' # atom character
  160. )+|.) # or any single character''', re.X)
  161. atomre = re.compile(atom) # match any one atom character
  162. def __init__(self, addr, defaultDomain=None):
  163. if isinstance(addr, User):
  164. addr = addr.dest
  165. if isinstance(addr, Address):
  166. self.__dict__ = addr.__dict__.copy()
  167. return
  168. elif not isinstance(addr, bytes):
  169. addr = str(addr).encode('ascii')
  170. self.addrstr = addr
  171. # Tokenize
  172. atl = list(filter(None, self.tstring.split(addr)))
  173. local = []
  174. domain = []
  175. while atl:
  176. if atl[0] == b'<':
  177. if atl[-1] != b'>':
  178. raise AddressError("Unbalanced <>")
  179. atl = atl[1:-1]
  180. elif atl[0] == b'@':
  181. atl = atl[1:]
  182. if not local:
  183. # Source route
  184. while atl and atl[0] != b':':
  185. # remove it
  186. atl = atl[1:]
  187. if not atl:
  188. raise AddressError("Malformed source route")
  189. atl = atl[1:] # remove :
  190. elif domain:
  191. raise AddressError("Too many @")
  192. else:
  193. # Now in domain
  194. domain = [b'']
  195. elif (len(atl[0]) == 1 and
  196. not self.atomre.match(atl[0]) and
  197. atl[0] != b'.'):
  198. raise AddressError("Parse error at %r of %r" % (atl[0], (addr, atl)))
  199. else:
  200. if not domain:
  201. local.append(atl[0])
  202. else:
  203. domain.append(atl[0])
  204. atl = atl[1:]
  205. self.local = b''.join(local)
  206. self.domain = b''.join(domain)
  207. if self.local != b'' and self.domain == b'':
  208. if defaultDomain is None:
  209. defaultDomain = DNSNAME
  210. self.domain = defaultDomain
  211. dequotebs = re.compile(br'\\(.)')
  212. def dequote(self, addr):
  213. """
  214. Remove RFC-2821 quotes from address.
  215. """
  216. res = []
  217. if not isinstance(addr, bytes):
  218. addr = str(addr).encode('ascii')
  219. atl = filter(None, self.tstring.split(addr))
  220. for t in atl:
  221. if t[0] == b'"' and t[-1] == b'"':
  222. res.append(t[1:-1])
  223. elif '\\' in t:
  224. res.append(self.dequotebs.sub(br'\1', t))
  225. else:
  226. res.append(t)
  227. return b''.join(res)
  228. if _PY3:
  229. def __str__(self):
  230. return nativeString(bytes(self))
  231. else:
  232. def __str__(self):
  233. return self.__bytes__()
  234. def __bytes__(self):
  235. if self.local or self.domain:
  236. return b'@'.join((self.local, self.domain))
  237. else:
  238. return b''
  239. def __repr__(self):
  240. return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
  241. repr(str(self)))
  242. class User:
  243. """
  244. Hold information about and SMTP message recipient,
  245. including information on where the message came from
  246. """
  247. def __init__(self, destination, helo, protocol, orig):
  248. try:
  249. host = protocol.host
  250. except AttributeError:
  251. host = None
  252. self.dest = Address(destination, host)
  253. self.helo = helo
  254. self.protocol = protocol
  255. if isinstance(orig, Address):
  256. self.orig = orig
  257. else:
  258. self.orig = Address(orig, host)
  259. def __getstate__(self):
  260. """
  261. Helper for pickle.
  262. protocol isn't picklabe, but we want User to be, so skip it in
  263. the pickle.
  264. """
  265. return { 'dest' : self.dest,
  266. 'helo' : self.helo,
  267. 'protocol' : None,
  268. 'orig' : self.orig }
  269. def __str__(self):
  270. return nativeString(bytes(self.dest))
  271. def __bytes__(self):
  272. return bytes(self.dest)
  273. class SMTP(basic.LineOnlyReceiver, policies.TimeoutMixin):
  274. """
  275. SMTP server-side protocol.
  276. @ivar host: The hostname of this mail server.
  277. @type host: L{bytes}
  278. """
  279. timeout = 600
  280. portal = None
  281. # Control whether we log SMTP events
  282. noisy = True
  283. # A factory for IMessageDelivery objects. If an
  284. # avatar implementing IMessageDeliveryFactory can
  285. # be acquired from the portal, it will be used to
  286. # create a new IMessageDelivery object for each
  287. # message which is received.
  288. deliveryFactory = None
  289. # An IMessageDelivery object. A new instance is
  290. # used for each message received if we can get an
  291. # IMessageDeliveryFactory from the portal. Otherwise,
  292. # a single instance is used throughout the lifetime
  293. # of the connection.
  294. delivery = None
  295. # Cred cleanup function.
  296. _onLogout = None
  297. def __init__(self, delivery=None, deliveryFactory=None):
  298. self.mode = COMMAND
  299. self._from = None
  300. self._helo = None
  301. self._to = []
  302. self.delivery = delivery
  303. self.deliveryFactory = deliveryFactory
  304. self.host = DNSNAME
  305. @property
  306. def host(self):
  307. return self._host
  308. @host.setter
  309. def host(self, toSet):
  310. if not isinstance(toSet, bytes):
  311. toSet = str(toSet).encode('ascii')
  312. self._host = toSet
  313. def timeoutConnection(self):
  314. msg = self.host + b' Timeout. Try talking faster next time!'
  315. self.sendCode(421, msg)
  316. self.transport.loseConnection()
  317. def greeting(self):
  318. return self.host + b' NO UCE NO UBE NO RELAY PROBES'
  319. def connectionMade(self):
  320. # Ensure user-code always gets something sane for _helo
  321. peer = self.transport.getPeer()
  322. try:
  323. host = peer.host
  324. except AttributeError: # not an IPv4Address
  325. host = str(peer)
  326. self._helo = (None, host)
  327. self.sendCode(220, self.greeting())
  328. self.setTimeout(self.timeout)
  329. def sendCode(self, code, message=b''):
  330. """
  331. Send an SMTP code with a message.
  332. """
  333. lines = message.splitlines()
  334. lastline = lines[-1:]
  335. for line in lines[:-1]:
  336. self.sendLine(networkString('%3.3d-' % (code,)) + line)
  337. self.sendLine(networkString('%3.3d ' % (code,)) +
  338. (lastline and lastline[0] or b''))
  339. def lineReceived(self, line):
  340. self.resetTimeout()
  341. return getattr(self, 'state_' + self.mode)(line)
  342. def state_COMMAND(self, line):
  343. # Ignore leading and trailing whitespace, as well as an arbitrary
  344. # amount of whitespace between the command and its argument, though
  345. # it is not required by the protocol, for it is a nice thing to do.
  346. line = line.strip()
  347. parts = line.split(None, 1)
  348. if parts:
  349. method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
  350. if len(parts) == 2:
  351. method(parts[1])
  352. else:
  353. method(b'')
  354. else:
  355. self.sendSyntaxError()
  356. def sendSyntaxError(self):
  357. self.sendCode(500, b'Error: bad syntax')
  358. def lookupMethod(self, command):
  359. """
  360. @param command: The command to get from this class.
  361. @type command: L{str}
  362. @return: The function which executes this command.
  363. """
  364. if not isinstance(command, str):
  365. command = nativeString(command)
  366. return getattr(self, 'do_' + command.upper(), None)
  367. def lineLengthExceeded(self, line):
  368. if self.mode is DATA:
  369. for message in self.__messages:
  370. message.connectionLost()
  371. self.mode = COMMAND
  372. del self.__messages
  373. self.sendCode(500, b'Line too long')
  374. def do_UNKNOWN(self, rest):
  375. self.sendCode(500, b'Command not implemented')
  376. def do_HELO(self, rest):
  377. peer = self.transport.getPeer()
  378. try:
  379. host = peer.host
  380. except AttributeError:
  381. host = str(peer)
  382. if not isinstance(host, bytes):
  383. host = host.encode('idna')
  384. self._helo = (rest, host)
  385. self._from = None
  386. self._to = []
  387. self.sendCode(250,
  388. self.host + b' Hello ' + host + b', nice to meet you')
  389. def do_QUIT(self, rest):
  390. self.sendCode(221, b'See you later')
  391. self.transport.loseConnection()
  392. # A string of quoted strings, backslash-escaped character or
  393. # atom characters + '@.,:'
  394. qstring = br'("[^"]*"|\\.|' + atom + br'|[@.,:])+'
  395. mail_re = re.compile(br'''\s*FROM:\s*(?P<path><> # Empty <>
  396. |<''' + qstring + br'''> # <addr>
  397. |''' + qstring + br''' # addr
  398. )\s*(\s(?P<opts>.*))? # Optional WS + ESMTP options
  399. $''', re.I|re.X)
  400. rcpt_re = re.compile(br'\s*TO:\s*(?P<path><' + qstring + br'''> # <addr>
  401. |''' + qstring + br''' # addr
  402. )\s*(\s(?P<opts>.*))? # Optional WS + ESMTP options
  403. $''', re.I|re.X)
  404. def do_MAIL(self, rest):
  405. if self._from:
  406. self.sendCode(503, b"Only one sender per message, please")
  407. return
  408. # Clear old recipient list
  409. self._to = []
  410. m = self.mail_re.match(rest)
  411. if not m:
  412. self.sendCode(501, b"Syntax error")
  413. return
  414. try:
  415. addr = Address(m.group('path'), self.host)
  416. except AddressError as e:
  417. self.sendCode(553, networkString(str(e)))
  418. return
  419. validated = defer.maybeDeferred(self.validateFrom, self._helo, addr)
  420. validated.addCallbacks(self._cbFromValidate, self._ebFromValidate)
  421. def _cbFromValidate(self, fromEmail, code=250,
  422. msg=b'Sender address accepted'):
  423. self._from = fromEmail
  424. self.sendCode(code, msg)
  425. def _ebFromValidate(self, failure):
  426. if failure.check(SMTPBadSender):
  427. self.sendCode(failure.value.code,
  428. (b'Cannot receive from specified address ' +
  429. quoteaddr(failure.value.addr) + b': ' +
  430. networkString(failure.value.resp)))
  431. elif failure.check(SMTPServerError):
  432. self.sendCode(failure.value.code,
  433. networkString(failure.value.resp))
  434. else:
  435. log.err(failure, "SMTP sender validation failure")
  436. self.sendCode(
  437. 451,
  438. b'Requested action aborted: local error in processing')
  439. def do_RCPT(self, rest):
  440. if not self._from:
  441. self.sendCode(503, b"Must have sender before recipient")
  442. return
  443. m = self.rcpt_re.match(rest)
  444. if not m:
  445. self.sendCode(501, b"Syntax error")
  446. return
  447. try:
  448. user = User(m.group('path'), self._helo, self, self._from)
  449. except AddressError as e:
  450. self.sendCode(553, networkString(str(e)))
  451. return
  452. d = defer.maybeDeferred(self.validateTo, user)
  453. d.addCallbacks(
  454. self._cbToValidate,
  455. self._ebToValidate,
  456. callbackArgs=(user,)
  457. )
  458. def _cbToValidate(self, to, user=None, code=250,
  459. msg=b'Recipient address accepted'):
  460. if user is None:
  461. user = to
  462. self._to.append((user, to))
  463. self.sendCode(code, msg)
  464. def _ebToValidate(self, failure):
  465. if failure.check(SMTPBadRcpt, SMTPServerError):
  466. self.sendCode(failure.value.code,
  467. networkString(failure.value.resp))
  468. else:
  469. log.err(failure)
  470. self.sendCode(
  471. 451,
  472. b'Requested action aborted: local error in processing'
  473. )
  474. def _disconnect(self, msgs):
  475. for msg in msgs:
  476. try:
  477. msg.connectionLost()
  478. except:
  479. log.msg("msg raised exception from connectionLost")
  480. log.err()
  481. def do_DATA(self, rest):
  482. if self._from is None or (not self._to):
  483. self.sendCode(503, b'Must have valid receiver and originator')
  484. return
  485. self.mode = DATA
  486. helo, origin = self._helo, self._from
  487. recipients = self._to
  488. self._from = None
  489. self._to = []
  490. self.datafailed = None
  491. msgs = []
  492. for (user, msgFunc) in recipients:
  493. try:
  494. msg = msgFunc()
  495. rcvdhdr = self.receivedHeader(helo, origin, [user])
  496. if rcvdhdr:
  497. msg.lineReceived(rcvdhdr)
  498. msgs.append(msg)
  499. except SMTPServerError as e:
  500. self.sendCode(e.code, e.resp)
  501. self.mode = COMMAND
  502. self._disconnect(msgs)
  503. return
  504. except:
  505. log.err()
  506. self.sendCode(550, b"Internal server error")
  507. self.mode = COMMAND
  508. self._disconnect(msgs)
  509. return
  510. self.__messages = msgs
  511. self.__inheader = self.__inbody = 0
  512. self.sendCode(354, b'Continue')
  513. if self.noisy:
  514. fmt = 'Receiving message for delivery: from=%s to=%s'
  515. log.msg(fmt % (origin, [str(u) for (u, f) in recipients]))
  516. def connectionLost(self, reason):
  517. # self.sendCode(421, 'Dropping connection.') # This does nothing...
  518. # Ideally, if we (rather than the other side) lose the connection,
  519. # we should be able to tell the other side that we are going away.
  520. # RFC-2821 requires that we try.
  521. if self.mode is DATA:
  522. try:
  523. for message in self.__messages:
  524. try:
  525. message.connectionLost()
  526. except:
  527. log.err()
  528. del self.__messages
  529. except AttributeError:
  530. pass
  531. if self._onLogout:
  532. self._onLogout()
  533. self._onLogout = None
  534. self.setTimeout(None)
  535. def do_RSET(self, rest):
  536. self._from = None
  537. self._to = []
  538. self.sendCode(250, b'I remember nothing.')
  539. def dataLineReceived(self, line):
  540. if line[:1] == b'.':
  541. if line == b'.':
  542. self.mode = COMMAND
  543. if self.datafailed:
  544. self.sendCode(self.datafailed.code,
  545. self.datafailed.resp)
  546. return
  547. if not self.__messages:
  548. self._messageHandled("thrown away")
  549. return
  550. defer.DeferredList([
  551. m.eomReceived() for m in self.__messages
  552. ], consumeErrors=True).addCallback(self._messageHandled
  553. )
  554. del self.__messages
  555. return
  556. line = line[1:]
  557. if self.datafailed:
  558. return
  559. try:
  560. # Add a blank line between the generated Received:-header
  561. # and the message body if the message comes in without any
  562. # headers
  563. if not self.__inheader and not self.__inbody:
  564. if b':' in line:
  565. self.__inheader = 1
  566. elif line:
  567. for message in self.__messages:
  568. message.lineReceived(b'')
  569. self.__inbody = 1
  570. if not line:
  571. self.__inbody = 1
  572. for message in self.__messages:
  573. message.lineReceived(line)
  574. except SMTPServerError as e:
  575. self.datafailed = e
  576. for message in self.__messages:
  577. message.connectionLost()
  578. state_DATA = dataLineReceived
  579. def _messageHandled(self, resultList):
  580. failures = 0
  581. for (success, result) in resultList:
  582. if not success:
  583. failures += 1
  584. log.err(result)
  585. if failures:
  586. msg = 'Could not send e-mail'
  587. resultLen = len(resultList)
  588. if resultLen > 1:
  589. msg += ' (%d failures out of %d recipients)'.format(
  590. failures, resultLen)
  591. self.sendCode(550, networkString(msg))
  592. else:
  593. self.sendCode(250, b'Delivery in progress')
  594. def _cbAnonymousAuthentication(self, result):
  595. """
  596. Save the state resulting from a successful anonymous cred login.
  597. """
  598. (iface, avatar, logout) = result
  599. if issubclass(iface, IMessageDeliveryFactory):
  600. self.deliveryFactory = avatar
  601. self.delivery = None
  602. elif issubclass(iface, IMessageDelivery):
  603. self.deliveryFactory = None
  604. self.delivery = avatar
  605. else:
  606. raise RuntimeError("%s is not a supported interface" % (iface.__name__,))
  607. self._onLogout = logout
  608. self.challenger = None
  609. # overridable methods:
  610. def validateFrom(self, helo, origin):
  611. """
  612. Validate the address from which the message originates.
  613. @type helo: C{(bytes, bytes)}
  614. @param helo: The argument to the HELO command and the client's IP
  615. address.
  616. @type origin: C{Address}
  617. @param origin: The address the message is from
  618. @rtype: C{Deferred} or C{Address}
  619. @return: C{origin} or a C{Deferred} whose callback will be
  620. passed C{origin}.
  621. @raise SMTPBadSender: Raised of messages from this address are
  622. not to be accepted.
  623. """
  624. if self.deliveryFactory is not None:
  625. self.delivery = self.deliveryFactory.getMessageDelivery()
  626. if self.delivery is not None:
  627. return defer.maybeDeferred(self.delivery.validateFrom,
  628. helo, origin)
  629. # No login has been performed, no default delivery object has been
  630. # provided: try to perform an anonymous login and then invoke this
  631. # method again.
  632. if self.portal:
  633. result = self.portal.login(
  634. cred.credentials.Anonymous(),
  635. None,
  636. IMessageDeliveryFactory, IMessageDelivery)
  637. def ebAuthentication(err):
  638. """
  639. Translate cred exceptions into SMTP exceptions so that the
  640. protocol code which invokes C{validateFrom} can properly report
  641. the failure.
  642. """
  643. if err.check(cred.error.UnauthorizedLogin):
  644. exc = SMTPBadSender(origin)
  645. elif err.check(cred.error.UnhandledCredentials):
  646. exc = SMTPBadSender(
  647. origin, resp="Unauthenticated senders not allowed")
  648. else:
  649. return err
  650. return defer.fail(exc)
  651. result.addCallbacks(
  652. self._cbAnonymousAuthentication, ebAuthentication)
  653. def continueValidation(ignored):
  654. """
  655. Re-attempt from address validation.
  656. """
  657. return self.validateFrom(helo, origin)
  658. result.addCallback(continueValidation)
  659. return result
  660. raise SMTPBadSender(origin)
  661. def validateTo(self, user):
  662. """
  663. Validate the address for which the message is destined.
  664. @type user: L{User}
  665. @param user: The address to validate.
  666. @rtype: no-argument callable
  667. @return: A C{Deferred} which becomes, or a callable which
  668. takes no arguments and returns an object implementing C{IMessage}.
  669. This will be called and the returned object used to deliver the
  670. message when it arrives.
  671. @raise SMTPBadRcpt: Raised if messages to the address are
  672. not to be accepted.
  673. """
  674. if self.delivery is not None:
  675. return self.delivery.validateTo(user)
  676. raise SMTPBadRcpt(user)
  677. def receivedHeader(self, helo, origin, recipients):
  678. if self.delivery is not None:
  679. return self.delivery.receivedHeader(helo, origin, recipients)
  680. heloStr = b""
  681. if helo[0]:
  682. heloStr = b" helo=" + helo[0]
  683. domain = networkString(self.transport.getHost().host)
  684. from_ = b"from " + helo[0] + b" ([" + helo[1] + b"]" + heloStr + b")"
  685. by = b"by %s with %s (%s)" % (domain,
  686. self.__class__.__name__,
  687. longversion)
  688. for_ = b"for %s; %s" % (' '.join(map(str, recipients)),
  689. rfc822date())
  690. return b"Received: " + from_ + b"\n\t" + by + b"\n\t" + for_
  691. class SMTPFactory(protocol.ServerFactory):
  692. """
  693. Factory for SMTP.
  694. """
  695. # override in instances or subclasses
  696. domain = DNSNAME
  697. timeout = 600
  698. protocol = SMTP
  699. portal = None
  700. def __init__(self, portal = None):
  701. self.portal = portal
  702. def buildProtocol(self, addr):
  703. p = protocol.ServerFactory.buildProtocol(self, addr)
  704. p.portal = self.portal
  705. p.host = self.domain
  706. return p
  707. class SMTPClient(basic.LineReceiver, policies.TimeoutMixin):
  708. """
  709. SMTP client for sending emails.
  710. After the client has connected to the SMTP server, it repeatedly calls
  711. L{SMTPClient.getMailFrom}, L{SMTPClient.getMailTo} and
  712. L{SMTPClient.getMailData} and uses this information to send an email.
  713. It then calls L{SMTPClient.getMailFrom} again; if it returns L{None}, the
  714. client will disconnect, otherwise it will continue as normal i.e. call
  715. L{SMTPClient.getMailTo} and L{SMTPClient.getMailData} and send a new email.
  716. """
  717. # If enabled then log SMTP client server communication
  718. debug = True
  719. # Number of seconds to wait before timing out a connection. If
  720. # None, perform no timeout checking.
  721. timeout = None
  722. def __init__(self, identity, logsize=10):
  723. if isinstance(identity, unicode):
  724. identity = identity.encode('ascii')
  725. self.identity = identity or b''
  726. self.toAddressesResult = []
  727. self.successAddresses = []
  728. self._from = None
  729. self.resp = []
  730. self.code = -1
  731. self.log = util.LineLog(logsize)
  732. def sendLine(self, line):
  733. # Log sendLine only if you are in debug mode for performance
  734. if self.debug:
  735. self.log.append(b'>>> ' + line)
  736. basic.LineReceiver.sendLine(self,line)
  737. def connectionMade(self):
  738. self.setTimeout(self.timeout)
  739. self._expected = [ 220 ]
  740. self._okresponse = self.smtpState_helo
  741. self._failresponse = self.smtpConnectionFailed
  742. def connectionLost(self, reason=protocol.connectionDone):
  743. """
  744. We are no longer connected
  745. """
  746. self.setTimeout(None)
  747. self.mailFile = None
  748. def timeoutConnection(self):
  749. self.sendError(
  750. SMTPTimeoutError(
  751. -1, b"Timeout waiting for SMTP server response",
  752. self.log.str()))
  753. def lineReceived(self, line):
  754. self.resetTimeout()
  755. # Log lineReceived only if you are in debug mode for performance
  756. if self.debug:
  757. self.log.append(b'<<< ' + line)
  758. why = None
  759. try:
  760. self.code = int(line[:3])
  761. except ValueError:
  762. # This is a fatal error and will disconnect the transport
  763. # lineReceived will not be called again.
  764. self.sendError(SMTPProtocolError(-1,
  765. "Invalid response from SMTP server: {}".format(line),
  766. self.log.str()))
  767. return
  768. if line[0:1] == b'0':
  769. # Verbose informational message, ignore it
  770. return
  771. self.resp.append(line[4:])
  772. if line[3:4] == b'-':
  773. # Continuation
  774. return
  775. if self.code in self._expected:
  776. why = self._okresponse(self.code, b'\n'.join(self.resp))
  777. else:
  778. why = self._failresponse(self.code, b'\n'.join(self.resp))
  779. self.code = -1
  780. self.resp = []
  781. return why
  782. def smtpConnectionFailed(self, code, resp):
  783. self.sendError(SMTPConnectError(code, resp, self.log.str()))
  784. def smtpTransferFailed(self, code, resp):
  785. if code < 0:
  786. self.sendError(SMTPProtocolError(code, resp, self.log.str()))
  787. else:
  788. self.smtpState_msgSent(code, resp)
  789. def smtpState_helo(self, code, resp):
  790. self.sendLine(b'HELO ' + self.identity)
  791. self._expected = SUCCESS
  792. self._okresponse = self.smtpState_from
  793. def smtpState_from(self, code, resp):
  794. self._from = self.getMailFrom()
  795. self._failresponse = self.smtpTransferFailed
  796. if self._from is not None:
  797. self.sendLine(b'MAIL FROM:' + quoteaddr(self._from))
  798. self._expected = [250]
  799. self._okresponse = self.smtpState_to
  800. else:
  801. # All messages have been sent, disconnect
  802. self._disconnectFromServer()
  803. def smtpState_disconnect(self, code, resp):
  804. self.transport.loseConnection()
  805. def smtpState_to(self, code, resp):
  806. self.toAddresses = iter(self.getMailTo())
  807. self.toAddressesResult = []
  808. self.successAddresses = []
  809. self._okresponse = self.smtpState_toOrData
  810. self._expected = range(0, 1000)
  811. self.lastAddress = None
  812. return self.smtpState_toOrData(0, b'')
  813. def smtpState_toOrData(self, code, resp):
  814. if self.lastAddress is not None:
  815. self.toAddressesResult.append((self.lastAddress, code, resp))
  816. if code in SUCCESS:
  817. self.successAddresses.append(self.lastAddress)
  818. try:
  819. self.lastAddress = next(self.toAddresses)
  820. except StopIteration:
  821. if self.successAddresses:
  822. self.sendLine(b'DATA')
  823. self._expected = [ 354 ]
  824. self._okresponse = self.smtpState_data
  825. else:
  826. return self.smtpState_msgSent(code,'No recipients accepted')
  827. else:
  828. self.sendLine(b'RCPT TO:' + quoteaddr(self.lastAddress))
  829. def smtpState_data(self, code, resp):
  830. s = basic.FileSender()
  831. d = s.beginFileTransfer(
  832. self.getMailData(), self.transport, self.transformChunk)
  833. def ebTransfer(err):
  834. self.sendError(err.value)
  835. d.addCallbacks(self.finishedFileTransfer, ebTransfer)
  836. self._expected = SUCCESS
  837. self._okresponse = self.smtpState_msgSent
  838. def smtpState_msgSent(self, code, resp):
  839. if self._from is not None:
  840. self.sentMail(code, resp, len(self.successAddresses),
  841. self.toAddressesResult, self.log)
  842. self.toAddressesResult = []
  843. self._from = None
  844. self.sendLine(b'RSET')
  845. self._expected = SUCCESS
  846. self._okresponse = self.smtpState_from
  847. ##
  848. ## Helpers for FileSender
  849. ##
  850. def transformChunk(self, chunk):
  851. """
  852. Perform the necessary local to network newline conversion and escape
  853. leading periods.
  854. This method also resets the idle timeout so that as long as process is
  855. being made sending the message body, the client will not time out.
  856. """
  857. self.resetTimeout()
  858. return chunk.replace(b'\n', b'\r\n').replace(b'\r\n.', b'\r\n..')
  859. def finishedFileTransfer(self, lastsent):
  860. if lastsent != b'\n':
  861. line = b'\r\n.'
  862. else:
  863. line = b'.'
  864. self.sendLine(line)
  865. ##
  866. # these methods should be overridden in subclasses
  867. def getMailFrom(self):
  868. """
  869. Return the email address the mail is from.
  870. """
  871. raise NotImplementedError
  872. def getMailTo(self):
  873. """
  874. Return a list of emails to send to.
  875. """
  876. raise NotImplementedError
  877. def getMailData(self):
  878. """
  879. Return file-like object containing data of message to be sent.
  880. Lines in the file should be delimited by '\\n'.
  881. """
  882. raise NotImplementedError
  883. def sendError(self, exc):
  884. """
  885. If an error occurs before a mail message is sent sendError will be
  886. called. This base class method sends a QUIT if the error is
  887. non-fatal and disconnects the connection.
  888. @param exc: The SMTPClientError (or child class) raised
  889. @type exc: C{SMTPClientError}
  890. """
  891. if isinstance(exc, SMTPClientError) and not exc.isFatal:
  892. self._disconnectFromServer()
  893. else:
  894. # If the error was fatal then the communication channel with the
  895. # SMTP Server is broken so just close the transport connection
  896. self.smtpState_disconnect(-1, None)
  897. def sentMail(self, code, resp, numOk, addresses, log):
  898. """
  899. Called when an attempt to send an email is completed.
  900. If some addresses were accepted, code and resp are the response
  901. to the DATA command. If no addresses were accepted, code is -1
  902. and resp is an informative message.
  903. @param code: the code returned by the SMTP Server
  904. @param resp: The string response returned from the SMTP Server
  905. @param numOK: the number of addresses accepted by the remote host.
  906. @param addresses: is a list of tuples (address, code, resp) listing
  907. the response to each RCPT command.
  908. @param log: is the SMTP session log
  909. """
  910. raise NotImplementedError
  911. def _disconnectFromServer(self):
  912. self._expected = range(0, 1000)
  913. self._okresponse = self.smtpState_disconnect
  914. self.sendLine(b'QUIT')
  915. class ESMTPClient(SMTPClient):
  916. """
  917. A client for sending emails over ESMTP.
  918. @ivar heloFallback: Whether or not to fall back to plain SMTP if the C{EHLO}
  919. command is not recognised by the server. If L{requireAuthentication} is
  920. C{True}, or L{requireTransportSecurity} is C{True} and the connection is
  921. not over TLS, this fallback flag will not be honored.
  922. @type heloFallback: L{bool}
  923. @ivar requireAuthentication: If C{True}, refuse to proceed if authentication
  924. cannot be performed. Overrides L{heloFallback}.
  925. @type requireAuthentication: L{bool}
  926. @ivar requireTransportSecurity: If C{True}, refuse to proceed if the
  927. transport cannot be secured. If the transport layer is not already
  928. secured via TLS, this will override L{heloFallback}.
  929. @type requireAuthentication: L{bool}
  930. @ivar context: The context factory to use for STARTTLS, if desired.
  931. @type context: L{ssl.ClientContextFactory}
  932. @ivar _tlsMode: Whether or not the connection is over TLS.
  933. @type _tlsMode: L{bool}
  934. """
  935. heloFallback = True
  936. requireAuthentication = False
  937. requireTransportSecurity = False
  938. context = None
  939. _tlsMode = False
  940. def __init__(self, secret, contextFactory=None, *args, **kw):
  941. SMTPClient.__init__(self, *args, **kw)
  942. self.authenticators = []
  943. self.secret = secret
  944. self.context = contextFactory
  945. def __getattr__(self, name):
  946. if name == "tlsMode":
  947. warnings.warn(
  948. "tlsMode attribute of twisted.mail.smtp.ESMTPClient "
  949. "is deprecated since Twisted 13.0",
  950. category=DeprecationWarning, stacklevel=2)
  951. return self._tlsMode
  952. else:
  953. raise AttributeError(
  954. '%s instance has no attribute %r' % (
  955. self.__class__.__name__, name,))
  956. def __setattr__(self, name, value):
  957. if name == "tlsMode":
  958. warnings.warn(
  959. "tlsMode attribute of twisted.mail.smtp.ESMTPClient "
  960. "is deprecated since Twisted 13.0",
  961. category=DeprecationWarning, stacklevel=2)
  962. self._tlsMode = value
  963. else:
  964. self.__dict__[name] = value
  965. def esmtpEHLORequired(self, code=-1, resp=None):
  966. """
  967. Fail because authentication is required, but the server does not support
  968. ESMTP, which is required for authentication.
  969. @param code: The server status code from the most recently received
  970. server message.
  971. @type code: L{int}
  972. @param resp: The server status response from the most recently received
  973. server message.
  974. @type resp: L{bytes}
  975. """
  976. self.sendError(EHLORequiredError(502, b"Server does not support ESMTP "
  977. b"Authentication", self.log.str()))
  978. def esmtpAUTHRequired(self, code=-1, resp=None):
  979. """
  980. Fail because authentication is required, but the server does not support
  981. any schemes we support.
  982. @param code: The server status code from the most recently received
  983. server message.
  984. @type code: L{int}
  985. @param resp: The server status response from the most recently received
  986. server message.
  987. @type resp: L{bytes}
  988. """
  989. tmp = []
  990. for a in self.authenticators:
  991. tmp.append(a.getName().upper())
  992. auth = b"[%s]" % b", ".join(tmp)
  993. self.sendError(AUTHRequiredError(502, b"Server does not support Client "
  994. b"Authentication schemes %s" % auth, self.log.str()))
  995. def esmtpTLSRequired(self, code=-1, resp=None):
  996. """
  997. Fail because TLS is required and the server does not support it.
  998. @param code: The server status code from the most recently received
  999. server message.
  1000. @type code: L{int}
  1001. @param resp: The server status response from the most recently received
  1002. server message.
  1003. @type resp: L{bytes}
  1004. """
  1005. self.sendError(TLSRequiredError(502, b"Server does not support secure "
  1006. b"communication via TLS / SSL", self.log.str()))
  1007. def esmtpTLSFailed(self, code=-1, resp=None):
  1008. """
  1009. Fail because the TLS handshake wasn't able to be completed.
  1010. @param code: The server status code from the most recently received
  1011. server message.
  1012. @type code: L{int}
  1013. @param resp: The server status response from the most recently received
  1014. server message.
  1015. @type resp: L{bytes}
  1016. """
  1017. self.sendError(TLSError(code, b"Could not complete the SSL/TLS "
  1018. b"handshake", self.log.str()))
  1019. def esmtpAUTHDeclined(self, code=-1, resp=None):
  1020. """
  1021. Fail because the authentication was rejected.
  1022. @param code: The server status code from the most recently received
  1023. server message.
  1024. @type code: L{int}
  1025. @param resp: The server status response from the most recently received
  1026. server message.
  1027. @type resp: L{bytes}
  1028. """
  1029. self.sendError(AUTHDeclinedError(code, resp, self.log.str()))
  1030. def esmtpAUTHMalformedChallenge(self, code=-1, resp=None):
  1031. """
  1032. Fail because the server sent a malformed authentication challenge.
  1033. @param code: The server status code from the most recently received
  1034. server message.
  1035. @type code: L{int}
  1036. @param resp: The server status response from the most recently received
  1037. server message.
  1038. @type resp: L{bytes}
  1039. """
  1040. self.sendError(AuthenticationError(501, b"Login failed because the "
  1041. b"SMTP Server returned a malformed Authentication Challenge",
  1042. self.log.str()))
  1043. def esmtpAUTHServerError(self, code=-1, resp=None):
  1044. """
  1045. Fail because of some other authentication error.
  1046. @param code: The server status code from the most recently received
  1047. server message.
  1048. @type code: L{int}
  1049. @param resp: The server status response from the most recently received
  1050. server message.
  1051. @type resp: L{bytes}
  1052. """
  1053. self.sendError(AuthenticationError(code, resp, self.log.str()))
  1054. def registerAuthenticator(self, auth):
  1055. """
  1056. Registers an Authenticator with the ESMTPClient. The ESMTPClient will
  1057. attempt to login to the SMTP Server in the order the Authenticators are
  1058. registered. The most secure Authentication mechanism should be
  1059. registered first.
  1060. @param auth: The Authentication mechanism to register
  1061. @type auth: L{IClientAuthentication} implementor
  1062. @return: L{None}
  1063. """
  1064. self.authenticators.append(auth)
  1065. def connectionMade(self):
  1066. """
  1067. Called when a connection has been made, and triggers sending an C{EHLO}
  1068. to the server.
  1069. """
  1070. self._tlsMode = ISSLTransport.providedBy(self.transport)
  1071. SMTPClient.connectionMade(self)
  1072. self._okresponse = self.esmtpState_ehlo
  1073. def esmtpState_ehlo(self, code, resp):
  1074. """
  1075. Send an C{EHLO} to the server.
  1076. If L{heloFallback} is C{True}, and there is no requirement for TLS or
  1077. authentication, the client will fall back to basic SMTP.
  1078. @param code: The server status code from the most recently received
  1079. server message.
  1080. @type code: L{int}
  1081. @param resp: The server status response from the most recently received
  1082. server message.
  1083. @type resp: L{bytes}
  1084. @return: L{None}
  1085. """
  1086. self._expected = SUCCESS
  1087. self._okresponse = self.esmtpState_serverConfig
  1088. self._failresponse = self.esmtpEHLORequired
  1089. if self._tlsMode:
  1090. needTLS = False
  1091. else:
  1092. needTLS = self.requireTransportSecurity
  1093. if self.heloFallback and not self.requireAuthentication and not needTLS:
  1094. self._failresponse = self.smtpState_helo
  1095. self.sendLine(b"EHLO " + self.identity)
  1096. def esmtpState_serverConfig(self, code, resp):
  1097. """
  1098. Handle a positive response to the I{EHLO} command by parsing the
  1099. capabilities in the server's response and then taking the most
  1100. appropriate next step towards entering a mail transaction.
  1101. """
  1102. items = {}
  1103. for line in resp.splitlines():
  1104. e = line.split(None, 1)
  1105. if len(e) > 1:
  1106. items[e[0]] = e[1]
  1107. else:
  1108. items[e[0]] = None
  1109. self.tryTLS(code, resp, items)
  1110. def tryTLS(self, code, resp, items):
  1111. """
  1112. Take a necessary step towards being able to begin a mail transaction.
  1113. The step may be to ask the server to being a TLS session. If TLS is
  1114. already in use or not necessary and not available then the step may be
  1115. to authenticate with the server. If TLS is necessary and not available,
  1116. fail the mail transmission attempt.
  1117. This is an internal helper method.
  1118. @param code: The server status code from the most recently received
  1119. server message.
  1120. @type code: L{int}
  1121. @param resp: The server status response from the most recently received
  1122. server message.
  1123. @type resp: L{bytes}
  1124. @param items: A mapping of ESMTP extensions offered by the server. Keys
  1125. are extension identifiers and values are the associated values.
  1126. @type items: L{dict} mapping L{bytes} to L{bytes}
  1127. @return: L{None}
  1128. """
  1129. # has tls can tls must tls result
  1130. # t t t authenticate
  1131. # t t f authenticate
  1132. # t f t authenticate
  1133. # t f f authenticate
  1134. # f t t STARTTLS
  1135. # f t f STARTTLS
  1136. # f f t esmtpTLSRequired
  1137. # f f f authenticate
  1138. hasTLS = self._tlsMode
  1139. canTLS = self.context and b"STARTTLS" in items
  1140. mustTLS = self.requireTransportSecurity
  1141. if hasTLS or not (canTLS or mustTLS):
  1142. self.authenticate(code, resp, items)
  1143. elif canTLS:
  1144. self._expected = [220]
  1145. self._okresponse = self.esmtpState_starttls
  1146. self._failresponse = self.esmtpTLSFailed
  1147. self.sendLine(b"STARTTLS")
  1148. else:
  1149. self.esmtpTLSRequired()
  1150. def esmtpState_starttls(self, code, resp):
  1151. """
  1152. Handle a positive response to the I{STARTTLS} command by starting a new
  1153. TLS session on C{self.transport}.
  1154. Upon success, re-handshake with the server to discover what capabilities
  1155. it has when TLS is in use.
  1156. """
  1157. try:
  1158. self.transport.startTLS(self.context)
  1159. self._tlsMode = True
  1160. except:
  1161. log.err()
  1162. self.esmtpTLSFailed(451)
  1163. # Send another EHLO once TLS has been started to
  1164. # get the TLS / AUTH schemes. Some servers only allow AUTH in TLS mode.
  1165. self.esmtpState_ehlo(code, resp)
  1166. def authenticate(self, code, resp, items):
  1167. if self.secret and items.get(b'AUTH'):
  1168. schemes = items[b'AUTH'].split()
  1169. tmpSchemes = {}
  1170. #XXX: May want to come up with a more efficient way to do this
  1171. for s in schemes:
  1172. tmpSchemes[s.upper()] = 1
  1173. for a in self.authenticators:
  1174. auth = a.getName().upper()
  1175. if auth in tmpSchemes:
  1176. self._authinfo = a
  1177. # Special condition handled
  1178. if auth == b"PLAIN":
  1179. self._okresponse = self.smtpState_from
  1180. self._failresponse = self._esmtpState_plainAuth
  1181. self._expected = [235]
  1182. challenge = base64.b64encode(
  1183. self._authinfo.challengeResponse(self.secret, 1))
  1184. self.sendLine(b"AUTH %s %s" % (auth, challenge))
  1185. else:
  1186. self._expected = [334]
  1187. self._okresponse = self.esmtpState_challenge
  1188. # If some error occurs here, the server declined the
  1189. # AUTH before the user / password phase. This would be
  1190. # a very rare case
  1191. self._failresponse = self.esmtpAUTHServerError
  1192. self.sendLine(b'AUTH ' + auth)
  1193. return
  1194. if self.requireAuthentication:
  1195. self.esmtpAUTHRequired()
  1196. else:
  1197. self.smtpState_from(code, resp)
  1198. def _esmtpState_plainAuth(self, code, resp):
  1199. self._okresponse = self.smtpState_from
  1200. self._failresponse = self.esmtpAUTHDeclined
  1201. self._expected = [235]
  1202. challenge = base64.b64encode(
  1203. self._authinfo.challengeResponse(self.secret, 2))
  1204. self.sendLine(b'AUTH PLAIN ' + challenge)
  1205. def esmtpState_challenge(self, code, resp):
  1206. self._authResponse(self._authinfo, resp)
  1207. def _authResponse(self, auth, challenge):
  1208. self._failresponse = self.esmtpAUTHDeclined
  1209. try:
  1210. challenge = base64.b64decode(challenge)
  1211. except binascii.Error:
  1212. # Illegal challenge, give up, then quit
  1213. self.sendLine(b'*')
  1214. self._okresponse = self.esmtpAUTHMalformedChallenge
  1215. self._failresponse = self.esmtpAUTHMalformedChallenge
  1216. else:
  1217. resp = auth.challengeResponse(self.secret, challenge)
  1218. self._expected = [235, 334]
  1219. self._okresponse = self.smtpState_maybeAuthenticated
  1220. self.sendLine(base64.b64encode(resp))
  1221. def smtpState_maybeAuthenticated(self, code, resp):
  1222. """
  1223. Called to handle the next message from the server after sending a
  1224. response to a SASL challenge. The server response might be another
  1225. challenge or it might indicate authentication has succeeded.
  1226. """
  1227. if code == 235:
  1228. # Yes, authenticated!
  1229. del self._authinfo
  1230. self.smtpState_from(code, resp)
  1231. else:
  1232. # No, not authenticated yet. Keep trying.
  1233. self._authResponse(self._authinfo, resp)
  1234. class ESMTP(SMTP):
  1235. ctx = None
  1236. canStartTLS = False
  1237. startedTLS = False
  1238. authenticated = False
  1239. def __init__(self, chal=None, contextFactory=None):
  1240. SMTP.__init__(self)
  1241. if chal is None:
  1242. chal = {}
  1243. self.challengers = chal
  1244. self.authenticated = False
  1245. self.ctx = contextFactory
  1246. def connectionMade(self):
  1247. SMTP.connectionMade(self)
  1248. self.canStartTLS = ITLSTransport.providedBy(self.transport)
  1249. self.canStartTLS = self.canStartTLS and (self.ctx is not None)
  1250. def greeting(self):
  1251. return SMTP.greeting(self) + b' ESMTP'
  1252. def extensions(self):
  1253. """
  1254. SMTP service extensions
  1255. @return: the SMTP service extensions that are supported.
  1256. @rtype: L{dict} with L{bytes} keys and a value of either L{None} or a
  1257. L{list} of L{bytes}.
  1258. """
  1259. ext = {b'AUTH': _keys(self.challengers)}
  1260. if self.canStartTLS and not self.startedTLS:
  1261. ext[b'STARTTLS'] = None
  1262. return ext
  1263. def lookupMethod(self, command):
  1264. command = nativeString(command)
  1265. m = SMTP.lookupMethod(self, command)
  1266. if m is None:
  1267. m = getattr(self, 'ext_' + command.upper(), None)
  1268. return m
  1269. def listExtensions(self):
  1270. r = []
  1271. for (c, v) in iteritems(self.extensions()):
  1272. if v is not None:
  1273. if v:
  1274. # Intentionally omit extensions with empty argument lists
  1275. r.append(c + b' ' + b' '.join(v))
  1276. else:
  1277. r.append(c)
  1278. return b'\n'.join(r)
  1279. def do_EHLO(self, rest):
  1280. peer = self.transport.getPeer().host
  1281. if not isinstance(peer, bytes):
  1282. peer = peer.encode('idna')
  1283. self._helo = (rest, peer)
  1284. self._from = None
  1285. self._to = []
  1286. self.sendCode(
  1287. 250,
  1288. (self.host + b' Hello ' + peer + b', nice to meet you\n' +
  1289. self.listExtensions())
  1290. )
  1291. def ext_STARTTLS(self, rest):
  1292. if self.startedTLS:
  1293. self.sendCode(503, b'TLS already negotiated')
  1294. elif self.ctx and self.canStartTLS:
  1295. self.sendCode(220, b'Begin TLS negotiation now')
  1296. self.transport.startTLS(self.ctx)
  1297. self.startedTLS = True
  1298. else:
  1299. self.sendCode(454, b'TLS not available')
  1300. def ext_AUTH(self, rest):
  1301. if self.authenticated:
  1302. self.sendCode(503, b'Already authenticated')
  1303. return
  1304. parts = rest.split(None, 1)
  1305. chal = self.challengers.get(parts[0].upper(), lambda: None)()
  1306. if not chal:
  1307. self.sendCode(504, b'Unrecognized authentication type')
  1308. return
  1309. self.mode = AUTH
  1310. self.challenger = chal
  1311. if len(parts) > 1:
  1312. chal.getChallenge() # Discard it, apparently the client does not
  1313. # care about it.
  1314. rest = parts[1]
  1315. else:
  1316. rest = None
  1317. self.state_AUTH(rest)
  1318. def _cbAuthenticated(self, loginInfo):
  1319. """
  1320. Save the state resulting from a successful cred login and mark this
  1321. connection as authenticated.
  1322. """
  1323. result = SMTP._cbAnonymousAuthentication(self, loginInfo)
  1324. self.authenticated = True
  1325. return result
  1326. def _ebAuthenticated(self, reason):
  1327. """
  1328. Handle cred login errors by translating them to the SMTP authenticate
  1329. failed. Translate all other errors into a generic SMTP error code and
  1330. log the failure for inspection. Stop all errors from propagating.
  1331. @param reason: Reason for failure.
  1332. """
  1333. self.challenge = None
  1334. if reason.check(cred.error.UnauthorizedLogin):
  1335. self.sendCode(535, b'Authentication failed')
  1336. else:
  1337. log.err(reason, "SMTP authentication failure")
  1338. self.sendCode(
  1339. 451,
  1340. b'Requested action aborted: local error in processing')
  1341. def state_AUTH(self, response):
  1342. """
  1343. Handle one step of challenge/response authentication.
  1344. @param response: The text of a response. If None, this
  1345. function has been called as a result of an AUTH command with
  1346. no initial response. A response of '*' aborts authentication,
  1347. as per RFC 2554.
  1348. """
  1349. if self.portal is None:
  1350. self.sendCode(454, b'Temporary authentication failure')
  1351. self.mode = COMMAND
  1352. return
  1353. if response is None:
  1354. challenge = self.challenger.getChallenge()
  1355. encoded = base64.b64encode(challenge)
  1356. self.sendCode(334, encoded)
  1357. return
  1358. if response == b'*':
  1359. self.sendCode(501, b'Authentication aborted')
  1360. self.challenger = None
  1361. self.mode = COMMAND
  1362. return
  1363. try:
  1364. uncoded = base64.b64decode(response)
  1365. except (TypeError, binascii.Error):
  1366. self.sendCode(501, b'Syntax error in parameters or arguments')
  1367. self.challenger = None
  1368. self.mode = COMMAND
  1369. return
  1370. self.challenger.setResponse(uncoded)
  1371. if self.challenger.moreChallenges():
  1372. challenge = self.challenger.getChallenge()
  1373. coded = base64.b64encode(challenge)
  1374. self.sendCode(334, coded)
  1375. return
  1376. self.mode = COMMAND
  1377. result = self.portal.login(
  1378. self.challenger, None,
  1379. IMessageDeliveryFactory, IMessageDelivery)
  1380. result.addCallback(self._cbAuthenticated)
  1381. result.addCallback(lambda ign: self.sendCode(235,
  1382. b'Authentication successful.'))
  1383. result.addErrback(self._ebAuthenticated)
  1384. class SenderMixin:
  1385. """
  1386. Utility class for sending emails easily.
  1387. Use with SMTPSenderFactory or ESMTPSenderFactory.
  1388. """
  1389. done = 0
  1390. def getMailFrom(self):
  1391. if not self.done:
  1392. self.done = 1
  1393. return str(self.factory.fromEmail)
  1394. else:
  1395. return None
  1396. def getMailTo(self):
  1397. return self.factory.toEmail
  1398. def getMailData(self):
  1399. return self.factory.file
  1400. def sendError(self, exc):
  1401. # Call the base class to close the connection with the SMTP server
  1402. SMTPClient.sendError(self, exc)
  1403. # Do not retry to connect to SMTP Server if:
  1404. # 1. No more retries left (This allows the correct error to be returned to the errorback)
  1405. # 2. retry is false
  1406. # 3. The error code is not in the 4xx range (Communication Errors)
  1407. if (self.factory.retries >= 0 or
  1408. (not exc.retry and not (exc.code >= 400 and exc.code < 500))):
  1409. self.factory.sendFinished = True
  1410. self.factory.result.errback(exc)
  1411. def sentMail(self, code, resp, numOk, addresses, log):
  1412. # Do not retry, the SMTP server acknowledged the request
  1413. self.factory.sendFinished = True
  1414. if code not in SUCCESS:
  1415. errlog = []
  1416. for addr, acode, aresp in addresses:
  1417. if acode not in SUCCESS:
  1418. errlog.append((addr + b": " +
  1419. networkString("%03d" % (acode,)) +
  1420. b" " + aresp))
  1421. errlog.append(log.str())
  1422. exc = SMTPDeliveryError(code, resp, b'\n'.join(errlog), addresses)
  1423. self.factory.result.errback(exc)
  1424. else:
  1425. self.factory.result.callback((numOk, addresses))
  1426. class SMTPSender(SenderMixin, SMTPClient):
  1427. """
  1428. SMTP protocol that sends a single email based on information it
  1429. gets from its factory, a L{SMTPSenderFactory}.
  1430. """
  1431. class SMTPSenderFactory(protocol.ClientFactory):
  1432. """
  1433. Utility factory for sending emails easily.
  1434. @type currentProtocol: L{SMTPSender}
  1435. @ivar currentProtocol: The current running protocol returned by
  1436. L{buildProtocol}.
  1437. @type sendFinished: C{bool}
  1438. @ivar sendFinished: When the value is set to True, it means the message has
  1439. been sent or there has been an unrecoverable error or the sending has
  1440. been cancelled. The default value is False.
  1441. """
  1442. domain = DNSNAME
  1443. protocol = SMTPSender
  1444. def __init__(self, fromEmail, toEmail, file, deferred, retries=5,
  1445. timeout=None):
  1446. """
  1447. @param fromEmail: The RFC 2821 address from which to send this
  1448. message.
  1449. @param toEmail: A sequence of RFC 2821 addresses to which to
  1450. send this message.
  1451. @param file: A file-like object containing the message to send.
  1452. @param deferred: A Deferred to callback or errback when sending
  1453. of this message completes.
  1454. @type deferred: L{defer.Deferred}
  1455. @param retries: The number of times to retry delivery of this
  1456. message.
  1457. @param timeout: Period, in seconds, for which to wait for
  1458. server responses, or None to wait forever.
  1459. """
  1460. assert isinstance(retries, (int, long))
  1461. if isinstance(toEmail, unicode):
  1462. toEmail = [toEmail.encode('ascii')]
  1463. elif isinstance(toEmail, bytes):
  1464. toEmail = [toEmail]
  1465. else:
  1466. toEmailFinal = []
  1467. for _email in toEmail:
  1468. if not isinstance(_email, bytes):
  1469. _email = _email.encode('ascii')
  1470. toEmailFinal.append(_email)
  1471. toEmail = toEmailFinal
  1472. self.fromEmail = Address(fromEmail)
  1473. self.nEmails = len(toEmail)
  1474. self.toEmail = toEmail
  1475. self.file = file
  1476. self.result = deferred
  1477. self.result.addBoth(self._removeDeferred)
  1478. self.sendFinished = False
  1479. self.currentProtocol = None
  1480. self.retries = -retries
  1481. self.timeout = timeout
  1482. def _removeDeferred(self, result):
  1483. del self.result
  1484. return result
  1485. def clientConnectionFailed(self, connector, err):
  1486. self._processConnectionError(connector, err)
  1487. def clientConnectionLost(self, connector, err):
  1488. self._processConnectionError(connector, err)
  1489. def _processConnectionError(self, connector, err):
  1490. self.currentProtocol = None
  1491. if (self.retries < 0) and (not self.sendFinished):
  1492. log.msg("SMTP Client retrying server. Retry: %s" % -self.retries)
  1493. # Rewind the file in case part of it was read while attempting to
  1494. # send the message.
  1495. self.file.seek(0, 0)
  1496. connector.connect()
  1497. self.retries += 1
  1498. elif not self.sendFinished:
  1499. # If we were unable to communicate with the SMTP server a ConnectionDone will be
  1500. # returned. We want a more clear error message for debugging
  1501. if err.check(error.ConnectionDone):
  1502. err.value = SMTPConnectError(-1, "Unable to connect to server.")
  1503. self.result.errback(err.value)
  1504. def buildProtocol(self, addr):
  1505. p = self.protocol(self.domain, self.nEmails*2+2)
  1506. p.factory = self
  1507. p.timeout = self.timeout
  1508. self.currentProtocol = p
  1509. self.result.addBoth(self._removeProtocol)
  1510. return p
  1511. def _removeProtocol(self, result):
  1512. """
  1513. Remove the protocol created in C{buildProtocol}.
  1514. @param result: The result/error passed to the callback/errback of
  1515. L{defer.Deferred}.
  1516. @return: The C{result} untouched.
  1517. """
  1518. if self.currentProtocol:
  1519. self.currentProtocol = None
  1520. return result
  1521. class LOGINCredentials(_lcredentials):
  1522. """
  1523. L{LOGINCredentials} generates challenges for I{LOGIN} authentication.
  1524. For interoperability with Outlook, the challenge generated does not exactly
  1525. match the one defined in the
  1526. U{draft specification<http://sepp.oetiker.ch/sasl-2.1.19-ds/draft-murchison-sasl-login-00.txt>}.
  1527. """
  1528. def __init__(self):
  1529. _lcredentials.__init__(self)
  1530. self.challenges = [b'Password:', b'Username:']
  1531. @implementer(IClientAuthentication)
  1532. class PLAINAuthenticator:
  1533. def __init__(self, user):
  1534. self.user = user
  1535. def getName(self):
  1536. return b"PLAIN"
  1537. def challengeResponse(self, secret, chal=1):
  1538. if chal == 1:
  1539. return self.user + b'\0' + self.user + b'\0' + secret
  1540. else:
  1541. return b'\0' + self.user + b'\0' + secret
  1542. class ESMTPSender(SenderMixin, ESMTPClient):
  1543. requireAuthentication = True
  1544. requireTransportSecurity = True
  1545. def __init__(self, username, secret, contextFactory=None, *args, **kw):
  1546. self.heloFallback = 0
  1547. self.username = username
  1548. if contextFactory is None:
  1549. contextFactory = self._getContextFactory()
  1550. ESMTPClient.__init__(self, secret, contextFactory, *args, **kw)
  1551. self._registerAuthenticators()
  1552. def _registerAuthenticators(self):
  1553. # Register Authenticator in order from most secure to least secure
  1554. self.registerAuthenticator(CramMD5ClientAuthenticator(self.username))
  1555. self.registerAuthenticator(LOGINAuthenticator(self.username))
  1556. self.registerAuthenticator(PLAINAuthenticator(self.username))
  1557. def _getContextFactory(self):
  1558. if self.context is not None:
  1559. return self.context
  1560. try:
  1561. from twisted.internet import ssl
  1562. except ImportError:
  1563. return None
  1564. else:
  1565. try:
  1566. context = ssl.ClientContextFactory()
  1567. context.method = ssl.SSL.TLSv1_METHOD
  1568. return context
  1569. except AttributeError:
  1570. return None
  1571. class ESMTPSenderFactory(SMTPSenderFactory):
  1572. """
  1573. Utility factory for sending emails easily.
  1574. @type currentProtocol: L{ESMTPSender}
  1575. @ivar currentProtocol: The current running protocol as made by
  1576. L{buildProtocol}.
  1577. """
  1578. protocol = ESMTPSender
  1579. def __init__(self, username, password, fromEmail, toEmail, file,
  1580. deferred, retries=5, timeout=None,
  1581. contextFactory=None, heloFallback=False,
  1582. requireAuthentication=True,
  1583. requireTransportSecurity=True):
  1584. SMTPSenderFactory.__init__(self, fromEmail, toEmail, file, deferred, retries, timeout)
  1585. self.username = username
  1586. self.password = password
  1587. self._contextFactory = contextFactory
  1588. self._heloFallback = heloFallback
  1589. self._requireAuthentication = requireAuthentication
  1590. self._requireTransportSecurity = requireTransportSecurity
  1591. def buildProtocol(self, addr):
  1592. """
  1593. Build an L{ESMTPSender} protocol configured with C{heloFallback},
  1594. C{requireAuthentication}, and C{requireTransportSecurity} as specified
  1595. in L{__init__}.
  1596. This sets L{currentProtocol} on the factory, as well as returning it.
  1597. @rtype: L{ESMTPSender}
  1598. """
  1599. p = self.protocol(self.username, self.password, self._contextFactory,
  1600. self.domain, self.nEmails*2+2)
  1601. p.heloFallback = self._heloFallback
  1602. p.requireAuthentication = self._requireAuthentication
  1603. p.requireTransportSecurity = self._requireTransportSecurity
  1604. p.factory = self
  1605. p.timeout = self.timeout
  1606. self.currentProtocol = p
  1607. self.result.addBoth(self._removeProtocol)
  1608. return p
  1609. def sendmail(smtphost, from_addr, to_addrs, msg, senderDomainName=None, port=25,
  1610. reactor=reactor, username=None, password=None,
  1611. requireAuthentication=False, requireTransportSecurity=False):
  1612. """
  1613. Send an email.
  1614. This interface is intended to be a replacement for L{smtplib.SMTP.sendmail}
  1615. and related methods. To maintain backwards compatibility, it will fall back
  1616. to plain SMTP, if ESMTP support is not available. If ESMTP support is
  1617. available, it will attempt to provide encryption via STARTTLS and
  1618. authentication if a secret is provided.
  1619. @param smtphost: The host the message should be sent to.
  1620. @type smtphost: L{bytes}
  1621. @param from_addr: The (envelope) address sending this mail.
  1622. @type from_addr: L{bytes}
  1623. @param to_addrs: A list of addresses to send this mail to. A string will
  1624. be treated as a list of one address.
  1625. @type to_addr: L{list} of L{bytes} or L{bytes}
  1626. @param msg: The message, including headers, either as a file or a string.
  1627. File-like objects need to support read() and close(). Lines must be
  1628. delimited by '\\n'. If you pass something that doesn't look like a file,
  1629. we try to convert it to a string (so you should be able to pass an
  1630. L{email.message} directly, but doing the conversion with
  1631. L{email.generator} manually will give you more control over the process).
  1632. @param senderDomainName: Name by which to identify. If None, try to pick
  1633. something sane (but this depends on external configuration and may not
  1634. succeed).
  1635. @type senderDomainName: L{bytes}
  1636. @param port: Remote port to which to connect.
  1637. @type port: L{int}
  1638. @param username: The username to use, if wanting to authenticate.
  1639. @type username: L{bytes} or L{unicode}
  1640. @param password: The secret to use, if wanting to authenticate. If you do
  1641. not specify this, SMTP authentication will not occur.
  1642. @type password: L{bytes} or L{unicode}
  1643. @param requireTransportSecurity: Whether or not STARTTLS is required.
  1644. @type requireTransportSecurity: L{bool}
  1645. @param requireAuthentication: Whether or not authentication is required.
  1646. @type requireAuthentication: L{bool}
  1647. @param reactor: The L{reactor} used to make the TCP connection.
  1648. @rtype: L{Deferred}
  1649. @returns: A cancellable L{Deferred}, its callback will be called if a
  1650. message is sent to ANY address, the errback if no message is sent. When
  1651. the C{cancel} method is called, it will stop retrying and disconnect
  1652. the connection immediately.
  1653. The callback will be called with a tuple (numOk, addresses) where numOk
  1654. is the number of successful recipient addresses and addresses is a list
  1655. of tuples (address, code, resp) giving the response to the RCPT command
  1656. for each address.
  1657. """
  1658. if not hasattr(msg, 'read'):
  1659. # It's not a file
  1660. msg = BytesIO(bytes(msg))
  1661. def cancel(d):
  1662. """
  1663. Cancel the L{twisted.mail.smtp.sendmail} call, tell the factory not to
  1664. retry and disconnect the connection.
  1665. @param d: The L{defer.Deferred} to be cancelled.
  1666. """
  1667. factory.sendFinished = True
  1668. if factory.currentProtocol:
  1669. factory.currentProtocol.transport.abortConnection()
  1670. else:
  1671. # Connection hasn't been made yet
  1672. connector.disconnect()
  1673. d = defer.Deferred(cancel)
  1674. if isinstance(username, unicode):
  1675. username = username.encode("utf-8")
  1676. if isinstance(password, unicode):
  1677. password = password.encode("utf-8")
  1678. factory = ESMTPSenderFactory(username, password, from_addr, to_addrs, msg,
  1679. d, heloFallback=True, requireAuthentication=requireAuthentication,
  1680. requireTransportSecurity=requireTransportSecurity)
  1681. if senderDomainName is not None:
  1682. factory.domain = networkString(senderDomainName)
  1683. connector = reactor.connectTCP(smtphost, port, factory)
  1684. return d
  1685. import codecs
  1686. def xtext_encode(s, errors=None):
  1687. r = []
  1688. for ch in iterbytes(s):
  1689. o = ord(ch)
  1690. if ch == '+' or ch == '=' or o < 33 or o > 126:
  1691. r.append(networkString('+%02X' % (o,)))
  1692. else:
  1693. r.append(_bytesChr(o))
  1694. return (b''.join(r), len(s))
  1695. def xtext_decode(s, errors=None):
  1696. """
  1697. Decode the xtext-encoded string C{s}.
  1698. @param s: String to decode.
  1699. @param errors: codec error handling scheme.
  1700. @return: The decoded string.
  1701. """
  1702. r = []
  1703. i = 0
  1704. while i < len(s):
  1705. if s[i:i+1] == b'+':
  1706. try:
  1707. r.append(chr(int(bytes(s[i + 1:i + 3]), 16)))
  1708. except ValueError:
  1709. r.append(ord(s[i:i + 3]))
  1710. i += 3
  1711. else:
  1712. r.append(bytes(s[i:i+1]).decode('ascii'))
  1713. i += 1
  1714. return (''.join(r), len(s))
  1715. class xtextStreamReader(codecs.StreamReader):
  1716. def decode(self, s, errors='strict'):
  1717. return xtext_decode(s)
  1718. class xtextStreamWriter(codecs.StreamWriter):
  1719. def decode(self, s, errors='strict'):
  1720. return xtext_encode(s)
  1721. def xtext_codec(name):
  1722. if name == 'xtext':
  1723. return (xtext_encode, xtext_decode, xtextStreamReader, xtextStreamWriter)
  1724. codecs.register(xtext_codec)