2020from test .test_support import HOST , HOSTv6
2121threading = test_support .import_module ('threading' )
2222
23-
23+ TIMEOUT = 3
2424# the dummy data returned by server over the data channel when
2525# RETR, LIST and NLST commands are issued
2626RETR_DATA = 'abcde12345\r \n ' * 1000
@@ -223,6 +223,7 @@ def __init__(self, address, af=socket.AF_INET):
223223 self .active = False
224224 self .active_lock = threading .Lock ()
225225 self .host , self .port = self .socket .getsockname ()[:2 ]
226+ self .handler_instance = None
226227
227228 def start (self ):
228229 assert not self .active
@@ -246,8 +247,7 @@ def stop(self):
246247
247248 def handle_accept (self ):
248249 conn , addr = self .accept ()
249- self .handler = self .handler (conn )
250- self .close ()
250+ self .handler_instance = self .handler (conn )
251251
252252 def handle_connect (self ):
253253 self .close ()
@@ -262,7 +262,8 @@ def handle_error(self):
262262
263263if ssl is not None :
264264
265- CERTFILE = os .path .join (os .path .dirname (__file__ ), "keycert.pem" )
265+ CERTFILE = os .path .join (os .path .dirname (__file__ ), "keycert3.pem" )
266+ CAFILE = os .path .join (os .path .dirname (__file__ ), "pycacert.pem" )
266267
267268 class SSLConnection (object , asyncore .dispatcher ):
268269 """An asyncore.dispatcher subclass supporting TLS/SSL."""
@@ -271,23 +272,25 @@ class SSLConnection(object, asyncore.dispatcher):
271272 _ssl_closing = False
272273
273274 def secure_connection (self ):
274- self .socket = ssl .wrap_socket (self .socket , suppress_ragged_eofs = False ,
275- certfile = CERTFILE , server_side = True ,
276- do_handshake_on_connect = False ,
277- ssl_version = ssl .PROTOCOL_SSLv23 )
275+ socket = ssl .wrap_socket (self .socket , suppress_ragged_eofs = False ,
276+ certfile = CERTFILE , server_side = True ,
277+ do_handshake_on_connect = False ,
278+ ssl_version = ssl .PROTOCOL_SSLv23 )
279+ self .del_channel ()
280+ self .set_socket (socket )
278281 self ._ssl_accepting = True
279282
280283 def _do_ssl_handshake (self ):
281284 try :
282285 self .socket .do_handshake ()
283- except ssl .SSLError , err :
286+ except ssl .SSLError as err :
284287 if err .args [0 ] in (ssl .SSL_ERROR_WANT_READ ,
285288 ssl .SSL_ERROR_WANT_WRITE ):
286289 return
287290 elif err .args [0 ] == ssl .SSL_ERROR_EOF :
288291 return self .handle_close ()
289292 raise
290- except socket .error , err :
293+ except socket .error as err :
291294 if err .args [0 ] == errno .ECONNABORTED :
292295 return self .handle_close ()
293296 else :
@@ -297,18 +300,21 @@ def _do_ssl_shutdown(self):
297300 self ._ssl_closing = True
298301 try :
299302 self .socket = self .socket .unwrap ()
300- except ssl .SSLError , err :
303+ except ssl .SSLError as err :
301304 if err .args [0 ] in (ssl .SSL_ERROR_WANT_READ ,
302305 ssl .SSL_ERROR_WANT_WRITE ):
303306 return
304- except socket .error , err :
307+ except socket .error as err :
305308 # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return
306309 # from OpenSSL's SSL_shutdown(), corresponding to a
307310 # closed socket condition. See also:
308311 # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html
309312 pass
310313 self ._ssl_closing = False
311- super (SSLConnection , self ).close ()
314+ if getattr (self , '_ccc' , False ) is False :
315+ super (SSLConnection , self ).close ()
316+ else :
317+ pass
312318
313319 def handle_read_event (self ):
314320 if self ._ssl_accepting :
@@ -329,7 +335,7 @@ def handle_write_event(self):
329335 def send (self , data ):
330336 try :
331337 return super (SSLConnection , self ).send (data )
332- except ssl .SSLError , err :
338+ except ssl .SSLError as err :
333339 if err .args [0 ] in (ssl .SSL_ERROR_EOF , ssl .SSL_ERROR_ZERO_RETURN ,
334340 ssl .SSL_ERROR_WANT_READ ,
335341 ssl .SSL_ERROR_WANT_WRITE ):
@@ -339,13 +345,13 @@ def send(self, data):
339345 def recv (self , buffer_size ):
340346 try :
341347 return super (SSLConnection , self ).recv (buffer_size )
342- except ssl .SSLError , err :
348+ except ssl .SSLError as err :
343349 if err .args [0 ] in (ssl .SSL_ERROR_WANT_READ ,
344350 ssl .SSL_ERROR_WANT_WRITE ):
345- return ''
351+ return b ''
346352 if err .args [0 ] in (ssl .SSL_ERROR_EOF , ssl .SSL_ERROR_ZERO_RETURN ):
347353 self .handle_close ()
348- return ''
354+ return b ''
349355 raise
350356
351357 def handle_error (self ):
@@ -355,6 +361,8 @@ def close(self):
355361 if (isinstance (self .socket , ssl .SSLSocket ) and
356362 self .socket ._sslobj is not None ):
357363 self ._do_ssl_shutdown ()
364+ else :
365+ super (SSLConnection , self ).close ()
358366
359367
360368 class DummyTLS_DTPHandler (SSLConnection , DummyDTPHandler ):
@@ -462,12 +470,12 @@ def test_acct(self):
462470
463471 def test_rename (self ):
464472 self .client .rename ('a' , 'b' )
465- self .server .handler .next_response = '200'
473+ self .server .handler_instance .next_response = '200'
466474 self .assertRaises (ftplib .error_reply , self .client .rename , 'a' , 'b' )
467475
468476 def test_delete (self ):
469477 self .client .delete ('foo' )
470- self .server .handler .next_response = '199'
478+ self .server .handler_instance .next_response = '199'
471479 self .assertRaises (ftplib .error_reply , self .client .delete , 'foo' )
472480
473481 def test_size (self ):
@@ -515,7 +523,7 @@ def test_retrlines(self):
515523 def test_storbinary (self ):
516524 f = StringIO .StringIO (RETR_DATA )
517525 self .client .storbinary ('stor' , f )
518- self .assertEqual (self .server .handler .last_received_data , RETR_DATA )
526+ self .assertEqual (self .server .handler_instance .last_received_data , RETR_DATA )
519527 # test new callback arg
520528 flag = []
521529 f .seek (0 )
@@ -527,12 +535,12 @@ def test_storbinary_rest(self):
527535 for r in (30 , '30' ):
528536 f .seek (0 )
529537 self .client .storbinary ('stor' , f , rest = r )
530- self .assertEqual (self .server .handler .rest , str (r ))
538+ self .assertEqual (self .server .handler_instance .rest , str (r ))
531539
532540 def test_storlines (self ):
533541 f = StringIO .StringIO (RETR_DATA .replace ('\r \n ' , '\n ' ))
534542 self .client .storlines ('stor' , f )
535- self .assertEqual (self .server .handler .last_received_data , RETR_DATA )
543+ self .assertEqual (self .server .handler_instance .last_received_data , RETR_DATA )
536544 # test new callback arg
537545 flag = []
538546 f .seek (0 )
@@ -551,14 +559,14 @@ def test_dir(self):
551559 def test_makeport (self ):
552560 self .client .makeport ()
553561 # IPv4 is in use, just make sure send_eprt has not been used
554- self .assertEqual (self .server .handler .last_received_cmd , 'port' )
562+ self .assertEqual (self .server .handler_instance .last_received_cmd , 'port' )
555563
556564 def test_makepasv (self ):
557565 host , port = self .client .makepasv ()
558566 conn = socket .create_connection ((host , port ), 10 )
559567 conn .close ()
560568 # IPv4 is in use, just make sure send_epsv has not been used
561- self .assertEqual (self .server .handler .last_received_cmd , 'pasv' )
569+ self .assertEqual (self .server .handler_instance .last_received_cmd , 'pasv' )
562570
563571 def test_line_too_long (self ):
564572 self .assertRaises (ftplib .Error , self .client .sendcmd ,
@@ -600,13 +608,13 @@ def test_af(self):
600608
601609 def test_makeport (self ):
602610 self .client .makeport ()
603- self .assertEqual (self .server .handler .last_received_cmd , 'eprt' )
611+ self .assertEqual (self .server .handler_instance .last_received_cmd , 'eprt' )
604612
605613 def test_makepasv (self ):
606614 host , port = self .client .makepasv ()
607615 conn = socket .create_connection ((host , port ), 10 )
608616 conn .close ()
609- self .assertEqual (self .server .handler .last_received_cmd , 'epsv' )
617+ self .assertEqual (self .server .handler_instance .last_received_cmd , 'epsv' )
610618
611619 def test_transfer (self ):
612620 def retr ():
@@ -642,7 +650,7 @@ class TestTLS_FTPClass(TestCase):
642650 def setUp (self ):
643651 self .server = DummyTLS_FTPServer ((HOST , 0 ))
644652 self .server .start ()
645- self .client = ftplib .FTP_TLS (timeout = 10 )
653+ self .client = ftplib .FTP_TLS (timeout = TIMEOUT )
646654 self .client .connect (self .server .host , self .server .port )
647655
648656 def tearDown (self ):
@@ -695,6 +703,59 @@ def test_auth_ssl(self):
695703 finally :
696704 self .client .ssl_version = ssl .PROTOCOL_TLSv1
697705
706+ def test_context (self ):
707+ self .client .quit ()
708+ ctx = ssl .SSLContext (ssl .PROTOCOL_TLSv1 )
709+ self .assertRaises (ValueError , ftplib .FTP_TLS , keyfile = CERTFILE ,
710+ context = ctx )
711+ self .assertRaises (ValueError , ftplib .FTP_TLS , certfile = CERTFILE ,
712+ context = ctx )
713+ self .assertRaises (ValueError , ftplib .FTP_TLS , certfile = CERTFILE ,
714+ keyfile = CERTFILE , context = ctx )
715+
716+ self .client = ftplib .FTP_TLS (context = ctx , timeout = TIMEOUT )
717+ self .client .connect (self .server .host , self .server .port )
718+ self .assertNotIsInstance (self .client .sock , ssl .SSLSocket )
719+ self .client .auth ()
720+ self .assertIs (self .client .sock .context , ctx )
721+ self .assertIsInstance (self .client .sock , ssl .SSLSocket )
722+
723+ self .client .prot_p ()
724+ sock = self .client .transfercmd ('list' )
725+ try :
726+ self .assertIs (sock .context , ctx )
727+ self .assertIsInstance (sock , ssl .SSLSocket )
728+ finally :
729+ sock .close ()
730+
731+ def test_check_hostname (self ):
732+ self .client .quit ()
733+ ctx = ssl .SSLContext (ssl .PROTOCOL_TLSv1 )
734+ ctx .verify_mode = ssl .CERT_REQUIRED
735+ ctx .check_hostname = True
736+ ctx .load_verify_locations (CAFILE )
737+ self .client = ftplib .FTP_TLS (context = ctx , timeout = TIMEOUT )
738+
739+ # 127.0.0.1 doesn't match SAN
740+ self .client .connect (self .server .host , self .server .port )
741+ with self .assertRaises (ssl .CertificateError ):
742+ self .client .auth ()
743+ # exception quits connection
744+
745+ self .client .connect (self .server .host , self .server .port )
746+ self .client .prot_p ()
747+ with self .assertRaises (ssl .CertificateError ):
748+ self .client .transfercmd ("list" ).close ()
749+ self .client .quit ()
750+
751+ self .client .connect ("localhost" , self .server .port )
752+ self .client .auth ()
753+ self .client .quit ()
754+
755+ self .client .connect ("localhost" , self .server .port )
756+ self .client .prot_p ()
757+ self .client .transfercmd ("list" ).close ()
758+
698759
699760class TestTimeouts (TestCase ):
700761
0 commit comments