~landscape/zope3/newer-from-ztk

« back to all changes in this revision

Viewing changes to src/twisted/conch/ssh/connection.py

  • Committer: Thomas Hervé
  • Date: 2009-07-08 13:52:04 UTC
  • Revision ID: thomas@canonical.com-20090708135204-df5eesrthifpylf8
Remove twisted copy

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
 
# See LICENSE for details.
3
 
 
4
 
5
 
 
6
 
"""This module contains the implementation of the ssh-connection service, which
7
 
allows access to the shell and port-forwarding.
8
 
 
9
 
This module is unstable.
10
 
 
11
 
Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>}
12
 
"""
13
 
 
14
 
import struct, types
15
 
 
16
 
from twisted.internet import protocol, reactor, defer
17
 
from twisted.python import log
18
 
from twisted.conch import error
19
 
import service, common, session, forwarding
20
 
 
21
 
class SSHConnection(service.SSHService):
22
 
    name = 'ssh-connection'
23
 
 
24
 
    def __init__(self):
25
 
        self.localChannelID = 0 # this is the current # to use for channel ID
26
 
        self.localToRemoteChannel = {} # local channel ID -> remote channel ID
27
 
        self.channels = {} # local channel ID -> subclass of SSHChannel
28
 
        self.channelsToRemoteChannel = {} # subclass of SSHChannel -> 
29
 
                                          # remote channel ID
30
 
        self.deferreds = {} # local channel -> list of deferreds for pending 
31
 
                            # requests or 'global' -> list of deferreds for 
32
 
                            # global requests
33
 
        self.transport = None # gets set later
34
 
 
35
 
    def serviceStarted(self):
36
 
        if hasattr(self.transport, 'avatar'): 
37
 
            self.transport.avatar.conn = self
38
 
    
39
 
    def serviceStopped(self):
40
 
        map(self.channelClosed, self.channels.values())
41
 
 
42
 
    # packet methods
43
 
    def ssh_GLOBAL_REQUEST(self, packet):
44
 
        requestType, rest = common.getNS(packet)
45
 
        wantReply, rest = ord(rest[0]), rest[1:]
46
 
        reply = MSG_REQUEST_FAILURE
47
 
        data = ''
48
 
        ret = self.gotGlobalRequest(requestType, rest)
49
 
        if ret:
50
 
            reply = MSG_REQUEST_SUCCESS
51
 
            if type(ret) in (types.TupleType, types.ListType):
52
 
                data = ret[1]
53
 
        else:
54
 
            reply = MSG_REQUEST_FAILURE
55
 
        if wantReply:
56
 
            self.transport.sendPacket(reply, data)
57
 
 
58
 
    def ssh_REQUEST_SUCCESS(self, packet):
59
 
        data = packet
60
 
        log.msg('RS')
61
 
        self.deferreds['global'].pop(0).callback(data)
62
 
 
63
 
    def ssh_REQUEST_FAILURE(self, packet):
64
 
        log.msg('RF')
65
 
        self.deferreds['global'].pop(0).errback(
66
 
            error.ConchError('global request failed', packet))
67
 
 
68
 
    def ssh_CHANNEL_OPEN(self, packet):
69
 
        channelType, rest = common.getNS(packet)
70
 
        senderChannel, windowSize, maxPacket = struct.unpack('>3L', rest[: 12])
71
 
        packet = rest[12:]
72
 
        try:
73
 
            channel = self.getChannel(channelType, windowSize, maxPacket, packet)
74
 
            localChannel = self.localChannelID
75
 
            self.localChannelID+=1
76
 
            channel.id = localChannel
77
 
            self.channels[localChannel] = channel
78
 
            self.channelsToRemoteChannel[channel] = senderChannel
79
 
            self.localToRemoteChannel[localChannel] = senderChannel
80
 
            self.transport.sendPacket(MSG_CHANNEL_OPEN_CONFIRMATION, 
81
 
                struct.pack('>4L', senderChannel, localChannel, 
82
 
                    channel.localWindowSize, 
83
 
                    channel.localMaxPacket)+channel.specificData)
84
 
            log.callWithLogger(channel, channel.channelOpen, '')
85
 
        except Exception, e:
86
 
            log.msg('channel open failed')
87
 
            log.err(e)
88
 
            if isinstance(e, error.ConchError):
89
 
                reason, textualInfo = e.args[0], e.data
90
 
            else:
91
 
                reason = OPEN_CONNECT_FAILED
92
 
                textualInfo = "unknown failure"
