~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/conch/scripts/conch.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.conch.test.test_conch -*-
 
2
#
 
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
 
 
6
#
 
7
# $Id: conch.py,v 1.65 2004/03/11 00:29:14 z3p Exp $
 
8
 
 
9
#""" Implementation module for the `conch` command.
 
10
#"""
 
11
from twisted.conch.client import connect, default, options
 
12
from twisted.conch.error import ConchError
 
13
from twisted.conch.ssh import connection, common
 
14
from twisted.conch.ssh import session, forwarding, channel
 
15
from twisted.internet import reactor, stdio, task
 
16
from twisted.python import log, usage
 
17
 
 
18
import os, sys, getpass, struct, tty, fcntl, signal
 
19
 
 
20
class ClientOptions(options.ConchOptions):
 
21
 
 
22
    synopsis = """Usage:   conch [options] host [command]
 
23
"""
 
24
    longdesc = ("conch is a SSHv2 client that allows logging into a remote "
 
25
                "machine and executing commands.")
 
26
 
 
27
    optParameters = [['escape', 'e', '~'],
 
28
                      ['localforward', 'L', None, 'listen-port:host:port   Forward local port to remote address'],
 
29
                      ['remoteforward', 'R', None, 'listen-port:host:port   Forward remote port to local address'],
 
30
                     ]
 
31
 
 
32
    optFlags = [['null', 'n', 'Redirect input from /dev/null.'],
 
33
                 ['fork', 'f', 'Fork to background after authentication.'],
 
34
                 ['tty', 't', 'Tty; allocate a tty even if command is given.'],
 
35
                 ['notty', 'T', 'Do not allocate a tty.'],
 
36
                 ['noshell', 'N', 'Do not execute a shell or command.'],
 
37
                 ['subsystem', 's', 'Invoke command (mandatory) as SSH2 subsystem.'],
 
38
                ]
 
39
 
 
40
    #zsh_altArgDescr = {"foo":"use this description for foo instead"}
 
41
    #zsh_multiUse = ["foo", "bar"]
 
42
    #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
 
43
    #zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)"}
 
44
    zsh_actionDescr = {"localforward":"listen-port:host:port",
 
45
                       "remoteforward":"listen-port:host:port"}
 
46
    zsh_extras = ["*:command: "]
 
47
 
 
48
    localForwards = []
 
49
    remoteForwards = []
 
50
 
 
51
    def opt_escape(self, esc):
 
52
        "Set escape character; ``none'' = disable"
 
53
        if esc == 'none':
 
54
            self['escape'] = None
 
55
        elif esc[0] == '^' and len(esc) == 2:
 
56
            self['escape'] = chr(ord(esc[1])-64)
 
57
        elif len(esc) == 1:
 
58
            self['escape'] = esc
 
59
        else:
 
60
            sys.exit("Bad escape character '%s'." % esc)
 
61
 
 
62
    def opt_localforward(self, f):
 
63
        "Forward local port to remote address (lport:host:port)"
 
64
        localPort, remoteHost, remotePort = f.split(':') # doesn't do v6 yet
 
65
        localPort = int(localPort)
 
66
        remotePort = int(remotePort)
 
67
        self.localForwards.append((localPort, (remoteHost, remotePort)))
 
68
 
 
69
    def opt_remoteforward(self, f):
 
70
        """Forward remote port to local address (rport:host:port)"""
 
71
        remotePort, connHost, connPort = f.split(':') # doesn't do v6 yet
 
72
        remotePort = int(remotePort)
 
73
        connPort = int(connPort)
 
74
        self.remoteForwards.append((remotePort, (connHost, connPort)))
 
75
 
 
76
    def parseArgs(self, host, *command):
 
77
        self['host'] = host
 
78
        self['command'] = ' '.join(command)
 
79
 
 
80
# Rest of code in "run"
 
81
options = None
 
82
conn = None
 
83
exitStatus = 0
 
84
old = None
 
85
_inRawMode = 0
 
86
_savedRawMode = None
 
87
 
 
88
def run():
 
89
    global options, old
 
90
    args = sys.argv[1:]
 
91
    if '-l' in args: # cvs is an idiot
 
92
        i = args.index('-l')
 
93
        args = args[i:i+2]+args
 
94
        del args[i+2:i+4]
 
95
    for arg in args[:]:
 
96
        try:
 
97
            i = args.index(arg)
 
98
            if arg[:2] == '-o' and args[i+1][0]!='-':
 
99
                args[i:i+2] = [] # suck on it scp
 
100
        except ValueError:
 
101
            pass
 
102
    options = ClientOptions()
 
