# Copyright (c) 2008, Thomas Hurst # # Use of this file is unrestricted provided this notice is retained. # If you use it, it'd be nice if you dropped me a note. Also beer. from terminatorlib.util import dbg, err from terminatorlib.version import APP_NAME, APP_VERSION import socket import threading import sys if sys.version_info < (3, 0): import SocketServer as socketserver else: import socketserver import code import readline import rlcompleter import re def ddbg(msg): # uncomment this to get lots of spam from debugserver return dbg(msg) class PythonConsoleServer(socketserver.BaseRequestHandler): env = None def setup(self): dbg('debugserver: connect from %s' % str(self.client_address)) ddbg('debugserver: env=%r' % PythonConsoleServer.env) self.console = TerminatorConsole(PythonConsoleServer.env) def handle(self): ddbg("debugserver: handling") try: self.socketio = self.request.makefile(mode='rw') sys.stdout = self.socketio sys.stdin = self.socketio sys.stderr = self.socketio self.console.run(self) finally: sys.stdout = sys.__stdout__ sys.stdin = sys.__stdin__ sys.stderr = sys.__stderr__ self.socketio.close() ddbg("debugserver: done handling") def verify_request(self, request, client_address): return True def finish(self): ddbg('debugserver: disconnect from %s' % str(self.client_address)) # rfc1116/rfc1184 LINEMODE = chr(34) # Linemode negotiation NULL = chr(0) ECHO = chr(1) CR = chr(13) LF = chr(10) SE = chr(240) # End subnegotiation NOP = chr(241) DM = chr(242) # Data Mark BRK = chr(243) # Break IP = chr(244) # Interrupt Process AO = chr(245) # Abort Output AYT = chr(246) # Are You There EC = chr(247) # Erase Character EL = chr(248) # Erase Line GA = chr(249) # Go Ahead SB = chr(250) # Subnegotiation follows WILL = chr(251) # Subnegotiation commands WONT = chr(252) DO = chr(253) DONT = chr(254) IAC = chr(255) # Interpret As Command UIAC = '(^|[^' + IAC + '])' + IAC # Unescaped IAC BareLF = re.compile('([^' + CR + '])' + CR) DoDont = re.compile(UIAC +'[' + DO + DONT + '](.)') WillWont = re.compile(UIAC + '[' + WILL + WONT + '](.)') AreYouThere = re.compile(UIAC + AYT) IpTelnet = re.compile(UIAC + IP) OtherTelnet = re.compile(UIAC + '[^' + IAC + ']') # See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/205335 for telnet bits # Python doesn't make this an especially neat conversion :( class TerminatorConsole(code.InteractiveConsole): def parse_telnet(self, data): odata = data data = re.sub(BareLF, '\\1', data) data = data.replace(CR + NULL, '') data = data.replace(NULL, '') bits = re.findall(DoDont, data) ddbg("bits = %r" % bits) if bits: data = re.sub(DoDont, '\\1', data) ddbg("telnet: DO/DON'T answer") # answer DO and DON'T with WON'T for bit in bits: self.write(IAC + WONT + bit[1]) bits = re.findall(WillWont, data) if bits: data = re.sub(WillWont, '\\1', data) ddbg("telnet: WILL/WON'T answer") for bit in bits: # answer WILLs and WON'T with DON'Ts self.write(IAC + DONT + bit[1]) bits = re.findall(AreYouThere, data) if bits: ddbg("telnet: am I there answer") data = re.sub(AreYouThere, '\\1', data) for bit in bits: self.write("Yes, I'm still here, I think.\n") (data, interrupts) = re.subn(IpTelnet, '\\1', data) if interrupts: ddbg("debugserver: Ctrl-C detected") raise KeyboardInterrupt data = re.sub(OtherTelnet, '\\1', data) # and any other Telnet codes data = data.replace(IAC + IAC, IAC) # and handle escapes if data != odata: ddbg("debugserver: Replaced %r with %r" % (odata, data)) return data def raw_input(self, prompt = None): ddbg("debugserver: raw_input prompt = %r" % prompt) if prompt: self.write(prompt) buf = '' compstate = 0 while True: data = self.server.socketio.read(1) ddbg('raw_input: char=%r' % data) if data == LF or data == '\006': buf = self.parse_telnet(buf + data) if buf != '': return buf elif data == '\004' or data == '': # ^D raise EOFError else: buf += data def write(self, data): ddbg("debugserver: write %r" % data) self.server.socketio.write(data) self.server.socketio.flush() def run(self, server): self.server = server self.write("Welcome to the %s-%s debug server, have a nice stay\n" % (APP_NAME, APP_VERSION)) self.interact() try: self.write("Time to go. Bye!\n") except: pass def spawn(env): PythonConsoleServer.env = env tcpserver = socketserver.TCPServer(('127.0.0.1', 0), PythonConsoleServer) dbg("debugserver: listening on %s" % str(tcpserver.server_address)) debugserver = threading.Thread(target=tcpserver.serve_forever, name="DebugServer") debugserver.setDaemon(True) debugserver.start() return(debugserver, tcpserver)