93
 
            self.transport.sendPacket(MSG_CHANNEL_OPEN_FAILURE, 
94
 
                                struct.pack('>2L', senderChannel, reason)+ \
95
 
                               common.NS(textualInfo)+common.NS(''))
96
 
 
97
 
    def ssh_CHANNEL_OPEN_CONFIRMATION(self, packet):
98
 
        localChannel, remoteChannel, windowSize, maxPacket = struct.unpack('>4L', packet[: 16])
99
 
        specificData = packet[16:]
100
 
        channel = self.channels[localChannel]
101
 
        channel.conn = self
102
 
        self.localToRemoteChannel[localChannel] = remoteChannel
103
 
        self.channelsToRemoteChannel[channel] = remoteChannel
104
 
        channel.remoteWindowLeft = windowSize
105
 
        channel.remoteMaxPacket = maxPacket
106
 
        log.callWithLogger(channel, channel.channelOpen, specificData)
107
 
 
108
 
    def ssh_CHANNEL_OPEN_FAILURE(self, packet):
109
 
        localChannel, reasonCode = struct.unpack('>2L', packet[: 8])
110
 
        reasonDesc = common.getNS(packet[8:])[0]
111
 
        channel = self.channels[localChannel]
112
 
        del self.channels[localChannel]
113
 
        channel.conn = self
114
 
        reason = error.ConchError(reasonDesc, reasonCode)
115
 
        log.callWithLogger(channel, channel.openFailed, reason)
116
 
 
117
 
    def ssh_CHANNEL_WINDOW_ADJUST(self, packet):
118
 
        localChannel, bytesToAdd = struct.unpack('>2L', packet[: 8])
119
 
        channel = self.channels[localChannel]
120
 
        log.callWithLogger(channel, channel.addWindowBytes, bytesToAdd)
121
 
 
122
 
    def ssh_CHANNEL_DATA(self, packet):
123
 
        localChannel, dataLength = struct.unpack('>2L', packet[: 8])
124
 
        channel = self.channels[localChannel]
125
 
        # XXX should this move to dataReceived to put client in charge?
126
 
        if dataLength > channel.localWindowLeft or \
127
 
           dataLength > channel.localMaxPacket: # more data than we want
128
 
            log.callWithLogger(channel, lambda s=self,c=channel: 
129
 
                                log.msg('too much data') and s.sendClose(c))
130
 
            return
131
 
            #packet = packet[:channel.localWindowLeft+4]
132
 
        data = common.getNS(packet[4:])[0]
133
 
        channel.localWindowLeft-=dataLength
134
 
        if channel.localWindowLeft < channel.localWindowSize/2:
135
 
            self.adjustWindow(channel, channel.localWindowSize - \
136
 
                                       channel.localWindowLeft)
137
 
            #log.msg('local window left: %s/%s' % (channel.localWindowLeft,
138
 
            #                                    channel.localWindowSize))
139
 
        log.callWithLogger(channel, channel.dataReceived, data)
140
 
 
141
 
    def ssh_CHANNEL_EXTENDED_DATA(self, packet):
142
 
        localChannel, typeCode, dataLength = struct.unpack('>3L', packet[: 12])
143
 
        channel = self.channels[localChannel]
144
 
        if dataLength > channel.localWindowLeft or \
145
 
           dataLength > channel.localMaxPacket:
146
 
            log.callWithLogger(channel, lambda s=self,c=channel: 
147
 
                                log.msg('too much extdata') and s.sendClose(c))
148
 
            return
149
 
        data = common.getNS(packet[8:])[0]
150
 
        channel.localWindowLeft -= dataLength
151
 
        if channel.localWindowLeft < channel.localWindowSize/2:
152
 
            self.adjustWindow(channel, channel.localWindowSize - \
153
 
                                       channel.localWindowLeft)
154
 
        log.callWithLogger(channel, channel.extReceived, typeCode, data)
155
 
 
156
 
    def ssh_CHANNEL_EOF(self, packet):
157
 
        localChannel = struct.unpack('>L', packet[: 4])[0]
158
 
        channel = self.channels[localChannel]
159
 
        log.callWithLogger(channel, channel.eofReceived)
160
 
 
161
 
    def ssh_CHANNEL_CLOSE(self, packet):
162
 
        localChannel = struct.unpack('>L', packet[: 4])[0]
163
 
        channel = self.channels[localChannel]
164
 
        if channel.remoteClosed:
165
 
            return
166
 
        log.callWithLogger(channel, channel.closeReceived)
167
 
        channel.remoteClosed = 1
168
 
        if channel.localClosed and channel.remoteClosed:
169
 
            self.channelClosed(channel)
