@@ -285,27 +285,44 @@ def testFailingHELO(self):
285285 }
286286
287287sim_auth = (
'[email protected] ' ,
'somepassword' )
288- sim_auth_b64encoded = 'AE1yLkFAc29tZXdoZXJlLmNvbQBzb21lcGFzc3dvcmQ='
288+ sim_cram_md5_challenge = ('PENCeUxFREJoU0NnbmhNWitOMjNGNn'
289+ 'dAZWx3b29kLmlubm9zb2Z0LmNvbT4=' )
290+ sim_auth_credentials = {
291+ 'login' : 'TXIuQUBzb21ld2hlcmUuY29t' ,
292+ 'plain' : 'AE1yLkFAc29tZXdoZXJlLmNvbQBzb21lcGFzc3dvcmQ=' ,
293+ 'cram-md5' : ('TXIUQUBZB21LD2HLCMUUY29TIDG4OWQ0MJ'
294+ 'KWZGQ4ODNMNDA4NTGXMDRLZWMYZJDMODG1' ),
295+ }
296+ sim_auth_login_password = 'C29TZXBHC3N3B3JK'
289297
290298291299292300 }
293301
294302# Simulated SMTP channel & server
295303class SimSMTPChannel (smtpd .SMTPChannel ):
304+
305+ def __init__ (self , * args , ** kw ):
306+ self .__extrafeatures = []
307+ super (SimSMTPChannel , self ).__init__ (* args , ** kw )
308+
309+ @property
310+ def _extrafeatures (self ):
311+ return '' .join ([ "250-{}\r \n " .format (x ) for x in self .__extrafeatures ])
312+
313+ def add_feature (self , feature ):
314+ self .__extrafeatures .append (feature )
315+
296316 def smtp_EHLO (self , arg ):
297- resp = '250-testhost\r \n ' \
298- '250-EXPN\r \n ' \
299- '250-SIZE 20000000\r \n ' \
300- '250-STARTTLS\r \n ' \
301- '250-DELIVERBY\r \n ' \
302- '250-AUTH PLAIN\r \n ' \
303- '250 HELP'
317+ resp = ('250-testhost\r \n '
318+ '250-EXPN\r \n '
319+ '250-SIZE 20000000\r \n '
320+ '250-STARTTLS\r \n '
321+ '250-DELIVERBY\r \n ' )
322+ resp = resp + self ._extrafeatures + '250 HELP'
304323 self .push (resp )
305324
306325 def smtp_VRFY (self , arg ):
307- # print '\nsmtp_VRFY(%r)\n' % arg
308-
309326 raw_addr = email .utils .parseaddr (arg )[1 ]
310327 quoted_addr = smtplib .quoteaddr (arg )
311328 if raw_addr in sim_users :
@@ -314,8 +331,6 @@ def smtp_VRFY(self, arg):
314331 self .push ('550 No such user: %s' % arg )
315332
316333 def smtp_EXPN (self , arg ):
317- # print '\nsmtp_EXPN(%r)\n' % arg
318-
319334 list_name = email .utils .parseaddr (arg )[1 ].lower ()
320335 if list_name in sim_lists :
321336 user_list = sim_lists [list_name ]
@@ -329,24 +344,34 @@ def smtp_EXPN(self, arg):
329344 self .push ('550 No access for you!' )
330345
331346 def smtp_AUTH (self , arg ):
347+ if arg .strip ().lower ()== 'cram-md5' :
348+ self .push ('334 {}' .format (sim_cram_md5_challenge ))
349+ return
332350 mech , auth = arg .split ()
333- if mech .lower () == 'plain' :
334- if auth == sim_auth_b64encoded :
335- self .push ('235 ok, go ahead' )
336- else :
337- self .push ('550 No access for you!' )
338- else :
351+ mech = mech .lower ()
352+ if mech not in sim_auth_credentials :
339353 self .push ('504 auth type unimplemented' )
354+ return
355+ if mech == 'plain' and auth == sim_auth_credentials ['plain' ]:
356+ self .push ('235 plain auth ok' )
357+ elif mech == 'login' and auth == sim_auth_credentials ['login' ]:
358+ self .push ('334 Password:' )
359+ else :
360+ self .push ('550 No access for you!' )
340361
341362
342363class SimSMTPServer (smtpd .SMTPServer ):
364+
343365 def handle_accept (self ):
344366 conn , addr = self .accept ()
345- channel = SimSMTPChannel (self , conn , addr )
367+ self . _SMTPchannel = SimSMTPChannel (self , conn , addr )
346368
347369 def process_message (self , peer , mailfrom , rcpttos , data ):
348370 pass
349371
372+ def add_feature (self , feature ):
373+ self ._SMTPchannel .add_feature (feature )
374+
350375
351376# Test various SMTP & ESMTP commands/behaviors that require a simulated server
352377# (i.e., something with more features than DebuggingServer)
@@ -386,7 +411,6 @@ def testEHLO(self):
386411 'size' : '20000000' ,
387412 'starttls' : '' ,
388413 'deliverby' : '' ,
389- 'auth' : ' PLAIN' ,
390414 'help' : '' ,
391415 }
392416
@@ -427,12 +451,41 @@ def testEXPN(self):
427451 self .assertEqual (smtp .expn (u ), expected_unknown )
428452 smtp .quit ()
429453
430- def testAUTH (self ):
454+ def testAUTH_PLAIN (self ):
431455 smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
456+ self .serv .add_feature ("AUTH PLAIN" )
432457
433- expected_auth_ok = (235 , b'ok, go ahead ' )
458+ expected_auth_ok = (235 , b'plain auth ok ' )
434459 self .assertEqual (smtp .login (sim_auth [0 ], sim_auth [1 ]), expected_auth_ok )
435460
461+ # SimSMTPChannel doesn't fully support LOGIN or CRAM-MD5 auth because they
462+ # require a synchronous read to obtain the credentials...so instead smtpd
463+ # sees the credential sent by smtplib's login method as an unknown command,
464+ # which results in smtplib raising an auth error. Fortunately the error
465+ # message contains the encoded credential, so we can partially check that it
466+ # was generated correctly (partially, because the 'word' is uppercased in
467+ # the error message).
468+
469+ def testAUTH_LOGIN (self ):
470+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
471+ self .serv .add_feature ("AUTH LOGIN" )
472+ try : smtp .login (sim_auth [0 ], sim_auth [1 ])
473+ except smtplib .SMTPAuthenticationError as err :
474+ if sim_auth_login_password not in str (err ):
475+ raise "expected encoded password not found in error message"
476+
477+ def testAUTH_CRAM_MD5 (self ):
478+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
479+ self .serv .add_feature ("AUTH CRAM-MD5" )
480+
481+ try : smtp .login (sim_auth [0 ], sim_auth [1 ])
482+ except smtplib .SMTPAuthenticationError as err :
483+ if sim_auth_credentials ['cram-md5' ] not in str (err ):
484+ raise "expected encoded credentials not found in error message"
485+
486+ #TODO: add tests for correct AUTH method fallback now that the
487+ #test infrastructure can support it.
488+
436489
437490def test_main (verbose = None ):
438491 support .run_unittest (GeneralTests , DebuggingServerTests ,
0 commit comments