~landscape/zope3/newer-from-ztk

« back to all changes in this revision

Viewing changes to src/twisted/conch/scripts/tkconch.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
 
# $Id: tkconch.py,v 1.6 2003/02/22 08:10:15 z3p Exp $
6
 
 
7
 
""" Implementation module for the `tkconch` command.
8
 
"""
9
 
 
10
 
from __future__ import nested_scopes
11
 
 
12
 
import Tkinter, tkFileDialog, tkFont, tkMessageBox, string
13
 
from twisted.conch.ui import tkvt100
14
 
from twisted.conch.ssh import transport, userauth, connection, common, keys
15
 
from twisted.conch.ssh import session, forwarding, channel
16
 
from twisted.conch.client.default import isInKnownHosts
17
 
from twisted.internet import reactor, defer, protocol, tksupport
18
 
from twisted.python import usage, log
19
 
 
20
 
import os, sys, getpass, struct, base64, signal
21
 
 
22
 
class TkConchMenu(Tkinter.Frame):
23
 
    def __init__(self, *args, **params):
24
 
        ## Standard heading: initialization
25
 
        apply(Tkinter.Frame.__init__, (self,) + args, params)
26
 
 
27
 
        self.master.title('TkConch')
28
 
        self.localRemoteVar = Tkinter.StringVar()
29
 
        self.localRemoteVar.set('local')
30
 
 
31
 
        Tkinter.Label(self, anchor='w', justify='left', text='Hostname').grid(column=1, row=1, sticky='w')
32
 
        self.host = Tkinter.Entry(self)
33
 
        self.host.grid(column=2, columnspan=2, row=1, sticky='nesw')
34
 
 
35
 
        Tkinter.Label(self, anchor='w', justify='left', text='Port').grid(column=1, row=2, sticky='w')
36
 
        self.port = Tkinter.Entry(self)
37
 
        self.port.grid(column=2, columnspan=2, row=2, sticky='nesw')
38
 
 
39
 
        Tkinter.Label(self, anchor='w', justify='left', text='Username').grid(column=1, row=3, sticky='w')
40
 
        self.user = Tkinter.Entry(self)
41
 
        self.user.grid(column=2, columnspan=2, row=3, sticky='nesw')
42
 
 
43
 
        Tkinter.Label(self, anchor='w', justify='left', text='Command').grid(column=1, row=4, sticky='w')
44
 
        self.command = Tkinter.Entry(self)
45
 
        self.command.grid(column=2, columnspan=2, row=4, sticky='nesw')
46
 
 
47
 
        Tkinter.Label(self, anchor='w', justify='left', text='Identity').grid(column=1, row=5, sticky='w')
48
 
        self.identity = Tkinter.Entry(self)
49
 
        self.identity.grid(column=2, row=5, sticky='nesw')
50
 
        Tkinter.Button(self, command=self.getIdentityFile, text='Browse').grid(column=3, row=5, sticky='nesw')
51
 
 
52
 
        Tkinter.Label(self, text='Port Forwarding').grid(column=1, row=6, sticky='w')
53
 
        self.forwards = Tkinter.Listbox(self, height=0, width=0)
54
 
        self.forwards.grid(column=2, columnspan=2, row=6, sticky='nesw')
55
 
        Tkinter.Button(self, text='Add', command=self.addForward).grid(column=1, row=7)
56
 
        Tkinter.Button(self, text='Remove', command=self.removeForward).grid(column=1, row=8)
57
 
        self.forwardPort = Tkinter.Entry(self)
58
 
        self.forwardPort.grid(column=2, row=7, sticky='nesw')
59
 
        Tkinter.Label(self, text='Port').grid(column=3, row=7, sticky='nesw')
60
 
        self.forwardHost = Tkinter.Entry(self)
61
 
        self.forwardHost.grid(column=2, row=8, sticky='nesw')
62
 
        Tkinter.Label(self, text='Host').grid(column=3, row=8, sticky='nesw')
63
 
        self.localForward = Tkinter.Radiobutton(self, text='Local', variable=self.localRemoteVar, value='local')
64
 
        self.localForward.grid(column=2, row=9)
