OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.conch.test.test_connection -*- | |
2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 # | |
6 | |
7 """ | |
8 This module contains the implementation of the ssh-connection service, which | |
9 allows access to the shell and port-forwarding. | |
10 | |
11 Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>} | |
12 """ | |
13 | |
14 import struct | |
15 | |
16 from twisted.conch.ssh import service, common | |
17 from twisted.conch import error | |
18 from twisted.internet import defer | |
19 from twisted.python import log | |
20 | |
21 class SSHConnection(service.SSHService): | |
22 """ | |
23 An implementation of the 'ssh-connection' service. It is used to | |
24 multiplex multiple channels over the single SSH connection. | |
25 | |
26 @ivar localChannelID: the next number to use as a local channel ID. | |
27 @type localChannelID: C{int} | |
28 @ivar channels: a C{dict} mapping a local channel ID to C{SSHChannel} | |
29 subclasses. | |
30 @type channels: C{dict} | |
31 @ivar localToRemoteChannel: a C{dict} mapping a local channel ID to a | |
32 remote channel ID. | |
33 @type localToRemoteChannel: C{dict} | |
34 @ivar channelsToRemoteChannel: a C{dict} mapping a C{SSHChannel} subclass | |
35 to remote channel ID. | |
36 @type channelsToRemoteChannel: C{dict} | |
37 @ivar deferreds: a C{dict} mapping a local channel ID to a C{list} of | |
38 C{Deferreds} for outstanding channel requests. Also, the 'global' | |
39 key stores the C{list} of pending global request C{Deferred}s. | |
40 """ | |
41 name = 'ssh-connection' | |
42 | |
43 def __init__(self): | |
44 self.localChannelID = 0 # this is the current # to use for channel ID | |
45 self.localToRemoteChannel = {} # local channel ID -> remote channel ID | |
46 self.channels = {} # local channel ID -> subclass of SSHChannel | |
47 self.channelsToRemoteChannel = {} # subclass of SSHChannel -> | |
48 # remote channel ID | |
49 self.deferreds = {} # local channel -> list of deferreds for pending | |
50 # requests or 'global' -> list of deferreds for | |
51 # global requests | |
52 self.transport = None # gets set later | |
53 | |
54 def serviceStarted(self): | |
55 if hasattr(self.transport, 'avatar'): | |
56 self.transport.avatar.conn = self | |
57 | |
58 def serviceStopped(self): | |
59 map(self.channelClosed, self.channels.values()) | |
60 | |
61 # packet methods | |
62 def ssh_GLOBAL_REQUEST(self, packet): | |
63 """ | |
64 The other side has made a global request. Payload:: | |
65 string request type | |
66 bool want reply | |
67 <request specific data> | |
68 | |
69 This dispatches to self.gotGlobalRequest. | |
70 """ | |
71 requestType, rest = common.getNS(packet) | |
72 wantReply, rest = ord(rest[0]), rest[1:] | |
73 ret = self.gotGlobalRequest(requestType, rest) | |
74 if wantReply: | |
75 reply = MSG_REQUEST_FAILURE | |
76 data = '' | |
77 if ret: | |
78 reply = MSG_REQUEST_SUCCESS | |
79 if isinstance(ret, (tuple, list)): | |
80 data = ret[1] | |
81 self.transport.sendPacket(reply, data) | |
82 | |
83 def ssh_REQUEST_SUCCESS(self, packet): | |
84 """ | |
85 Our global request succeeded. Get the appropriate Deferred and call | |
86 it back with the packet we received. | |
87 """ | |
88 log.msg('RS') | |
89 self.deferreds['global'].pop(0).callback(packet) | |
90 | |
91 def ssh_REQUEST_FAILURE(self, packet): | |
92 """ | |
93 Our global request failed. Get the appropriate Deferred and errback | |
94 it with the packet we received. | |
95 """ | |
96 log.msg('RF') | |
97 self.deferreds['global'].pop(0).errback( | |
98 error.ConchError('global request failed', packet)) | |
99 | |
100 def ssh_CHANNEL_OPEN(self, packet): | |
101 """ | |
102 The other side wants to get a channel. Payload:: | |
103 string channel name | |
104 uint32 remote channel number | |
105 uint32 remote window size | |
106 uint32 remote maximum packet size | |
107 <channel specific data> | |
108 | |
109 We get a channel from self.getChannel(), give it a local channel number | |
110 and notify the other side. Then notify the channel by calling its | |
111 channelOpen method. | |
112 """ | |
113 channelType, rest = common.getNS(packet) | |
114 senderChannel, windowSize, maxPacket = struct.unpack('>3L', rest[:12]) | |
115 packet = rest[12:] | |
116 try: | |
117 channel = self.getChannel(channelType, windowSize, maxPacket, | |
118 packet) | |
119 localChannel = self.localChannelID | |
120 self.localChannelID += 1 | |
121 channel.id = localChannel | |
122 self.channels[localChannel] = channel | |
123 self.channelsToRemoteChannel[channel] = senderChannel | |
124 self.localToRemoteChannel[localChannel] = senderChannel | |
125 self.transport.sendPacket(MSG_CHANNEL_OPEN_CONFIRMATION, | |
126 struct.pack('>4L', senderChannel, localChannel, | |
127 channel.localWindowSize, | |
128 channel.localMaxPacket)+channel.specificData) | |
129 log.callWithLogger(channel, channel.channelOpen, packet) | |
130 except Exception, e: | |
131 log.msg('channel open failed') | |
132 log.err(e) | |
133 if isinstance(e, error.ConchError): | |
134 textualInfo, reason = e.args | |
135 else: | |
136 reason = OPEN_CONNECT_FAILED | |
137 textualInfo = "unknown failure" | |
138 self.transport.sendPacket(MSG_CHANNEL_OPEN_FAILURE, | |
139 struct.pack('>2L', senderChannel, reason) + | |
140 common.NS(textualInfo) + common.NS('')) | |
141 | |
142 def ssh_CHANNEL_OPEN_CONFIRMATION(self, packet): | |
143 """ | |
144 The other side accepted our MSG_CHANNEL_OPEN request. Payload:: | |
145 uint32 local channel number | |
146 uint32 remote channel number | |
147 uint32 remote window size | |
148 uint32 remote maximum packet size | |
149 <channel specific data> | |
150 | |
151 Find the channel using the local channel number and notify its | |
152 channelOpen method. | |
153 """ | |
154 (localChannel, remoteChannel, windowSize, | |
155 maxPacket) = struct.unpack('>4L', packet[: 16]) | |
156 specificData = packet[16:] | |
157 channel = self.channels[localChannel] | |
158 channel.conn = self | |
159 self.localToRemoteChannel[localChannel] = remoteChannel | |
160 self.channelsToRemoteChannel[channel] = remoteChannel | |
161 channel.remoteWindowLeft = windowSize | |
162 channel.remoteMaxPacket = maxPacket | |
163 log.callWithLogger(channel, channel.channelOpen, specificData) | |
164 | |
165 def ssh_CHANNEL_OPEN_FAILURE(self, packet): | |
166 """ | |
167 The other side did not accept our MSG_CHANNEL_OPEN request. Payload:: | |
168 uint32 local channel number | |
169 uint32 reason code | |
170 string reason description | |
171 | |
172 Find the channel using the local channel number and notify it by | |
173 calling its openFailed() method. | |
174 """ | |
175 localChannel, reasonCode = struct.unpack('>2L', packet[:8]) | |
176 reasonDesc = common.getNS(packet[8:])[0] | |
177 channel = self.channels[localChannel] | |
178 del self.channels[localChannel] | |
179 channel.conn = self | |
180 reason = error.ConchError(reasonDesc, reasonCode) | |
181 log.callWithLogger(channel, channel.openFailed, reason) | |
182 | |
183 def ssh_CHANNEL_WINDOW_ADJUST(self, packet): | |
184 """ | |
185 The other side is adding bytes to its window. Payload:: | |
186 uint32 local channel number | |
187 uint32 bytes to add | |
188 | |
189 Call the channel's addWindowBytes() method to add new bytes to the | |
190 remote window. | |
191 """ | |
192 localChannel, bytesToAdd = struct.unpack('>2L', packet[:8]) | |
193 channel = self.channels[localChannel] | |
194 log.callWithLogger(channel, channel.addWindowBytes, bytesToAdd) | |
195 | |
196 def ssh_CHANNEL_DATA(self, packet): | |
197 """ | |
198 The other side is sending us data. Payload:: | |
199 uint32 local channel number | |
200 string data | |
201 | |
202 Check to make sure the other side hasn't sent too much data (more | |
203 than what's in the window, or more than the maximum packet size). If | |
204 they have, close the channel. Otherwise, decrease the available | |
205 window and pass the data to the channel's dataReceived(). | |
206 """ | |
207 localChannel, dataLength = struct.unpack('>2L', packet[:8]) | |
208 channel = self.channels[localChannel] | |
209 # XXX should this move to dataReceived to put client in charge? | |
210 if (dataLength > channel.localWindowLeft or | |
211 dataLength > channel.localMaxPacket): # more data than we want | |
212 log.callWithLogger(channel, log.msg, 'too much data') | |
213 self.sendClose(channel) | |
214 return | |
215 #packet = packet[:channel.localWindowLeft+4] | |
216 data = common.getNS(packet[4:])[0] | |
217 channel.localWindowLeft -= dataLength | |
218 if channel.localWindowLeft < channel.localWindowSize / 2: | |
219 self.adjustWindow(channel, channel.localWindowSize - \ | |
220 channel.localWindowLeft) | |
221 #log.msg('local window left: %s/%s' % (channel.localWindowLeft, | |
222 # channel.localWindowSize)) | |
223 log.callWithLogger(channel, channel.dataReceived, data) | |
224 | |
225 def ssh_CHANNEL_EXTENDED_DATA(self, packet): | |
226 """ | |
227 The other side is sending us exteneded data. Payload:: | |
228 uint32 local channel number | |
229 uint32 type code | |
230 string data | |
231 | |
232 Check to make sure the other side hasn't sent too much data (more | |
233 than what's in the window, or or than the maximum packet size). If | |
234 they have, close the channel. Otherwise, decrease the available | |
235 window and pass the data and type code to the channel's | |
236 extReceived(). | |
237 """ | |
238 localChannel, typeCode, dataLength = struct.unpack('>3L', packet[:12]) | |
239 channel = self.channels[localChannel] | |
240 if (dataLength > channel.localWindowLeft or | |
241 dataLength > channel.localMaxPacket): | |
242 log.callWithLogger(channel, log.msg, 'too much extdata') | |
243 self.sendClose(channel) | |
244 return | |
245 data = common.getNS(packet[8:])[0] | |
246 channel.localWindowLeft -= dataLength | |
247 if channel.localWindowLeft < channel.localWindowSize / 2: | |
248 self.adjustWindow(channel, channel.localWindowSize - | |
249 channel.localWindowLeft) | |
250 log.callWithLogger(channel, channel.extReceived, typeCode, data) | |
251 | |
252 def ssh_CHANNEL_EOF(self, packet): | |
253 """ | |
254 The other side is not sending any more data. Payload:: | |
255 uint32 local channel number | |
256 | |
257 Notify the channel by calling its eofReceived() method. | |
258 """ | |
259 localChannel = struct.unpack('>L', packet[:4])[0] | |
260 channel = self.channels[localChannel] | |
261 log.callWithLogger(channel, channel.eofReceived) | |
262 | |
263 def ssh_CHANNEL_CLOSE(self, packet): | |
264 """ | |
265 The other side is closing its end; it does not want to receive any | |
266 more data. Payload:: | |
267 uint32 local channel number | |
268 | |
269 Notify the channnel by calling its closeReceived() method. If | |
270 the channel has also sent a close message, call self.channelClosed(). | |
271 """ | |
272 localChannel = struct.unpack('>L', packet[:4])[0] | |
273 channel = self.channels[localChannel] | |
274 log.callWithLogger(channel, channel.closeReceived) | |
275 channel.remoteClosed = True | |
276 if channel.localClosed and channel.remoteClosed: | |
277 self.channelClosed(channel) | |
278 | |
279 def ssh_CHANNEL_REQUEST(self, packet): | |
280 """ | |
281 The other side is sending a request to a channel. Payload:: | |
282 uint32 local channel number | |
283 string request name | |
284 bool want reply | |
285 <request specific data> | |
286 | |
287 Pass the message to the channel's requestReceived method. If the | |
288 other side wants a reply, add callbacks which will send the | |
289 reply. | |
290 """ | |
291 localChannel = struct.unpack('>L', packet[: 4])[0] | |
292 requestType, rest = common.getNS(packet[4:]) | |
293 wantReply = ord(rest[0]) | |
294 channel = self.channels[localChannel] | |
295 d = defer.maybeDeferred(log.callWithLogger, channel, | |
296 channel.requestReceived, requestType, rest[1:]) | |
297 if wantReply: | |
298 d.addCallback(self._cbChannelRequest, localChannel) | |
299 d.addErrback(self._ebChannelRequest, localChannel) | |
300 return d | |
301 | |
302 def _cbChannelRequest(self, result, localChannel): | |
303 """ | |
304 Called back if the other side wanted a reply to a channel request. If | |
305 the result is true, send a MSG_CHANNEL_SUCCESS. Otherwise, raise | |
306 a C{error.ConchError} | |
307 | |
308 @param result: the value returned from the channel's requestReceived() | |
309 method. If it's False, the request failed. | |
310 @type result: C{bool} | |
311 @param localChannel: the local channel ID of the channel to which the | |
312 request was made. | |
313 @type localChannel: C{int} | |
314 @raises ConchError: if the result is False. | |
315 """ | |
316 if not result: | |
317 raise error.ConchError('failed request') | |
318 self.transport.sendPacket(MSG_CHANNEL_SUCCESS, struct.pack('>L', | |
319 self.localToRemoteChannel[localChannel])) | |
320 | |
321 def _ebChannelRequest(self, result, localChannel): | |
322 """ | |
323 Called if the other wisde wanted a reply to the channel requeset and | |
324 the channel request failed. | |
325 | |
326 @param result: a Failure, but it's not used. | |
327 @param localChannel: the local channel ID of the channel to which the | |
328 request was made. | |
329 @type localChannel: C{int} | |
330 """ | |
331 self.transport.sendPacket(MSG_CHANNEL_FAILURE, struct.pack('>L', | |
332 self.localToRemoteChannel[localChannel])) | |
333 | |
334 def ssh_CHANNEL_SUCCESS(self, packet): | |
335 """ | |
336 Our channel request to the other other side succeeded. Payload:: | |
337 uint32 local channel number | |
338 | |
339 Get the C{Deferred} out of self.deferreds and call it back. | |
340 """ | |
341 localChannel = struct.unpack('>L', packet[:4])[0] | |
342 if self.deferreds.get(localChannel): | |
343 d = self.deferreds[localChannel].pop(0) | |
344 log.callWithLogger(self.channels[localChannel], | |
345 d.callback, '') | |
346 | |
347 def ssh_CHANNEL_FAILURE(self, packet): | |
348 """ | |
349 Our channel request to the other side failed. Payload:: | |
350 uint32 local channel number | |
351 | |
352 Get the C{Deferred} out of self.deferreds and errback it with a | |
353 C{error.ConchError}. | |
354 """ | |
355 localChannel = struct.unpack('>L', packet[:4])[0] | |
356 if self.deferreds.get(localChannel): | |
357 d = self.deferreds[localChannel].pop(0) | |
358 log.callWithLogger(self.channels[localChannel], | |
359 d.errback, | |
360 error.ConchError('channel request failed')) | |
361 | |
362 # methods for users of the connection to call | |
363 | |
364 def sendGlobalRequest(self, request, data, wantReply=0): | |
365 """ | |
366 Send a global request for this connection. Current this is only used | |
367 for remote->local TCP forwarding. | |
368 | |
369 @type request: C{str} | |
370 @type data: C{str} | |
371 @type wantReply: C{bool} | |
372 @rtype C{Deferred}/C{None} | |
373 """ | |
374 self.transport.sendPacket(MSG_GLOBAL_REQUEST, | |
375 common.NS(request) | |
376 + (wantReply and '\xff' or '\x00') | |
377 + data) | |
378 if wantReply: | |
379 d = defer.Deferred() | |
380 self.deferreds.setdefault('global', []).append(d) | |
381 return d | |
382 | |
383 def openChannel(self, channel, extra=''): | |
384 """ | |
385 Open a new channel on this connection. | |
386 | |
387 @type channel: subclass of C{SSHChannel} | |
388 @type extra: C{str} | |
389 """ | |
390 log.msg('opening channel %s with %s %s'%(self.localChannelID, | |
391 channel.localWindowSize, channel.localMaxPacket)) | |
392 self.transport.sendPacket(MSG_CHANNEL_OPEN, common.NS(channel.name) | |
393 + struct.pack('>3L', self.localChannelID, | |
394 channel.localWindowSize, channel.localMaxPacket) | |
395 + extra) | |
396 channel.id = self.localChannelID | |
397 self.channels[self.localChannelID] = channel | |
398 self.localChannelID += 1 | |
399 | |
400 def sendRequest(self, channel, requestType, data, wantReply=0): | |
401 """ | |
402 Send a request to a channel. | |
403 | |
404 @type channel: subclass of C{SSHChannel} | |
405 @type requestType: C{str} | |
406 @type data: C{str} | |
407 @type wantReply: C{bool} | |
408 @rtype C{Deferred}/C{None} | |
409 """ | |
410 if channel.localClosed: | |
411 return | |
412 log.msg('sending request %s' % requestType) | |
413 self.transport.sendPacket(MSG_CHANNEL_REQUEST, struct.pack('>L', | |
414 self.channelsToRemoteChannel[channel]) | |
415 + common.NS(requestType)+chr(wantReply) | |
416 + data) | |
417 if wantReply: | |
418 d = defer.Deferred() | |
419 self.deferreds.setdefault(channel.id, []).append(d) | |
420 return d | |
421 | |
422 def adjustWindow(self, channel, bytesToAdd): | |
423 """ | |
424 Tell the other side that we will receive more data. This should not | |
425 normally need to be called as it is managed automatically. | |
426 | |
427 @type channel: subclass of L{SSHChannel} | |
428 @type bytesToAdd: C{int} | |
429 """ | |
430 if channel.localClosed: | |
431 return # we're already closed | |
432 self.transport.sendPacket(MSG_CHANNEL_WINDOW_ADJUST, struct.pack('>2L', | |
433 self.channelsToRemoteChannel[channel], | |
434 bytesToAdd)) | |
435 log.msg('adding %i to %i in channel %i' % (bytesToAdd, | |
436 channel.localWindowLeft, channel.id)) | |
437 channel.localWindowLeft += bytesToAdd | |
438 | |
439 def sendData(self, channel, data): | |
440 """ | |
441 Send data to a channel. This should not normally be used: instead use | |
442 channel.write(data) as it manages the window automatically. | |
443 | |
444 @type channel: subclass of L{SSHChannel} | |
445 @type data: C{str} | |
446 """ | |
447 if channel.localClosed: | |
448 return # we're already closed | |
449 self.transport.sendPacket(MSG_CHANNEL_DATA, struct.pack('>L', | |
450 self.channelsToRemoteChannel[channel]) + | |
451 common.NS(data)) | |
452 | |
453 def sendExtendedData(self, channel, dataType, data): | |
454 """ | |
455 Send extended data to a channel. This should not normally be used: | |
456 instead use channel.writeExtendedData(data, dataType) as it manages | |
457 the window automatically. | |
458 | |
459 @type channel: subclass of L{SSHChannel} | |
460 @type dataType: C{int} | |
461 @type data: C{str} | |
462 """ | |
463 if channel.localClosed: | |
464 return # we're already closed | |
465 self.transport.sendPacket(MSG_CHANNEL_EXTENDED_DATA, struct.pack('>2L', | |
466 self.channelsToRemoteChannel[channel],dataType) \ | |
467 + common.NS(data)) | |
468 | |
469 def sendEOF(self, channel): | |
470 """ | |
471 Send an EOF (End of File) for a channel. | |
472 | |
473 @type channel: subclass of L{SSHChannel} | |
474 """ | |
475 if channel.localClosed: | |
476 return # we're already closed | |
477 log.msg('sending eof') | |
478 self.transport.sendPacket(MSG_CHANNEL_EOF, struct.pack('>L', | |
479 self.channelsToRemoteChannel[channel])) | |
480 | |
481 def sendClose(self, channel): | |
482 """ | |
483 Close a channel. | |
484 | |
485 @type channel: subclass of L{SSHChannel} | |
486 """ | |
487 if channel.localClosed: | |
488 return # we're already closed | |
489 log.msg('sending close %i' % channel.id) | |
490 self.transport.sendPacket(MSG_CHANNEL_CLOSE, struct.pack('>L', | |
491 self.channelsToRemoteChannel[channel])) | |
492 channel.localClosed = True | |
493 if channel.localClosed and channel.remoteClosed: | |
494 self.channelClosed(channel) | |
495 | |
496 # methods to override | |
497 def getChannel(self, channelType, windowSize, maxPacket, data): | |
498 """ | |
499 The other side requested a channel of some sort. | |
500 channelType is the type of channel being requested, | |
501 windowSize is the initial size of the remote window, | |
502 maxPacket is the largest packet we should send, | |
503 data is any other packet data (often nothing). | |
504 | |
505 We return a subclass of L{SSHChannel}. | |
506 | |
507 By default, this dispatches to a method 'channel_channelType' with any | |
508 non-alphanumerics in the channelType replace with _'s. If it cannot | |
509 find a suitable method, it returns an OPEN_UNKNOWN_CHANNEL_TYPE error. | |
510 The method is called with arguments of windowSize, maxPacket, data. | |
511 | |
512 @type channelType: C{str} | |
513 @type windowSize: C{int} | |
514 @type maxPacket: C{int} | |
515 @type data: C{str} | |
516 @rtype: subclass of L{SSHChannel}/C{tuple} | |
517 """ | |
518 log.msg('got channel %s request' % channelType) | |
519 if hasattr(self.transport, "avatar"): # this is a server! | |
520 chan = self.transport.avatar.lookupChannel(channelType, | |
521 windowSize, | |
522 maxPacket, | |
523 data) | |
524 else: | |
525 channelType = channelType.translate(TRANSLATE_TABLE) | |
526 f = getattr(self, 'channel_%s' % channelType, None) | |
527 if f is not None: | |
528 chan = f(windowSize, maxPacket, data) | |
529 else: | |
530 chan = None | |
531 if chan is None: | |
532 raise error.ConchError('unknown channel', | |
533 OPEN_UNKNOWN_CHANNEL_TYPE) | |
534 else: | |
535 chan.conn = self | |
536 return chan | |
537 | |
538 def gotGlobalRequest(self, requestType, data): | |
539 """ | |
540 We got a global request. pretty much, this is just used by the client | |
541 to request that we forward a port from the server to the client. | |
542 Returns either: | |
543 - 1: request accepted | |
544 - 1, <data>: request accepted with request specific data | |
545 - 0: request denied | |
546 | |
547 By default, this dispatches to a method 'global_requestType' with | |
548 -'s in requestType replaced with _'s. The found method is passed data. | |
549 If this method cannot be found, this method returns 0. Otherwise, it | |
550 returns the return value of that method. | |
551 | |
552 @type requestType: C{str} | |
553 @type data: C{str} | |
554 @rtype: C{int}/C{tuple} | |
555 """ | |
556 log.msg('got global %s request' % requestType) | |
557 if hasattr(self.transport, 'avatar'): # this is a server! | |
558 return self.transport.avatar.gotGlobalRequest(requestType, data) | |
559 | |
560 requestType = requestType.replace('-','_') | |
561 f = getattr(self, 'global_%s' % requestType, None) | |
562 if not f: | |
563 return 0 | |
564 return f(data) | |
565 | |
566 def channelClosed(self, channel): | |
567 """ | |
568 Called when a channel is closed. | |
569 It clears the local state related to the channel, and calls | |
570 channel.closed(). | |
571 MAKE SURE YOU CALL THIS METHOD, even if you subclass L{SSHConnection}. | |
572 If you don't, things will break mysteriously. | |
573 """ | |
574 if channel in self.channelsToRemoteChannel: # actually open | |
575 channel.localClosed = channel.remoteClosed = True | |
576 del self.localToRemoteChannel[channel.id] | |
577 del self.channels[channel.id] | |
578 del self.channelsToRemoteChannel[channel] | |
579 self.deferreds[channel.id] = [] | |
580 log.callWithLogger(channel, channel.closed) | |
581 | |
582 MSG_GLOBAL_REQUEST = 80 | |
583 MSG_REQUEST_SUCCESS = 81 | |
584 MSG_REQUEST_FAILURE = 82 | |
585 MSG_CHANNEL_OPEN = 90 | |
586 MSG_CHANNEL_OPEN_CONFIRMATION = 91 | |
587 MSG_CHANNEL_OPEN_FAILURE = 92 | |
588 MSG_CHANNEL_WINDOW_ADJUST = 93 | |
589 MSG_CHANNEL_DATA = 94 | |
590 MSG_CHANNEL_EXTENDED_DATA = 95 | |
591 MSG_CHANNEL_EOF = 96 | |
592 MSG_CHANNEL_CLOSE = 97 | |
593 MSG_CHANNEL_REQUEST = 98 | |
594 MSG_CHANNEL_SUCCESS = 99 | |
595 MSG_CHANNEL_FAILURE = 100 | |
596 | |
597 OPEN_ADMINISTRATIVELY_PROHIBITED = 1 | |
598 OPEN_CONNECT_FAILED = 2 | |
599 OPEN_UNKNOWN_CHANNEL_TYPE = 3 | |
600 OPEN_RESOURCE_SHORTAGE = 4 | |
601 | |
602 EXTENDED_DATA_STDERR = 1 | |
603 | |
604 messages = {} | |
605 for name, value in locals().copy().items(): | |
606 if name[:4] == 'MSG_': | |
607 messages[value] = name # doesn't handle doubles | |
608 | |
609 import string | |
610 alphanums = string.letters + string.digits | |
611 TRANSLATE_TABLE = ''.join([chr(i) in alphanums and chr(i) or '_' | |
612 for i in range(256)]) | |
613 SSHConnection.protocolMessages = messages | |
OLD | NEW |