103
    try:
 
104
        options.parseOptions(args)
 
105
    except usage.UsageError, u:
 
106
        print 'ERROR: %s' % u
 
107
        options.opt_help()
 
108
        sys.exit(1)
 
109
    if options['log']:
 
110
        if options['logfile']:
 
111
            if options['logfile'] == '-':
 
112
                f = sys.stdout
 
113
            else:
 
114
                f = file(options['logfile'], 'a+')
 
115
        else:
 
116
            f = sys.stderr
 
117
        realout = sys.stdout
 
118
        log.startLogging(f)
 
119
        sys.stdout = realout
 
120
    else:
 
121
        log.discardLogs()
 
122
    doConnect()
 
123
    fd = sys.stdin.fileno()
 
124
    try:
 
125
        old = tty.tcgetattr(fd)
 
126
    except:
 
127
        old = None
 
128
    try:
 
129
        oldUSR1 = signal.signal(signal.SIGUSR1, lambda *a: reactor.callLater(0, reConnect))
 
130
    except:
 
131
        oldUSR1 = None
 
132
    try:
 
133
        reactor.run()
 
134
    finally:
 
135
        if old:
 
136
            tty.tcsetattr(fd, tty.TCSANOW, old)
 
137
        if oldUSR1:
 
138
            signal.signal(signal.SIGUSR1, oldUSR1)
 
139
        if (options['command'] and options['tty']) or not options['notty']:
 
140
            signal.signal(signal.SIGWINCH, signal.SIG_DFL)
 
141
    if sys.stdout.isatty() and not options['command']:
 
142
        print 'Connection to %s closed.' % options['host']
 
143
    sys.exit(exitStatus)
 
144
 
 
145
def handleError():
 
146
    from twisted.python import failure
 
147
    global exitStatus
 
148
    exitStatus = 2
 
149
    reactor.callLater(0.01, _stopReactor)
 
150
    log.err(failure.Failure())
 
151
    raise
 
152
 
 
153
def _stopReactor():
 
154
    try:
 
155
        reactor.stop()
 
156
    except: pass
 
157
 
 
158
def doConnect():
 
159
#    log.deferr = handleError # HACK
 
160
    if '@' in options['host']:
 
161
        options['user'], options['host'] = options['host'].split('@',1)
 
162
    if not options.identitys:
 
163
        options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
 
164
    host = options['host']
 
165
    if not options['user']:
 
166
        options['user'] = getpass.getuser()
 
167
    if not options['port']:
 
168
        options['port'] = 22
 
169
    else:
 
170
        options['port'] = int(options['port'])
 
171
    host = options['host']
 
172
    port = options['port']
 
173
    vhk = default.verifyHostKey
 
174
    uao = default.SSHUserAuthClient(options['user'], options, SSHConnection())
 
175
    connect.connect(host, port, options, vhk, uao).addErrback(_ebExit)
 
176
 
 
177
def _ebExit(f):
 
178
    global exitStatus
 
179
    if hasattr(f.value, 'value'):
 
180
        s = f.value.value
 
181
    else:
 
182
        s = str(f)
 
183
    exitStatus = "conch: exiting with error %s" % f
 
184
    reactor.callLater(0.1, _stopReactor)
 
185
 
 
186
def onConnect():
 
187
#    if keyAgent and options['agent']:
 
188
#        cc = protocol.ClientCreator(reactor, SSHAgentForwardingLocal, conn)
 
189
#        cc.connectUNIX(os.environ['SSH_AUTH_SOCK'])
 
190
    if hasattr(conn.transport, 'sendIgnore'):
 
191
        _KeepAlive(conn)
 
192
    if options.localForwards:
 
193
        for localPort, hostport in options.localForwards:
 
194
            s = reactor.listenTCP(localPort,
 
195
                        forwarding.SSHListenForwardingFactory(conn,
 
196
                            hostport,
 
197
                            SSHListenClientForwardingChannel))
 
198
            conn.localForwards.append(s)
 
199
    if options.remoteForwards:
 
200
        for remotePort, hostport in options.remoteForwards:
 
201
            log.msg('asking for remote forwarding for %s:%s' %
 
202
                    (remotePort, hostport))
 
203
            conn.requestRemoteForwarding(remotePort, hostport)
 
204
        reactor.addSystemEventTrigger('before', 'shutdown', beforeShutdown)
 
205
    if not options['noshell'] or options['agent']:
 
206
        conn.openChannel(SSHSession())
 
207
    if options['fork']:
 
208
        if os.fork():
 
209
            os._exit(0)
 
210
        os.setsid()
 
