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