65
 
        self.remoteForward = Tkinter.Radiobutton(self, text='Remote', variable=self.localRemoteVar, value='remote')
66
 
        self.remoteForward.grid(column=3, row=9)
67
 
 
68
 
        Tkinter.Label(self, text='Advanced Options').grid(column=1, columnspan=3, row=10, sticky='nesw')
69
 
 
70
 
        Tkinter.Label(self, anchor='w', justify='left', text='Cipher').grid(column=1, row=11, sticky='w')
71
 
        self.cipher = Tkinter.Entry(self, name='cipher')
72
 
        self.cipher.grid(column=2, columnspan=2, row=11, sticky='nesw')
73
 
 
74
 
        Tkinter.Label(self, anchor='w', justify='left', text='MAC').grid(column=1, row=12, sticky='w')
75
 
        self.mac = Tkinter.Entry(self, name='mac')
76
 
        self.mac.grid(column=2, columnspan=2, row=12, sticky='nesw')
77
 
 
78
 
        Tkinter.Label(self, anchor='w', justify='left', text='Escape Char').grid(column=1, row=13, sticky='w')
79
 
        self.escape = Tkinter.Entry(self, name='escape')
80
 
        self.escape.grid(column=2, columnspan=2, row=13, sticky='nesw')
81
 
        Tkinter.Button(self, text='Connect!', command=self.doConnect).grid(column=1, columnspan=3, row=14, sticky='nesw')
82
 
 
83
 
        # Resize behavior(s)
84
 
        self.grid_rowconfigure(6, weight=1, minsize=64)
85
 
        self.grid_columnconfigure(2, weight=1, minsize=2)
86
 
 
87
 
        self.master.protocol("WM_DELETE_WINDOW", sys.exit)
88
 
        
89
 
 
90
 
    def getIdentityFile(self):
91
 
        r = tkFileDialog.askopenfilename()
92
 
        if r:
93
 
            self.identity.delete(0, Tkinter.END)
94
 
            self.identity.insert(Tkinter.END, r)
95
 
 
96
 
    def addForward(self):
97
 
        port = self.forwardPort.get()
98
 
        self.forwardPort.delete(0, Tkinter.END)
99
 
        host = self.forwardHost.get()
100
 
        self.forwardHost.delete(0, Tkinter.END)
101
 
        if self.localRemoteVar.get() == 'local':
102
 
            self.forwards.insert(Tkinter.END, 'L:%s:%s' % (port, host))
103
 
        else:
104
 
            self.forwards.insert(Tkinter.END, 'R:%s:%s' % (port, host))
105
 
 
106
 
    def removeForward(self):
107
 
        cur = self.forwards.curselection()
108
 
        if cur:
109
 
            self.forwards.remove(cur[0])
110
 
 
111
 
    def doConnect(self):
112
 
        finished = 1
113
 
        options['host'] = self.host.get()
114
 
        options['port'] = self.port.get()
115
 
        options['user'] = self.user.get()
116
 
        options['command'] = self.command.get()
117
 
        cipher = self.cipher.get()
118
 
        mac = self.mac.get()
119
 
        escape = self.escape.get()
120
 
        if cipher:
121
 
            if cipher in SSHClientTransport.supportedCiphers:
122
 
                SSHClientTransport.supportedCiphers = [cipher]
123
 
            else:
124
 
                tkMessageBox.showerror('TkConch', 'Bad cipher.')
125
 
                finished = 0
126
 
 
127
 
        if mac:
128
 
            if mac in SSHClientTransport.supportedMACs:
129
 
                SSHClientTransport.supportedMACs = [mac]
130
 
            elif finished:
131
 
                tkMessageBox.showerror('TkConch', 'Bad MAC.')
132
 
                finished = 0
133
 
 
134
 
        if escape:
135
 
            if escape == 'none':
136
 
                options['escape'] = None
137
 
            elif escape[0] == '^' and len(escape) == 2:
138
 
                options['escape'] = chr(ord(escape[1])-64)
139
 
            elif len(escape) == 1:
140
 
                options['escape'] = escape
141
 
            elif finished:
142
 
                tkMessageBox.showerror('TkConch', "Bad escape character '%s'." % escape)