170
 
 
171
 
    def ssh_CHANNEL_REQUEST(self, packet):
172
 
        localChannel = struct.unpack('>L', packet[: 4])[0]
173
 
        requestType, rest = common.getNS(packet[4:])
174
 
        wantReply = ord(rest[0])
175
 
        channel = self.channels[localChannel]
176
 
        d = log.callWithLogger(channel, channel.requestReceived, requestType, rest[1:])
177
 
        if wantReply:
178
 
            if isinstance(d, defer.Deferred):
179
 
                d.addCallback(self._cbChannelRequest, localChannel)
180
 
                d.addErrback(self._ebChannelRequest, localChannel)
181
 
            elif d:
182
 
                self._cbChannelRequest(None, localChannel)
183
 
            else:
184
 
                self._ebChannelRequest(None, localChannel)
185
 
 
186
 
    def _cbChannelRequest(self, result, localChannel):
187
 
            self.transport.sendPacket(MSG_CHANNEL_SUCCESS, struct.pack('>L', 
188
 
                                    self.localToRemoteChannel[localChannel]))
189
 
 
190
 
    def _ebChannelRequest(self, result, localChannel):
191
 
            self.transport.sendPacket(MSG_CHANNEL_FAILURE, struct.pack('>L', 
192
 
                                    self.localToRemoteChannel[localChannel]))
193
 
 
194
 
    def ssh_CHANNEL_SUCCESS(self, packet):
195
 
        localChannel = struct.unpack('>L', packet[: 4])[0]
196
 
        if self.deferreds.get(localChannel):
197
 
            d = self.deferreds[localChannel].pop(0)
198
 
            log.callWithLogger(self.channels[localChannel], 
199
 
                               d.callback, packet[4:])
200
 
 
201
 
    def ssh_CHANNEL_FAILURE(self, packet):
202
 
        localChannel = struct.unpack('>L', packet[: 4])[0]
203
 
        if self.deferreds.get(localChannel):
204
 
            d = self.deferreds[localChannel].pop(0)
205
 
            log.callWithLogger(self.channels[localChannel],
206
 
                               d.errback, 
207
 
                               error.ConchError('channel request failed'))
208
 
 
209
 
    # methods for users of the connection to call
210
 
 
211
 
    def sendGlobalRequest(self, request, data, wantReply = 0):
212
 
        """
213
 
        Send a global request for this connection.  Current this is only used
214
 
        for remote->local TCP forwarding.
215
 
 
216
 
        @type request:      C{str}
217
 
        @type data:         C{str}
218
 
        @type wantReply:    C{bool}
219
 
        @rtype              C{Deferred}/C{None}
220
 
        """
221
 
        self.transport.sendPacket(MSG_GLOBAL_REQUEST,
222
 
                                  common.NS(request)
223
 
                                  + (wantReply and '\xff' or '\x00')
224
 
                                  + data)
225
 
        if wantReply:
226
 
            d = defer.Deferred()
227
 
            self.deferreds.setdefault('global', []).append(d)
228
 
            return d
229
 
 
230
 
    def openChannel(self, channel, extra = ''):
231
 
        """
232
 
        Open a new channel on this connection.
233
 
 
234
 
        @type channel:  subclass of C{SSHChannel}
235
 
        @type extra:    C{str}
236
 
        """
237
 
        log.msg('opening channel %s with %s %s'%(self.localChannelID, 
238
 
                channel.localWindowSize, channel.localMaxPacket))
239
 
        self.transport.sendPacket(MSG_CHANNEL_OPEN, common.NS(channel.name)
240
 
                    +struct.pack('>3L', self.localChannelID, 
241
 
                    channel.localWindowSize, channel.localMaxPacket)
242
 
                    +extra)
243
 
        channel.id = self.localChannelID
244
 
        self.channels[self.localChannelID] = channel
245
 
        self.localChannelID+=1
246
 
 
247
 
    def sendRequest(self, channel, requestType, data, wantReply = 0):
248
 
        """
249
 
        Send a request to a channel.
250
 
 
251
 
        @type channel:      subclass of C{SSHChannel}
252
 
        @type requestType:  C{str}
253
 
        @type data:         C{str}
254
 
        @type wantReply:    C{bool}
255
 
        @rtype              C{Deferred}/C{None}
256
 
        """
257
 
        if channel.localClosed:
258
 
            return
259
 
        log.msg('sending request %s' % requestType)
260
 
        self.transport.sendPacket(MSG_CHANNEL_REQUEST, struct.pack('>L', 
261
 
                                    self.channelsToRemoteChannel[channel])
262
 
                                  + common.NS(requestType)+chr(wantReply)
263
 
                                  + data)
