1
+ def _on_smpt_client (socket ):
2
+ """A client processor for a test SMTP server."""
3
+ socket .send (b'220 testhost Python SMTP proxy version 0.3\r \n ' )
4
+ forced_exit = False
5
+ while not forced_exit and (msg := socket .recv (1024 )):
6
+ ss
7
+
8
+ class SMTPChannel (asynchat .async_chat ):
9
+ COMMAND = 0
10
+ DATA = 1
11
+
12
+ command_size_limit = 512
13
+ command_size_limits = collections .defaultdict (lambda x = command_size_limit : x )
14
+
15
+ @property
16
+ def max_command_size_limit (self ):
17
+ try :
18
+ return max (self .command_size_limits .values ())
19
+ except ValueError :
20
+ return self .command_size_limit
21
+
22
+ """
23
+ def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
24
+ map=None, enable_SMTPUTF8=False, decode_data=False):
25
+ asynchat.async_chat.__init__(self, conn, map=map)
26
+ self.smtp_server = server
27
+ self.conn = conn
28
+ self.addr = addr
29
+ self.data_size_limit = data_size_limit
30
+ self.enable_SMTPUTF8 = enable_SMTPUTF8
31
+ self._decode_data = decode_data
32
+ if enable_SMTPUTF8 and decode_data:
33
+ raise ValueError("decode_data and enable_SMTPUTF8 cannot"
34
+ " be set to True at the same time")
35
+ if decode_data:
36
+ self._emptystring = ''
37
+ self._linesep = '\r \n '
38
+ self._dotsep = '.'
39
+ self._newline = NEWLINE
40
+ else:
41
+ self._emptystring = b''
42
+ self._linesep = b'\r \n '
43
+ self._dotsep = ord(b'.')
44
+ self._newline = b'\n '
45
+ self._set_rset_state()
46
+ self.seen_greeting = ''
47
+ self.extended_smtp = False
48
+ self.command_size_limits.clear()
49
+ self.fqdn = socket.getfqdn()
50
+ try:
51
+ self.peer = conn.getpeername()
52
+ except OSError as err:
53
+ # a race condition may occur if the other end is closing
54
+ # before we can get the peername
55
+ self.close()
56
+ if err.errno != errno.ENOTCONN:
57
+ raise
58
+ return
59
+ print('Peer:', repr(self.peer), file=DEBUGSTREAM)
60
+ self.push('220 %s %s' % (self.fqdn, __version__))
61
+ """
62
+
63
+ def _set_post_data_state (self ):
64
+ """Reset state variables to their post-DATA state."""
65
+ self .smtp_state = self .COMMAND
66
+ self .mailfrom = None
67
+ self .rcpttos = []
68
+ self .require_SMTPUTF8 = False
69
+ self .num_bytes = 0
70
+ self .set_terminator (b'\r \n ' )
71
+
72
+ def _set_rset_state (self ):
73
+ """Reset all state variables except the greeting."""
74
+ self ._set_post_data_state ()
75
+ self .received_data = ''
76
+ self .received_lines = []
77
+
78
+ # Overrides base class for convenience.
79
+ """ def push(self, msg):
80
+ asynchat.async_chat.push(self, bytes(
81
+ msg + '\r \n ', 'utf-8' if self.require_SMTPUTF8 else 'ascii'))
82
+ """
83
+
84
+ # Implementation of base class abstract method
85
+ def collect_incoming_data (self , data ):
86
+ limit = None
87
+ if self .smtp_state == self .COMMAND :
88
+ limit = self .max_command_size_limit
89
+ elif self .smtp_state == self .DATA :
90
+ limit = self .data_size_limit
91
+ if limit and self .num_bytes > limit :
92
+ return
93
+ elif limit :
94
+ self .num_bytes += len (data )
95
+ if self ._decode_data :
96
+ self .received_lines .append (str (data , 'utf-8' ))
97
+ else :
98
+ self .received_lines .append (data )
99
+
100
+ # Implementation of base class abstract method
101
+ def found_terminator (self ):
102
+ line = self ._emptystring .join (self .received_lines )
103
+ print ('Data:' , repr (line ), file = DEBUGSTREAM )
104
+ self .received_lines = []
105
+ if self .smtp_state == self .COMMAND :
106
+ sz , self .num_bytes = self .num_bytes , 0
107
+ if not line :
108
+ self .push ('500 Error: bad syntax' )
109
+ return
110
+ if not self ._decode_data :
111
+ line = str (line , 'utf-8' )
112
+ i = line .find (' ' )
113
+ if i < 0 :
114
+ command = line .upper ()
115
+ arg = None
116
+ else :
117
+ command = line [:i ].upper ()
118
+ arg = line [i + 1 :].strip ()
119
+ max_sz = (self .command_size_limits [command ]
120
+ if self .extended_smtp else self .command_size_limit )
121
+ if sz > max_sz :
122
+ self .push ('500 Error: line too long' )
123
+ return
124
+ method = getattr (self , 'smtp_' + command , None )
125
+ if not method :
126
+ self .push ('500 Error: command "%s" not recognized' % command )
127
+ return
128
+ method (arg )
129
+ return
130
+ else :
131
+ if self .smtp_state != self .DATA :
132
+ self .push ('451 Internal confusion' )
133
+ self .num_bytes = 0
134
+ return
135
+ if self .data_size_limit and self .num_bytes > self .data_size_limit :
136
+ self .push ('552 Error: Too much mail data' )
137
+ self .num_bytes = 0
138
+ return
139
+ # Remove extraneous carriage returns and de-transparency according
140
+ # to RFC 5321, Section 4.5.2.
141
+ data = []
142
+ for text in line .split (self ._linesep ):
143
+ if text and text [0 ] == self ._dotsep :
144
+ data .append (text [1 :])
145
+ else :
146
+ data .append (text )
147
+ self .received_data = self ._newline .join (data )
148
+ args = (self .peer , self .mailfrom , self .rcpttos , self .received_data )
149
+ kwargs = {}
150
+ if not self ._decode_data :
151
+ kwargs = {
152
+ 'mail_options' : self .mail_options ,
153
+ 'rcpt_options' : self .rcpt_options ,
154
+ }
155
+ status = self .smtp_server .process_message (* args , ** kwargs )
156
+ self ._set_post_data_state ()
157
+ if not status :
158
+ self .push ('250 OK' )
159
+ else :
160
+ self .push (status )
161
+
162
+ # SMTP and ESMTP commands
163
+ def smtp_HELO (self , arg ): - - - - - - - - - - - - - - - - - - - - - - - переключение между состояниями . Возможно , пару "состояние + команда" стоит обединить в единую матрицу
164
+ if not arg :
165
+ self .push ('501 Syntax: HELO hostname' )
166
+ return
167
+ # See issue #21783 for a discussion of this behavior.
168
+ if self .seen_greeting :
169
+ self .push ('503 Duplicate HELO/EHLO' )
170
+ return
171
+ self ._set_rset_state ()
172
+ self .seen_greeting = arg
173
+ self .push ('250 %s' % self .fqdn )
174
+
175
+ def smtp_EHLO (self , arg ):
176
+ if not arg :
177
+ self .push ('501 Syntax: EHLO hostname' )
178
+ return
179
+ # See issue #21783 for a discussion of this behavior.
180
+ if self .seen_greeting :
181
+ self .push ('503 Duplicate HELO/EHLO' )
182
+ return
183
+ self ._set_rset_state ()
184
+ self .seen_greeting = arg
185
+ self .extended_smtp = True
186
+ self .push ('250-%s' % self .fqdn )
187
+ if self .data_size_limit :
188
+ self .push ('250-SIZE %s' % self .data_size_limit )
189
+ self .command_size_limits ['MAIL' ] += 26
190
+ if not self ._decode_data :
191
+ self .push ('250-8BITMIME' )
192
+ if self .enable_SMTPUTF8 :
193
+ self .push ('250-SMTPUTF8' )
194
+ self .command_size_limits ['MAIL' ] += 10
195
+ self .push ('250 HELP' )
196
+
197
+ def smtp_NOOP (self , arg ):
198
+ if arg :
199
+ self .push ('501 Syntax: NOOP' )
200
+ else :
201
+ self .push ('250 OK' )
202
+
203
+ def smtp_QUIT (self , arg ):
204
+ # args is ignored
205
+ self .push ('221 Bye' )
206
+ self .close_when_done ()
207
+
208
+ def _strip_command_keyword (self , keyword , arg ):
209
+ keylen = len (keyword )
210
+ if arg [:keylen ].upper () == keyword :
211
+ return arg [keylen :].strip ()
212
+ return ''
213
+
214
+ def _getaddr (self , arg ):
215
+ if not arg :
216
+ return '' , ''
217
+ if arg .lstrip ().startswith ('<' ):
218
+ address , rest = get_angle_addr (arg )
219
+ else :
220
+ address , rest = get_addr_spec (arg )
221
+ if not address :
222
+ return address , rest
223
+ return address .addr_spec , rest
224
+
225
+ def _getparams (self , params ):
226
+ # Return params as dictionary. Return None if not all parameters
227
+ # appear to be syntactically valid according to RFC 1869.
228
+ result = {}
229
+ for param in params :
230
+ param , eq , value = param .partition ('=' )
231
+ if not param .isalnum () or eq and not value :
232
+ return None
233
+ result [param ] = value if eq else True
234
+ return result
235
+
236
+ def smtp_HELP (self , arg ):
237
+ if arg :
238
+ extended = ' [SP <mail-parameters>]'
239
+ lc_arg = arg .upper ()
240
+ if lc_arg == 'EHLO' :
241
+ self .push ('250 Syntax: EHLO hostname' )
242
+ elif lc_arg == 'HELO' :
243
+ self .push ('250 Syntax: HELO hostname' )
244
+ elif lc_arg == 'MAIL' :
245
+ msg = '250 Syntax: MAIL FROM: <address>'
246
+ if self .extended_smtp :
247
+ msg += extended
248
+ self .push (msg )
249
+ elif lc_arg == 'RCPT' :
250
+ msg = '250 Syntax: RCPT TO: <address>'
251
+ if self .extended_smtp :
252
+ msg += extended
253
+ self .push (msg )
254
+ elif lc_arg == 'DATA' :
255
+ self .push ('250 Syntax: DATA' )
256
+ elif lc_arg == 'RSET' :
257
+ self .push ('250 Syntax: RSET' )
258
+ elif lc_arg == 'NOOP' :
259
+ self .push ('250 Syntax: NOOP' )
260
+ elif lc_arg == 'QUIT' :
261
+ self .push ('250 Syntax: QUIT' )
262
+ elif lc_arg == 'VRFY' :
263
+ self .push ('250 Syntax: VRFY <address>' )
264
+ else :
265
+ self .push ('501 Supported commands: EHLO HELO MAIL RCPT '
266
+ 'DATA RSET NOOP QUIT VRFY' )
267
+ else :
268
+ self .push ('250 Supported commands: EHLO HELO MAIL RCPT DATA '
269
+ 'RSET NOOP QUIT VRFY' )
270
+
271
+ def smtp_VRFY (self , arg ):
272
+ if arg :
273
+ address , params = self ._getaddr (arg )
274
+ if address :
275
+ self .push ('252 Cannot VRFY user, but will accept message '
276
+ 'and attempt delivery' )
277
+ else :
278
+ self .push ('502 Could not VRFY %s' % arg )
279
+ else :
280
+ self .push ('501 Syntax: VRFY <address>' )
281
+
282
+ def smtp_MAIL (self , arg ):
283
+ if not self .seen_greeting :
284
+ self .push ('503 Error: send HELO first' )
285
+ return
286
+ print ('===> MAIL' , arg , file = DEBUGSTREAM )
287
+ syntaxerr = '501 Syntax: MAIL FROM: <address>'
288
+ if self .extended_smtp :
289
+ syntaxerr += ' [SP <mail-parameters>]'
290
+ if arg is None :
291
+ self .push (syntaxerr )
292
+ return
293
+ arg = self ._strip_command_keyword ('FROM:' , arg )
294
+ address , params = self ._getaddr (arg )
295
+ if not address :
296
+ self .push (syntaxerr )
297
+ return
298
+ if not self .extended_smtp and params :
299
+ self .push (syntaxerr )
300
+ return
301
+ if self .mailfrom :
302
+ self .push ('503 Error: nested MAIL command' )
303
+ return
304
+ self .mail_options = params .upper ().split ()
305
+ params = self ._getparams (self .mail_options )
306
+ if params is None :
307
+ self .push (syntaxerr )
308
+ return
309
+ if not self ._decode_data :
310
+ body = params .pop ('BODY' , '7BIT' )
311
+ if body not in ['7BIT' , '8BITMIME' ]:
312
+ self .push ('501 Error: BODY can only be one of 7BIT, 8BITMIME' )
313
+ return
314
+ if self .enable_SMTPUTF8 :
315
+ smtputf8 = params .pop ('SMTPUTF8' , False )
316
+ if smtputf8 is True :
317
+ self .require_SMTPUTF8 = True
318
+ elif smtputf8 is not False :
319
+ self .push ('501 Error: SMTPUTF8 takes no arguments' )
320
+ return
321
+ size = params .pop ('SIZE' , None )
322
+ if size :
323
+ if not size .isdigit ():
324
+ self .push (syntaxerr )
325
+ return
326
+ elif self .data_size_limit and int (size ) > self .data_size_limit :
327
+ self .push ('552 Error: message size exceeds fixed maximum message size' )
328
+ return
329
+ if len (params .keys ()) > 0 :
330
+ self .push ('555 MAIL FROM parameters not recognized or not implemented' )
331
+ return
332
+ self .mailfrom = address
333
+ print ('sender:' , self .mailfrom , file = DEBUGSTREAM )
334
+ self .push ('250 OK' )
335
+
336
+ def smtp_RCPT (self , arg ):
337
+ if not self .seen_greeting :
338
+ self .push ('503 Error: send HELO first' );
339
+ return
340
+ print ('===> RCPT' , arg , file = DEBUGSTREAM )
341
+ if not self .mailfrom :
342
+ self .push ('503 Error: need MAIL command' )
343
+ return
344
+ syntaxerr = '501 Syntax: RCPT TO: <address>'
345
+ if self .extended_smtp :
346
+ syntaxerr += ' [SP <mail-parameters>]'
347
+ if arg is None :
348
+ self .push (syntaxerr )
349
+ return
350
+ arg = self ._strip_command_keyword ('TO:' , arg )
351
+ address , params = self ._getaddr (arg )
352
+ if not address :
353
+ self .push (syntaxerr )
354
+ return
355
+ if not self .extended_smtp and params :
356
+ self .push (syntaxerr )
357
+ return
358
+ self .rcpt_options = params .upper ().split ()
359
+ params = self ._getparams (self .rcpt_options )
360
+ if params is None :
361
+ self .push (syntaxerr )
362
+ return
363
+ # XXX currently there are no options we recognize.
364
+ if len (params .keys ()) > 0 :
365
+ self .push ('555 RCPT TO parameters not recognized or not implemented' )
366
+ return
367
+ self .rcpttos .append (address )
368
+ print ('recips:' , self .rcpttos , file = DEBUGSTREAM )
369
+ self .push ('250 OK' )
370
+
371
+ def smtp_RSET (self , arg ):
372
+ if arg :
373
+ self .push ('501 Syntax: RSET' )
374
+ return
375
+ self ._set_rset_state ()
376
+ self .push ('250 OK' )
377
+
378
+ def smtp_DATA (self , arg ):
379
+ if not self .seen_greeting :
380
+ self .push ('503 Error: send HELO first' );
381
+ return
382
+ if not self .rcpttos :
383
+ self .push ('503 Error: need RCPT command' )
384
+ return
385
+ if arg :
386
+ self .push ('501 Syntax: DATA' )
387
+ return
388
+ self .smtp_state = self .DATA
389
+ self .set_terminator (b'\r \n .\r \n ' )
390
+ self .push ('354 End data with <CR><LF>.<CR><LF>' )
391
+
392
+ # Commands that have not been implemented
393
+ def smtp_EXPN (self , arg ):
394
+ self .push ('502 EXPN not implemented' )
0 commit comments