| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.conch.test.test_channel -*- | |
| 2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 # | |
| 6 """ | |
| 7 The parent class for all the SSH Channels. Currently implemented channels | |
| 8 are session. direct-tcp, and forwarded-tcp. | |
| 9 | |
| 10 Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>} | |
| 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 # only needed for client channels | |
| 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 # gets set later by SSHConnection | |
| 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 # transport stuff | |
| 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() # try again | |
| 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() # try again | |
| 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 """ | |
| OLD | NEW |