Ticket #2710: changes-for-jml.diff

File changes-for-jml.diff, 77.6 KB (added by z3p, 10 years ago)

changes made to this branch to address jml's review

  • test/test_session.py

     
    33
    44"""
    55Tests for the 'session' channel implementation in twisted.conch.ssh.session.
     6
     7See also RFC 4254.
    68"""
    79
     10import os, signal, sys, struct
     11
     12from zope.interface import implements
     13
    814from twisted.conch.ssh import common, connection, session
    915from twisted.internet import defer, protocol, error
    1016from twisted.python import components, failure
     17from twisted.python.versions import Version
    1118from twisted.trial import unittest
    12 from zope.interface import implements
    13 import os, signal, sys
    1419
    1520
    1621
     
    2429    def lookupSubsystem(self, name, data):
    2530        """
    2631        If the other side requests the 'subsystem' subsystem, allow it by
    27         returning a MockProcessProtocol to implement it.  Otherwise, fail.
     32        returning a MockProcessProtocol to implement it.  Otherwise, return
     33        None which is interpreted by SSHSession as a failure.
    2834        """
    2935        if name == 'subsystem':
    3036            return MockProcessProtocol()
    3137
    3238
    3339
    34 class StubOldAvatar(object):
     40class StubOldAvatar:
    3541    """
    36     A stub class representing the avatar representing the authenticated user.
     42    A stub class representing the avata representing the authenticated user.
     43    It implements the old I{ISession} interface.
    3744    """
    3845
    3946
     
    4148        """
    4249        If the user requests the TestSubsystem subsystem, connect them
    4350        to our MockProcessProtocol.  If they request the protocol
    44         subsystem, connect them to a MockProtocol.
     51        subsystem, connect them to a MockProtocol.  If they request neither,
     52        then None is returned which is interpreted by SSHSession as a failure.
    4553        """
    4654        if name == 'TestSubsystem':
    4755            self.subsystem = MockProcessProtocol()
     
    5664
    5765class StubSessionForStubOldAvatar(object):
    5866    """
    59     A stub ISession implementation for our StubOldAvatar.
     67    A stub ISession implementation for our StubOldAvatar.  The instance
     68    variables generally keep track of method invocations so that we can test
     69    that the methods were called.
    6070
    6171    @ivar avatar: the C{StubOldAvatar} we are adapting.
    6272    @ivar ptyRequest: if present, the terminal, window size, and modes passed
     
    97107        if terminal != 'bad':
    98108            self.ptyRequest = (terminal, window, modes)
    99109        else:
    100             raise NotImplementedError('not getting a pty')
     110            raise RuntimeError('not getting a pty')
    101111
    102112
    103113    def windowChanged(self, window):
     
    106116        windowChange variable.
    107117        """
    108118        if window == (0, 0, 0, 0):
    109             raise NotImplementedError('not changing the window size')
     119            raise RuntimeError('not changing the window size')
    110120        else:
    111121            self.windowChange = window
    112122
    113123
    114124    def openShell(self, pp):
    115125        """
    116         If we have gotten a shell request before, fail.  Otherwise,
    117         store the process protocol in the shellProtocol variable,
    118         connect it to the EchoTransport and store that as
    119         shellTransport.
     126        If we have gotten a shell request before, fail.  Otherwise, store the
     127        process protocol in the shellProtocol variable, connect it to the
     128        EchoTransport and store that as shellTransport.
    120129        """
    121130        if self.shellProtocol is not None:
    122             raise NotImplementedError('not getting a shell this time')
     131            raise RuntimeError('not getting a shell this time')
    123132        else:
    124133            self.shellProtocol = pp
    125134            self.shellTransport = EchoTransport(pp)
     
    127136
    128137    def execCommand(self, pp, command):
    129138        """
    130         If the command is 'true', store the command, the process protocol,
    131         and the transport we connect to the process protocol
    132         Otherwise, just store the command and raise an error.
     139        If the command is 'true', store the command, the process protocol, and
     140        the transport we connect to the process protocol.  Otherwise, just
     141        store the command and raise an error.
    133142        """
    134143        self.execCommandLine = command
    135         if command == 'true':
     144        if command == 'success':
    136145            self.execProtocol = pp
    137         elif command[:4] == 'echo':
     146        elif command[:6] == 'repeat':
    138147            self.execProtocol = pp
    139148            self.execTransport = EchoTransport(pp)
    140             pp.outReceived(command[5:])
     149            pp.outReceived(command[7:])
    141150        else:
    142             raise NotImplementedError('not getting a command')
     151            raise RuntimeError('not getting a command')
    143152
    144153
    145154    def eofReceived(self):
     
    173182class StubApplicationFactoryForStubNewAvatar:
    174183    """
    175184    A stub ISessionApplicationFactory implementation for our StubNewAvatar.
     185    The instance variables generally keep track of method calls so that the
     186    tests can verify that those methods were called.
    176187
    177188    @ivar avatar: the C{StubOldAvatar} we are adapting.
    178189    @ivar inConnectionOpen: C{True} if the input side is open.
     
    214225        """
    215226        if self.subsystem is not None:
    216227            raise ValueError('already opened a subsystem')
    217         if subsystem == 'echo':
     228        if subsystem == 'repeat':
    218229            self.subsystem = MockApplication()
    219230            self.subsystem.packetData = data
    220231            return self.subsystem
     
    222233
    223234    def getPTY(self, term, windowSize, modes):
    224235        """
    225         Request a psuedoterminal.  Store the data passed to us.
     236        Request a pseudoterminal.  Store the data passed to us.
    226237        """
    227238        self.term = term
    228239        self.windowSize = windowSize
     
    244255        """
    245256
    246257        if self.command is not None:
    247             raise ValueError('already executed a command')
     258            raise RuntimeError('already executed a command')
    248259        self.command = MockApplication()
    249260        self.command.command = command
    250261        return self.command
     
    258269        """
    259270
    260271        if self.shell is not None:
    261             raise ValueError('already opened a shell')
     272            raise RuntimeError('already opened a shell')
    262273        self.shell = MockApplication()
    263274        return self.shell
    264275
     
    322333    def dataReceived(self, data):
    323334        """
    324335        We got some data.  Store it, and echo it back with a tilde appended.
     336        The tilde is appended so that the tests can verify that this method
     337        was called by checking for the extra byte.
    325338        """
    326339        self.data.append(data)
    327340        self.channel.write(data + '~')
     
    330343    def extendedDataReceived(self, type, data):
    331344        """
    332345        We got some extended data.  Store it, and echo it back with an
    333         incremented data type and with a tilde appended to the data.
     346        incremented data type and with a tilde appended to the data.  Also see
     347        the docstring for dataReceived().
    334348        """
    335349        self.extendedData.append((type, data))
    336350        self.channel.writeExtended(type + 1, data + '~')
     
    377391class MockProcessProtocol(protocol.ProcessProtocol):
    378392    """
    379393    A mock ProcessProtocol which echoes back data sent to it and
    380     appends a tilde.
     394    appends a tilde.  The tilde is appended so the tests can verify that
     395    we received and processed the data.
    381396
     397    @ivar packetData: C{str} of data to be sent when the connection is made.
    382398    @ivar data: a C{str} of data received.
    383399    @ivar err: a C{str} of error data received.
    384400    @ivar inConnectionOpen: True if the input side is open.
     
    429445
    430446    def outConnectionLost(self):
    431447        """
    432         close the output side.
     448        Close the output side.
    433449        """
    434450        self.outConnectionOpen = False
    435451
     
    452468class EchoTransport:
    453469    """
    454470    A transport for a ProcessProtocol which echos data that is sent to it with
    455     a Window newline (\\r\\n) appended to it.  If a null byte is in the data,
    456     disconnect.  When we are asked to disconnect, disconnect the C{ProcessProtocol}
    457     with a 0 exit code.
     471    a Window newline (CR LF) appended to it.  If a null byte is in the data,
     472    disconnect.  When we are asked to disconnect, disconnect the
     473    C{ProcessProtocol} with a 0 exit code.
    458474
    459475    @ivar proto: the C{ProcessProtocol} connected to us.
    460476    @ivar data: a C{str} of data written to us.
    461477    """
    462478
    463479
    464     def __init__(self, p):
     480    def __init__(self, processProtocol):
    465481        """
    466482        Initialize our instance variables.
    467483
    468         @param p: a C{ProcessProtocol} to connect to ourself.
     484        @param processProtocol: a C{ProcessProtocol} to connect to ourself.
    469485        """
    470         self.proto = p
    471         p.makeConnection(self)
     486        self.proto = processProtocol
     487        processProtocol.makeConnection(self)
    472488        self.closed = False
    473489        self.data = ''
    474490
     
    490506        If we're asked to disconnect (and we haven't already) shut down
    491507        the C{ProcessProtocol} with a 0 exit code.
    492508        """
    493         if self.closed: return
     509        if self.closed:
     510            return
    494511        self.closed = 1
    495512        self.proto.inConnectionLost()
    496513        self.proto.outConnectionLost()
     
    502519
    503520class MockProtocol(protocol.Protocol):
    504521    """
    505     A sample Process which stores the data passed to it.
     522    A sample Protocol which stores the data passed to it.
    506523
     524    @ivar packetData: a C{str} of data to be sent when the connection is made.
    507525    @ivar data: a C{str} of the data passed to us.
    508526    @ivar open: True if the channel is open.
    509527    @ivar reason: if not None, the reason the protocol was closed.
     
    513531
    514532    def connectionMade(self):
    515533        """
    516         Set up the instance variables.
     534        Set up the instance variables.  If we have any packetData, send it
     535        along.
    517536        """
    518537        self.data = ''
    519538        self.open = True
     
    524543
    525544    def dataReceived(self, data):
    526545        """
    527         Store the received data.
     546        Store the received data and write it back with a tilde appended.
     547        The tilde is appended so that the tests can verify that we processed
     548        the data.
    528549        """
    529550        self.data += data
    530551        self.transport.write(data + '~')
     
    621642    @ivar buf: the data sent to the transport.
    622643    @type buf: C{str}
    623644
    624     @ivar err: the extended data sent to the transport.
    625     @type err: C{str}
    626 
    627645    @ivar close: flags indicating if the transport has been closed.
    628646    @type close: C{bool}
    629647    """
    630648
    631649    buf = ''
    632     err = ''
    633650    close = False
    634651
    635652
     
    640657        self.buf += data
    641658
    642659
    643     def writeErr(self, data):
     660    def loseConnection(self):
    644661        """
    645         Record the extended data in the buffer.
     662        Note that the connection was closed.
    646663        """
    647         self.err += data
     664        self.close = True
    648665
    649666
    650     def loseConnection(self):
     667class StubTransportWithWriteErr(StubTransport):
     668    """
     669    A version of StubTransport which records the error data sent to it.
     670
     671    @ivar err: the extended data sent to the transport.
     672    @type err: C{str}
     673    """
     674
     675    err = ''
     676
     677
     678    def writeErr(self, data):
    651679        """
    652         Note that the connection was closed.
     680        Record the extended data in the buffer.  This was an old interface
     681        that allowed the Transports from ISession.openShell() or
     682        ISession.execCommand() to receive extended data from the client.
    653683        """
    654         self.close = True
     684        self.err += data
    655685
    656686
    657 
     687   
    658688class StubClient(object):
    659689    """
    660690    A stub class representing the client to a SSHSession.
     
    665695
    666696
    667697    def __init__(self):
    668         self.transport = StubTransport()
     698        self.transport = StubTransportWithWriteErr()
    669699
    670700
    671 
    672701class OldSessionInterfaceTestCase(unittest.TestCase):
    673702    """
    674703    Tests for the old SSHSession class interface.  This interface is not ideal,
     
    678707
    679708    def setUp(self):
    680709        """
    681         Make an SSHSession object to test.  Give the chanel some window
    682         so that it's allowed to send packets.
     710        Make an SSHSession object to test.  Give the channel some window
     711        so that it's allowed to send packets.  500 and 100 are arbitrary
     712        values.
    683713        """
    684714        self.session = session.SSHSession(remoteWindow=500,
    685715                remoteMaxPacket=100, conn=StubConnection(),
    686716                avatar=StubOldAvatar())
    687717
    688718
     719    def assertSessionIsStubSession(self):
     720        """
     721        Asserts that self.session.session is an instance of
     722        StubSessionForStubOldAvatar.
     723        """
     724        self.assertIsInstance(self.session.session,
     725                              StubSessionForStubOldAvatar)
     726       
     727
     728    def _wrapWithAssertWarns(self, function, *args):
     729        """
     730        Some of the methods we test give a warning that using an old avatar
     731        (one that implements ISession instead of ISessionApplication) is
     732        deprecated.
     733        """
     734        return self.assertWarns(DeprecationWarning, "Using an avatar that doesn't implement ISessionApplicationFactory is deprecated since Twisted 8.2.",
     735                         __file__, function, *args)
     736
     737               
    689738    def test_init(self):
    690739        """
    691         Test that SSHSession initializes its buffer (buf), client, and
    692         ISession adapter.  The avatar should not need to be adaptable to an
    693         ISession immediately.
     740        SSHSession initializes its buffer (buf), client, and ISession adapter.
     741        The avatar should not need to be adaptable to an ISession immediately.
    694742        """
    695743        s = session.SSHSession(avatar=object) # use object because it doesn't
    696744                                              # have an adapter
     
    699747        self.assertIdentical(s.session, None)
    700748
    701749
    702     def _testGetsSession(self):
     750    def test_client_dataReceived(self):
    703751        """
    704         Check that a function will cause SSHSession to generate a session.
    705 
    706         We return a C{tuple} of a C{SSHSession} and aC{Deferred} which expects
    707         to be called back with a function and arguments to be called.  After
    708         the function is called, the C{SSHSession} should have a session
    709         attribute which provides I{ISession}.
     752        SSHSession.dataReceived() passes data along to a client.  If the data
     753        comes before there is a client, the data should be discarded.
    710754        """
    711         s = session.SSHSession()
    712         d = defer.Deferred()
    713         def check(fargs):
    714             f = fargs[0]
    715             args = fargs[1:]
    716             s.avatar = StubOldAvatar()
    717             self.assertIdentical(s.session, None)
    718             f(*args)
    719             self.assertTrue(session.ISession.providedBy(s.session))
    720         d.addCallback(check)
    721         return s, d
     755        self.session.dataReceived('1')
     756        self.session.client = StubClient()
     757        self.session.dataReceived('2')
     758        self.assertEquals(self.session.client.transport.buf, '2')
    722759
    723 
    724     def test_requestShellGetsSession(self):
     760    def test_client_extReceived(self):
    725761        """
    726         If an ISession adapter isn't already present, request_shell should get
    727         one.
     762        SSHSession.extReceived() passed data of type EXTENDED_DATA_STDERR along
     763        to the client.  If the data comes before there is a client, or if the
     764        data is not of type EXTENDED_DATA_STDERR, it is discared.
    728765        """
    729         s, d = self._testGetsSession()
    730         return d.callback((s.requestReceived, 'shell', ''))
     766        self.session.extReceived(connection.EXTENDED_DATA_STDERR, '1')
     767        self.session.extReceived(255, '2') # 255 is arbitrary
     768        self.session.client = StubClient()
     769        self.session.extReceived(connection.EXTENDED_DATA_STDERR, '3')
     770        self.assertEquals(self.session.client.transport.err, '3')
    731771
    732 
    733     def test_requestExecGetsSession(self):
     772    def test_client_extReceivedWithoutWriteErr(self):
    734773        """
    735         If an ISession adapter isn't already present, request_exec should get
    736         one.
     774        SSHSession.extReceived() should handle the case where the transport
     775        on the client doesn't have a writeErr method.
    737776        """
    738         s, d = self._testGetsSession()
    739         return d.callback((s.requestReceived, 'exec', common.NS('true')))
     777        client = StubClient()
     778        client.transport = StubTransport() # doesn't have writeErr
     779        self.session.client = client
    740780
     781        # should not raise an error
     782        self.session.extReceived(connection.EXTENDED_DATA_STDERR, 'ignored')
    741783
    742     def test_requestPtyReqGetsSession(self):
     784       
     785    def test_client_eofReceived(self):
    743786        """
    744         If an ISession adapter isn't already present, request_pty_req should
    745         get one.
     787        SSHSession.eofReceived() should send a close message to the remote
     788        side.
    746789        """
    747         s, d = self._testGetsSession()
    748         return d.callback((s.requestReceived, 'pty_req',
    749             session.packRequest_pty_req(
    750             'term', (0, 0, 0, 0), [])))
     790        self.session.client = StubClient()
     791        self._wrapWithAssertWarns(self.session.eofReceived)
     792        self.assertTrue(self.session.conn.closes[self.session])
    751793
    752 
    753     def test_requestWindowChangeGetsSession(self):
     794    def test_client_closed(self):
    754795        """
    755         If an ISession adapter isn't already present, request_window_change
    756         should get one.
     796        SSHSession.closed() should tell the transport connected to the client
     797        that the connection was lost.
    757798        """
    758         s, d = self._testGetsSession()
    759         return d.callback((s.requestReceived, 'window_change',
    760             session.packRequest_window_change((1, 1, 1, 1))))
     799        self.session.client = StubClient()
     800        self._wrapWithAssertWarns(self.session.closed)
     801        self.assertTrue(self.session.client.transport.close)
     802        self.session.client.transport.close = False
    761803
    762804
    763     def test_client(self):
     805    def test_badSubsystemDoesNotCreateClient(self):
    764806        """
    765         Test that SSHSession passes data and extended data along to a client.
     807        When a subsystem request fails, SSHSession.client should not be set.
    766808        """
    767         self.session.dataReceived('1')
    768         self.session.extReceived(connection.EXTENDED_DATA_STDERR, '3')
    769         # these don't get sent as part of the client interface
    770         self.session.client = StubClient()
    771         self.session.dataReceived('2')
    772         self.session.extReceived(connection.EXTENDED_DATA_STDERR, '4')
    773         self.assertEquals(self.session.client.transport.buf, '2')
    774         self.assertEquals(self.session.client.transport.err, '4')
    775         self.session.eofReceived()
    776         self.assertTrue(self.session.conn.closes[self.session])
    777         self.session.closed()
    778         self.assertTrue(self.session.client.transport.close)
    779         self.session.client.transport.close = False
     809        ret = self._wrapWithAssertWarns(self.session.requestReceived,
     810                'subsystem', common.NS('BadSubsystem'))
     811        self.assertFalse(ret)
     812        self.assertIdentical(self.session.client, None)
    780813
    781 
     814       
    782815    def test_lookupSubsystem(self):
    783816        """
    784817        When a client requests a subsystem, the SSHSession object should get
     
    786819        the client.
    787820        """
    788821        self.session.session = None # old SSHSession didn't have this attribute
    789         self.session.dataReceived('1')
    790         self.session.extReceived(connection.EXTENDED_DATA_STDERR, '2')
    791         self.assertFalse(self.session.requestReceived(
    792                 'subsystem', common.NS('BadSubsystem')))
    793         self.assertIdentical(self.session.client, None)
    794         self.assertTrue(self.session.requestReceived(
    795                 'subsystem', common.NS('TestSubsystem') + 'data'))
     822        ret = self._wrapWithAssertWarns(self.session.requestReceived,
     823                'subsystem', common.NS('TestSubsystem') + 'data')
     824        self.assertTrue(ret)
    796825        self.assertIsInstance(self.session.client, protocol.ProcessProtocol)
    797826        self.assertIdentical(self.session.sessionApplication.processProtocol,
    798827                             self.session.avatar.subsystem)
    799828
    800         self.assertEquals(self.session.conn.data[self.session],
    801                 ['\x00\x00\x00\x0dTestSubsystemdata~'])
    802         self.session.dataReceived('more data')
    803         self.assertEquals(self.session.conn.data[self.session][-1],
    804                 'more data~')
    805         self.session.closeReceived()
    806         self.assertTrue(self.session.conn.closes[self.session])
    807829
    808 
    809830    def test_lookupSubsystemProtocol(self):
    810831        """
    811         Test that lookupSubsystem correctly handles being returned a
    812         Protocol.
     832        Test that lookupSubsystem handles being returned a Protocol by wrapping
     833        it in a ProcessProtocol.  The ProcessProtocol that wraps the Protocol
     834        should pass attributes along to the protocol.
    813835        """
    814836        self.session.session = None # old SSHSession didn't have this attribute
    815         self.session.dataReceived('1')
    816         self.session.extReceived(connection.EXTENDED_DATA_STDERR, '2')
    817         self.assertIdentical(self.session.client, None)
    818         self.assertTrue(self.session.requestReceived(
    819                 'subsystem', common.NS('protocol') + 'data'))
     837       
     838        ret = self._wrapWithAssertWarns(self.session.requestReceived,
     839                'subsystem', common.NS('protocol') + 'data')
     840        self.assertTrue(ret)
    820841        self.assertIsInstance(self.session.client,
    821842                              protocol.ProcessProtocol)
    822843        self.assertIdentical(
     
    832853            self.session.sessionApplication.processProtocol.transport.proto,
    833854            self.session.sessionApplication.processProtocol)
    834855
    835         self.assertEquals(self.session.conn.data[self.session],
    836                 ['\x00\x00\x00\x08protocoldata~'])
    837         self.session.dataReceived('more data')
    838         self.assertEquals(self.session.conn.data[self.session][-1],
    839                 'more data~')
    840         self.session.closeReceived()
    841         self.assertTrue(self.session.conn.closes[self.session])
    842         self.session.closed()
    843         self.assertEquals(self.session.sessionApplication.processProtocol.proto.reason,
    844                           protocol.connectionDone)
    845856
    846 
    847857    def test_lookupSubsystemDoesNotNeedISession(self):
    848858        """
    849         Previously, if one only wanted to implement a subsystem, an
    850         ISession adapter wasn't needed.
     859        Previously, if one only wanted to implement a subsystem, an ISession
     860        adapter wasn't needed because subsystems were looked up using the
     861        lookupSubsystem method on the avatar.
    851862        """
    852863        s = session.SSHSession(avatar=SubsystemOnlyAvatar(),
    853864                               conn=StubConnection())
    854         self.assertTrue(s.request_subsystem(common.NS('subsystem') + 'data'))
     865        ret = self._wrapWithAssertWarns(s.request_subsystem,
     866                                        common.NS('subsystem') + 'data')
     867        self.assertTrue(ret)
    855868        self.assertNotIdentical(s.applicationFactory, None)
    856869        self.assertIdentical(s.conn.closes.get(s), None)
    857870        s.eofReceived()
     
    861874        s.closed()
    862875
    863876
     877    def test_lookupSubsystem_data(self):
     878        """
     879        After having looked up a subsystem, data should be passed along to the
     880        client.  Additionally, subsystems were passed the entire request packet
     881        as data, instead of just the additional data.
     882
     883        We check for the additional tidle to verify that the data passed
     884        through the client.
     885        """
     886        self.session.session = None # old SSHSession didn't have this attribute
     887        self.session.dataReceived('1')
     888        # subsystems didn't get extended data
     889        self.session.extReceived(connection.EXTENDED_DATA_STDERR, '2')
     890
     891        self._wrapWithAssertWarns(self.session.requestReceived, 'subsystem',
     892                                     common.NS('TestSubsystem') + 'data')
     893
     894        self.assertEquals(self.session.conn.data[self.session],
     895                ['\x00\x00\x00\x0dTestSubsystemdata~'])
     896        self.session.dataReceived('more data')
     897        self.assertEquals(self.session.conn.data[self.session][-1],
     898                'more data~')
     899
     900
     901    def test_lookupSubsystem_closeReceived(self):
     902        """
     903        SSHSession.closeReceived() should sent a close message to the remote
     904        side.
     905        """
     906        self.session.session = None # old SSHSession didn't have this attribute
     907
     908        self._wrapWithAssertWarns(self.session.requestReceived, 'subsystem',
     909                                     common.NS('TestSubsystem') + 'data')
     910
     911        self.session.closeReceived()
     912        self.assertTrue(self.session.conn.closes[self.session])
     913
     914
     915    def assertRequestRaisedRuntimeError(self):
     916        """
     917        Assert that the request we just made raised a RuntimeError (and only a
     918        RuntimeError).
     919        """
     920        errors = self.flushLoggedErrors(RuntimeError)
     921        self.assertEquals(len(errors), 1, "Multiple RuntimeErrors raised: %s" %
     922                          '\n'.join([repr(error) for error in errors]))
     923        errors[0].trap(RuntimeError)
     924
     925
    864926    def test_requestShell(self):
    865927        """
    866928        When a client requests a shell, the SSHSession object should get
     
    868930        calling openShell() with a ProcessProtocol to attach.
    869931        """
    870932        # gets a shell the first time
    871         self.assertTrue(self.session.requestReceived('shell', ''))
    872         self.assertIsInstance(self.session.session,
    873                               StubSessionForStubOldAvatar)
     933        ret = self._wrapWithAssertWarns(self.session.requestReceived, 'shell',
     934                                        '')
     935        self.assertTrue(ret)
     936        self.assertSessionIsStubSession()
    874937        self.assertIsInstance(self.session.client,
    875938                              session.SSHSessionProcessProtocol)
    876939        self.assertIdentical(self.session.session.shellProtocol,
    877940                self.session.client)
    878941        # doesn't get a shell the second time
    879942        self.assertFalse(self.session.requestReceived('shell', ''))
    880         self.flushLoggedErrors()
     943        self.assertRequestRaisedRuntimeError()
     944       
    881945
    882 
    883946    def test_requestShellWithData(self):
    884947        """
    885         When a client executes a shell, it should be able to give pass data back
    886         and forth.
     948        When a client executes a shell, it should be able to give pass data
     949        back and forth between the local and the remote side.
    887950        """
    888         self.assertTrue(self.session.requestReceived('shell', ''))
    889         self.assertIsInstance(self.session.session,
    890                               StubSessionForStubOldAvatar)
     951        ret = self._wrapWithAssertWarns(self.session.requestReceived, 'shell',
     952                                        '')
     953        self.assertTrue(ret)
     954        self.assertSessionIsStubSession()
    891955        self.session.dataReceived('some data\x00')
    892956        self.assertEquals(self.session.session.shellTransport.data,
    893957                          'some data\x00')
     
    905969        calling execCommand with a ProcessProtocol to attach and the
    906970        command line.
    907971        """
    908         self.assertFalse(self.session.requestReceived('exec', common.NS('false')))
    909         self.flushLoggedErrors()
     972        ret = self._wrapWithAssertWarns(self.session.requestReceived, 'exec',
     973                                                      common.NS('failure'))
     974        self.assertFalse(ret)
     975        self.assertRequestRaisedRuntimeError()
    910976        self.assertIdentical(self.session.client, None)
    911977
    912         self.assertTrue(self.session.requestReceived('exec', common.NS('true')))
    913         self.assertIsInstance(self.session.session,
    914                               StubSessionForStubOldAvatar)
     978        self.assertTrue(self.session.requestReceived('exec',
     979                                                     common.NS('success')))
     980        self.assertSessionIsStubSession()
    915981        self.assertIsInstance(self.session.client,
    916982                              session.SSHSessionProcessProtocol)
    917983        self.assertIdentical(self.session.session.execProtocol,
    918984                self.session.client)
    919985        self.assertEquals(self.session.session.execCommandLine,
    920                 'true')
     986                'success')
    921987
    922988
    923989    def test_requestExecWithData(self):
    924990        """
    925         When a client executes a command, it should be able to give pass data back
    926         and forth.  Setting the remoteWindowLeft to 4 tests that the C{SSHChannel}
    927         buf instance variable isn't overriden by SSHSession.
     991        When a client executes a command, it should be able to give pass data
     992        back and forth.
    928993        """
    929         self.session.remoteWindowLeft = 4
    930         self.assertTrue(self.session.requestReceived('exec', common.NS('echo hello')))
    931         self.assertIsInstance(self.session.session,
    932                               StubSessionForStubOldAvatar)
     994        ret = self._wrapWithAssertWarns(self.session.requestReceived, 'exec',
     995                                                     common.NS('repeat hello'))
     996        self.assertTrue(ret)
     997        self.assertSessionIsStubSession()
    933998        self.session.dataReceived('some data')
    934999        self.assertEquals(self.session.session.execTransport.data, 'some data')
    935         self.session.addWindowBytes(20)
    9361000        self.assertEquals(self.session.conn.data[self.session],
    937                           ['hell', 'osome data\r\n'])
     1001                          ['hello', 'some data', '\r\n'])
    9381002        self.session.eofReceived()
    9391003        self.session.closeReceived()
    9401004        self.session.closed()
     
    9431007                          [('exit-status', '\x00\x00\x00\x00', False)])
    9441008
    9451009
     1010    def test_requestExecBuffering(self):
     1011        """
     1012        Channel buffering should not interfere with the buffering that
     1013        SSHSession does.
     1014        """
     1015        self.session.remoteWindowLeft = 4 # arbitrary, to force channel
     1016                                          # buffering
     1017        self.session.dataReceived(' world')
     1018        self._wrapWithAssertWarns(self.session.requestReceived, 'exec',
     1019                                  common.NS('repeat hello'))
     1020        self.assertEquals(self.session.conn.data[self.session],
     1021                          ['hell'])
     1022        self.session.dataReceived('another line')
     1023        self.session.addWindowBytes(30)
     1024        self.assertEquals(''.join(self.session.conn.data[self.session]),
     1025                          'hello world\r\nanother line\r\n')
     1026
     1027
    9461028    def test_requestPty(self):
    9471029        """
    9481030        When a client requests a PTY, the SSHSession object should make
     
    9501032        calling getPty with the terminal type, the window size, and any modes
    9511033        the client gave us.
    9521034        """
    953         self.assertFalse(
    954                 self.session.requestReceived('pty_req',
    955             session.packRequest_pty_req('bad', (1, 2, 3, 4), [])))
    956         self.assertIsInstance(self.session.session,
    957                               StubSessionForStubOldAvatar)
    958         self.flushLoggedErrors()
     1035        # 'bad' terminal type fails
     1036        ret = self._wrapWithAssertWarns(self.session.requestReceived, 'pty_req',
     1037            session.packRequest_pty_req('bad', (1, 2, 3, 4), []))
     1038        self.assertFalse(ret)
     1039        self.assertSessionIsStubSession()
     1040        self.assertRequestRaisedRuntimeError()
     1041        # 'good' terminal type succeeds
    9591042        self.assertTrue(self.session.requestReceived('pty_req',
    9601043            session.packRequest_pty_req('good', (1, 2, 3, 4), [])))
    9611044        self.assertEquals(self.session.session.ptyRequest,
     
    9681051        object should make the request by getting an ISession adapter for the
    9691052        avatar, then calling windowChanged with the new window size.
    9701053        """
    971         self.assertFalse(self.session.requestReceived('window_change',
    972             session.packRequest_window_change((0, 0, 0, 0))))
    973         self.flushLoggedErrors()
    974         self.assertIsInstance(self.session.session,
    975                               StubSessionForStubOldAvatar)
     1054        ret = self._wrapWithAssertWarns(self.session.requestReceived,
     1055                                        'window_change',
     1056            session.packRequest_window_change((0, 0, 0, 0)))
     1057        self.assertFalse(ret)
     1058        self.assertRequestRaisedRuntimeError()
     1059        self.assertSessionIsStubSession()
    9761060        self.assertTrue(self.session.requestReceived('window_change',
    9771061            session.packRequest_window_change((1, 2, 3, 4))))
    9781062        self.assertEquals(self.session.session.windowChange,
     
    9931077        """
    9941078        When a close is received, the session should send a close message.
    9951079        """
    996         self.assertIdentical(self.session.closeReceived(), None)
     1080        ret = self._wrapWithAssertWarns(self.session.closeReceived)
     1081        self.assertIdentical(ret, None)
    9971082        self.assertTrue(self.session.conn.closes[self.session])
    9981083
    9991084
     
    10081093
    10091094
    10101095
     1096class OldSessionWithNoAvatarTestCase(unittest.TestCase):
     1097    """
     1098    Test for the old SSHSession interface.  Several of the old methods
     1099    (request_shell, request_exec, request_pty_req, request_window_change) would
     1100    create a 'session' instance variable from the avatar if one didn't exist
     1101    when they were called.
     1102    """
     1103
     1104   
     1105    def setUp(self):
     1106        self.session = session.SSHSession()
     1107        self.session.avatar = StubOldAvatar()
     1108        self.assertIdentical(self.session.session, None)
     1109
     1110
     1111    def assertSessionProvidesISession(self):
     1112        """
     1113        self.session.session should provide I{ISession}.
     1114        """
     1115        self.assertTrue(session.ISession.providedBy(self.session.session),
     1116                        "ISession not provided by %r" % self.session.session)
     1117
     1118
     1119    def _wrapWithAssertWarns(self, function, *args):
     1120        """
     1121        All of the methods that we test give a warning that using an old avatar
     1122        (one that implements ISession instead of ISessionApplication) is
     1123        deprecated.
     1124        """
     1125        self.assertWarns(DeprecationWarning, "Using an avatar that doesn't implement ISessionApplicationFactory is deprecated since Twisted 8.2.",
     1126                         __file__, function, *args)
     1127
     1128       
     1129    def test_requestShellGetsSession(self):
     1130        """
     1131        If an ISession adapter isn't already present, request_shell should get
     1132        one.
     1133        """
     1134        self._wrapWithAssertWarns(self.session.requestReceived, 'shell', '')
     1135        self.assertSessionProvidesISession()
     1136
     1137
     1138    def test_requestExecGetsSession(self):
     1139        """
     1140        If an ISession adapter isn't already present, request_exec should get
     1141        one.
     1142        """
     1143        self._wrapWithAssertWarns(self.session.requestReceived, 'exec',
     1144                                  common.NS('success'))
     1145        self.assertSessionProvidesISession()
     1146
     1147
     1148    def test_requestPtyReqGetsSession(self):
     1149        """
     1150        If an ISession adapter isn't already present, request_pty_req should
     1151        get one.
     1152        """
     1153        self._wrapWithAssertWarns(self.session.requestReceived, 'pty_req',
     1154                                  session.packRequest_pty_req(
     1155                'term', (0, 0, 0, 0), []))
     1156        self.assertSessionProvidesISession()
     1157
     1158
     1159    def test_requestWindowChangeGetsSession(self):
     1160        """
     1161        If an ISession adapter isn't already present, request_window_change
     1162        should get one.
     1163        """
     1164        self._wrapWithAssertWarns(self.session.requestReceived,
     1165                                  'window_change',
     1166                                  session.packRequest_window_change(
     1167                (1, 1, 1, 1)))
     1168        self.assertSessionProvidesISession()
     1169                                     
     1170   
     1171
    10111172class TestHelpers(unittest.TestCase):
    10121173    """
    10131174    Tests for the 4 helper functions: parseRequest_* and packRequest_*.
     
    10291190            uint32  mode value
    10301191        """
    10311192        self.assertEquals(session.parseRequest_pty_req(common.NS('xterm') +
    1032                 '\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
    1033                 '\x04\x00\x00\x00\x05\x05\x00\x00\x00\x06'),
    1034                 ('xterm', (2, 1, 3, 4), [(5, 6)]))
     1193                                                       struct.pack('>4L',
     1194                                                                   1, 2, 3, 4)
     1195                                                       + common.NS(
     1196                    struct.pack('>BL', 5, 6))),
     1197                          ('xterm', (2, 1, 3, 4), [(5, 6)]))
    10351198
    10361199
     1200    def test_packRequest_pty_req_old(self):
     1201        """
     1202        See test_parseRequest_pty_req for the payload format.
     1203        """
     1204        def _():
     1205            return session.packRequest_pty_req('xterm', (2, 1, 3, 4),
     1206                                               '\x05\x00\x00\x00\x06')
     1207
     1208        packed = self.assertWarns(DeprecationWarning,
     1209                                  "packRequest_pty_req should be packing "
     1210                                  "the modes.",
     1211                                  __file__, _)
     1212        self.assertEquals(packed,
     1213                          common.NS('xterm') + struct.pack('>4L', 1, 2, 3, 4) +
     1214                          common.NS(struct.pack('>BL', 5, 6)))
     1215
     1216
    10371217    def test_packRequest_pty_req(self):
    10381218        """
    10391219        See test_parseRequest_pty_req for the payload format.
    10401220        """
    1041         self.assertEquals(self.assertWarns(DeprecationWarning,
    1042             "packRequest_pty_req should be packing the modes.",
    1043             unittest.__file__, session.packRequest_pty_req, 'xterm',
    1044             (2, 1, 3, 4), '\x05\x00\x00\x00\x06'),
    1045             common.NS('xterm') + '\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00'
    1046             '\x00\x03\x00\x00\x00\x04\x00\x00\x00\x05\x05\x00\x00\x00\x06')
     1221        packed = session.packRequest_pty_req('xterm', (2, 1, 3, 4), [(5, 6)])
     1222        self.assertEquals(packed,
     1223                          common.NS('xterm') + struct.pack('>4L', 1, 2, 3, 4) +
     1224                          common.NS(struct.pack('>BL', 5, 6)))
    10471225
    10481226
    10491227    def test_parseRequest_window_change(self):
     
    10531231            uint32  rows
    10541232            uint32  x pixels
    10551233            uint32  y pixels
     1234
     1235        parseRequest_window_change() returns (rows, columns, x pixels,
     1236        y pixels).
    10561237        """
    1057         self.assertEquals(session.parseRequest_window_change('\x00\x00\x00\x01'
    1058             '\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04'), (2, 1, 3, 4))
     1238        self.assertEquals(session.parseRequest_window_change(
     1239                struct.pack('>4L', 1, 2, 3, 4)), (2, 1, 3, 4))
    10591240
    10601241
    10611242    def test_packRequest_window_change(self):
     
    10631244        See test_parseRequest_window_change for the payload format.
    10641245        """
    10651246        self.assertEquals(session.packRequest_window_change((2, 1, 3, 4)),
    1066             '\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04')
     1247                          struct.pack('>4L', 1, 2, 3, 4))
    10671248
    10681249
    10691250
    10701251class SSHSessionProcessProtocolTestCase(unittest.TestCase):
    10711252    """
    10721253    SSHSessionProcessProtocol is an obsolete class used as a ProcessProtocol
    1073     for executed programs.  It has has a couple transport methods, namely
     1254    for executed programs.  It has has a couple of transport methods, namely
    10741255    write() and loseConnection()
    10751256    """
    10761257
     
    10941275    def test_getSignalName(self):
    10951276        """
    10961277        _getSignalName should return the name of a signal when given the
    1097         signal number.  Also, it should only generate the signal dictionary
    1098         once.
     1278        signal number.
    10991279        """
     1280        for signalName in session.SUPPORTED_SIGNALS:
     1281            signalName = 'SIG' + signalName
     1282            signalValue = getattr(signal, signalName)
     1283            sshName = self.pp._getSignalName(signalValue)
     1284            self.assertEquals(sshName, signalName,
     1285                              "%i: %s != %s" % (signalValue, sshName,
     1286                                                signalName))
     1287
     1288
     1289    def test_getSignalNameWithLocalSignal(self):
     1290        """
     1291        If there are signals in the signal module which aren't in the SSH RFC,
     1292        we map their name to [signal name]@[platform].
     1293        """
    11001294        signal.SIGTwistedTest = signal.NSIG + 1 # value can't exist normally
    11011295        # Force reinitialization of signals
    1102         self.pp.__class__._signalNames = None
    1103         try:
    1104             for signame in session.supportedSignals:
    1105                 signame = 'SIG' + signame
    1106                 value = getattr(signal, signame)
    1107                 self.assertEquals(self.pp._getSignalName(value),
    1108                         signame, "%i: %s != %s" % (value,
    1109                             self.pp._getSignalName(value), signame))
    1110             self.assertEquals(self.pp._getSignalName(signal.SIGTwistedTest),
    1111                 'SIGTwistedTest@' + sys.platform)
    1112             newPP = session.SSHSessionProcessProtocol(self.session)
    1113             self.assertNotIdentical(newPP._signalNames, None)
    1114             self.assertIdentical(newPP._signalNames, self.pp._signalNames)
    1115         finally:
    1116             del signal.SIGTwistedTest
    1117             # Force reinitialization of signals
    1118             self.pp.__class__._signalNames = None
     1296        self.pp._signalNames = None
     1297        self.assertEquals(self.pp._getSignalName(signal.SIGTwistedTest),
     1298                          'SIGTwistedTest@' + sys.platform)
    11191299
     1300       
    11201301    if getattr(signal, 'SIGALRM', None) is None:
    1121         test_getSignalName.skip = "Not all signals available"
     1302        test_getSignalName.skip = test_getSignalNameWithLocalSignal = \
     1303            "Not all signals available"
    11221304
    11231305
    11241306    def test_outReceived(self):
     
    11361318        When data is passed to the write method, it should be sent to the
    11371319        session channel's write method.
    11381320        """
    1139         self.pp.write('test data')
     1321        self.callDeprecated(Version('Twisted', 8, 2, 0), self.pp.write,
     1322                            'test data')
    11401323        self.assertEquals(self.session.conn.data[self.session],
    11411324                ['test data'])
    11421325
     
    11731356        When loseConnection() is called, it should call loseConnection
    11741357        on the session channel.
    11751358        """
    1176         self.pp.loseConnection()
     1359        self.callDeprecated(Version('Twisted', 8, 2, 0),self.pp.loseConnection)
    11771360        self.assertTrue(self.session.conn.closes[self.session])
    11781361
    11791362
     
    11851368        """
    11861369        self.pp.processEnded(failure.Failure(error.ProcessDone(None)))
    11871370        self.assertEquals(self.session.conn.requests[self.session],
    1188                 [('exit-status', '\x00' * 4, False)])
     1371                [('exit-status', struct.pack('>I', 0) , False)])
    11891372        self.assertTrue(self.session.conn.closes[self.session])
    11901373
    11911374
     
    11961379        closed.
    11971380        """
    11981381        self.pp.processEnded(failure.Failure(error.ProcessTerminated(None,
    1199             signal.SIGTERM, 128)))
     1382            signal.SIGTERM, 1 << 7))) # 7th bit means core dumped
    12001383        self.assertEqual(self.session.conn.requests[self.session],
    1201                 [('exit-signal', common.NS('TERM') + '\x01' + common.NS('') +
    1202                     common.NS(''), False)])
     1384                [('exit-signal',
     1385                  common.NS('TERM') # signal name
     1386                  + '\x01' # core dumped is true
     1387                  + common.NS('') # error message
     1388                  + common.NS(''), # language tag
     1389                  False)])
    12031390        self.assertTrue(self.session.conn.closes[self.session])
    12041391
    12051392
     
    12111398        """
    12121399        self.pp.processEnded(
    12131400            failure.Failure(error.ProcessTerminated(None, signal.SIGTERM, 0)))
     1401        # see comments in test_processEndedWithExitSignalCoreDump for the
     1402        # meaning of the parts in the request
    12141403        self.assertEqual(self.session.conn.requests[self.session],
    12151404                         [('exit-signal', common.NS('TERM') + '\x00' +
    12161405                           common.NS('') + common.NS(''), False)])
     
    12461435        received, it should be passed along to SSHSessionProcessProtocol's
    12471436        transport.writeErr.
    12481437        """
    1249         transport = StubTransport()
     1438        transport = StubTransportWithWriteErr()
    12501439        pp = MockProcessProtocol()
    12511440        pp.makeConnection(transport)
    12521441        app = session.SSHSessionProcessProtocolApplication(pp)
     1442        # 255 is an arbitrary value that's not EXTENDED_DATA_STDERR so the data
     1443        # should be ignored.
    12531444        app.extendedDataReceived(255, "ignore this")
    12541445        app.extendedDataReceived(connection.EXTENDED_DATA_STDERR, "data")
    12551446        self.assertEquals(transport.err, "data")
    1256         # should not raise an error if writeErr doesn't exist
     1447
     1448    def test_extendedDataReceivedWithoutWrteErr(self):
     1449        """
     1450        If the transport doesn't support extended data by implementing
     1451        writeErr, then the extended data should silently be dropped on the
     1452        floor.
     1453        """
     1454        transport = StubTransport()
     1455        pp = MockProcessProtocol()
     1456        pp.makeConnection(transport)
     1457        app = session.SSHSessionProcessProtocolApplication(pp)
    12571458        app.extendedDataReceived(connection.EXTENDED_DATA_STDERR, "more")
    12581459
    12591460
     
    12691470        """
    12701471        When data is received, it should be sent to the transport.
    12711472        """
    1272         client = session.SSHSessionClient()
     1473        def _():
     1474            return session.SSHSessionClient()
     1475        client = self.assertWarns(DeprecationWarning,
     1476                                  "twisted.conch.ssh.session.SSHSessionClient "
     1477                                  "was deprecated in Twisted 8.2.0",
     1478                                  __file__,
     1479                                  _)
    12731480        client.transport = StubTransport()
    12741481        client.dataReceived('test data')
    12751482        self.assertEquals(client.transport.buf, 'test data')
     
    12891496                remoteMaxPacket=32768, conn=self.conn,
    12901497                avatar=self.avatar)
    12911498
     1499       
     1500    def assertRequestRaisedError(self):
     1501        """
     1502        Assert that the request we just made raised an error (and only one
     1503        error).
     1504        """
     1505        errors = self.flushLoggedErrors()
     1506        self.assertEquals(len(errors), 1, "Multiple errors raised: %s" %
     1507                          '\n'.join([repr(error) for error in errors]))
    12921508
     1509
    12931510    def test_init(self):
    12941511        """
    1295         Test that the session is initialized correctly,
     1512        After the session is created, self. client should be None, and
     1513        self..applicationFactory should provide I{ISessionApplicationFactory}.
    12961514        """
    12971515        self.assertEquals(self.session.client, None)
    12981516        self.assertTrue(session.ISessionApplicationFactory.providedBy(
     
    13001518                        str(self.session.applicationFactory))
    13011519
    13021520
    1303     def test_client(self):
     1521    def test_client_data(self):
    13041522        """
    1305         Test that the session performs correctly when it has a client
    1306         application.
     1523        SSHSession should pass data and extended data to its client, buffering
     1524        that data if the client isn't present at the time the data is received.
     1525        The client should also be able to send data back to the remote side.
    13071526        """
    13081527        client = MockApplication()
    13091528        self.session.dataReceived('1')
    13101529        self.session.extReceived(connection.EXTENDED_DATA_STDERR, '2')
    13111530        self.session.extReceived(2, '3')
    1312         self.session.setupSession(client)
     1531        self.session._setUpSession(client)
    13131532        self.session.dataReceived('out')
     1533
    13141534        self.assertEquals(client.data, ['1', 'out'])
     1535        # the tilde means the data was sent by the client
    13151536        self.assertEquals(self.session.conn.data[self.session],
    13161537                          ['1~', 'out~'])
     1538       
    13171539        self.session.extReceived(connection.EXTENDED_DATA_STDERR, 'err')
     1540
    13181541        self.assertEquals(client.extendedData,
    13191542                          [(connection.EXTENDED_DATA_STDERR, '2'),
    13201543                           (2, '3'),
    13211544                           (connection.EXTENDED_DATA_STDERR, 'err')])
     1545        # the tilde and the incrementing of the data type means the extended
     1546        # data was sent by the client
    13221547        self.assertEquals(self.session.conn.extData[self.session],
    13231548                          [(connection.EXTENDED_DATA_STDERR + 1, '2~'),
    13241549                           (2 + 1, '3~'),
    13251550                           (connection.EXTENDED_DATA_STDERR + 1, 'err~')])
     1551
     1552       
     1553    def test_client_closing(self):
     1554        """
     1555        SSHSession should notify the client of EOF and Close messages.
     1556        """
     1557        client = MockApplication()
     1558        self.session._setUpSession(client)
    13261559        self.session.eofReceived()
    13271560        self.assertTrue(client.gotEOF)
    13281561        self.assertFalse(client.hasClosed, 'closed() called during EOF')
     
    13361569
    13371570    def test_applicationFactory(self):
    13381571        """
    1339         Test that SSHSession handles dispatching to applicationFactory correctly.
     1572        SSHSession should notify the applicationFactory of EOF and close
     1573        messages.
    13401574        """
    13411575        self.session.eofReceived()
    13421576        self.assertFalse(self.session.applicationFactory.inConnectionOpen)
     
    13481582
    13491583    def test_subsystem(self):
    13501584        """
    1351         Test that SSHSession handles subsystem requests by dispatching to the
    1352         application factory's requestSubsytem() method and connecting the
    1353         ISessionApplication to itself.
     1585        SSHSession should handle subsystem requests by dispatching to the
     1586        application factory's requestSubsytem() method and connecting to the
     1587        ISessionApplication returned by requestSubsytem().
    13541588        """
    13551589        ret = self.session.requestReceived('subsystem', common.NS('bad'))
    13561590        self.assertFalse(ret)
    13571591        self.assertEquals(self.session.client, None)
    13581592        ret = self.session.requestReceived('subsystem',
    1359                 common.NS('echo') + 'abc')
     1593                common.NS('repeat') + 'abc')
    13601594        self.assertTrue(ret)
    13611595        self.assertTrue(session.ISessionApplication.providedBy(
    13621596                self.session.sessionApplication))
     
    13711605    def test_shell(self):
    13721606        """
    13731607        Test that SSHSession handles shell requests by dispatching to the
    1374         application factory's openShell() method and connecting the
    1375         ISessionApplication to itself.
     1608        application factory's openShell() method and connecting itself to the
     1609        ISessionApplication returned by openShell().
    13761610        """
    13771611        self.assertTrue(self.session.requestReceived('shell', ''))
    13781612        self.assertNotEquals(self.session.sessionApplication, None)
     
    13811615        self.assertEquals(self.session.sessionApplication.channel,
    13821616                self.session)
    13831617        self.assertFalse(self.session.sessionApplication.hasClosed)
    1384         self.assertFalse(self.session.requestReceived('shell',
    1385             '')) # fail if we have
    1386         # a shell already
    1387         self.flushLoggedErrors()
    13881618
     1619        # fail if we have a shell already
     1620        self.assertFalse(self.session.requestReceived('shell', ''))
     1621        self.assertRequestRaisedError()
    13891622
     1623
    13901624    def test_exec(self):
    13911625        """
    1392         Test that SSHSession handles command requests by dispatching to the
    1393         the application factory's execCommand method and connecting the
    1394         ISessionApplication to itself.
     1626        Test that SSHSession handles command requests by dispatching to the the
     1627        application factory's execCommand method and connecting itself to the
     1628        ISessionApplication returned by execCommand().
    13951629        """
    13961630        self.assertTrue(self.session.requestReceived('exec',
    13971631            common.NS('good')))
     
    14021636                self.session)
    14031637        self.assertEquals(self.session.sessionApplication.command, 'good')
    14041638        self.assertFalse(self.session.sessionApplication.hasClosed)
     1639
     1640        # fail if we already have a command
    14051641        self.assertFalse(self.session.requestReceived('exec',
    1406             common.NS('bad')))
    1407         self.flushLoggedErrors()
     1642            common.NS('good')))
     1643        self.assertRequestRaisedError()
    14081644
    14091645
    14101646    def test_ptyRequest(self):
     
    14161652        windowSize = (80, 25, 0, 0)
    14171653        modes = [(0, 1), (2, 3)]
    14181654        ret = self.session.requestReceived('pty_req',
    1419                 common.NS(term) +
    1420                 '\x00\x00\x00\x19' + '\x00\x00\x00\x50' +
    1421                 '\x00\x00\x00\x00' * 2 + common.NS('\x00\x00\x00\x00\x01'
    1422                     '\x02\x00\x00\x00\x03'))
     1655                                           session.packRequest_pty_req(
     1656                term, windowSize, modes))
    14231657        self.assertTrue(ret)
    14241658        self.assertEquals(self.session.applicationFactory.term, term)
    1425         self.assertEquals(self.session.applicationFactory.windowSize, windowSize)
     1659        self.assertEquals(self.session.applicationFactory.windowSize,
     1660                          windowSize)
    14261661        self.assertEquals(self.session.applicationFactory.modes, modes)
     1662
     1663       
     1664    def test_ptyRequestFailure(self):
     1665        """
     1666        If the PTY request can't be parsed, the request should fail.
     1667        """
    14271668        self.assertFalse(self.session.requestReceived('pty_req', ''))
    1428         self.flushLoggedErrors()
     1669        self.assertRequestRaisedError()
    14291670
    14301671
    14311672    def test_windowChange(self):
     
    14351676        """
    14361677        windowSize = (1, 1, 1, 1)
    14371678        ret = self.session.requestReceived('window_change',
    1438                 '\x00\x00\x00\x01' * 4)
     1679                                           session.packRequest_window_change(
     1680                windowSize))
    14391681        self.assertTrue(ret)
    14401682        self.assertEquals(self.session.applicationFactory.windowSize, windowSize)
     1683
     1684    def test_windowChangeFailure(self):
     1685        """
     1686        If the window change request can't be parsed, the request should fail.
     1687        """
    14411688        self.assertFalse(self.session.requestReceived('window_change', ''))
    1442         self.flushLoggedErrors()
     1689        self.assertRequestRaisedError()
    14431690
     1691       
    14441692
    1445 
    14461693class ClientSessionTestCase(unittest.TestCase):
    14471694    """
    14481695    Test methods that use SSHSession as a client.
     
    14571704
    14581705    def test_getPty(self):
    14591706        """
    1460         Test that getPty sends the correct request.
     1707        Test that getPty sends a correctly formatted request.  See the
     1708        TestHelpers class for a description of this packet.
    14611709        """
     1710        terminalType = 'term'
     1711        windowSize = (80, 25, 0, 0)
     1712        modes = [(0, 1), (2, 3)]
    14621713        d = self.session.getPty('term', (80, 25, 0, 0), [(0, 1), (2, 3)],
    14631714                True)
    14641715        def check(value):
    14651716            self.assertEquals(self.conn.requests[self.session],
    1466                     [('pty-req', common.NS('term') + '\x00\x00\x00\x19' +
    1467                         '\x00\x00\x00\x50' + '\x00\x00\x00\x00' * 2 +
    1468                         common.NS('\x00\x00\x00\x00\x01\x02\x00\x00\x00\x03'),
    1469                         True)])
     1717                              [('pty-req', session.packRequest_pty_req(
     1718                            terminalType,
     1719                            windowSize,
     1720                            modes), True)])
    14701721            self.assertEquals(self.session.getPty('term', (80, 25, 0, 0), []),
    1471                     None)
    1472         d.addCallback(check)
    1473         return d
     1722                              None)
     1723           
     1724        return d.addCallback(check)
    14741725
    1475 
     1726   
    14761727    def test_changeWindowSize(self):
    14771728        """
    1478         Test that windowChange sends the correct request.
     1729        Test that windowChange sends the correct request.  See the TestHelpers
     1730        class for a description of this packet.
    14791731        """
    1480         d = self.session.changeWindowSize((80, 25, 0, 0), True)
     1732        size = (80, 25, 0, 0)
     1733        d = self.session.changeWindowSize(size, True)
    14811734        def check(value):
    14821735            self.assertEquals(self.conn.requests[self.session],
    1483                     [('window-change', '\x00\x00\x00\x19\x00\x00\x00\x50' +
    1484                         '\x00\x00\x00\x00' * 2, True)])
    1485             self.assertEquals(self.session.changeWindowSize((80, 25, 0, 0)),
     1736                              [('window-change',
     1737                                session.packRequest_window_change(size),
     1738                                True)])
     1739
     1740            self.assertEquals(self.session.changeWindowSize(size),
    14861741                    None)
    1487         d.addCallback(check)
    1488         return d
     1742        return d.addCallback(check)
    14891743
    14901744
    14911745    def test_openSubsystem(self):
    14921746        """
    1493         Test that openSubsystem sends the correct request.
     1747        Test that openSubsystem sends the correct request.  Format::
     1748
     1749            string subsystem type
     1750            [any other packet data]
    14941751        """
    1495         d = self.session.openSubsystem('echo', 'data', True)
     1752        d = self.session.openSubsystem('repeat', 'data', True)
    14961753        def check(value):
    14971754            self.assertEquals(self.conn.requests[self.session],
    1498                     [('subsystem', common.NS('echo') + 'data', True)])
    1499             self.assertEquals(self.session.openSubsystem('echo', 'data'),
     1755                    [('subsystem', common.NS('repeat') + 'data', True)])
     1756            self.assertEquals(self.session.openSubsystem('repeat', 'data'),
    15001757                    None)
    1501         d.addCallback(check)
    1502         return d
     1758        return d.addCallback(check)
    15031759
    15041760
    15051761    def test_openShell(self):
    15061762        """
    1507         Test that openShell sends the correct request.
     1763        Test that openShell sends the correct request.  No data.
    15081764        """
    15091765        d = self.session.openShell(True)
    15101766        def check(value):
     
    15171773
    15181774    def test_execCommand(self):
    15191775        """
    1520         Test that execCommand sentds the correct request.
     1776        Test that execCommand sends the correct request.  Format::
     1777
     1778            string command line
    15211779        """
    15221780        d = self.session.execCommand('true', True)
    15231781        def check(value):
  • scripts/conch.py

     
    2424"""
    2525
    2626    optParameters = [['escape', 'e', '~'],
    27                       ['localforward', 'L', None, 'listen-port:host:port   Forward local port to remote address'],
    28                       ['remoteforward', 'R', None, 'listen-port:host:port   Forward remote port to local address'],
     27                     ['localforward', 'L', None,
     28                      'listen-port:host:port   '
     29                      'Forward local port to remote address'],
     30                     ['remoteforward', 'R', None,
     31                      'listen-port:host:port   '
     32                      'Forward remote port to local address'],
    2933                     ]
    3034
    3135    optFlags = [['null', 'n', 'Redirect input from /dev/null.'],
  • ssh/session.py

     
    11# -*- test-case-name: twisted.conch.test.test_session -*-
    2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories.
     2# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
    33# See LICENSE for details.
    44
    55"""
    66This module contains the implementation of SSHSession, which (by default)
    77allows access to a shell and a python interpreter over SSH.
    88
     9For more information see RFC 4254.
     10
    911Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>}
    1012"""
    1113
     
    1517
    1618from twisted.internet import protocol
    1719from twisted.python import components, log
     20from twisted.python.deprecate import deprecated
     21from twisted.python.versions import Version
    1822from twisted.conch.interfaces import ISessionApplication
    1923from twisted.conch.interfaces import ISessionApplicationFactory, ISession
    2024from twisted.conch.ssh import common, channel, connection
    2125
    2226
    23 # supportedSignals is a list of signals that every session channel is supposed
    24 # to support.
    25 supportedSignals = ["ABRT", "ALRM", "FPE", "HUP", "ILL", "INT", "KILL", "PIPE",
    26                     "QUIT", "SEGV", "TERM", "USR1", "USR2"]
    2727
     28__all__ = ['SSHSession', 'packRequest_window_change', 'packRequest_pty_req',
     29           'parseRequest_window_change', 'parseRequest_pty_req']
    2830
    2931
     32
     33# SUPPORTED_SIGNALS is a list of signals that every session channel is supposed
     34# to accept.  See RFC 4254
     35SUPPORTED_SIGNALS = ["ABRT", "ALRM", "FPE", "HUP", "ILL", "INT", "KILL",
     36                     "PIPE", "QUIT", "SEGV", "TERM", "USR1", "USR2"]
     37
     38
     39
    3040class SSHSession(channel.SSHChannel):
    3141    """
    3242    A channel implementing the server side of the 'session' channel.  This is
     
    3444    command.
    3545
    3646    @ivar earlyData: data sent to this channel before a client is present.
    37     @type earlyData: C{str}
     47    @type earlyData: C{list} of C{str}
    3848    @ivar earlyExtended: extended data sent to this channel before a client is
    3949    present.
    4050    @type earlyExtended: C{list}
     
    6575            # buffers (because this would not have previously delivered any
    6676            # data, only delivered subsequent data) and set the old-style
    6777            # "client" object up as a new-style processProtocol.
    68             self.earlyData = ''
     78
     79            # XXX DeprecationWarning
     80            self.earlyData = []
    6981            self.earlyExtended = []
    70             self.setupSession(_TransportToProcessProtocol(v.transport))
     82            self._setUpSession(_TransportToProcessProtocol(v.transport))
    7183        if k == 'session' and v is not None:
     84            # XXX DeprecationWarning
    7285            # Someone is trying to inform us of an old-style session.  Wrap it
    7386            # in a _DeprecatedSSHSession.
    7487            self.applicationFactory = _DeprecatedSSHSession(self, v)
     
    7891    def applicationFactory(self):
    7992        """
    8093        Produce an applicationFactory dynamically if one does not yet exist.
     94        We set self.appplicationFactory when we have created the application
     95        factory because old-style classes don't support getters for
     96        properties.
    8197        """
    8298        if self.avatar is not None:
    8399            factoryCandidate = ISessionApplicationFactory(self.avatar, None)
    84100            if factoryCandidate is None:
     101                warnings.warn("Using an avatar that doesn't implement "
     102                              "ISessionApplicationFactory is deprecated since "
     103                              "Twisted 8.2.",
     104                              DeprecationWarning,
     105                              stacklevel=5)
     106                             
    85107                # Maybe it implements the old version.
    86108                oldStyle = ISession(self.avatar, None)
    87109                if oldStyle is not None:
     
    97119            else:
    98120                self.applicationFactory = factoryCandidate
    99121            return self.applicationFactory
     122        else:
     123            raise RuntimeError('Cannot get an application factory without an '
     124                               'avatar')
    100125    applicationFactory = property(applicationFactory)
    101126
    102127
     
    107132        fake it.
    108133        """
    109134        if isinstance(self.sessionApplication,
    110                       _ProcessProtocolToSessionApplication):
     135                      (_ProcessProtocolToSessionApplication,
     136                        SSHSessionProcessProtocolApplication)):
    111137            return self.sessionApplication.processProtocol
    112         elif isinstance(self.sessionApplication,
    113                         SSHSessionProcessProtocolApplication):
    114             return self.sessionApplication.processProtocol
    115138    client = property(client)
    116139
    117140
    118141    # This used to be set in the older SSHSession implementation.
     142    # We set it when we create an application factory (see
     143    # applicationFactory() above.
    119144    session = None
    120145
    121146
    122147    def __init__(self, *args, **kw):
    123148        channel.SSHChannel.__init__(self, *args, **kw)
    124         self.earlyData = ''
     149        self.earlyData = []
    125150        self.earlyExtended = []
    126151        self.sessionApplication = None
    127152
    128153
    129     def setupSession(self, sessionApplication):
     154    def _setUpSession(self, sessionApplication):
    130155        """
    131156        Connect us to the application.  We set ourselves as it's channel
    132157        instance variable, and the application becomes our sessionApplication.
     
    136161        sessionApplication = ISessionApplication(sessionApplication)
    137162        sessionApplication.channel = self
    138163        sessionApplication.connectionMade()
    139         # Maybe change this name too...
    140164        self.sessionApplication = sessionApplication
    141165        if self.earlyData:
    142             b, self.earlyData = self.earlyData, None
    143             self.dataReceived(b)
     166            bytes, self.earlyData = self.earlyData, None
     167            self.dataReceived(''.join(bytes))
    144168        if self.earlyExtended:
    145             b, self.earlyExtended = self.earlyExtended, None
    146             for dataType, data in b:
     169            bytes, self.earlyExtended = self.earlyExtended, None
     170            for dataType, data in bytes:
    147171                self.extReceived(dataType, data)
    148172
    149173
     
    153177            string  subsystem name
    154178
    155179        Try to get a subsystem object by calling our adapter's lookupSubsystem
    156         method.  If that method returns a subsystem, then connect to our
    157         client and return True.  Otherwise, return False.
     180        method.  If that method returns a subsystem, then connect it to
     181        ourself and return True.  Otherwise, return False.
    158182        """
    159183        subsystem, rest = common.getNS(data)
    160184        log.msg('asking for subsystem "%s"' % subsystem)
    161         client = self.applicationFactory.lookupSubsystem(subsystem, rest)
    162         if client:
    163             self.setupSession(client)
    164             return True
    165         else:
    166             return False
     185        try:
     186            client = self.applicationFactory.lookupSubsystem(subsystem, rest)
     187            if client:
     188                self._setUpSession(client)
     189                return True
     190        except KeyboardInterrupt:
     191            raise
     192        except:
     193            log.err()
     194        return False
    167195
    168 
     196   
    169197    def request_shell(self, data):
    170198        """
    171199        The remote side has requested a shell.  No payload.  Call the
     
    176204        """
    177205        log.msg('getting shell')
    178206        try:
    179             self.setupSession(self.applicationFactory.openShell())
    180         except Exception:
     207            self._setUpSession(self.applicationFactory.openShell())
     208        except KeyboardInterrupt:
     209            raise
     210        except:
    181211            log.err()
    182212            return False
    183213        else:
     
    197227        command, data = common.getNS(data)
    198228        log.msg('executing command "%s"' % command)
    199229        try:
    200             self.setupSession(self.applicationFactory.execCommand(command))
    201         except Exception:
     230            self._setUpSession(self.applicationFactory.execCommand(command))
     231        except KeyboardInterrupt:
     232            raise
     233        except:
    202234            log.err()
    203235            return False
    204236        else:
     
    227259            term, windowSize, modes = parseRequest_pty_req(data)
    228260            log.msg('pty request: %s %s' % (term, windowSize))
    229261            self.applicationFactory.getPTY(term, windowSize, modes)
    230         except Exception:
     262        except KeyboardInterrupt:
     263            raise
     264        except:
    231265            log.err()
    232266            return False
    233267        else:
     
    249283        try:
    250284            winSize = parseRequest_window_change(data)
    251285            self.applicationFactory.windowChanged(winSize)
    252         except Exception:
     286        except KeyboardInterrupt:
     287            raise
     288        except:
    253289            log.msg('error changing window size')
    254290            log.err()
    255291            return False
     
    265301        @type data: C{str}
    266302        """
    267303        if self.sessionApplication is None:
    268             self.earlyData += data
     304            self.earlyData.append(data)
    269305            return
    270306        self.sessionApplication.dataReceived(data)
    271307
     
    301337        The channel is closed.  If we have an application factory,
    302338        notify it.  If we have an application, tell it the connection
    303339        is lost.
     340
     341        Note: closing the channel is separate from closing the connection
     342        to the client.  To do that, call self.conn.transport.loseConnection().
    304343        """
    305344        if self.applicationFactory is not None:
    306345            self.applicationFactory.closed()
     
    321360            self.sessionApplication.closeReceived()
    322361
    323362
    324     # client methods
     363    # Below are methods that are used by client implementations of the
     364    # 'session' channel.
    325365
    326366
    327367    def getPty(self, term, windowSize, modes, wantReply=False):
     
    333373        @param windowSize: the size of the window: (rows, cols, xpixels,
    334374                           ypixels)
    335375        @type windowSize: C{tuple}
    336         @param modes: termanal modes; a list of (modeNumber, modeValue) pairs.
     376        @param modes: terminal modes; a list of (modeNumber, modeValue) pairs.
    337377        @type modes: C{list}
    338         @param wantReply: True if we want a reply to this request.
     378        @param wantReply: True if we want a reply to this request.x
    339379        @type wantReply: C{bool}
    340380
    341         @returns: if wantReply, a Deferred that will be called back when
    342                   the request has succeeded or failed; else, None.
     381        @returns: if wantReply, a Deferred that will be called back when the
     382                  request has succeeded or failed; else, None.
    343383        @rtype: C{Deferred}/C{None}
    344384        """
    345385        data = packRequest_pty_req(term, windowSize, modes)
     
    417457
    418458
    419459
    420 class _SubsystemOnlyApplicationFactory:
     460class _SubsystemOnlyApplicationFactory(object):
    421461    """
    422462    An application factory which which is only good for looking up a
    423463    subsystem.  It is used when there is not an ISession adapter
     
    425465    """
    426466    implements(ISessionApplicationFactory)
    427467
    428 
     468   
    429469    def __init__(self, sessionChannel):
    430470        self.sessionChannel = sessionChannel
    431471
     
    436476        the avatar instead of through the ISession adapter.  To be backwards
    437477        compatible, try to look up the subsystem through the avatar.
    438478        """
    439         self.sessionChannel.earlyData = ''
     479        self.sessionChannel.earlyData = []
    440480        self.sessionChannel.earlyExtended = []
    441481        client = self.sessionChannel.avatar.lookupSubsystem(subsystem,
    442                                               common.NS(subsystem) + data)
     482                                                            common.NS(subsystem)
     483                                                            + data)
    443484        return wrapProcessProtocol(client)
    444485
    445486
     
    531572    """
    532573    This class wraps a L{Protocol} instance in a L{ProcessProtocol} instance.
    533574
     575    XXX: This belongs in twisted.internet, not twisted.conch. - jml 2008-04-06
     576
    534577    @ivar proto: the C{Protocol} we're wrapping.
    535578    """
    536579
     
    590633        return _ProtocolToProcessProtocol(inst)
    591634    else:
    592635        return inst
     636       
    593637
    594 
    595 
    596638class _TransportToProcessProtocol(protocol.ProcessProtocol):
    597639    """
    598640    This wraps something implementing I{ITransport} in a
    599     C{ProcessProtocol} because that's how the old interface worked.
     641    C{ProcessProtocol}.  Some old implementations directly set
     642    SSHSession().client to a transport, and this wrapper supports that
     643    obscure use of SSHSession.  We wrap it in a C{ProcessProtocol} instead
     644    of directly in an I{ISessionApplication} because C{_DeprecatedSSHSession}
     645    expects the client to be a C{ProcessProtocol}.  This class was added in
     646    Twisted 8.2.
    600647
    601648    @ivar applicationTransport: the ITransport we're wrapping.
    602649    """
     
    613660        self.applicationTransport.write(data)
    614661
    615662
    616     def errReceived(self, errData):
     663    def errReceived(self, data):
    617664        """
    618665        When we get extended data, give it to the writeErr method of
    619         our transport.
     666        our transport if it exists.
    620667        """
    621         # XXX need to test when writeErr isn't available
    622         self.applicationTransport.writeErr(errData)
     668        if hasattr(self.applicationTransport, 'writeErr'):
     669            self.applicationTransport.writeErr(data)
    623670
    624 
     671   
    625672    def processEnded(self, reason):
    626673        """
    627674        When we're told the process ended, tell the transport to drop
     
    633680
    634681class _ProcessProtocolToSessionApplication:
    635682    """
    636     This adapts a C{ProcessProtocol} to an ISessionApplication.
    637 
     683    This adapts a C{ProcessProtocol} to an C{ISessionApplication.}  The old
     684    I{ISession} interface returned C{ProcessProtocol}s from lookupSubsystem().
     685    We wrap those objects with this class in order to provide the new
     686    I{ISessionApplication} interface.  This class was added in Twisted 8.2.
     687   
    638688    @ivar processProtocol: the C{ProcessProtocol} we're adapting.
    639689    """
    640690
     
    709759    """
    710760
    711761
     762    # once initialized, a dictionary mapping signal values to strings
     763    # that follow RFC 4254.
    712764    _signalNames = None
    713765
    714766
     
    721773        Get a signal name given a signal number.
    722774        """
    723775        if self._signalNames is None:
    724             self.__class__._signalNames = {}
     776            self._signalNames = {}
    725777            # make sure that the POSIX ones are the defaults
    726             for signame in supportedSignals:
     778            for signame in SUPPORTED_SIGNALS:
    727779                signame = 'SIG' + signame
    728780                sigvalue = getattr(signal, signame, None)
    729781                if sigvalue is not None:
     
    777829            err = reason.value
    778830            if err.signal is not None:
    779831                signame = self._getSignalName(err.signal)
    780                 log.msg('exitSignal: %s' % (signame,))
    781832                if hasattr(os, 'WCOREDUMP') and os.WCOREDUMP(err.status):
     833                    log.msg('exitSignal: %s (core dumped)' % (signame,))
    782834                    coreDumped = 1
    783835                else:
     836                    log.msg('exitSignal: %s' % (signame,))
    784837                    coreDumped = 0
    785838                self.session.conn.sendRequest(self.session, 'exit-signal',
    786839                        common.NS(signame[3:]) + chr(coreDumped) +
     
    792845        self.session.loseConnection()
    793846
    794847
    795     # also a transport :(
     848    # also a transport :( Some old code used SSHSessionProcessProtocol() like a
     849    # transport, so we have to continue to support this interface.
    796850    def write(self, data):
    797851        """
    798852        If we're used like a transport, just send the data to the channel.
    799853        """
    800854        self.session.write(data)
     855    write = deprecated(Version("Twisted", 8, 2, 0))(write)
    801856
    802 
    803857    def loseConnection(self):
    804858        """
    805         If we're use like a transport, send the close message.
     859        If we're used like a transport, send the close message.
    806860        """
    807861        self.session.loseConnection()
     862    loseConnection = deprecated(Version("Twisted", 8, 2, 0))(loseConnection)
    808863
    809864
    810 
    811865class SSHSessionProcessProtocolApplication:
    812866    """
    813867    Another layer of wrapping to make the old-style ISession
     
    881935    A class the conch command-line client uses to connect the channel
    882936    to standard output.  Deprecated.
    883937    """
     938
     939    def __init__(self):
     940        warnings.warn(
     941            "twisted.conch.ssh.session.SSHSessionClient was deprecated "
     942            "in Twisted 8.2.0", DeprecationWarning, stacklevel=2)
     943
     944
    884945    def dataReceived(self, data):
    885946        """
    886947        Send data to the transport.
     
    892953# methods factored out to make live easier on server writers
    893954def parseRequest_pty_req(data):
    894955    """
    895     Parse the data from a pty-req request into usable data.
     956    Parse the data from a pty-req request into usable data.  See RFC 4254 6.2
     957    and 8.
    896958
    897959    @returns: a tuple of (terminal type, (rows, cols, xpixel, ypixel), modes)
    898960    """
    899961    term, rest = common.getNS(data)
    900     cols, rows, xpixel, ypixel = struct.unpack('>4L', rest[: 16])
     962    cols, rows, xpixel, ypixel = struct.unpack('>4L', rest[:16])
    901963    modes, ignored= common.getNS(rest[16:])
    902964    winSize = (rows, cols, xpixel, ypixel)
    903     modes = [(ord(modes[i]), struct.unpack('>L', modes[i+1: i+5])[0]) for i in
    904             range(0, len(modes)-1, 5)]
     965    modes = [(ord(modes[i]), struct.unpack('>L', modes[i+1:i+5])[0]) for i in
     966            range(0, len(modes) - 1, 5)]
    905967    return term, winSize, modes
    906968
    907969
    908970
    909971def packRequest_pty_req(term, (rows, cols, xpixel, ypixel), modes):
    910972    """
    911     Pack a pty-req request so that it is suitable for sending.
    912 
    913     NOTE: modes must be packed before being sent here.
     973    Pack a pty-req request so that it is suitable for sending.  See RFC 4254
     974    6.2 and 8.
    914975    """
    915976    termPacked = common.NS(term)
    916977    winSizePacked = struct.pack('>4L', cols, rows, xpixel, ypixel)
     
    9401001    Pack a window-change request so that it is suitable for sending.
    9411002    """
    9421003    return struct.pack('>4L', cols, rows, xpixel, ypixel)
    943 
    944 
    945 
    946 __all__ = ['SSHSession', 'packRequest_window_change', 'packRequest_pty_req',
    947            'parseRequest_window_change', 'parseRequest_pty_req']
  • avatar.py

     
    11# -*- test-case-name: twisted.conch.test.test_conch -*-
    2 # Copyright (c) 2007 Twisted Matrix Laboratories.
     2# Copyright (c) 2008 Twisted Matrix Laboratories.
    33# See LICENSE for details.
    44
    55from zope import interface
  • interfaces.py

     
    4646class ISession(Interface):
    4747    """
    4848    The old, deprecated interface for implementing SSH session applications.
    49     Please don't use this for anything.
     49    Please don't use this for anything.  Deprecated in Twisted 8.2.
    5050    """
    5151
    5252    def getPty(term, windowSize, modes):
    5353        """
    54         Get a psuedo-terminal for use by a shell or command.
     54        Get a pseudo-terminal for use by a shell or command.
    5555
    56         If a psuedo-terminal is not available, or the request otherwise
     56        If a pseudo-terminal is not available, or the request otherwise
    5757        fails, raise an exception.
    5858        """
    5959
     
    110110    def lookupSubsystem(subsystem, data):
    111111        """
    112112        The other side requested a subsystem.
     113       
    113114        @param subsystem: the name of the subsystem being requested.
    114115        @param data: any other packet data (often nothing).
    115116
     
    163164        """
    164165
    165166
     167
    166168class ISessionApplication(Interface):
    167169    """
    168170    Objects which are returned by an ISessionApplicationFactory implementor