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 |