Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tkconch.py 23KB

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