11
11
import time
12
12
import traceback
13
13
14
+ from aiosmtpd .controller import Controller
15
+ from aiosmtpd .lmtp import LMTP
16
+ from aiosmtpd .smtp import SMTP
14
17
from dns import resolver
15
18
import lmtpd
16
19
17
20
from salmon import __version__ , mail , queue , routing
18
21
from salmon .bounce import COMBINED_STATUS_CODES , PRIMARY_STATUS_CODES , SECONDARY_STATUS_CODES
19
22
20
- lmtpd .__version__ = "Salmon Mail router LMTPD, version %s" % __version__
21
- smtpd .__version__ = "Salmon Mail router SMTPD, version %s" % __version__
23
+ ROUTER_VERSION_STRING = "Salmon Mail router, version %s" % __version__
24
+ SMTP_MULTIPLE_RCPTS_ERROR = "451 Will not accept multiple recipients in one transaction"
25
+
26
+ lmtpd .__version__ = ROUTER_VERSION_STRING
27
+ smtpd .__version__ = ROUTER_VERSION_STRING
22
28
23
29
24
30
def undeliverable_message (raw_message , failure_type ):
@@ -153,6 +159,19 @@ def send(self, To, From, Subject, Body):
153
159
self .deliver (msg )
154
160
155
161
162
+ def _deliver (receiver , Peer , From , To , Data , ** kwargs ):
163
+ try :
164
+ logging .debug ("Message received from Peer: %r, From: %r, to To %r." , Peer , From , To )
165
+ routing .Router .deliver (mail .MailRequest (Peer , From , To , Data ))
166
+ except SMTPError as err :
167
+ # looks like they want to return an error, so send it out
168
+ return str (err )
169
+ except Exception :
170
+ logging .exception ("Exception while processing message from Peer: %r, From: %r, to To %r." ,
171
+ Peer , From , To )
172
+ undeliverable_message (Data , "Error in message %r:%r:%r, look in logs." % (Peer , From , To ))
173
+
174
+
156
175
class SMTPChannel (smtpd .SMTPChannel ):
157
176
"""Replaces the standard SMTPChannel with one that rejects more than one recipient"""
158
177
@@ -175,7 +194,7 @@ def smtp_RCPT(self, arg):
175
194
# Of course, if smtpd.SMTPServer or SMTPReceiver implemented a
176
195
# queue and bounces like you're meant too...
177
196
logging .warning ("Client attempted to deliver mail with multiple RCPT TOs. This is not supported." )
178
- self .push ("451 Will not accept multiple recipients in one transaction" )
197
+ self .push (SMTP_MULTIPLE_RCPTS_ERROR )
179
198
else :
180
199
smtpd .SMTPChannel .smtp_RCPT (self , arg )
181
200
@@ -216,17 +235,7 @@ def process_message(self, Peer, From, To, Data, **kwargs):
216
235
"""
217
236
Called by smtpd.SMTPServer when there's a message received.
218
237
"""
219
-
220
- try :
221
- logging .debug ("Message received from Peer: %r, From: %r, to To %r." , Peer , From , To )
222
- routing .Router .deliver (mail .MailRequest (Peer , From , To , Data ))
223
- except SMTPError as err :
224
- # looks like they want to return an error, so send it out
225
- return str (err )
226
- except Exception :
227
- logging .exception ("Exception while processing message from Peer: %r, From: %r, to To %r." ,
228
- Peer , From , To )
229
- undeliverable_message (Data , "Error in message %r:%r:%r, look in logs." % (Peer , From , To ))
238
+ return _deliver (self , Peer , From , To , Data , ** kwargs )
230
239
231
240
def close (self ):
232
241
"""Doesn't do anything except log who called this, since nobody should. Ever."""
@@ -268,25 +277,69 @@ def process_message(self, Peer, From, To, Data, **kwargs):
268
277
"""
269
278
Called by lmtpd.LMTPServer when there's a message received.
270
279
"""
271
-
272
- try :
273
- logging .debug ("Message received from Peer: %r, From: %r, to To %r." , Peer , From , To )
274
- routing .Router .deliver (mail .MailRequest (Peer , From , To , Data ))
275
- except SMTPError as err :
276
- # looks like they want to return an error, so send it out
277
- # and yes, you should still use SMTPError in your handlers
278
- return str (err )
279
- except Exception :
280
- logging .exception ("Exception while processing message from Peer: %r, From: %r, to To %r." ,
281
- Peer , From , To )
282
- undeliverable_message (Data , "Error in message %r:%r:%r, look in logs." % (Peer , From , To ))
280
+ return _deliver (self , Peer , From , To , Data , ** kwargs )
283
281
284
282
def close (self ):
285
283
"""Doesn't do anything except log who called this, since nobody should. Ever."""
286
284
trace = traceback .format_exc (chain = False )
287
285
logging .error (trace )
288
286
289
287
288
+ class SMTPOnlyOneRcpt (SMTP ):
289
+ async def smtp_RCPT (self , arg ):
290
+ if self .envelope .rcpt_tos :
291
+ await self .push (SMTP_MULTIPLE_RCPTS_ERROR )
292
+ else :
293
+ await super ().smtp_RCPT (arg )
294
+
295
+
296
+ class SMTPHandler :
297
+ async def handle_DATA (self , server , session , envelope ):
298
+ assert len (envelope .rcpt_tos ) == 1 , "There should only be one RCPT TO"
299
+ return _deliver (self , session .peer , envelope .mail_from , envelope .rcpt_tos [0 ], envelope .content )
300
+
301
+
302
+ class AsyncSMTPReceiver (Controller ):
303
+ """Receives emails and hands it to the Router for further processing."""
304
+ def __init__ (self , handler = None , ** kwargs ):
305
+ if handler is None :
306
+ handler = SMTPHandler ()
307
+ super ().__init__ (handler , ** kwargs )
308
+
309
+ def factory (self ):
310
+ return SMTPOnlyOneRcpt (self .handler , enable_SMTPUTF8 = self .enable_SMTPUTF8 , ident = ROUTER_VERSION_STRING )
311
+
312
+ def stop (self ):
313
+ """Doesn't do anything except log who called this, since nobody should. Ever."""
314
+ trace = traceback .format_exc (chain = False )
315
+ logging .error (trace )
316
+
317
+
318
+ class LMTPHandler :
319
+ async def handle_DATA (self , server , session , envelope ):
320
+ statuses = []
321
+ for rcpt in envelope .rcpt_tos :
322
+ statuses .append (_deliver (self , session .peer , envelope .mail_from , rcpt , envelope .content ) or "250 Ok" )
323
+ return "\r \n " .join (statuses )
324
+
325
+
326
+ class AsyncLMTPReceiver (Controller ):
327
+ """Receives emails and hands it to the Router for further processing."""
328
+ # TODO: override Controller._run and make it choose between create_server and create_unix_server
329
+ def __init__ (self , handler = None , ** kwargs ):
330
+ if handler is None :
331
+ handler = LMTPHandler ()
332
+ super ().__init__ (handler , ** kwargs )
333
+
334
+ def factory (self ):
335
+ return LMTP (self .handler , enable_SMTPUTF8 = self .enable_SMTPUTF8 , ident = ROUTER_VERSION_STRING )
336
+
337
+ def stop (self ):
338
+ """Doesn't do anything except log who called this, since nobody should. Ever."""
339
+ trace = traceback .format_exc (chain = False )
340
+ logging .error (trace )
341
+
342
+
290
343
class QueueReceiver :
291
344
"""
292
345
Rather than listen on a socket this will watch a queue directory and
0 commit comments