143
 
                finished = 0
144
 
 
145
 
        if self.identity.get():
146
 
            options.identitys.append(self.identity.get())
147
 
 
148
 
        for line in self.forwards.get(0,Tkinter.END):
149
 
            if line[0]=='L':
150
 
                options.opt_localforward(line[2:])
151
 
            else:
152
 
                options.opt_remoteforward(line[2:])
153
 
 
154
 
        if '@' in options['host']:
155
 
            options['user'], options['host'] = options['host'].split('@',1)
156
 
 
157
 
        if (not options['host'] or not options['user']) and finished:
158
 
            tkMessageBox.showerror('TkConch', 'Missing host or username.')
159
 
            finished = 0
160
 
        if finished:
161
 
            self.master.quit()
162
 
            self.master.destroy()        
163
 
            if options['log']:
164
 
                realout = sys.stdout
165
 
                log.startLogging(sys.stderr)
166
 
                sys.stdout = realout
167
 
            else:
168
 
                log.discardLogs()
169
 
            log.deferr = handleError # HACK
170
 
            if not options.identitys:
171
 
                options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
172
 
            host = options['host']
173
 
            port = int(options['port'] or 22)
174
 
            log.msg((host,port))
175
 
            reactor.connectTCP(host, port, SSHClientFactory())
176
 
            frame.master.deiconify()
177
 
            frame.master.title('%s@%s - TkConch' % (options['user'], options['host']))
178
 
        else:
179
 
            self.focus()
180
 
 
181
 
class GeneralOptions(usage.Options):
182
 
    synopsis = """Usage:    tkconch [options] host [command]
183
 
 """
184
 
 
185
 
    optParameters = [['user', 'l', None, 'Log in using this user name.'],
186
 
                    ['identity', 'i', '~/.ssh/identity', 'Identity for public key authentication'],
187
 
                    ['escape', 'e', '~', "Set escape character; ``none'' = disable"],
188
 
                    ['cipher', 'c', None, 'Select encryption algorithm.'],
189
 
                    ['macs', 'm', None, 'Specify MAC algorithms for protocol version 2.'],
190
 
                    ['port', 'p', None, 'Connect to this port.  Server must be on the same port.'],
191
 
                    ['localforward', 'L', None, 'listen-port:host:port   Forward local port to remote address'],
192
 
                    ['remoteforward', 'R', None, 'listen-port:host:port   Forward remote port to local address'],
193
 
                    ]
194
 
    
195
 
    optFlags = [['tty', 't', 'Tty; allocate a tty even if command is given.'],
196
 
                ['notty', 'T', 'Do not allocate a tty.'],
197
 
                ['version', 'V', 'Display version number only.'],
198
 
                ['compress', 'C', 'Enable compression.'],
199
 
                ['noshell', 'N', 'Do not execute a shell or command.'],
200
 
                ['subsystem', 's', 'Invoke command (mandatory) as SSH2 subsystem.'],
201
 
                ['log', 'v', 'Log to stderr'],
202
 
                ['ansilog', 'a', 'Print the receieved data to stdout']]
203
 
 
204
 
    #zsh_altArgDescr = {"foo":"use this description for foo instead"}
205
 
    #zsh_multiUse = ["foo", "bar"]
206
 
    zsh_mutuallyExclusive = [("tty", "notty")]
207
 
    zsh_actions = {"cipher":"(%s)" % " ".join(transport.SSHClientTransport.supportedCiphers),
208
 
                   "macs":"(%s)" % " ".join(transport.SSHClientTransport.supportedMACs)}
209
 
    zsh_actionDescr = {"localforward":"listen-port:host:port",
210
 
                       "remoteforward":"listen-port:host:port"}
211
 
    # user, host, or user@host completion similar to zsh's ssh completion
212
 
    zsh_extras = ['1:host | user@host:{_ssh;if compset -P "*@"; then _wanted hosts expl "remote host name" _ssh_hosts && ret=0 elif compset -S "@*"; then _wanted users expl "login name" _ssh_users -S "" && ret=0 else if (( $+opt_args[-l] )); then tmp=() else tmp=( "users:login name:_ssh_users -qS@" ) fi; _alternative "hosts:remote host name:_ssh_hosts" "$tmp[@]" && ret=0 fi}',
213
 
                  '*:command: ']