211
        for i in range(3):
 
212
            try:
 
213
                os.close(i)
 
214
            except OSError, e:
 
215
                import errno
 
216
                if e.errno != errno.EBADF:
 
217
                    raise
 
218
 
 
219
def reConnect():
 
220
    beforeShutdown()
 
221
    conn.transport.transport.loseConnection()
 
222
 
 
223
def beforeShutdown():
 
224
    remoteForwards = options.remoteForwards
 
225
    for remotePort, hostport in remoteForwards:
 
226
        log.msg('cancelling %s:%s' % (remotePort, hostport))
 
227
        conn.cancelRemoteForwarding(remotePort)
 
228
 
 
229
def stopConnection():
 
230
    if not options['reconnect']:
 
231
        reactor.callLater(0.1, _stopReactor)
 
232
 
 
233
class _KeepAlive:
 
234
 
 
235
    def __init__(self, conn):
 
236
        self.conn = conn
 
237
        self.globalTimeout = None
 
238
        self.lc = task.LoopingCall(self.sendGlobal)
 
239
        self.lc.start(300)
 
240
 
 
241
    def sendGlobal(self):
 
242
        d = self.conn.sendGlobalRequest("conch-keep-alive@twistedmatrix.com",
 
243
                "", wantReply = 1)
 
244
        d.addBoth(self._cbGlobal)
 
245
        self.globalTimeout = reactor.callLater(30, self._ebGlobal)
 
246
 
 
247
    def _cbGlobal(self, res):
 
248
        if self.globalTimeout:
 
249
            self.globalTimeout.cancel()
 
250
            self.globalTimeout = None
 
251
 
 
252
    def _ebGlobal(self):
 
253
        if self.globalTimeout:
 
254
            self.globalTimeout = None
 
255
            self.conn.transport.loseConnection()
 
256
 
 
257
class SSHConnection(connection.SSHConnection):
 
258
    def serviceStarted(self):
 
259
        global conn
 
260
        conn = self
 
261
        self.localForwards = []
 
262
        self.remoteForwards = {}
 
263
        if not isinstance(self, connection.SSHConnection):
 
264
            # make these fall through
 
265
            del self.__class__.requestRemoteForwarding
 
266
            del self.__class__.cancelRemoteForwarding
 
267
        onConnect()
 
268
 
 
269
    def serviceStopped(self):
 
270
        lf = self.localForwards
 
271
        self.localForwards = []
 
272
        for s in lf:
 
273
            s.loseConnection()
 
274
        stopConnection()
 
275
 
 
276
    def requestRemoteForwarding(self, remotePort, hostport):
 
277
        data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort))
 
278
        d = self.sendGlobalRequest('tcpip-forward', data,
 
279
                                   wantReply=1)
 
280
        log.msg('requesting remote forwarding %s:%s' %(remotePort, hostport))
 
281
        d.addCallback(self._cbRemoteForwarding, remotePort, hostport)
 
282
        d.addErrback(self._ebRemoteForwarding, remotePort, hostport)
 
283
 
 
284
    def _cbRemoteForwarding(self, result, remotePort, hostport):
 
285
        log.msg('accepted remote forwarding %s:%s' % (remotePort, hostport))
 
286
        self.remoteForwards[remotePort] = hostport
 
287
        log.msg(repr(self.remoteForwards))
 
288
 
 
289
    def _ebRemoteForwarding(self, f, remotePort, hostport):
 
290
        log.msg('remote forwarding %s:%s failed' % (remotePort, hostport))
 
291
        log.msg(f)
 
292
 
 
293
    def cancelRemoteForwarding(self, remotePort):
 
294
        data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort))
 
295
        self.sendGlobalRequest('cancel-tcpip-forward', data)
 
296
        log.msg('cancelling remote forwarding %s' % remotePort)
 
297
        try:
 
298
            del self.remoteForwards[remotePort]
 
299
        except:
 
300
            pass
 
301
        log.msg(repr(self.remoteForwards))
 
302
 
 
303
    def channel_forwarded_tcpip(self, windowSize, maxPacket, data):
 
304
        log.msg('%s %s' % ('FTCP', repr(data)))
 
305
        remoteHP, origHP = forwarding.unpackOpen_forwarded_tcpip(data)
 
306
        log.msg(self.remoteForwards)
 
307
        log.msg(remoteHP)
 
308
        if self.remoteForwards.has_key(remoteHP[1]):
 
309
            connectHP = self.remoteForwards[remoteHP[1]]
 
310
            log.msg('connect forwarding %s' % (connectHP,))
 