264
 
        if wantReply:
265
 
            d = defer.Deferred()
266
 
            self.deferreds.setdefault(channel.id, []).append(d)
267
 
            return d
268
 
 
269
 
    def adjustWindow(self, channel, bytesToAdd):
270
 
        """
271
 
        Tell the other side that we will receive more data.  This should not
272
 
        normally need to be called as it is managed automatically.
273
 
 
274
 
        @type channel:      subclass of L{SSHChannel}
275
 
        @type bytesToAdd:   C{int}
276
 
        """
277
 
        if channel.localClosed:
278
 
            return # we're already closed
279
 
        self.transport.sendPacket(MSG_CHANNEL_WINDOW_ADJUST, struct.pack('>2L', 
280
 
                                    self.channelsToRemoteChannel[channel], 
281
 
                                    bytesToAdd))
282
 
        log.msg('adding %i to %i in channel %i' % (bytesToAdd, channel.localWindowLeft, channel.id))
283
 
        channel.localWindowLeft+=bytesToAdd
284
 
 
285
 
    def sendData(self, channel, data):
286
 
        """
287
 
        Send data to a channel.  This should not normally be used: instead use
288
 
        channel.write(data) as it manages the window automatically.
289
 
 
290
 
        @type channel:  subclass of L{SSHChannel}
291
 
        @type data:     C{str}
292
 
        """
293
 
        if channel.localClosed:
294
 
            return # we're already closed
295
 
        self.transport.sendPacket(MSG_CHANNEL_DATA, struct.pack('>L', 
296
 
                                    self.channelsToRemoteChannel[channel])+ \
297
 
                                   common.NS(data))
298
 
 
299
 
    def sendExtendedData(self, channel, dataType, data):
300
 
        """
301
 
        Send extended data to a channel.  This should not normally be used:
302
 
        instead use channel.writeExtendedData(data, dataType) as it manages
303
 
        the window automatically.
304
 
 
305
 
        @type channel:  subclass of L{SSHChannel}
306
 
        @type dataType: C{int}
307
 
        @type data:     C{str}
308
 
        """
309
 
        if channel.localClosed:
310
 
            return # we're already closed
311
 
        self.transport.sendPacket(MSG_CHANNEL_EXTENDED_DATA, struct.pack('>2L', 
312
 
                            self.channelsToRemoteChannel[channel],dataType) \
313
 
                            + common.NS(data))
314
 
 
315
 
    def sendEOF(self, channel):
316
 
        """
317
 
        Send an EOF (End of File) for a channel.
318
 
 
319
 
        @type channel:  subclass of L{SSHChannel}
320
 
        """
321
 
        if channel.localClosed:
322
 
            return # we're already closed
323
 
        log.msg('sending eof')
324
 
        self.transport.sendPacket(MSG_CHANNEL_EOF, struct.pack('>L', 
325
 
                                    self.channelsToRemoteChannel[channel]))
326
 
 
327
 
    def sendClose(self, channel):
328
 
        """
329
 
        Close a channel.
330
 
 
331
 
        @type channel:  subclass of L{SSHChannel}
332
 
        """
333
 
        if channel.localClosed:
334
 
            return # we're already closed
335
 
        log.msg('sending close %i' % channel.id)
336
 
        self.transport.sendPacket(MSG_CHANNEL_CLOSE, struct.pack('>L', 
337
 
                                    self.channelsToRemoteChannel[channel]))
338
 
        channel.localClosed = 1
339
 
        if channel.localClosed and channel.remoteClosed:
340
 
            self.channelClosed(channel)
341
 
 
342
 
    # methods to override
343
 
    def getChannel(self, channelType, windowSize, maxPacket, data):
344
 
        """
345
 
        The other side requested a channel of some sort.
346
 
        channelType is the type of channel being requested,
347
 
        windowSize is the initial size of the remote window,
348
 
        maxPacket is the largest packet we should send,
349
 
        data is any other packet data (often nothing).
350
 
 
351
 
        We return a subclass of L{SSHChannel}.
352
 
 
353
 
        By default, this dispatches to a method 'channel_channelType' with any
354
 
        non-alphanumerics in the channelType replace with _'s.  If it cannot 
355
 
        find a suitable method, it returns an OPEN_UNKNOWN_CHANNEL_TYPE error. 
356
 
        The method is called with arguments of windowSize, maxPacket, data.
357
 
 
358
 
        @type channelType:  C{str}
359
 
        @type windowSize:   C{int}
360
 
        @type maxPacket:    C{int}
361
 
        @type data:         C{str}
362
 
        @rtype:             subclass of L{SSHChannel}/C{tuple}
363
 
        """