214
 
 
215
 
    identitys = []
216
 
    localForwards = []
217
 
    remoteForwards = []
218
 
 
219
 
    def opt_identity(self, i):
220
 
        self.identitys.append(i)
221
 
 
222
 
    def opt_localforward(self, f):
223
 
        localPort, remoteHost, remotePort = f.split(':') # doesn't do v6 yet
224
 
        localPort = int(localPort)
225
 
        remotePort = int(remotePort)
226
 
        self.localForwards.append((localPort, (remoteHost, remotePort)))
227
 
 
228
 
    def opt_remoteforward(self, f):
229
 
        remotePort, connHost, connPort = f.split(':') # doesn't do v6 yet
230
 
        remotePort = int(remotePort)
231
 
        connPort = int(connPort)
232
 
        self.remoteForwards.append((remotePort, (connHost, connPort)))
233
 
 
234
 
    def opt_compress(self):
235
 
        SSHClientTransport.supportedCompressions[0:1] = ['zlib']
236
 
 
237
 
    def parseArgs(self, *args):
238
 
        if args:
239
 
            self['host'] = args[0]
240
 
            self['command'] = ' '.join(args[1:])
241
 
        else:
242
 
            self['host'] = ''
243
 
            self['command'] = ''
244
 
 
245
 
# Rest of code in "run"
246
 
options = None
247
 
menu = None
248
 
exitStatus = 0
249
 
frame = None
250
 
 
251
 
def deferredAskFrame(question, echo):
252
 
    if frame.callback:
253
 
        raise "can't ask 2 questions at once!"
254
 
    d = defer.Deferred()
255
 
    resp = []
256
 
    def gotChar(ch, resp=resp):
257
 
        if not ch: return
258
 
        if ch=='\x03': # C-c
259
 
            reactor.stop()
260
 
        if ch=='\r':
261
 
            frame.write('\r\n')
262
 
            stresp = ''.join(resp)
263
 
            del resp
264
 
            frame.callback = None
265
 
            d.callback(stresp)
266
 
            return
267
 
        elif 32 <= ord(ch) < 127:
268
 
            resp.append(ch)
269
 
            if echo:
270
 
                frame.write(ch)
271
 
        elif ord(ch) == 8 and resp: # BS
272
 
            if echo: frame.write('\x08 \x08')
273
 
            resp.pop()
274
 
    frame.callback = gotChar
275
 
    frame.write(question)
276
 
    frame.canvas.focus_force()
277
 
    return d
278
 
 
279
 
def run():
280
 
    global menu, options, frame
281
 
    args = sys.argv[1:]
282
 
    if '-l' in args: # cvs is an idiot
283
 
        i = args.index('-l')
284
 
        args = args[i:i+2]+args
285
 
        del args[i+2:i+4]
286
 
    for arg in args[:]:
287
 
        try:
288
 
            i = args.index(arg)
289
 
            if arg[:2] == '-o' and args[i+1][0]!='-':
290
 
                args[i:i+2] = [] # suck on it scp
291
 
        except ValueError:
292
 
            pass
293
 
    root = Tkinter.Tk()
294
 
    root.withdraw()
295
 
    top = Tkinter.Toplevel()
296
 
    menu = TkConchMenu(top)
297
 
    menu.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
298
 
    options = GeneralOptions()
299
 
    try:
300
 
        options.parseOptions(args)
301
 
    except usage.UsageError, u:
302
 
        print 'ERROR: %s' % u
303
 
        options.opt_help()
304
 
        sys.exit(1)
305
 
    for k,v in options.items():
306
 
        if v and hasattr(menu, k):
307
 
            getattr(menu,k).insert(Tkinter.END, v)
308
 
    for (p, (rh, rp)) in options.localForwards:
309
 
        menu.forwards.insert(Tkinter.END, 'L:%s:%s:%s' % (p, rh, rp))
310
 
    options.localForwards = []
311
 
    for (p, (rh, rp)) in options.remoteForwards:
312
 
        menu.forwards.insert(Tkinter.END, 'R:%s:%s:%s' % (p, rh, rp))
313
 
    options.remoteForwards = []
314
 
    frame = tkvt100.VT100Frame(root, callback=None)
315
 
    root.geometry('%dx%d'%(tkvt100.fontWidth*frame.width+3, tkvt100.fontHeight*frame.height+3))
316
 
    frame.pack(side = Tkinter.TOP)
317
 
    tksupport.install(root)
318
 
    root.withdraw()
319
 
    if (options['host'] and options['user']) or '@' in options['host']:
320
 
        menu.doConnect()
321
 
    else:
322
 
        top.mainloop()
323
 
    reactor.run()
324
 
    sys.exit(exitStatus)
325
 
 
326
 
def handleError():
327
 
    from twisted.python import failure
328
 
    global exitStatus
329
 
    exitStatus = 2
330
 
    log.err(failure.Failure())
331
 
    reactor.stop()
332
 
    raise
333
 
 
334
 
class SSHClientFactory(protocol.ClientFactory):
335
 
    noisy = 1 
336
 
 
337
 
    def stopFactory(self):
338
 
        reactor.stop()
339
 
 
340
 
    def buildProtocol(self, addr):
341
 
        return SSHClientTransport()
342
 
 
343
 
    def clientConnectionFailed(self, connector, reason):
344
 
        tkMessageBox.showwarning('TkConch','Connection Failed, Reason:\n %s: %s' % (reason.type, reason.value))
345
 
 
346
 
class SSHClientTransport(transport.SSHClientTransport):
347
 
 
348
 
    def receiveError(self, code, desc):
349
 
        global exitStatus
350
 
        exitStatus = 'conch:\tRemote side disconnected with error code %i\nconch:\treason: %s' % (code, desc)
351
 
 
352
 
    def sendDisconnect(self, code, reason):
353
 
        global exitStatus
354
 
        exitStatus = 'conch:\tSending disconnect with error code %i\nconch:\treason: %s' % (code, reason)
355
 
        transport.SSHClientTransport.sendDisconnect(self, code, reason)
356
 
 
357
 
    def receiveDebug(self, alwaysDisplay, message, lang):
358
 
        global options
359
 
        if alwaysDisplay or options['log']:
360
 
            log.msg('Received Debug Message: %s' % message)
361
 
 
362
 
    def verifyHostKey(self, pubKey, fingerprint):
363
 
        #d = defer.Deferred()
364
 
        #d.addCallback(lambda x:defer.succeed(1))
365
 
        #d.callback(2)
366
 
        #return d
367
 
        goodKey = isInKnownHosts(options['host'], pubKey, {'known-hosts': None})
368
 
        if goodKey == 1: # good key
369
 
            return defer.succeed(1)
370
 
        elif goodKey == 2: # AAHHHHH changed
371
 
            return defer.fail(error.ConchError('bad host key'))
372
 
        else:
373
 
            if options['host'] == self.transport.getPeer()[1]:
374
 
                host = options['host']
375
 
                khHost = options['host']
376
 
            else:
377
 
                host = '%s (%s)' % (options['host'], 
378
 
                                    self.transport.getPeer()[1])
379
 
                khHost = '%s,%s' % (options['host'], 
380
 
                                    self.transport.getPeer()[1])
381
 
            keyType = common.getNS(pubKey)[0]
382
 
            ques = """The authenticity of host '%s' can't be established.\r
383
 
%s key fingerprint is %s.""" % (host, 
384
 
                                {'ssh-dss':'DSA', 'ssh-rsa':'RSA'}[keyType], 
385
 
                                fingerprint) 
386
 
            ques+='\r\nAre you sure you want to continue connecting (yes/no)? '
387
 
            return deferredAskFrame(ques, 1).addCallback(self._cbVerifyHostKey, pubKey, khHost, keyType)
388
 
 
389
 
    def _cbVerifyHostKey(self, ans, pubKey, khHost, keyType):
390
 
        if ans.lower() not in ('yes', 'no'):
391
 
            return deferredAskFrame("Please type  'yes' or 'no': ",1).addCallback(self._cbVerifyHostKey, pubKey, khHost, keyType)