311
            return SSHConnectForwardingChannel(connectHP,
 
312
                                            remoteWindow = windowSize,
 
313
                                            remoteMaxPacket = maxPacket,
 
314
                                            conn = self)
 
315
        else:
 
316
            raise ConchError(connection.OPEN_CONNECT_FAILED, "don't know about that port")
 
317
 
 
318
#    def channel_auth_agent_openssh_com(self, windowSize, maxPacket, data):
 
319
#        if options['agent'] and keyAgent:
 
320
#            return agent.SSHAgentForwardingChannel(remoteWindow = windowSize,
 
321
#                                             remoteMaxPacket = maxPacket,
 
322
#                                             conn = self)
 
323
#        else:
 
324
#            return connection.OPEN_CONNECT_FAILED, "don't have an agent"
 
325
 
 
326
    def channelClosed(self, channel):
 
327
        log.msg('connection closing %s' % channel)
 
328
        log.msg(self.channels)
 
329
        if len(self.channels) == 1: # just us left
 
330
            log.msg('stopping connection')
 
331
            stopConnection()
 
332
        else:
 
333
            # because of the unix thing
 
334
            self.__class__.__bases__[0].channelClosed(self, channel)
 
335
 
 
336
class SSHSession(channel.SSHChannel):
 
337
 
 
338
    name = 'session'
 
339
 
 
340
    def channelOpen(self, foo):
 
341
        log.msg('session %s open' % self.id)
 
342
        if options['agent']:
 
343
            d = self.conn.sendRequest(self, 'auth-agent-req@openssh.com', '', wantReply=1)
 
344
            d.addBoth(lambda x:log.msg(x))
 
345
        if options['noshell']: return
 
346
        if (options['command'] and options['tty']) or not options['notty']:
 
347
            _enterRawMode()
 
348
        c = session.SSHSessionClient()
 
349
        if options['escape'] and not options['notty']:
 
350
            self.escapeMode = 1
 
351
            c.dataReceived = self.handleInput
 
352
        else:
 
353
            c.dataReceived = self.write
 
354
        c.connectionLost = lambda x=None,s=self:s.sendEOF()
 
355
        self.stdio = stdio.StandardIO(c)
 
356
        fd = 0
 
357
        if options['subsystem']:
 
358
            self.conn.sendRequest(self, 'subsystem', \
 
359
                common.NS(options['command']))
 
360
        elif options['command']:
 
361
            if options['tty']:
 
362
                term = os.environ['TERM']
 
363
                winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
 
364
                winSize = struct.unpack('4H', winsz)
 
365
                ptyReqData = session.packRequest_pty_req(term, winSize, '')
 
366
                self.conn.sendRequest(self, 'pty-req', ptyReqData)
 
367
                signal.signal(signal.SIGWINCH, self._windowResized)
 
368
            self.conn.sendRequest(self, 'exec', \
 
369
                common.NS(options['command']))
 
370
        else:
 
371
            if not options['notty']:
 
372
                term = os.environ['TERM']
 
373
                winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
 
374
                winSize = struct.unpack('4H', winsz)
 
375
                ptyReqData = session.packRequest_pty_req(term, winSize, '')
 
376
                self.conn.sendRequest(self, 'pty-req', ptyReqData)
 
377
                signal.signal(signal.SIGWINCH, self._windowResized)
 
378
            self.conn.sendRequest(self, 'shell', '')
 
379
            #if hasattr(conn.transport, 'transport'):
 
380
            #    conn.transport.transport.setTcpNoDelay(1)
 
381
 
 
382
    def handleInput(self, char):
 
383
        #log.msg('handling %s' % repr(char))
 
384
        if char in ('\n', '\r'):
 
385
            self.escapeMode = 1
 
386
            self.write(char)
 
387
        elif self.escapeMode == 1 and char == options['escape']:
 
388
            self.escapeMode = 2
 
389
        elif self.escapeMode == 2:
 
390
            self.escapeMode = 1 # so we can chain escapes together
 
391
            if char == '.': # disconnect
 
392
                log.msg('disconnecting from escape')
 
393
                stopConnection()
 
394
                return
 
395
            elif char == '\x1a': # ^Z, suspend
 
396
                def _():
 
397
                    _leaveRawMode()
 
398
                    sys.stdout.flush()
 
399
                    sys.stdin.flush()
 
400
                    os.kill(os.getpid(), signal.SIGTSTP)
 
401
                    _enterRawMode()
 
402
                reactor.callLater(0, _)
 
403
                return
 
404
            elif char == 'R': # rekey connection
 
405
                log.msg('rekeying connection')
 
