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