Theseus007

Untitled

Feb 10th, 2015
239
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.16 KB | None | 0 0
  1. # Copyright (C) 2006-2015 by the Free Software Foundation, Inc.
  2. #
  3. # This file is part of GNU Mailman.
  4. #
  5. # GNU Mailman is free software: you can redistribute it and/or modify it under
  6. # the terms of the GNU General Public License as published by the Free
  7. # Software Foundation, either version 3 of the License, or (at your option)
  8. # any later version.
  9. #
  10. # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
  11. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  13. # more details.
  14. #
  15. # You should have received a copy of the GNU General Public License along with
  16. # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
  17.  
  18. # XXX This module needs to be refactored to avoid direct access to the
  19. # config.db global.
  20.  
  21. """Mailman LMTP runner (server).
  22.  
  23. Most mail servers can be configured to deliver local messages via 'LMTP'[1].
  24. This module is actually an LMTP server rather than a standard runner.
  25.  
  26. The LMTP runner opens a local TCP port and waits for the mail server to
  27. connect to it. The messages it receives over LMTP are very minimally parsed
  28. for sanity and if they look okay, they are accepted and injected into
  29. Mailman's incoming queue for normal processing. If they don't look good, or
  30. are destined for a bogus sub-address, they are rejected right away, hopefully
  31. so that the peer mail server can provide better diagnostics.
  32.  
  33. [1] RFC 2033 Local Mail Transport Protocol
  34. http://www.faqs.org/rfcs/rfc2033.html
  35. """
  36.  
  37. __all__ = [
  38. 'LMTPRunner',
  39. ]
  40.  
  41.  
  42. import email
  43. import smtpd
  44. import logging
  45. import asyncore
  46.  
  47. from email.utils import parseaddr
  48. from mailman.config import config
  49. from mailman.core.runner import Runner
  50. from mailman.database.transaction import transactional
  51. from mailman.email.message import Message
  52. from mailman.interfaces.listmanager import IListManager
  53. from mailman.utilities.datetime import now
  54. from mailman.interfaces.messages import IMessageStore
  55. from mailman.utilities.email import add_message_hash
  56. from zope.component import getUtility
  57.  
  58.  
  59. elog = logging.getLogger('mailman.error')
  60. qlog = logging.getLogger('mailman.runner')
  61. slog = logging.getLogger('mailman.smtp')
  62.  
  63.  
  64. # We only care about the listname and the sub-addresses as in listname@ or
  65. # listname-request@. This maps user visible subaddress names (which may
  66. # include aliases) to the internal canonical subaddress name.
  67. SUBADDRESS_NAMES = dict(
  68. admin='bounces',
  69. bounces='bounces',
  70. confirm='confirm',
  71. join='join',
  72. leave='leave',
  73. owner='owner',
  74. request='request',
  75. subscribe='join',
  76. unsubscribe='leave',
  77. )
  78.  
  79. # This maps subaddress canonical name to the destination queue that handles
  80. # messages sent to that subaddress.
  81. SUBADDRESS_QUEUES = dict(
  82. bounces='bounces',
  83. confirm='command',
  84. join='command',
  85. leave='command',
  86. owner='in',
  87. request='command',
  88. )
  89.  
  90. DASH = '-'
  91. CRLF = '\r\n'
  92. ERR_451 = '451 Requested action aborted: error in processing'
  93. ERR_501 = '501 Message has defects'
  94. ERR_502 = '502 Error: command HELO not implemented'
  95. ERR_550 = '550 Requested action not taken: mailbox unavailable'
  96. ERR_550_MID = '550 No Message-ID header provided'
  97. ERR_551_MID='551 Duplicate Message'
  98.  
  99. # XXX Blech
  100. smtpd.__version__ = 'Python LMTP runner 1.0'
  101.  
  102.  
  103. def split_recipient(address):
  104. """Split an address into listname, subaddress and domain parts.
  105.  
  106. For example:
  107.  
  108. >>> split_recipient('[email protected]')
  109. ('mylist', None, 'example.com')
  110.  
  111. >>> split_recipient('[email protected]')
  112. ('mylist', 'request', 'example.com')
  113.  
  114. :param address: The destination address.
  115. :return: A 3-tuple of the form (list-shortname, subaddress, domain).
  116. subaddress may be None if this is the list's posting address.
  117. """
  118. localpart, domain = address.split('@', 1)
  119. localpart = localpart.split(config.mta.verp_delimiter, 1)[0]
  120. parts = localpart.split(DASH)
  121. if parts[-1] in SUBADDRESS_NAMES:
  122. listname = DASH.join(parts[:-1])
  123. subaddress = parts[-1]
  124. else:
  125. listname = localpart
  126. subaddress = None
  127. return listname, subaddress, domain
  128.  
  129.  
  130. class Channel(smtpd.SMTPChannel):
  131. """An LMTP channel."""
  132.  
  133. def __init__(self, server, conn, addr):
  134. smtpd.SMTPChannel.__init__(self, server, conn, addr)
  135. # Stash this here since the subclass uses private attributes. :(
  136. self._server = server
  137.  
  138. def smtp_LHLO(self, arg):
  139. """The LMTP greeting, used instead of HELO/EHLO."""
  140. smtpd.SMTPChannel.smtp_HELO(self, arg)
  141.  
  142. def smtp_HELO(self, arg):
  143. """HELO is not a valid LMTP command."""
  144. self.push(ERR_502)
  145.  
  146. ## def push(self, arg):
  147. ## import pdb; pdb.set_trace()
  148. ## return super().push(arg)
  149.  
  150.  
  151. class LMTPRunner(Runner, smtpd.SMTPServer):
  152. # Only __init__ is called on startup. Asyncore is responsible for later
  153. # connections from the MTA. slice and numslices are ignored and are
  154. # necessary only to satisfy the API.
  155.  
  156. is_queue_runner = False
  157.  
  158. def __init__(self, name, slice=None):
  159. localaddr = config.mta.lmtp_host, int(config.mta.lmtp_port)
  160. # Do not call Runner's constructor because there's no QDIR to create
  161. qlog.debug('LMTP server listening on %s:%s',
  162. localaddr[0], localaddr[1])
  163. smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None)
  164. super(LMTPRunner, self).__init__(name, slice)
  165.  
  166. def handle_accept(self):
  167. conn, addr = self.accept()
  168. Channel(self, conn, addr)
  169. slog.debug('LMTP accept from %s', addr)
  170.  
  171. @transactional
  172. def process_message(self, peer, mailfrom, rcpttos, data):
  173. try:
  174. # Refresh the list of list names every time we process a message
  175. # since the set of mailing lists could have changed.
  176. listnames = set(getUtility(IListManager).names)
  177.  
  178. # Parse the message data. If there are any defects in the
  179. # message, reject it right away; it's probably spam.
  180. msg = email.message_from_string(data, Message)
  181. except Exception:
  182. elog.exception('LMTP message parsing')
  183. config.db.abort()
  184. return CRLF.join(ERR_451 for to in rcpttos)
  185. # Do basic post-processing of the message, checking it for defects or
  186. # other missing information.
  187. message_id = msg.get('message-id')
  188. msg-obj=getUtility(IMessageStore)
  189. messages = set(msg-obj.messages)
  190. for msg_a in messages:
  191. if message_id==(msg_a).get('message-id')
  192. return ERR_551_MID
  193.  
  194.  
  195. if message_id is None:
  196. return ERR_550_MID
  197. if msg.defects:
  198. return ERR_501
  199. msg.original_size = len(data)
  200. add_message_hash(msg)
  201.  
  202. msg['X-MailFrom'] = mailfrom
  203. # RFC 2033 requires us to return a status code for every recipient.
  204. status = []
  205. # Now for each address in the recipients, parse the address to first
  206. # see if it's destined for a valid mailing list. If so, then queue
  207. # the message to the appropriate place and record a 250 status for
  208. # that recipient. If not, record a failure status for that recipient.
  209. received_time = now()
  210. for to in rcpttos:
  211. try:
  212. to = parseaddr(to)[1].lower()
  213. local, subaddress, domain = split_recipient(to)
  214. slog.debug('%s to: %s, list: %s, sub: %s, dom: %s',
  215. message_id, to, local, subaddress, domain)
  216. listname = '{}@{}'.format(local, domain)
  217. if listname not in listnames:
  218. status.append(ERR_550)
  219. continue
  220. listid = '{}.{}'.format(local, domain)
  221. # The recipient is a valid mailing list. Find the subaddress
  222. # if there is one, and set things up to enqueue to the proper
  223. # queue.
  224. queue = None
  225. msgdata = dict(listid=listid,
  226. original_size=msg.original_size,
  227. received_time=received_time)
  228. canonical_subaddress = SUBADDRESS_NAMES.get(subaddress)
  229. queue = SUBADDRESS_QUEUES.get(canonical_subaddress)
  230. if subaddress is None:
  231. # The message is destined for the mailing list.
  232. msgdata['to_list'] = True
  233. queue = 'in'
  234. elif canonical_subaddress is None:
  235. # The subaddress was bogus.
  236. slog.error('%s unknown sub-address: %s',
  237. message_id, subaddress)
  238. status.append(ERR_550)
  239. continue
  240. else:
  241. # A valid subaddress.
  242. msgdata['subaddress'] = canonical_subaddress
  243. if canonical_subaddress == 'owner':
  244. msgdata.update(dict(
  245. to_owner=True,
  246. envsender=config.mailman.site_owner,
  247. ))
  248. queue = 'in'
  249. # If we found a valid destination, enqueue the message and add
  250. # a success status for this recipient.
  251. if queue is not None:
  252. config.switchboards[queue].enqueue(msg, msgdata)
  253. slog.debug('%s subaddress: %s, queue: %s',
  254. message_id, canonical_subaddress, queue)
  255. status.append('250 Ok')
  256. except Exception:
  257. slog.exception('Queue detection: %s', msg['message-id'])
  258. config.db.abort()
  259. status.append(ERR_550)
  260. # All done; returning this big status string should give the expected
  261. # response to the LMTP client.
  262. return CRLF.join(status)
  263.  
  264. def run(self):
  265. """See `IRunner`."""
  266. asyncore.loop()
  267.  
  268. def stop(self):
  269. """See `IRunner`."""
  270. asyncore.socket_map.clear()
  271. asyncore.close_all()
  272. self.close()
Advertisement
Add Comment
Please, Sign In to add comment