~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/conch/scripts/tkconch.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

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()