392
 
        if ans.lower() == 'no':
393
 
            frame.write('Host key verification failed.\r\n')
394
 
            raise error.ConchError('bad host key')
395
 
        try:
396
 
            frame.write("Warning: Permanently added '%s' (%s) to the list of known hosts.\r\n" % (khHost, {'ssh-dss':'DSA', 'ssh-rsa':'RSA'}[keyType]))
397
 
            known_hosts = open(os.path.expanduser('~/.ssh/known_hosts'), 'a')
398
 
            encodedKey = base64.encodestring(pubKey).replace('\n', '')
399
 
            known_hosts.write('\n%s %s %s' % (khHost, keyType, encodedKey))
400
 
            known_hosts.close()
401
 
        except:
402
 
            log.deferr()
403
 
            raise error.ConchError 
404
 
 
405
 
    def connectionSecure(self):
406
 
        if options['user']:
407
 
            user = options['user']
408
 
        else:
409
 
            user = getpass.getuser()
410
 
        self.requestService(SSHUserAuthClient(user, SSHConnection()))
411
 
 
412
 
class SSHUserAuthClient(userauth.SSHUserAuthClient):
413
 
    usedFiles = []
414
 
 
415
 
    def getPassword(self, prompt = None):
416
 
        if not prompt:
417
 
            prompt = "%s@%s's password: " % (self.user, options['host'])
418
 
        return deferredAskFrame(prompt,0) 
419
 
 
420
 
    def getPublicKey(self):
421
 
        files = [x for x in options.identitys if x not in self.usedFiles]
422
 
        if not files:
423
 
            return None
424
 
        file = files[0]
425
 
        log.msg(file)
426
 
        self.usedFiles.append(file)
427
 
        file = os.path.expanduser(file) 
428
 
        file += '.pub'
429
 
        if not os.path.exists(file):
430
 
            return
431
 
        try:
432
 
            return keys.getPublicKeyString(file) 
433
 
        except:
434
 
            return self.getPublicKey() # try again
435
 
    
436
 
    def getPrivateKey(self):
437
 
        file = os.path.expanduser(self.usedFiles[-1])
438
 
        if not os.path.exists(file):
439
 
            return None
440
 
        try:
441
 
            return defer.succeed(keys.getPrivateKeyObject(file))
442
 
        except keys.BadKeyError, e:
443
 
            if e.args[0] == 'encrypted key with no password':
444
 
                prompt = "Enter passphrase for key '%s': " % \
445
 
                       self.usedFiles[-1]
446
 
                return deferredAskFrame(prompt, 0).addCallback(self._cbGetPrivateKey, 0)
447
 
    def _cbGetPrivateKey(self, ans, count):
448
 
        file = os.path.expanduser(self.usedFiles[-1])
449
 
        try:
450
 
            return keys.getPrivateKeyObject(file, password = ans)
451
 
        except keys.BadKeyError:
452
 
            if count == 2:
453
 
                raise
454
 
            prompt = "Enter passphrase for key '%s': " % \
455
 
                   self.usedFiles[-1]
456
 
            return deferredAskFrame(prompt, 0).addCallback(self._cbGetPrivateKey, count+1)
457
 
 
458
 
class SSHConnection(connection.SSHConnection):
459
 
    def serviceStarted(self):
460
 
        if not options['noshell']:
461
 
            self.openChannel(SSHSession())
462
 
        if options.localForwards:
463
 
            for localPort, hostport in options.localForwards:
464
 
                reactor.listenTCP(localPort,
465
 
                            forwarding.SSHListenForwardingFactory(self, 
466
 
                                hostport,
467
 
                                forwarding.SSHListenClientForwardingChannel))
468
 
        if options.remoteForwards:
469
 
            for remotePort, hostport in options.remoteForwards:
470
 
                log.msg('asking for remote forwarding for %s:%s' %
471
 
                        (remotePort, hostport))
472
 
                data = forwarding.packGlobal_tcpip_forward(
473
 
                    ('0.0.0.0', remotePort))
474
 
                d = self.sendGlobalRequest('tcpip-forward', data)