406
                self.conn.transport.sendKexInit()
 
407
                return
 
408
            elif char == '#': # display connections
 
409
                self.stdio.write('\r\nThe following connections are open:\r\n')
 
410
                channels = self.conn.channels.keys()
 
411
                channels.sort()
 
412
                for channelId in channels:
 
413
                    self.stdio.write('  #%i %s\r\n' % (channelId, str(self.conn.channels[channelId])))
 
414
                return
 
415
            self.write('~' + char)
 
416
        else:
 
417
            self.escapeMode = 0
 
418
            self.write(char)
 
419
 
 
420
    def dataReceived(self, data):
 
421
        self.stdio.write(data)
 
422
 
 
423
    def extReceived(self, t, data):
 
424
        if t==connection.EXTENDED_DATA_STDERR:
 
425
            log.msg('got %s stderr data' % len(data))
 
426
            sys.stderr.write(data)
 
427
 
 
428
    def eofReceived(self):
 
429
        log.msg('got eof')
 
430
        self.stdio.loseWriteConnection()
 
431
 
 
432
    def closeReceived(self):
 
433
        log.msg('remote side closed %s' % self)
 
434
        self.conn.sendClose(self)
 
435
 
 
436
    def closed(self):
 
437
        global old
 
438
        log.msg('closed %s' % self)
 
439
        log.msg(repr(self.conn.channels))
 
440
 
 
441
    def request_exit_status(self, data):
 
442
        global exitStatus
 
443
        exitStatus = int(struct.unpack('>L', data)[0])
 
444
        log.msg('exit status: %s' % exitStatus)
 
445
 
 
446
    def sendEOF(self):
 
447
        self.conn.sendEOF(self)
 
448
 
 
449
    def stopWriting(self):
 
450
        self.stdio.pauseProducing()
 
451
 
 
452
    def startWriting(self):
 
453
        self.stdio.resumeProducing()
 
454
 
 
455
    def _windowResized(self, *args):
 
456
        winsz = fcntl.ioctl(0, tty.TIOCGWINSZ, '12345678')
 
457
        winSize = struct.unpack('4H', winsz)
 
458
        newSize = winSize[1], winSize[0], winSize[2], winSize[3]
 
459
        self.conn.sendRequest(self, 'window-change', struct.pack('!4L', *newSize))
 
460
 
 
461
 
 
462
class SSHListenClientForwardingChannel(forwarding.SSHListenClientForwardingChannel): pass
 
463
class SSHConnectForwardingChannel(forwarding.SSHConnectForwardingChannel): pass
 
464
 
 
465
def _leaveRawMode():
 
466
    global _inRawMode
 
467
    if not _inRawMode:
 
468
        return
 
469
    fd = sys.stdin.fileno()
 
470
    tty.tcsetattr(fd, tty.TCSANOW, _savedMode)
 
471
    _inRawMode = 0
 
472
 
 
473
def _enterRawMode():
 
474
    global _inRawMode, _savedMode
 
475
    if _inRawMode:
 
476
        return
 
477
    fd = sys.stdin.fileno()
 
478
    try:
 
479
        old = tty.tcgetattr(fd)
 
480
        new = old[:]
 
481
    except:
 
482
        log.msg('not a typewriter!')
 
483
    else:
 
484
        # iflage
 
485
        new[0] = new[0] | tty.IGNPAR
 
486
        new[0] = new[0] & ~(tty.ISTRIP | tty.INLCR | tty.IGNCR | tty.ICRNL |
 
487
                            tty.IXON | tty.IXANY | tty.IXOFF)
 
488
        if hasattr(tty, 'IUCLC'):
 
489
            new[0] = new[0] & ~tty.IUCLC
 
490
 
 
491
        # lflag
 
492
        new[3] = new[3] & ~(tty.ISIG | tty.ICANON | tty.ECHO | tty.ECHO |
 
493
                            tty.ECHOE | tty.ECHOK | tty.ECHONL)
 
494
        if hasattr(tty, 'IEXTEN'):
 
495
            new[3] = new[3] & ~tty.IEXTEN
 
496
 
 
497
        #oflag
 
498
        new[1] = new[1] & ~tty.OPOST
 
499
 
 
500
        new[6][tty.VMIN] = 1
 
501
        new[6][tty.VTIME] = 0
 
502
 
 
503
        _savedMode = old
 
504
        tty.tcsetattr(fd, tty.TCSANOW, new)
 
505
        #tty.setraw(fd)
 
506
        _inRawMode = 1
 
507
 
 
508
if __name__ == '__main__':
 
509
    run()
 
510