Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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 71KB

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