postfix.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. # -*- test-case-name: twisted.test.test_postfix -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Postfix mail transport agent related protocols.
  6. """
  7. import sys
  8. try:
  9. # Python 2
  10. from UserDict import UserDict
  11. except ImportError:
  12. # Python 3
  13. from collections import UserDict
  14. try:
  15. # Python 2
  16. from urllib import quote as _quote, unquote as _unquote
  17. except ImportError:
  18. # Python 3
  19. from urllib.parse import quote as _quote, unquote as _unquote
  20. from twisted.protocols import basic
  21. from twisted.protocols import policies
  22. from twisted.internet import protocol, defer
  23. from twisted.python import log
  24. from twisted.python.compat import intToBytes, nativeString, networkString
  25. # urllib's quote functions just happen to match
  26. # the postfix semantics.
  27. def quote(s):
  28. return networkString(_quote(s))
  29. def unquote(s):
  30. return networkString(_unquote(nativeString(s)))
  31. class PostfixTCPMapServer(basic.LineReceiver, policies.TimeoutMixin):
  32. """Postfix mail transport agent TCP map protocol implementation.
  33. Receive requests for data matching given key via lineReceived,
  34. asks it's factory for the data with self.factory.get(key), and
  35. returns the data to the requester. None means no entry found.
  36. You can use postfix's postmap to test the map service::
  37. /usr/sbin/postmap -q KEY tcp:localhost:4242
  38. """
  39. timeout = 600
  40. delimiter = b'\n'
  41. def connectionMade(self):
  42. self.setTimeout(self.timeout)
  43. def sendCode(self, code, message=b''):
  44. """
  45. Send an SMTP-like code with a message.
  46. """
  47. self.sendLine(intToBytes(code) + b' ' + message)
  48. def lineReceived(self, line):
  49. self.resetTimeout()
  50. try:
  51. request, params = line.split(None, 1)
  52. except ValueError:
  53. request = line
  54. params = None
  55. try:
  56. f = getattr(self, 'do_' + nativeString(request))
  57. except AttributeError:
  58. self.sendCode(400, b'unknown command')
  59. else:
  60. try:
  61. f(params)
  62. except:
  63. self.sendCode(400, b'Command ' + request + b' failed: ' +
  64. networkString(str(sys.exc_info()[1])))
  65. def do_get(self, key):
  66. if key is None:
  67. self.sendCode(400, b"Command 'get' takes 1 parameters.")
  68. else:
  69. d = defer.maybeDeferred(self.factory.get, key)
  70. d.addCallbacks(self._cbGot, self._cbNot)
  71. d.addErrback(log.err)
  72. def _cbNot(self, fail):
  73. self.sendCode(400, fail.getErrorMessage())
  74. def _cbGot(self, value):
  75. if value is None:
  76. self.sendCode(500)
  77. else:
  78. self.sendCode(200, quote(value))
  79. def do_put(self, keyAndValue):
  80. if keyAndValue is None:
  81. self.sendCode(400, b"Command 'put' takes 2 parameters.")
  82. else:
  83. try:
  84. key, value = keyAndValue.split(None, 1)
  85. except ValueError:
  86. self.sendCode(400, b"Command 'put' takes 2 parameters.")
  87. else:
  88. self.sendCode(500, b'put is not implemented yet.')
  89. class PostfixTCPMapDictServerFactory(UserDict, protocol.ServerFactory):
  90. """An in-memory dictionary factory for PostfixTCPMapServer."""
  91. protocol = PostfixTCPMapServer
  92. class PostfixTCPMapDeferringDictServerFactory(protocol.ServerFactory):
  93. """
  94. An in-memory dictionary factory for PostfixTCPMapServer.
  95. """
  96. protocol = PostfixTCPMapServer
  97. def __init__(self, data=None):
  98. self.data = {}
  99. if data is not None:
  100. self.data.update(data)
  101. def get(self, key):
  102. return defer.succeed(self.data.get(key))