1
# -*- test-case-name: twisted.conch.test.test_conch -*-
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
"""This module contains the implementation of SSHSession, which (by default)
8
allows access to a shell and a python interpreter over SSH.
10
This module is unstable.
12
Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>}
17
from twisted.internet import protocol, reactor
18
from twisted.python import log
19
from twisted.conch.interfaces import ISession
20
import common, channel
22
class SSHSession(channel.SSHChannel):
25
def __init__(self, *args, **kw):
26
channel.SSHChannel.__init__(self, *args, **kw)
31
def request_subsystem(self, data):
32
subsystem, ignored= common.getNS(data)
33
log.msg('asking for subsystem "%s"' % subsystem)
34
client = self.avatar.lookupSubsystem(subsystem, data)
36
pp = SSHSessionProcessProtocol(self)
37
proto = wrapProcessProtocol(pp)
38
client.makeConnection(proto)
39
pp.makeConnection(wrapProtocol(client))
43
log.msg('failed to get subsystem')
46
def request_shell(self, data):
47
log.msg('getting shell')
49
self.session = ISession(self.avatar)
51
pp = SSHSessionProcessProtocol(self)
52
self.session.openShell(pp)
60
def request_exec(self, data):
62
self.session = ISession(self.avatar)
63
f,data = common.getNS(data)
64
log.msg('executing command "%s"' % f)
66
pp = SSHSessionProcessProtocol(self)
67
self.session.execCommand(pp, f)
75
def request_pty_req(self, data):
77
self.session = ISession(self.avatar)
78
term, windowSize, modes = parseRequest_pty_req(data)
79
log.msg('pty request: %s %s' % (term, windowSize))
81
self.session.getPty(term, windowSize, modes)
88
def request_window_change(self, data):
90
self.session = ISession(self.avatar)
92
winSize = parseRequest_window_change(data)
94
self.session.windowChanged(winSize)
96
log.msg('error changing window size')
102
def dataReceived(self, data):
104
#self.conn.sendClose(self)
107
self.client.transport.write(data)
109
def extReceived(self, dataType, data):
110
if dataType == connection.EXTENDED_DATA_STDERR:
111
if self.client and hasattr(self.client.transport, 'writeErr'):
112
self.client.transport.writeErr(data)
114
log.msg('weird extended data: %s'%dataType)
116
def eofReceived(self):
118
self.session.eofReceived()
120
self.conn.sendClose(self)
124
self.session.closed()
126
#def closeReceived(self):
127
# self.loseConnection() # don't know what to do with this
129
def loseConnection(self):
131
self.client.transport.loseConnection()
132
channel.SSHChannel.loseConnection(self)
134
class _ProtocolWrapper(protocol.ProcessProtocol):
136
This class wraps a L{Protocol} instance in a L{ProcessProtocol} instance.
138
def __init__(self, proto):
141
def connectionMade(self): self.proto.connectionMade()
143
def outReceived(self, data): self.proto.dataReceived(data)
145
def processEnded(self, reason): self.proto.connectionLost(reason)
147
class _DummyTransport:
149
def __init__(self, proto):
152
def dataReceived(self, data):
153
self.proto.transport.write(data)
155
def write(self, data):
156
self.proto.dataReceived(data)
158
def writeSequence(self, seq):
159
self.write(''.join(seq))
161
def loseConnection(self):
162
self.proto.connectionLost(protocol.connectionDone)
164
def wrapProcessProtocol(inst):
165
if isinstance(inst, protocol.Protocol):
166
return _ProtocolWrapper(inst)
170
def wrapProtocol(proto):
171
return _DummyTransport(proto)
173
class SSHSessionProcessProtocol(protocol.ProcessProtocol):
176
def __init__(self, session):
177
self.session = session
179
def connectionMade(self):
181
self.transport.write(self.session.buf)
182
self.session.buf = None
184
def outReceived(self, data):
185
self.session.write(data)
187
def errReceived(self, err):
188
self.session.writeExtended(connection.EXTENDED_DATA_STDERR, err)
190
def inConnectionLost(self):
191
self.session.conn.sendEOF(self.session)
193
def connectionLost(self, reason = None):
194
self.session.loseConnection()
196
def processEnded(self, reason = None):
197
if reason and hasattr(reason.value, 'exitCode'):
198
log.msg('exitCode: %s' % repr(reason.value.exitCode))
199
self.session.conn.sendRequest(self.session, 'exit-status', struct.pack('!L', reason.value.exitCode))
200
self.session.loseConnection()
202
# transport stuff (we are also a transport!)
204
def write(self, data):
205
self.session.write(data)
207
def writeSequence(self, seq):
208
self.session.write(''.join(seq))
210
def loseConnection(self):
211
self.session.loseConnection()
213
class SSHSessionClient(protocol.Protocol):
215
def dataReceived(self, data):
217
self.transport.write(data)
219
# methods factored out to make live easier on server writers
220
def parseRequest_pty_req(data):
221
"""Parse the data from a pty-req request into usable data.
223
@returns: a tuple of (terminal type, (rows, cols, xpixel, ypixel), modes)
225
term, rest = common.getNS(data)
226
cols, rows, xpixel, ypixel = struct.unpack('>4L', rest[: 16])
227
modes, ignored= common.getNS(rest[16:])
228
winSize = (rows, cols, xpixel, ypixel)
229
modes = [(ord(modes[i]), struct.unpack('>L', modes[i+1: i+5])[0]) for i in range(0, len(modes)-1, 5)]
230
return term, winSize, modes
232
def packRequest_pty_req(term, (rows, cols, xpixel, ypixel), modes):
233
"""Pack a pty-req request so that it is suitable for sending.
235
NOTE: modes must be packed before being sent here.
237
termPacked = common.NS(term)
238
winSizePacked = struct.pack('>4L', cols, rows, xpixel, ypixel)
239
modesPacked = common.NS(modes) # depend on the client packing modes
240
return termPacked + winSizePacked + modesPacked
242
def parseRequest_window_change(data):
243
"""Parse the data from a window-change request into usuable data.
245
@returns: a tuple of (rows, cols, xpixel, ypixel)
247
cols, rows, xpixel, ypixel = struct.unpack('>4L', data)
248
return rows, cols, xpixel, ypixel
250
def packRequest_window_change((rows, cols, xpixel, ypixel)):
251
"""Pack a window-change request so that it is suitable for sending.
253
return struct.pack('>4L', cols, rows, xpixel, ypixel)