chatServer/clientBlaster.py
2021-06-30 17:26:55 +02:00

323 lines
7.9 KiB
Python

#!/usr/bin/env python3
import sys
oldexcepthook = sys.excepthook
def newexcepthook(type,value,traceback):
oldexcepthook(type,value,traceback)
input("Press ENTER to quit.")
sys.excepthook = newexcepthook
import os
p = os.path.join
pUp = os.path.dirname
s = False
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
s = os.path.realpath(sys.executable)
else:
s = os.path.realpath(__file__)
sp = pUp(s)
# script start
import threading
import queue
import socket
import traceback
import time
import colorama
colorama.init()
maxConnections = 10000
maxConnectionsPerIp = 10
maxQueueSize = 1000
maxRequestSize = 4096
pauseBetweenCommands = 0.1
serverAddr = ("127.0.0.1",21779)
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connectionsLock = threading.Lock()
connections = {}
connectionsId = 0
heartbeatTime = 600
threadCount = 0
threadCountLock = threading.Lock()
commands = {}
def commandlineToList(cmd):
args = []
cArg = ""
escape = False
quoted = False
for letter in cmd:
if escape == True:
cArg += letter
escape = False
continue
if letter == "\\":
escape = True
continue
#if quoted == False and letter == ",":
if letter == ",":
if cArg == "": continue
args.append(cArg)
cArg = ""
continue
#if letter == '"':
# quoted = not quoted
# continue
cArg += letter
args.append(cArg)
return args
def listToCommandline(lst):
cmd = ""
for arg in lst:
arg = arg.replace("\\","\\\\")
arg = arg.replace(",","\\,")
#arg = arg.replace('"','\\"')
#if " " in arg: arg = '"' +arg+ '"'
cmd += arg + ","
return cmd[:-1]
printLock = threading.Lock()
def tprint(st):
with printLock:
print(st)
def runCode(str, lcs = False, description = "loose-code"):
if lcs == False: lcs = {}
code = compile(str,description,"exec")
exec(code,globals(),lcs)
return lcs
def runScript(sf, lcs = False):
if lcs == False: lcs = {}
with open(sf) as script:
runCode(script.read(),lcs,sf)
return lcs
def getModlist(path):
modList = []
for root,dirs,files in os.walk(path):
for file in dirs:
ffile = p(root,file)
lfile = ffile.replace(path + os.path.sep,"",1)
if lfile[0] == "-": continue
if lfile[0] == "[" and lfile[-1] == "]":
modList = modList + sorted(getModlist(ffile))
continue
modList.append(ffile)
break
return modList
modulesLoaded = []
modulePath = p(sp,"modules")
def moduleRun(localModule):
if not localModule in modulesLoaded: modulesLoaded.append(localModule)
print("> " +localModule+ "...")
runScript(p(modulePath,localModule,"module.py"))
def moduleDepends(localModules):
if type(localModules) == str: localModules = [localModules]
for localModule in localModules:
if localModule in modulesLoaded: return
print("depend ",end="")
moduleRun(localModule)
def addThread():
global threadCount
with threadCountLock:
threadCount += 1
tprint(colorama.Fore.YELLOW + colorama.Style.BRIGHT + "Thread opened. Threads: " +str(threadCount)+ " (Actual: " +str(threading.active_count())+ ")" + colorama.Style.RESET_ALL)
def removeThread():
global threadCount
with threadCountLock:
threadCount -= 1
tprint(colorama.Fore.YELLOW + colorama.Style.BRIGHT + "Thread closed. Threads: " +str(threadCount)+ " (Actual: " +str(threading.active_count())+ ")" + colorama.Style.RESET_ALL)
def sendResponse(connection,data):
connection.sendall(len(data).to_bytes(4,"big") + b"\x00" + data)
def getResponse(connection):
data = b''
data = connection.recv(4)
if not data: return False
nul = connection.recv(1)
if not nul: return False
if nul != b"\x00": return False
requestLength = int.from_bytes(data,"big")
if requestLength > maxRequestSize: raise Exception("security","request_too_large")
return connection.recv(requestLength)
def closeConnection(connectionId):
if not connectionId in connections: return False
try:
connections[connectionId]["connection"].close()
except Exception as e:
tprint("Failed to close connection: " +str(e))
pass
try:
connections[connectionId]["threadOut"].queue.put(False)
except:
with printLock:
print(colorama.Fore.GREEN + colorama.Style.BRIGHT)
traceback.print_exc()
print(colorama.Style.RESET_ALL)
del connections[connectionId]
return True
class connectionThreadOut(threading.Thread):
def __init__(self,connectionId):
threading.Thread.__init__(self)
self.queue = queue.Queue()
self.connectionId = connectionId
def getConnection(self):
with connectionsLock:
if self.connectionId in connections:
return connections[self.connectionId]["connection"]
return False
def run(self):
try:
while True:
data = self.queue.get(timeout=heartbeatTime)
if data == False: return
connection = self.getConnection()
if not connection:
with connectionsLock: closeConnection(self.connectionId)
return
sendResponse(connection,data)
except Exception as e:
with connectionsLock: closeConnection(self.connectionId)
with printLock:
print(colorama.Fore.GREEN + colorama.Style.BRIGHT)
traceback.print_exc()
print(colorama.Style.RESET_ALL)
finally:
removeThread()
class connectionThreadIn(threading.Thread):
def __init__(self,connectionId):
threading.Thread.__init__(self)
self.connectionId = connectionId
def getConnection(self):
with connectionsLock:
if self.connectionId in connections:
return connections[self.connectionId]["connection"]
return False
def runCommand(self,cmd):
command = False
if not cmd[0] in commands:
return ["error","nonfatal","command_not_found"]
command = commands[cmd[0]]
rtn = command["function"](self,cmd)
time.sleep(pauseBetweenCommands)
return rtn
def run(self):
try:
while True:
connection = self.getConnection()
if not connection:
with connectionsLock: closeConnection(self.connectionId)
return
data = getResponse(connection)
if data == False:
with connectionsLock: closeConnection(self.connectionId)
return
queue = False
with connectionsLock:
queue = connections[self.connectionId]["threadOut"].queue
if queue.qsize() >= maxQueueSize:
closeConnection(self.connectionId)
return
dataString = data.decode(encoding="utf-8")
commandList = commandlineToList(dataString)
queue.put(listToCommandline(self.runCommand(commandList)).encode(encoding="utf-8"))
except Exception as e:
with connectionsLock: closeConnection(self.connectionId)
with printLock:
print(colorama.Fore.GREEN + colorama.Style.BRIGHT)
traceback.print_exc()
print(colorama.Style.RESET_ALL)
finally:
removeThread()
def main():
print("Loading modules...")
for path in getModlist(modulePath):
if os.path.isfile(p(path,"module.py")):
localModule = path.replace(modulePath + os.path.sep,"",1)
if not localModule in modulesLoaded:
moduleRun(localModule)
global connectionsId
serverSocket.bind(serverAddr)
serverSocket.listen(65535)
while True:
connection,address = serverSocket.accept()
connection.settimeout(heartbeatTime)
with connectionsLock:
# Count connections
connectionsCount = 0
connectionsCountIp = 0
for connectionId in connections:
connectionsCount += 1
if connections[connectionId]["address"][0] == address[0]:
connectionsCountIp += 1
if connectionsCount >= maxConnections:
tprint("Connection closed - too many clients.")
closeConnection(connectionId)
continue
if connectionsCountIp >= maxConnectionsPerIp:
tprint("Connection closed - same IP connected too many times.")
closeConnection(connectionId)
continue
# Create connection
connectionsId += 1
threadIn = connectionThreadIn(str(connectionsId))
threadOut = connectionThreadOut(str(connectionsId))
connections[str(connectionsId)] = {
"connection": connection,
"address": address,
"threadOut": threadOut,
"threadIn": threadIn,
"user": False
}
addThread()
addThread()
threadOut.start()
threadIn.start()
if __name__ == '__main__':
main()