| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
""" |
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
from twisted.python import log |
|---|
| 14 |
from twisted.internet import interfaces |
|---|
| 15 |
from zope.interface import implements |
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
class SSHChannel(log.Logger): |
|---|
| 19 |
""" |
|---|
| 20 |
A class that represents a multiplexed channel over an SSH connection. |
|---|
| 21 |
The channel has a local window which is the maximum amount of data it will |
|---|
| 22 |
receive, and a remote which is the maximum amount of data the remote side |
|---|
| 23 |
will accept. There is also a maximum packet size for any individual data |
|---|
| 24 |
packet going each way. |
|---|
| 25 |
|
|---|
| 26 |
@ivar name: the name of the channel. |
|---|
| 27 |
@type name: C{str} |
|---|
| 28 |
@ivar localWindowSize: the maximum size of the local window in bytes. |
|---|
| 29 |
@type localWindowSize: C{int} |
|---|
| 30 |
@ivar localWindowLeft: how many bytes are left in the local window. |
|---|
| 31 |
@type localWindowLeft: C{int} |
|---|
| 32 |
@ivar localMaxPacket: the maximum size of packet we will accept in bytes. |
|---|
| 33 |
@type localMaxPacket: C{int} |
|---|
| 34 |
@ivar remoteWindowLeft: how many bytes are left in the remote window. |
|---|
| 35 |
@type remoteWindowLeft: C{int} |
|---|
| 36 |
@ivar remoteMaxPacket: the maximum size of a packet the remote side will |
|---|
| 37 |
accept in bytes. |
|---|
| 38 |
@type remoteMaxPacket: C{int} |
|---|
| 39 |
@ivar conn: the connection this channel is multiplexed through. |
|---|
| 40 |
@type conn: L{SSHConnection} |
|---|
| 41 |
@ivar data: any data to send to the other size when the channel is |
|---|
| 42 |
requested. |
|---|
| 43 |
@type data: C{str} |
|---|
| 44 |
@ivar avatar: an avatar for the logged-in user (if a server channel) |
|---|
| 45 |
@ivar localClosed: True if we aren't accepting more data. |
|---|
| 46 |
@type localClosed: C{bool} |
|---|
| 47 |
@ivar remoteClosed: True if the other size isn't accepting more data. |
|---|
| 48 |
@type remoteClosed: C{bool} |
|---|
| 49 |
""" |
|---|
| 50 |
|
|---|
| 51 |
implements(interfaces.ITransport) |
|---|
| 52 |
|
|---|
| 53 |
name = None |
|---|
| 54 |
|
|---|
| 55 |
def __init__(self, localWindow = 0, localMaxPacket = 0, |
|---|
| 56 |
remoteWindow = 0, remoteMaxPacket = 0, |
|---|
| 57 |
conn = None, data=None, avatar = None): |
|---|
| 58 |
self.localWindowSize = localWindow or 131072 |
|---|
| 59 |
self.localWindowLeft = self.localWindowSize |
|---|
| 60 |
self.localMaxPacket = localMaxPacket or 32768 |
|---|
| 61 |
self.remoteWindowLeft = remoteWindow |
|---|
| 62 |
self.remoteMaxPacket = remoteMaxPacket |
|---|
| 63 |
self.areWriting = 1 |
|---|
| 64 |
self.conn = conn |
|---|
| 65 |
self.data = data |
|---|
| 66 |
self.avatar = avatar |
|---|
| 67 |
self.specificData = '' |
|---|
| 68 |
self.buf = '' |
|---|
| 69 |
self.extBuf = [] |
|---|
| 70 |
self.closing = 0 |
|---|
| 71 |
self.localClosed = 0 |
|---|
| 72 |
self.remoteClosed = 0 |
|---|
| 73 |
self.id = None |
|---|
| 74 |
|
|---|
| 75 |
def __str__(self): |
|---|
| 76 |
return '<SSHChannel %s (lw %i rw %i)>' % (self.name, |
|---|
| 77 |
self.localWindowLeft, self.remoteWindowLeft) |
|---|
| 78 |
|
|---|
| 79 |
def logPrefix(self): |
|---|
| 80 |
id = (self.id is not None and str(self.id)) or "unknown" |
|---|
| 81 |
return "SSHChannel %s (%s) on %s" % (self.name, id, |
|---|
| 82 |
self.conn.logPrefix()) |
|---|
| 83 |
|
|---|
| 84 |
def channelOpen(self, specificData): |
|---|
| 85 |
""" |
|---|
| 86 |
Called when the channel is opened. specificData is any data that the |
|---|
| 87 |
other side sent us when opening the channel. |
|---|
| 88 |
|
|---|
| 89 |
@type specificData: C{str} |
|---|
| 90 |
""" |
|---|
| 91 |
log.msg('channel open') |
|---|
| 92 |
|
|---|
| 93 |
def openFailed(self, reason): |
|---|
| 94 |
""" |
|---|
| 95 |
Called when the the open failed for some reason. |
|---|
| 96 |
reason.desc is a string descrption, reason.code the the SSH error code. |
|---|
| 97 |
|
|---|
| 98 |
@type reason: L{error.ConchError} |
|---|
| 99 |
""" |
|---|
| 100 |
log.msg('other side refused open\nreason: %s'% reason) |
|---|
| 101 |
|
|---|
| 102 |
def addWindowBytes(self, bytes): |
|---|
| 103 |
""" |
|---|
| 104 |
Called when bytes are added to the remote window. By default it clears |
|---|
| 105 |
the data buffers. |
|---|
| 106 |
|
|---|
| 107 |
@type bytes: C{int} |
|---|
| 108 |
""" |
|---|
| 109 |
self.remoteWindowLeft = self.remoteWindowLeft+bytes |
|---|
| 110 |
if not self.areWriting and not self.closing: |
|---|
| 111 |
self.areWriting = True |
|---|
| 112 |
self.startWriting() |
|---|
| 113 |
if self.buf: |
|---|
| 114 |
b = self.buf |
|---|
| 115 |
self.buf = '' |
|---|
| 116 |
self.write(b) |
|---|
| 117 |
if self.extBuf: |
|---|
| 118 |
b = self.extBuf |
|---|
| 119 |
self.extBuf = [] |
|---|
| 120 |
for (type, data) in b: |
|---|
| 121 |
self.writeExtended(type, data) |
|---|
| 122 |
|
|---|
| 123 |
def requestReceived(self, requestType, data): |
|---|
| 124 |
""" |
|---|
| 125 |
Called when a request is sent to this channel. By default it delegates |
|---|
| 126 |
to self.request_<requestType>. |
|---|
| 127 |
If this function returns true, the request succeeded, otherwise it |
|---|
| 128 |
failed. |
|---|
| 129 |
|
|---|
| 130 |
@type requestType: C{str} |
|---|
| 131 |
@type data: C{str} |
|---|
| 132 |
@rtype: C{bool} |
|---|
| 133 |
""" |
|---|
| 134 |
foo = requestType.replace('-', '_') |
|---|
| 135 |
f = getattr(self, 'request_%s'%foo, None) |
|---|
| 136 |
if f: |
|---|
| 137 |
return f(data) |
|---|
| 138 |
log.msg('unhandled request for %s'%requestType) |
|---|
| 139 |
return 0 |
|---|
| 140 |
|
|---|
| 141 |
def dataReceived(self, data): |
|---|
| 142 |
""" |
|---|
| 143 |
Called when we receive data. |
|---|
| 144 |
|
|---|
| 145 |
@type data: C{str} |
|---|
| 146 |
""" |
|---|
| 147 |
log.msg('got data %s'%repr(data)) |
|---|
| 148 |
|
|---|
| 149 |
def extReceived(self, dataType, data): |
|---|
| 150 |
""" |
|---|
| 151 |
Called when we receive extended data (usually standard error). |
|---|
| 152 |
|
|---|
| 153 |
@type dataType: C{int} |
|---|
| 154 |
@type data: C{str} |
|---|
| 155 |
""" |
|---|
| 156 |
log.msg('got extended data %s %s'%(dataType, repr(data))) |
|---|
| 157 |
|
|---|
| 158 |
def eofReceived(self): |
|---|
| 159 |
""" |
|---|
| 160 |
Called when the other side will send no more data. |
|---|
| 161 |
""" |
|---|
| 162 |
log.msg('remote eof') |
|---|
| 163 |
|
|---|
| 164 |
def closeReceived(self): |
|---|
| 165 |
""" |
|---|
| 166 |
Called when the other side has closed the channel. |
|---|
| 167 |
""" |
|---|
| 168 |
log.msg('remote close') |
|---|
| 169 |
self.loseConnection() |
|---|
| 170 |
|
|---|
| 171 |
def closed(self): |
|---|
| 172 |
""" |
|---|
| 173 |
Called when the channel is closed. This means that both our side and |
|---|
| 174 |
the remote side have closed the channel. |
|---|
| 175 |
""" |
|---|
| 176 |
log.msg('closed') |
|---|
| 177 |
|
|---|
| 178 |
|
|---|
| 179 |
def write(self, data): |
|---|
| 180 |
""" |
|---|
| 181 |
Write some data to the channel. If there is not enough remote window |
|---|
| 182 |
available, buffer until it is. Otherwise, split the data into |
|---|
| 183 |
packets of length remoteMaxPacket and send them. |
|---|
| 184 |
|
|---|
| 185 |
@type data: C{str} |
|---|
| 186 |
""" |
|---|
| 187 |
if self.buf: |
|---|
| 188 |
self.buf += data |
|---|
| 189 |
return |
|---|
| 190 |
top = len(data) |
|---|
| 191 |
if top > self.remoteWindowLeft: |
|---|
| 192 |
data, self.buf = (data[:self.remoteWindowLeft], |
|---|
| 193 |
data[self.remoteWindowLeft:]) |
|---|
| 194 |
self.areWriting = 0 |
|---|
| 195 |
self.stopWriting() |
|---|
| 196 |
top = self.remoteWindowLeft |
|---|
| 197 |
rmp = self.remoteMaxPacket |
|---|
| 198 |
write = self.conn.sendData |
|---|
| 199 |
r = range(0, top, rmp) |
|---|
| 200 |
for offset in r: |
|---|
| 201 |
write(self, data[offset: offset+rmp]) |
|---|
| 202 |
self.remoteWindowLeft -= top |
|---|
| 203 |
if self.closing and not self.buf: |
|---|
| 204 |
self.loseConnection() |
|---|
| 205 |
|
|---|
| 206 |
def writeExtended(self, dataType, data): |
|---|
| 207 |
""" |
|---|
| 208 |
Send extended data to this channel. If there is not enough remote |
|---|
| 209 |
window available, buffer until there is. Otherwise, split the data |
|---|
| 210 |
into packets of length remoteMaxPacket and send them. |
|---|
| 211 |
|
|---|
| 212 |
@type dataType: C{int} |
|---|
| 213 |
@type data: C{str} |
|---|
| 214 |
""" |
|---|
| 215 |
if self.extBuf: |
|---|
| 216 |
if self.extBuf[-1][0] == dataType: |
|---|
| 217 |
self.extBuf[-1][1] += data |
|---|
| 218 |
else: |
|---|
| 219 |
self.extBuf.append([dataType, data]) |
|---|
| 220 |
return |
|---|
| 221 |
if len(data) > self.remoteWindowLeft: |
|---|
| 222 |
data, self.extBuf = (data[:self.remoteWindowLeft], |
|---|
| 223 |
[[dataType, data[self.remoteWindowLeft:]]]) |
|---|
| 224 |
self.areWriting = 0 |
|---|
| 225 |
self.stopWriting() |
|---|
| 226 |
while len(data) > self.remoteMaxPacket: |
|---|
| 227 |
self.conn.sendExtendedData(self, dataType, |
|---|
| 228 |
data[:self.remoteMaxPacket]) |
|---|
| 229 |
data = data[self.remoteMaxPacket:] |
|---|
| 230 |
self.remoteWindowLeft -= self.remoteMaxPacket |
|---|
| 231 |
if data: |
|---|
| 232 |
self.conn.sendExtendedData(self, dataType, data) |
|---|
| 233 |
self.remoteWindowLeft -= len(data) |
|---|
| 234 |
if self.closing: |
|---|
| 235 |
self.loseConnection() |
|---|
| 236 |
|
|---|
| 237 |
def writeSequence(self, data): |
|---|
| 238 |
""" |
|---|
| 239 |
Part of the Transport interface. Write a list of strings to the |
|---|
| 240 |
channel. |
|---|
| 241 |
|
|---|
| 242 |
@type data: C{list} of C{str} |
|---|
| 243 |
""" |
|---|
| 244 |
self.write(''.join(data)) |
|---|
| 245 |
|
|---|
| 246 |
def loseConnection(self): |
|---|
| 247 |
""" |
|---|
| 248 |
Close the channel if there is no buferred data. Otherwise, note the |
|---|
| 249 |
request and return. |
|---|
| 250 |
""" |
|---|
| 251 |
self.closing = 1 |
|---|
| 252 |
if not self.buf and not self.extBuf: |
|---|
| 253 |
self.conn.sendClose(self) |
|---|
| 254 |
|
|---|
| 255 |
def getPeer(self): |
|---|
| 256 |
""" |
|---|
| 257 |
Return a tuple describing the other side of the connection. |
|---|
| 258 |
|
|---|
| 259 |
@rtype: C{tuple} |
|---|
| 260 |
""" |
|---|
| 261 |
return('SSH', )+self.conn.transport.getPeer() |
|---|
| 262 |
|
|---|
| 263 |
def getHost(self): |
|---|
| 264 |
""" |
|---|
| 265 |
Return a tuple describing our side of the connection. |
|---|
| 266 |
|
|---|
| 267 |
@rtype: C{tuple} |
|---|
| 268 |
""" |
|---|
| 269 |
return('SSH', )+self.conn.transport.getHost() |
|---|
| 270 |
|
|---|
| 271 |
def stopWriting(self): |
|---|
| 272 |
""" |
|---|
| 273 |
Called when the remote buffer is full, as a hint to stop writing. |
|---|
| 274 |
This can be ignored, but it can be helpful. |
|---|
| 275 |
""" |
|---|
| 276 |
|
|---|
| 277 |
def startWriting(self): |
|---|
| 278 |
""" |
|---|
| 279 |
Called when the remote buffer has more room, as a hint to continue |
|---|
| 280 |
writing. |
|---|
| 281 |
""" |
|---|