364
 
        log.msg('got channel %s request' % channelType)
365
 
        if hasattr(self.transport, "avatar"): # this is a server!
366
 
            chan = self.transport.avatar.lookupChannel(channelType, 
367
 
                                                       windowSize, 
368
 
                                                       maxPacket, 
369
 
                                                       data)
370
 
            chan.conn = self
371
 
            return chan
372
 
        else:
373
 
            channelType = channelType.translate(TRANSLATE_TABLE)
374
 
            f = getattr(self, 'channel_%s' % channelType, None)
375
 
            if not f:
376
 
                return OPEN_UNKNOWN_CHANNEL_TYPE, "don't know that channel"
377
 
            return f(windowSize, maxPacket, data)
378
 
 
379
 
    def gotGlobalRequest(self, requestType, data):
380
 
        """
381
 
        We got a global request.  pretty much, this is just used by the client
382
 
        to request that we forward a port from the server to the client.
383
 
        Returns either:
384
 
            - 1: request accepted
385
 
            - 1, <data>: request accepted with request specific data
386
 
            - 0: request denied
387
 
 
388
 
        By default, this dispatches to a method 'global_requestType' with
389
 
        -'s in requestType replaced with _'s.  The found method is passed data.
390
 
        If this method cannot be found, this method returns 0.  Otherwise, it 
391
 
        returns the return value of that method.
392
 
 
393
 
        @type requestType:  C{str}
394
 
        @type data:         C{str}
395
 
        @rtype:             C{int}/C{tuple}
396
 
        """
397
 
        log.msg('got global %s request' % requestType)
398
 
        if hasattr(self.transport, 'avatar'): # this is a server!
399
 
            return self.transport.avatar.gotGlobalRequest(requestType, data)
400
 
 
401
 
        requestType = requestType.replace('-','_')
402
 
        f = getattr(self, 'global_%s' % requestType, None)
403
 
        if not f:
404
 
            return 0
405
 
        return f(data)
406
 
 
407
 
    def channelClosed(self, channel):
408
 
        """
409
 
        Called when a channel is closed.
410
 
        It clears the local state related to the channel, and calls 
411
 
        channel.closed().
412
 
        MAKE SURE YOU CALL THIS METHOD, even if you subclass L{SSHConnection}.
413
 
        If you don't, things will break mysteriously.
414
 
        """
415
 
        channel.localClosed = channel.remoteClosed = 1
416
 
        del self.localToRemoteChannel[channel.id]
417
 
        del self.channels[channel.id]
418
 
        del self.channelsToRemoteChannel[channel]
419
 
        self.deferreds[channel.id] = []
420
 
        log.callWithLogger(channel, channel.closed)
421
 
 
422
 
MSG_GLOBAL_REQUEST = 80
423
 
MSG_REQUEST_SUCCESS = 81
424
 
MSG_REQUEST_FAILURE = 82
425
 
MSG_CHANNEL_OPEN = 90
426
 
MSG_CHANNEL_OPEN_CONFIRMATION = 91
427
 
MSG_CHANNEL_OPEN_FAILURE = 92
428
 
MSG_CHANNEL_WINDOW_ADJUST = 93
429
 
MSG_CHANNEL_DATA = 94
430
 
MSG_CHANNEL_EXTENDED_DATA = 95
431
 
MSG_CHANNEL_EOF = 96
432
 
MSG_CHANNEL_CLOSE = 97
433
 
MSG_CHANNEL_REQUEST = 98
434
 
MSG_CHANNEL_SUCCESS = 99
435
 
MSG_CHANNEL_FAILURE = 100
436
 
 
437
 
OPEN_ADMINISTRATIVELY_PROHIBITED = 1
438
 
OPEN_CONNECT_FAILED = 2
439
 
OPEN_UNKNOWN_CHANNEL_TYPE = 3
440
 
OPEN_RESOURCE_SHORTAGE = 4
441
 
 
442
 
EXTENDED_DATA_STDERR = 1
443
 
 
444
 
messages = {}
445
 
import connection
446
 
for v in dir(connection):
447
 
    if v[: 4] == 'MSG_':
448
 
        messages[getattr(connection, v)] = v # doesn't handle doubles
449
 
 
450
 
import string
451
 
alphanums = string.letters + string.digits
452
 
TRANSLATE_TABLE = ''.join([chr(i) in alphanums and chr(i) or '_' for i in range(256)])
453
 
SSHConnection.protocolMessages = messages