@@ -374,6 +374,8 @@ def start(self, readline, push_data):
374374 self .allow_posting = True
375375 self ._readline = readline
376376 self ._push_data = push_data
377+ self ._logged_in = False
378+ self ._user_sent = False
377379 # Our welcome
378380 self .handle_welcome ()
379381
@@ -666,27 +668,56 @@ def handle_BODY(self, message_spec=None):
666668 self .push_lit (self .sample_body )
667669 self .push_lit ("." )
668670
671+ def handle_AUTHINFO (self , cred_type , data ):
672+ if self ._logged_in :
673+ self .push_lit ('502 Already Logged In' )
674+ elif cred_type == 'user' :
675+ if self ._user_sent :
676+ self .push_lit ('482 User Credential Already Sent' )
677+ else :
678+ self .push_lit ('381 Password Required' )
679+ self ._user_sent = True
680+ elif cred_type == 'pass' :
681+ self .push_lit ('281 Login Successful' )
682+ self ._logged_in = True
683+ else :
684+ raise Exception ('Unknown cred type {}' .format (cred_type ))
685+
669686
670687class NNTPv2Handler (NNTPv1Handler ):
671688 """A handler for RFC 3977 (NNTP "v2")"""
672689
673690 def handle_CAPABILITIES (self ):
674- self . push_lit ( """\
691+ fmt = """\
675692 101 Capability list:
676693 VERSION 2 3
677- IMPLEMENTATION INN 2.5.1
678- AUTHINFO USER
694+ IMPLEMENTATION INN 2.5.1{}
679695 HDR
680696 LIST ACTIVE ACTIVE.TIMES DISTRIB.PATS HEADERS NEWSGROUPS OVERVIEW.FMT
681697 OVER
682698 POST
683699 READER
684- .""" )
700+ ."""
701+
702+ if not self ._logged_in :
703+ self .push_lit (fmt .format ('\n AUTHINFO USER' ))
704+ else :
705+ self .push_lit (fmt .format ('' ))
685706
686707 def handle_OVER (self , message_spec = None ):
687708 return self .handle_XOVER (message_spec )
688709
689710
711+ class CapsAfterLoginNNTPv2Handler (NNTPv2Handler ):
712+ """A handler that allows CAPABILITIES only after login"""
713+
714+ def handle_CAPABILITIES (self ):
715+ if not self ._logged_in :
716+ self .push_lit ('480 You must log in.' )
717+ else :
718+ super ().handle_CAPABILITIES ()
719+
720+
690721class NNTPv1v2TestsMixin :
691722
692723 def setUp (self ):
@@ -695,6 +726,14 @@ def setUp(self):
695726 def test_welcome (self ):
696727 self .assertEqual (self .server .welcome , self .handler .welcome )
697728
729+ def test_authinfo (self ):
730+ if self .nntp_version == 2 :
731+ self .assertIn ('AUTHINFO' , self .server ._caps )
732+ self .server .login ('testuser' , 'testpw' )
733+ # if AUTHINFO is gone from _caps we also know that getcapabilities()
734+ # has been called after login as it should
735+ self .assertNotIn ('AUTHINFO' , self .server ._caps )
736+
698737 def test_date (self ):
699738 resp , date = self .server .date ()
700739 self .assertEqual (resp , "111 20100914001155" )
@@ -1073,6 +1112,18 @@ def test_caps(self):
10731112 self .assertEqual (self .server .nntp_implementation , 'INN 2.5.1' )
10741113
10751114
1115+ class CapsAfterLoginNNTPv2Tests (MockedNNTPTestsMixin , unittest .TestCase ):
1116+ """Tests a probably NNTP v2 server with capabilities only after login."""
1117+
1118+ nntp_version = 2
1119+ handler_class = CapsAfterLoginNNTPv2Handler
1120+
1121+ def test_caps_only_after_login (self ):
1122+ self .assertEqual (self .server ._caps , {})
1123+ self .server .login ('testuser' , 'testpw' )
1124+ self .assertIn ('VERSION' , self .server ._caps )
1125+
1126+
10761127class MiscTests (unittest .TestCase ):
10771128
10781129 def test_decode_header (self ):
@@ -1232,7 +1283,8 @@ def gives(y, M, d, date_str, time_str):
12321283
12331284
12341285def test_main ():
1235- tests = [MiscTests , NNTPv1Tests , NNTPv2Tests , NetworkedNNTPTests ]
1286+ tests = [MiscTests , NNTPv1Tests , NNTPv2Tests , CapsAfterLoginNNTPv2Tests ,
1287+ NetworkedNNTPTests ]
12361288 if _have_ssl :
12371289 tests .append (NetworkedNNTP_SSLTests )
12381290 support .run_unittest (* tests )
0 commit comments