1818HOST = test_support .HOST
1919PORT = 0
2020
21+ SUPPORTS_SSL = False
22+ if hasattr (poplib , 'POP3_SSL' ):
23+ import ssl
24+
25+ SUPPORTS_SSL = True
26+ CERTFILE = os .path .join (os .path .dirname (__file__ ) or os .curdir , "keycert.pem" )
27+
2128# the dummy data returned by server when LIST and RETR commands are issued
2229LIST_RESP = b'1 1\r \n 2 2\r \n 3 3\r \n 4 4\r \n 5 5\r \n .\r \n '
2330RETR_RESP = b"""From: [email protected] \ @@ -40,6 +47,8 @@ def __init__(self, conn):
4047 self .set_terminator (b"\r \n " )
4148 self .in_buffer = []
4249 self .push ('+OK dummy pop3 server ready. <timestamp>' )
50+ self .tls_active = False
51+ self .tls_starting = False
4352
4453 def collect_incoming_data (self , data ):
4554 self .in_buffer .append (data )
@@ -114,16 +123,65 @@ def cmd_quit(self, arg):
114123 self .push ('+OK closing.' )
115124 self .close_when_done ()
116125
126+ def _get_capas (self ):
127+ _capas = dict (self .CAPAS )
128+ if not self .tls_active and SUPPORTS_SSL :
129+ _capas ['STLS' ] = []
130+ return _capas
131+
117132 def cmd_capa (self , arg ):
118133 self .push ('+OK Capability list follows' )
119- if self .CAPAS :
120- for cap , params in self .CAPAS .items ():
134+ if self ._get_capas () :
135+ for cap , params in self ._get_capas () .items ():
121136 _ln = [cap ]
122137 if params :
123138 _ln .extend (params )
124139 self .push (' ' .join (_ln ))
125140 self .push ('.' )
126141
142+ if SUPPORTS_SSL :
143+
144+ def cmd_stls (self , arg ):
145+ if self .tls_active is False :
146+ self .push ('+OK Begin TLS negotiation' )
147+ tls_sock = ssl .wrap_socket (self .socket , certfile = CERTFILE ,
148+ server_side = True ,
149+ do_handshake_on_connect = False ,
150+ suppress_ragged_eofs = False )
151+ self .del_channel ()
152+ self .set_socket (tls_sock )
153+ self .tls_active = True
154+ self .tls_starting = True
155+ self .in_buffer = []
156+ self ._do_tls_handshake ()
157+ else :
158+ self .push ('-ERR Command not permitted when TLS active' )
159+
160+ def _do_tls_handshake (self ):
161+ try :
162+ self .socket .do_handshake ()
163+ except ssl .SSLError as err :
164+ if err .args [0 ] in (ssl .SSL_ERROR_WANT_READ ,
165+ ssl .SSL_ERROR_WANT_WRITE ):
166+ return
167+ elif err .args [0 ] == ssl .SSL_ERROR_EOF :
168+ return self .handle_close ()
169+ raise
170+ except socket .error as err :
171+ if err .args [0 ] == errno .ECONNABORTED :
172+ return self .handle_close ()
173+ else :
174+ self .tls_active = True
175+ self .tls_starting = False
176+
177+ def handle_read (self ):
178+ if self .tls_starting :
179+ self ._do_tls_handshake ()
180+ else :
181+ try :
182+ asynchat .async_chat .handle_read (self )
183+ except ssl .SSLEOFError :
184+ self .handle_close ()
127185
128186class DummyPOP3Server (asyncore .dispatcher , threading .Thread ):
129187
@@ -254,13 +312,25 @@ def test_quit(self):
254312 self .assertIsNone (self .client .sock )
255313 self .assertIsNone (self .client .file )
256314
315+ if SUPPORTS_SSL :
257316
258- SUPPORTS_SSL = False
259- if hasattr ( poplib , 'POP3_SSL' ):
260- import ssl
317+ def test_stls_capa ( self ):
318+ capa = self . client . capa ()
319+ self . assertTrue ( 'STLS' in capa . keys ())
261320
262- SUPPORTS_SSL = True
263- CERTFILE = os .path .join (os .path .dirname (__file__ ) or os .curdir , "keycert.pem" )
321+ def test_stls (self ):
322+ expected = b'+OK Begin TLS negotiation'
323+ resp = self .client .stls ()
324+ self .assertEqual (resp , expected )
325+
326+ def test_stls_context (self ):
327+ expected = b'+OK Begin TLS negotiation'
328+ ctx = ssl .SSLContext (ssl .PROTOCOL_TLSv1 )
329+ resp = self .client .stls (context = ctx )
330+ self .assertEqual (resp , expected )
331+
332+
333+ if SUPPORTS_SSL :
264334
265335 class DummyPOP3_SSLHandler (DummyPOP3Handler ):
266336
@@ -272,34 +342,13 @@ def __init__(self, conn):
272342 self .del_channel ()
273343 self .set_socket (ssl_socket )
274344 # Must try handshake before calling push()
275- self ._ssl_accepting = True
276- self ._do_ssl_handshake ()
345+ self .tls_active = True
346+ self .tls_starting = True
347+ self ._do_tls_handshake ()
277348 self .set_terminator (b"\r \n " )
278349 self .in_buffer = []
279350 self .push ('+OK dummy pop3 server ready. <timestamp>' )
280351
281- def _do_ssl_handshake (self ):
282- try :
283- self .socket .do_handshake ()
284- except ssl .SSLError as err :
285- if err .args [0 ] in (ssl .SSL_ERROR_WANT_READ ,
286- ssl .SSL_ERROR_WANT_WRITE ):
287- return
288- elif err .args [0 ] == ssl .SSL_ERROR_EOF :
289- return self .handle_close ()
290- raise
291- except socket .error as err :
292- if err .args [0 ] == errno .ECONNABORTED :
293- return self .handle_close ()
294- else :
295- self ._ssl_accepting = False
296-
297- def handle_read (self ):
298- if self ._ssl_accepting :
299- self ._do_ssl_handshake ()
300- else :
301- DummyPOP3Handler .handle_read (self )
302-
303352
304353 class TestPOP3_SSLClass (TestPOP3Class ):
305354 # repeat previous tests by using poplib.POP3_SSL
@@ -330,6 +379,39 @@ def test_context(self):
330379 self .assertIs (self .client .sock .context , ctx )
331380 self .assertTrue (self .client .noop ().startswith (b'+OK' ))
332381
382+ def test_stls (self ):
383+ self .assertRaises (poplib .error_proto , self .client .stls )
384+
385+ test_stls_context = test_stls
386+
387+ def test_stls_capa (self ):
388+ capa = self .client .capa ()
389+ self .assertFalse ('STLS' in capa .keys ())
390+
391+
392+ class TestPOP3_TLSClass (TestPOP3Class ):
393+ # repeat previous tests by using poplib.POP3.stls()
394+
395+ def setUp (self ):
396+ self .server = DummyPOP3Server ((HOST , PORT ))
397+ self .server .start ()
398+ self .client = poplib .POP3 (self .server .host , self .server .port , timeout = 3 )
399+ self .client .stls ()
400+
401+ def tearDown (self ):
402+ if self .client .file is not None and self .client .sock is not None :
403+ self .client .quit ()
404+ self .server .stop ()
405+
406+ def test_stls (self ):
407+ self .assertRaises (poplib .error_proto , self .client .stls )
408+
409+ test_stls_context = test_stls
410+
411+ def test_stls_capa (self ):
412+ capa = self .client .capa ()
413+ self .assertFalse (b'STLS' in capa .keys ())
414+
333415
334416class TestTimeouts (TestCase ):
335417
@@ -389,6 +471,7 @@ def test_main():
389471 tests = [TestPOP3Class , TestTimeouts ]
390472 if SUPPORTS_SSL :
391473 tests .append (TestPOP3_SSLClass )
474+ tests .append (TestPOP3_TLSClass )
392475 thread_info = test_support .threading_setup ()
393476 try :
394477 test_support .run_unittest (* tests )
0 commit comments