475
 
                self.remoteForwards[remotePort] = hostport
476
 
 
477
 
class SSHSession(channel.SSHChannel):
478
 
 
479
 
    name = 'session'
480
 
    
481
 
    def channelOpen(self, foo):
482
 
        #global globalSession
483
 
        #globalSession = self
484
 
        # turn off local echo
485
 
        self.escapeMode = 1
486
 
        c = session.SSHSessionClient()
487
 
        if options['escape']:
488
 
            c.dataReceived = self.handleInput
489
 
        else:
490
 
            c.dataReceived = self.write
491
 
        c.connectionLost = self.sendEOF
492
 
        frame.callback = c.dataReceived
493
 
        frame.canvas.focus_force()
494
 
        if options['subsystem']:
495
 
            self.conn.sendRequest(self, 'subsystem', \
496
 
                common.NS(options['command']))
497
 
        elif options['command']:
498
 
            if options['tty']:
499
 
                term = os.environ.get('TERM', 'xterm')
500
 
                #winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
501
 
                winSize = (25,80,0,0) #struct.unpack('4H', winsz)
502
 
                ptyReqData = session.packRequest_pty_req(term, winSize, '')
503
 
                self.conn.sendRequest(self, 'pty-req', ptyReqData)                
504
 
            self.conn.sendRequest(self, 'exec', \
505
 
                common.NS(options['command']))
506
 
        else:
507
 
            if not options['notty']:
508
 
                term = os.environ.get('TERM', 'xterm')
509
 
                #winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
510
 
                winSize = (25,80,0,0) #struct.unpack('4H', winsz)
511
 
                ptyReqData = session.packRequest_pty_req(term, winSize, '')
512
 
                self.conn.sendRequest(self, 'pty-req', ptyReqData)
513
 
            self.conn.sendRequest(self, 'shell', '')
514
 
        self.conn.transport.transport.setTcpNoDelay(1)
515
 
 
516
 
    def handleInput(self, char):
517
 
        #log.msg('handling %s' % repr(char))
518
 
        if char in ('\n', '\r'):
519
 
            self.escapeMode = 1
520
 
            self.write(char)
521
 
        elif self.escapeMode == 1 and char == options['escape']:
522
 
            self.escapeMode = 2
523
 
        elif self.escapeMode == 2:
524
 
            self.escapeMode = 1 # so we can chain escapes together
525
 
            if char == '.': # disconnect
526
 
                log.msg('disconnecting from escape')
527
 
                reactor.stop()
528
 
                return
529
 
            elif char == '\x1a': # ^Z, suspend
530
 
                # following line courtesy of Erwin@freenode
531
 
                os.kill(os.getpid(), signal.SIGSTOP)
532
 
                return
533
 
            elif char == 'R': # rekey connection
534
 
                log.msg('rekeying connection')
535
 
                self.conn.transport.sendKexInit()
536
 
                return
537
 
            self.write('~' + char)
538
 
        else:
539
 
            self.escapeMode = 0
540
 
            self.write(char)
541
 
 
542
 
    def dataReceived(self, data):
543
 
        if options['ansilog']:
544
 
            print repr(data)
545
 
        frame.write(data)
546
 
 
547
 
    def extReceived(self, t, data):
548
 
        if t==connection.EXTENDED_DATA_STDERR:
549
 
            log.msg('got %s stderr data' % len(data))
550
 
            sys.stderr.write(data)
551
 
            sys.stderr.flush()
552
 
 
553
 
    def eofReceived(self):
554
 
        log.msg('got eof')
555
 
        sys.stdin.close()
556
 
 
557
 
    def closed(self):
558
 
        log.msg('closed %s' % self)
559
 
        if len(self.conn.channels) == 1: # just us left
560
 
            reactor.stop()
561
 
 
562
 
    def request_exit_status(self, data):
563
 
        global exitStatus
564
 
        exitStatus = int(struct.unpack('>L', data)[0])
565
 
        log.msg('exit status: %s' % exitStatus)
566
 
 
567
 
    def sendEOF(self):
568
 
        self.conn.sendEOF(self)
569
 
 
570
 
if __name__=="__main__":
571
 
    run()