123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- #!/usr/bin/env python3
- import sys
- import re
- from twisted.internet import defer
- from twisted.internet import protocol
- from twisted.internet import reactor
- from twisted.python import log
- from twisted.enterprise import adbapi
- import pendulum
- from subprocess import check_output
- RAW = True
- try:
- from config_dev import *
- except ModuleNotFoundError:
- from config import *
- # from config import *
- # Connect to:
- # HOST = "twgs"
- # PORT = 2002
- # Listen on:
- # LISTEN_PORT = 2002
- # LISTEN_ON = "0.0.0.0"
- version = check_output(
- [
- "git",
- "describe",
- "--long",
- "--tags",
- # "--dirty",
- "--always",
- "--match",
- "v[0-9]\.[0-9]\.[0-9]",
- ],
- universal_newlines=True,
- ).strip()
- cleaner = re.compile(r"\x1b\[[0-9;]*[A-Zmh]")
- makeNL = re.compile(r"\x1b\[[0-9;]*[J]")
- def treatAsNL(line):
- global makeNL
- return makeNL.sub("\n", line)
- def cleanANSI(line):
- global cleaner
- return cleaner.sub("", line)
- # return re.sub(r'\x1b\[([0-9,A-Z]{1,2}(;[0-9]{1,2})?(;[0-9]{3})?)?[m|K]?', '', line)
- class PlayerProtocol(protocol.Protocol):
- def __init__(self):
- self.user = None
- self.dbinit = False
- self.db = None
- self.buffer = ""
- def connectionMade(self):
- log.msg("Client: connected to peer")
- self.queue_twgs = self.factory.queue_twgs
- self.queue_twgs.get().addCallback(self.serverDataReceived)
- def serverDataReceived(self, chunk):
- # rlogin looks like this: \x00 password \x00 username \x00 terminal/speed \x00
- # b'\x00up2lat3\x00bugz\x00ansi-bbs/115200\x00'
- # We're looking for 4 \x00!
- # TODO: Line processing, and line cleaning (remove ANSI color codes)
- if chunk is False:
- self.queue_twgs = None
- log.msg("Client: disconnecting from peer")
- self.factory.continueTrying = False
- self.transport.loseConnection()
- else:
- if self.user is None:
- # Decode the rlogin data
- self.buffer += chunk.decode("utf-8", "ignore")
- # Ok, process this
- # self.buffer += chunk.decode('utf-8')
- # We don't have the username yet
- parts = self.buffer.split("\x00")
- if len(parts) >= 5:
- # Got it!
- self.user = parts[1]
- log.msg("User: {0}".format(self.user))
- # Reset buffer -- remove everything before last \x00
- zpos = self.buffer.rindex("\x00")
- self.buffer = self.buffer[zpos + 1 :]
- self.buffer = ""
- # init sqlite db using the username
- self.factory.getUser(self.user)
- # Pass received data to the server
- self.transport.write(chunk)
- self.queue_twgs.get().addCallback(self.serverDataReceived)
- def dataReceived(self, chunk):
- # log.msg("Client: %d bytes received from peer" % len(chunk))
- # clean, strip ANSI, etc.
- # log.msg("<<", chunk.decode("utf-8", "ignore"))
- # log.msg("<<", repr(chunk))
- self.factory.queue_client.put(chunk)
- def connectionLost(self, why):
- log.msg("connectionLost because: %s" % why)
- # if self.cli_queue:
- # self.cli_queue = None
- # log.msg("Client: peer disconnect unexpectedly")
- self.factory.queue_client.put(False)
- self.queue_twgs = None
- self.transport.loseConnection()
- class GlueFactory(protocol.ClientFactory):
- # class GlueFactory(protocol.Factory):
- maxDelay = 10
- protocol = PlayerProtocol
- def __init__(self, queue_client, queue_twgs, twgs):
- self.queue_client = queue_client
- self.queue_twgs = queue_twgs
- self.twgs = twgs
- def closeIt(self):
- log.msg("closeIt")
- self.queue_client.put(False)
- def getUser(self, user):
- log.msg("getUser( %s )" % user)
- self.twgs.logUser(user)
- # This was needed when I replaced ClientFactory with Factory.
- # def clientConnectionLost(self, connector, why):
- # log.msg("clientconnectionlost: %s" % why)
- # self.queue_client.put(False)
- def clientConnectionFailed(self, connector, why):
- log.msg("connectionFailed: %s" % why)
- self.queue_client.put(b"Sorry! I'm Unable to connect to the game server.\r\n")
- # syncterm gets cranky/locks up if we close this here.
- # (Because it is still sending rlogin information?)
- reactor.callLater(2, self.closeIt)
- # ProxyServer is created for each connection
- class ProxyAction:
- def __init__(self, twgs):
- self.active = False
- self.twgs = twgs
- self.queue_client = twgs.queue_client
- self.queue_twgs = twgs.queue_twgs
- self.buffer = ""
- self.prompt = ""
- self.rptstate = 0
- def isActive(self):
- return self.active
- def keepAlive(self):
- if self.active:
- self.queue_twgs.put(b" ")
- reactor.callLater(30, self.keepAlive)
- def menu(self):
- self.send("\r\n**********\r\nTWGS Proxy ready...\r\n")
- self.send("(T) Display Time\r\n")
- self.send("(P) CIM Port Report\r\n")
- self.send("(Q) Quit\r\n")
- self.send(" --==> ")
- def activate(self, prompt):
- cleaned = cleanANSI(prompt)
- if cleaned.startswith("Command [TL=0"):
- self.active = True
- self.prompt = prompt
- self.menu()
- reactor.callLater(30, self.keepAlive)
- else:
- self.send("\a")
- def sendtwgs(self, text):
- self.queue_twgs.put(text.encode())
- def server(self, line):
- # 2019-11-20 18:37:28-0500 [PlayerProtocol,client] >>> [: ]
- # 2019-11-20 18:37:28-0500 [PlayerProtocol,client] >>> [ 436 2870 100% - 1520 100% - 2820 100% ]
- # 2019-11-20 18:37:28-0500 [PlayerProtocol,client] >>> []
- # 2019-11-20 18:37:42-0500 [PlayerProtocol,client] >>> [: ENDINTERROG]
- # 2019-11-20 18:37:42-0500 [PlayerProtocol,client] >>> []
- if self.rptstate == 1:
- if line.startswith(":"):
- self.sendtwgs("R")
- self.rptstate == 2
- if self.rptstate == 2:
- if line.startswith(":"):
- self.sendtwgs("Q")
- self.rptstate == 3
- else:
- log.msg("[[{0}]]".format(line))
- if self.rptstate == 3:
- if line == ": ENDINTERROG":
- self.rptstate == 3
- self.twgs.passon = True
- self.menu()
- def send(self, text):
- self.queue_client.put((text.encode(),))
- def received(self, chunk):
- # self.buffer += chunk.encode('UTF-8', 'ignore')
- text = chunk.decode("utf-8", "ignore").upper()
- if text == "T":
- now = pendulum.now("America/New_York")
- self.send("\r\nThe time is: {0}.\r\n".format(now.to_rss_string()))
- if text == "P":
- # Port Report
- self.sendtwgs("^")
- self.twgs.passon = False
- self.rptstate = 1
- if text == "Q":
- self.send("\r\nReturning to TWGS.\r\n{0}".format(self.prompt))
- self.active = False
- self.twgs.passon = True
- class TWGSServer(protocol.Protocol):
- def __init__(self):
- self.buffer = ""
- self.fpRaw = None
- self.fpLines = None
- self.action = None
- self.user = ""
- self.game = ""
- self.passon = True
- def connectionMade(self):
- self.queue_twgs = defer.DeferredQueue()
- self.queue_client = defer.DeferredQueue()
- self.queue_client.get().addCallback(self.clientDataReceived)
- self.action = ProxyAction(self)
- factory = GlueFactory(self.queue_client, self.queue_twgs, self)
- reactor.connectTCP(HOST, PORT, factory, 5)
- def logUser(self, user):
- now = pendulum.now()
- self.user = user
- filename = now.format("YYYY-MM-DD_HHmm") + "-" + user.lower()
- if RAW:
- self.fpRaw = open(filename + ".raw", "ab")
- self.fpLines = open(filename + ".lines", "a")
- # print("Log created:", now.to_rss_string(), "\n", file=self.fpRaw)
- print("Log created:", now.to_rss_string(), "\n", file=self.fpLines)
- def setGame(self, game):
- if self.game != game:
- log.msg("USER {0} ENTERED {1}".format(self.user, self.game))
- self.data = {}
- self.game = game
- def gotLine(self, line):
- # log.msg(">>> [{0}]".format(line.decode("utf-8", "ignore")))
- log.msg(">>> [{0}]".format(line))
- if self.fpLines is not None:
- print(line, file=self.fpLines)
- if "TWGS v2.20b" in line:
- if "www.eisonline.com" in line:
- # Must not be unicode
- # Is there a way to NOT have this logged?
- self.queue_client.put(
- (
- b"TWGS Proxy build "
- + version.encode()
- + b" is active. \x1b[1;34m~\x1b[0m to activate.\n\r\n\r",
- )
- )
- if "TradeWars Game Server" in line and "Copyright (C) EIS" in line:
- # We are not in a game
- self.game = ""
- if "Selection (? for menu): " in line:
- game = line[-1]
- if game >= "A" and game < "Q":
- self.setGame(game)
- # If we're not passing it on to the user, we better be looking at it.
- if self.action and not self.passon:
- self.action.server(line)
- def clientDataReceived(self, chunk):
- if chunk is False:
- self.transport.loseConnection()
- else:
- if type(chunk) is tuple:
- self.transport.write(chunk[0])
- self.queue_client.get().addCallback(self.clientDataReceived)
- else:
- if self.fpRaw is not None:
- self.fpRaw.write(chunk)
- self.buffer += chunk.decode("utf-8", "ignore")
- # Process any backspaces in the buffer
- while "\x08" in self.buffer:
- part = self.buffer.partition("\x08")
- self.buffer = part[0][:-1] + part[2]
- # Treat some ANSI codes as a newline (for purposes of Lines)
- self.buffer = treatAsNL(self.buffer)
- # I think I need something else in here. When something enters or leaves the sector
- # The message isn't shown on it's own line. I think they are using ANSI codes to
- # clear the line. (Which certainly would be faster!)
- # Break the buffer into lines
- while "\n" in self.buffer:
- part = self.buffer.partition("\n")
- line = part[0].replace("\r", "")
- # Clean ANSI codes from the line
- line = cleanANSI(line)
- self.gotLine(line)
- self.buffer = part[2]
- # log.msg("Server: writing %d bytes to original client" % len(chunk))
- if self.passon:
- self.transport.write(chunk)
- self.queue_client.get().addCallback(self.clientDataReceived)
- def dataReceived(self, chunk):
- # log.msg("Server: %d bytes received" % len(chunk))
- if self.action and self.action.isActive():
- # Do something completely different here
- self.action.received(chunk)
- else:
- if chunk == b"~":
- self.action.activate(self.buffer)
- else:
- self.queue_twgs.put(chunk)
- def connectionLost(self, why):
- log.msg("lost connection %s" % why)
- self.queue_twgs.put(False)
- if self.fpRaw is not None:
- self.fpRaw.close()
- self.fpRaw = None
- if self.fpLines is not None:
- if self.buffer != "":
- print(self.buffer, file=self.fpLines)
- self.fpLines.close()
- self.fpLines = None
- def connectionFailed(self, why):
- log.msg("connectionFailed: %s" % why)
- if __name__ == "__main__":
- log.startLogging(sys.stdout)
- log.msg("This is version: %s" % version)
- factory = protocol.Factory()
- factory.protocol = TWGSServer
- reactor.listenTCP(LISTEN_PORT, factory, interface=LISTEN_ON)
- reactor.run()
|