relay.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. # -*- test-case-name: twisted.mail.test.test_mail -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Support for relaying mail.
  6. """
  7. from twisted.mail import smtp
  8. from twisted.python import log
  9. from twisted.internet.address import UNIXAddress
  10. import os
  11. try:
  12. import cPickle as pickle
  13. except ImportError:
  14. import pickle
  15. class DomainQueuer:
  16. """
  17. An SMTP domain which add messages to a queue intended for relaying.
  18. """
  19. def __init__(self, service, authenticated=False):
  20. self.service = service
  21. self.authed = authenticated
  22. def exists(self, user):
  23. """
  24. Check whether mail can be relayed to a user.
  25. @type user: L{User}
  26. @param user: A user.
  27. @rtype: no-argument callable which returns L{IMessage <smtp.IMessage>}
  28. provider
  29. @return: A function which takes no arguments and returns a message
  30. receiver for the user.
  31. @raise SMTPBadRcpt: When mail cannot be relayed to the user.
  32. """
  33. if self.willRelay(user.dest, user.protocol):
  34. # The most cursor form of verification of the addresses
  35. orig = filter(None, str(user.orig).split('@', 1))
  36. dest = filter(None, str(user.dest).split('@', 1))
  37. if len(orig) == 2 and len(dest) == 2:
  38. return lambda: self.startMessage(user)
  39. raise smtp.SMTPBadRcpt(user)
  40. def willRelay(self, address, protocol):
  41. """
  42. Check whether we agree to relay.
  43. The default is to relay for all connections over UNIX
  44. sockets and all connections from localhost.
  45. """
  46. peer = protocol.transport.getPeer()
  47. return (self.authed or isinstance(peer, UNIXAddress) or
  48. peer.host == '127.0.0.1')
  49. def startMessage(self, user):
  50. """
  51. Create an envelope and a message receiver for the relay queue.
  52. @type user: L{User}
  53. @param user: A user.
  54. @rtype: L{IMessage <smtp.IMessage>}
  55. @return: A message receiver.
  56. """
  57. queue = self.service.queue
  58. envelopeFile, smtpMessage = queue.createNewMessage()
  59. with envelopeFile:
  60. log.msg('Queueing mail %r -> %r' % (str(user.orig),
  61. str(user.dest)))
  62. pickle.dump([str(user.orig), str(user.dest)], envelopeFile)
  63. return smtpMessage
  64. class RelayerMixin:
  65. # XXX - This is -totally- bogus
  66. # It opens about a -hundred- -billion- files
  67. # and -leaves- them open!
  68. def loadMessages(self, messagePaths):
  69. self.messages = []
  70. self.names = []
  71. for message in messagePaths:
  72. with open(message + '-H') as fp:
  73. messageContents = pickle.load(fp)
  74. fp = open(message + '-D')
  75. messageContents.append(fp)
  76. self.messages.append(messageContents)
  77. self.names.append(message)
  78. def getMailFrom(self):
  79. if not self.messages:
  80. return None
  81. return self.messages[0][0]
  82. def getMailTo(self):
  83. if not self.messages:
  84. return None
  85. return [self.messages[0][1]]
  86. def getMailData(self):
  87. if not self.messages:
  88. return None
  89. return self.messages[0][2]
  90. def sentMail(self, code, resp, numOk, addresses, log):
  91. """Since we only use one recipient per envelope, this
  92. will be called with 0 or 1 addresses. We probably want
  93. to do something with the error message if we failed.
  94. """
  95. if code in smtp.SUCCESS:
  96. # At least one, i.e. all, recipients successfully delivered
  97. os.remove(self.names[0] + '-D')
  98. os.remove(self.names[0] + '-H')
  99. del self.messages[0]
  100. del self.names[0]
  101. class SMTPRelayer(RelayerMixin, smtp.SMTPClient):
  102. """
  103. A base class for SMTP relayers.
  104. """
  105. def __init__(self, messagePaths, *args, **kw):
  106. """
  107. @type messagePaths: L{list} of L{bytes}
  108. @param messagePaths: The base filename for each message to be relayed.
  109. @type args: 1-L{tuple} of (0) L{bytes} or 2-L{tuple} of
  110. (0) L{bytes}, (1) L{int}
  111. @param args: Positional arguments for L{SMTPClient.__init__}
  112. @type kw: L{dict}
  113. @param kw: Keyword arguments for L{SMTPClient.__init__}
  114. """
  115. smtp.SMTPClient.__init__(self, *args, **kw)
  116. self.loadMessages(messagePaths)
  117. class ESMTPRelayer(RelayerMixin, smtp.ESMTPClient):
  118. """
  119. A base class for ESMTP relayers.
  120. """
  121. def __init__(self, messagePaths, *args, **kw):
  122. """
  123. @type messagePaths: L{list} of L{bytes}
  124. @param messagePaths: The base filename for each message to be relayed.
  125. @type args: 3-L{tuple} of (0) L{bytes}, (1) L{None} or
  126. L{ClientContextFactory
  127. <twisted.internet.ssl.ClientContextFactory>},
  128. (2) L{bytes} or 4-L{tuple} of (0) L{bytes}, (1) L{None}
  129. or L{ClientContextFactory
  130. <twisted.internet.ssl.ClientContextFactory>}, (2) L{bytes},
  131. (3) L{int}
  132. @param args: Positional arguments for L{ESMTPClient.__init__}
  133. @type kw: L{dict}
  134. @param kw: Keyword arguments for L{ESMTPClient.__init__}
  135. """
  136. smtp.ESMTPClient.__init__(self, *args, **kw)
  137. self.loadMessages(messagePaths)