2021-04-13 20:06:16 +00:00
#!/usr/bin/env python3
import sys
oldexcepthook = sys . excepthook
def newexcepthook ( type , value , traceback ) :
oldexcepthook ( type , value , traceback )
2021-04-14 01:12:13 +00:00
#input("Press ENTER to quit.")
2021-04-13 20:06:16 +00:00
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
2021-04-13 18:33:16 +00:00
import threading
import queue
import socket
import subprocess
2021-04-14 01:12:13 +00:00
import configparser
2021-04-14 23:20:21 +00:00
import time
2021-04-13 18:33:16 +00:00
2021-04-14 16:28:54 +00:00
connections = { }
2021-04-22 02:29:12 +00:00
connectionsId = 0
2021-04-14 16:28:54 +00:00
connectionsLock = threading . Lock ( )
2021-04-13 18:33:16 +00:00
2021-04-22 02:29:12 +00:00
threadCount = 0
threadCountLock = threading . Lock ( )
fileLock = threading . Lock ( )
2021-04-15 18:19:45 +00:00
serverAddr = ( " 127.0.0.1 " , 61920 )
2021-04-13 18:33:16 +00:00
serverSocket = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
2021-04-20 14:32:56 +00:00
bufferSize = 10000 # Buffer size in bytes
2021-04-14 16:52:47 +00:00
timeout = 15 # How long to wait for a connection to respond before timing out?
2021-04-15 18:13:03 +00:00
maxClients = 20 # How many clients can be connected at maximum?
maxClientsPerIP = 3 # How many clients can be connected at maximum, per IP?
maxAccumulatedData = 50 * 1000 * 1000 # How much data can be in an outbound thread's queue at maximum before the connection is closed? The maximum amount of queue data is maxAccumulatedData * maxClients bytes.
maxInboundTransferRate = 0 # Maximum upload speed for broadcasters. This sucks right now. Set to 0 to disable.
2021-04-13 18:33:16 +00:00
2021-04-22 02:29:12 +00:00
def addThread ( ) :
global threadCount
with threadCountLock :
threadCount + = 1
print ( " Thread opened. Threads: " + str ( threadCount ) + " (Actual: " + str ( threading . active_count ( ) ) + " ) " )
def removeThread ( ) :
global threadCount
with threadCountLock :
threadCount - = 1
print ( " Thread closed. Threads: " + str ( threadCount ) + " (Actual: " + str ( threading . active_count ( ) ) + " ) " )
2021-04-13 23:09:34 +00:00
def commandToList ( 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
2021-04-13 18:33:16 +00:00
2021-04-22 02:29:12 +00:00
def closeConnection ( connectionId ) :
if connectionId in connections :
2021-04-22 03:34:11 +00:00
try :
connections [ connectionId ] [ " connection " ] . close ( )
except Exception as e :
print ( " Failed to close connection: " + str ( e ) )
pass
2021-04-22 02:29:12 +00:00
del connections [ connectionId ]
2021-04-13 18:33:16 +00:00
class outThread ( threading . Thread ) :
2021-04-22 02:29:12 +00:00
def __init__ ( self , connectionId ) :
2021-04-13 18:33:16 +00:00
threading . Thread . __init__ ( self )
self . queue = queue . Queue ( )
2021-04-22 02:29:12 +00:00
self . connectionId = connectionId
2021-04-14 16:28:54 +00:00
def getConnection ( self ) :
with connectionsLock :
2021-04-22 02:29:12 +00:00
if self . connectionId in connections :
return connections [ self . connectionId ] [ " connection " ]
return False
2021-04-13 18:33:16 +00:00
def run ( self ) :
try :
while True :
2021-04-16 02:29:28 +00:00
data = self . queue . get ( timeout = 15 )
2021-04-22 02:29:12 +00:00
connection = self . getConnection ( )
if not connection :
removeThread ( )
return
connection . sendall ( data )
with connectionsLock : closeConnection ( self . connectionId )
removeThread ( )
return
2021-04-13 18:33:16 +00:00
except :
2021-04-22 02:29:12 +00:00
with connectionsLock : closeConnection ( self . connectionId )
removeThread ( )
2021-04-21 12:02:53 +00:00
print ( " Thread closed - Exception: " )
2021-04-13 18:33:16 +00:00
raise
2021-04-13 23:09:34 +00:00
class inThread ( threading . Thread ) :
2021-04-22 02:29:12 +00:00
def __init__ ( self , connectionId ) :
2021-04-13 18:33:16 +00:00
threading . Thread . __init__ ( self )
2021-04-22 02:29:12 +00:00
self . connectionId = connectionId
2021-04-14 16:28:54 +00:00
def getConnection ( self ) :
with connectionsLock :
2021-04-22 02:29:12 +00:00
if self . connectionId in connections :
return connections [ self . connectionId ] [ " connection " ]
return False
2021-04-13 18:33:16 +00:00
def run ( self ) :
2021-04-13 23:09:34 +00:00
try :
2021-04-22 02:29:12 +00:00
connection = self . getConnection ( )
if not connection :
removeThread ( )
return
data = connection . recv ( 1000 )
2021-04-14 03:12:35 +00:00
if data == b " " :
2021-04-22 02:29:12 +00:00
with connectionsLock : closeConnection ( self . connectionId )
2021-04-21 12:02:53 +00:00
print ( " Thread closed - Client disconnected. " )
2021-04-22 02:29:12 +00:00
removeThread ( )
2021-04-14 03:12:35 +00:00
return
data = data . decode ( " utf-8 " )
2021-04-13 23:09:34 +00:00
while data [ - 1 ] == " " : data = data [ : - 1 ]
args = commandToList ( data )
cmd = args . pop ( 0 )
user = args [ 0 ]
self . user = user
2021-04-14 01:12:13 +00:00
password = False
if len ( args ) > 1 :
password = args [ 1 ]
userPath = p ( sp , " users " , user + " .ini " )
with fileLock :
if not os . path . isfile ( userPath ) :
2021-04-22 02:29:12 +00:00
with connectionsLock : closeConnection ( self . connectionId )
2021-04-21 12:02:53 +00:00
print ( " Thread closed - Invalid user given: " + user )
2021-04-22 02:29:12 +00:00
removeThread ( )
2021-04-14 01:12:13 +00:00
return
config = configparser . ConfigParser ( )
config . read ( userPath )
2021-04-13 23:09:34 +00:00
if cmd == " watch " :
2021-04-14 01:12:13 +00:00
if cmd in config and " pass " in config [ cmd ] and config [ cmd ] [ " pass " ] != " " and config [ cmd ] [ " pass " ] != password :
2021-04-22 02:29:12 +00:00
with connectionsLock : closeConnection ( self . connectionId )
2021-04-21 12:02:53 +00:00
print ( " Thread closed - Invalid password given for user: " + user )
2021-04-22 02:29:12 +00:00
removeThread ( )
2021-04-14 01:12:13 +00:00
return
2021-04-22 02:29:12 +00:00
with connectionsLock :
if not self . connectionId in connections :
removeThread ( )
return
connections [ self . connectionId ] [ " action " ] = " watch "
connections [ self . connectionId ] [ " user " ] = user
thread = outThread ( self . connectionId )
connections [ self . connectionId ] [ " outThread " ] = thread
2021-04-13 23:09:34 +00:00
thread . start ( )
2021-04-22 02:29:12 +00:00
addThread ( )
connections [ self . connectionId ] [ " inThread " ] = False
removeThread ( )
return
2021-04-13 23:09:34 +00:00
if cmd == " broadcast " :
2021-04-14 01:12:13 +00:00
if cmd in config and " pass " in config [ cmd ] and config [ cmd ] [ " pass " ] != " " and config [ cmd ] [ " pass " ] != password :
2021-04-22 02:29:12 +00:00
with connectionsLock : closeConnection ( self . connectionId )
2021-04-21 12:02:53 +00:00
print ( " Thread closed - Invalid password given for user: " + user )
2021-04-22 02:29:12 +00:00
removeThread ( )
2021-04-14 01:12:13 +00:00
return
2021-04-22 02:29:12 +00:00
with connectionsLock :
2021-04-22 03:34:55 +00:00
if not self . connectionId in connections :
removeThread ( )
return
2021-04-14 20:03:26 +00:00
deleteList = [ ]
2021-04-22 02:29:12 +00:00
for connectionId in connections :
if connectionId == self . connectionId : continue
if connections [ connectionId ] [ " action " ] != " broadcast " : continue
if connections [ connectionId ] [ " user " ] != user : continue
deleteList . append ( connectionId )
2021-04-14 20:03:26 +00:00
2021-04-22 02:29:12 +00:00
for connectionId in deleteList :
2021-04-22 03:34:55 +00:00
closeConnection ( connectionId )
2021-04-22 02:29:12 +00:00
2021-04-22 03:34:55 +00:00
connections [ self . connectionId ] [ " action " ] = " broadcast "
connections [ self . connectionId ] [ " user " ] = user
2021-04-14 20:03:26 +00:00
2021-04-15 18:13:03 +00:00
if maxInboundTransferRate > 0 : startTime = time . time ( )
2021-04-13 23:09:34 +00:00
while True :
2021-04-22 02:29:12 +00:00
connection = self . getConnection ( )
if not connection :
removeThread ( )
return
data = connection . recv ( bufferSize )
2021-04-14 03:12:35 +00:00
if data == b " " :
2021-04-22 02:29:12 +00:00
with connectionsLock : closeConnection ( self . connectionId )
2021-04-21 12:02:53 +00:00
print ( " Thread closed - Client disconnected. " )
2021-04-22 02:29:12 +00:00
removeThread ( )
2021-04-14 03:12:35 +00:00
return
2021-04-14 23:20:21 +00:00
2021-04-22 02:29:12 +00:00
with connectionsLock :
for connectionId in connections :
connectionData = connections [ connectionId ]
thread = connectionData [ " outThread " ]
if not thread : continue
if connectionData [ " user " ] != user : continue
accumulatedData = thread . queue . qsize ( ) * bufferSize
if accumulatedData > maxAccumulatedData :
print ( " Thread closed - Too much accumulated data. " )
closeConnection ( connectionId )
removeThread ( )
return
thread . queue . put ( data )
2021-04-14 23:20:21 +00:00
2021-04-15 18:13:03 +00:00
if maxInboundTransferRate > 0 :
endTime = time . time ( )
timeTaken = endTime - startTime
if timeTaken < = 0 : timeTaken = 0.00001 # this isn't good enough
transferSpeed = bufferSize / timeTaken
if transferSpeed > maxInboundTransferRate :
maxSleep = ( bufferSize / maxInboundTransferRate ) - timeTaken
if maxSleep > 0 :
sleepTime = ( transferSpeed / maxInboundTransferRate ) - 1
if sleepTime > maxSleep : sleepTime = maxSleep
time . sleep ( sleepTime )
startTime = endTime
2021-04-13 23:09:34 +00:00
2021-04-22 02:29:12 +00:00
with connectionsLock : closeConnection ( self . connectionId )
removeThread ( )
2021-04-14 01:12:13 +00:00
return
2021-04-13 23:09:34 +00:00
except :
2021-04-22 02:29:12 +00:00
with connectionsLock : closeConnection ( self . connectionId )
removeThread ( )
2021-04-21 12:02:53 +00:00
print ( " Thread closed - Exception: " )
2021-04-13 23:09:34 +00:00
raise
2021-04-13 18:33:16 +00:00
2021-04-14 03:12:49 +00:00
class debugThread ( threading . Thread ) :
def __init__ ( self ) :
threading . Thread . __init__ ( self )
def run ( self ) :
while True :
2021-04-22 02:29:12 +00:00
with connectionsLock :
2021-04-14 16:28:54 +00:00
print ( " \n --- \n " )
2021-04-14 03:12:49 +00:00
print ( " Threads - IN: " + str ( len ( inThreads ) ) )
print ( " Threads - OUT: " + str ( len ( outThreads ) ) )
2021-04-14 16:28:54 +00:00
print ( " \n ACCUMULATED DATA: " )
2021-04-14 15:03:25 +00:00
for threadId in outThreads :
thread = outThreads [ threadId ]
print ( threadId + " : " + str ( thread . queue . qsize ( ) * bufferSize ) )
2021-04-14 16:28:54 +00:00
print ( " \n CONNECTIONS: " )
connCount = 0
connCountIp = { }
with connectionsLock :
for connId in connections :
conn = connections [ connId ]
ip = conn [ 1 ] [ 0 ]
if not ip in connCountIp :
connCountIp [ ip ] = 0
connCountIp [ ip ] + = 1
connCount + = 1
for ip in connCountIp :
print ( ip + " : " + str ( connCountIp [ ip ] ) )
print ( " Overall: " + str ( connCount ) )
2021-04-14 15:03:25 +00:00
time . sleep ( 1 )
2021-04-14 03:12:49 +00:00
2021-04-15 18:54:38 +00:00
def readConfig ( ) :
config = configparser . ConfigParser ( )
config . read ( p ( os . path . splitext ( s ) [ 0 ] + " .ini " ) )
global serverAddr
serverAddrSplit = config [ " default " ] [ " serverAddr " ] . rsplit ( " : " , 1 )
serverAddrSplit [ 1 ] = int ( serverAddrSplit [ 1 ] )
serverAddr = tuple ( serverAddrSplit )
2021-04-13 18:33:16 +00:00
def main ( ) :
2021-04-22 02:29:12 +00:00
global connectionsId
2021-04-15 18:54:38 +00:00
readConfig ( )
2021-04-13 20:06:16 +00:00
serverSocket . bind ( serverAddr )
serverSocket . listen ( 1024 )
2021-04-14 03:12:49 +00:00
# DEBUG
2021-04-22 02:29:12 +00:00
#debug = debugThread()
#debug.start()
2021-04-14 03:12:49 +00:00
# DEBUG END
2021-04-13 18:33:16 +00:00
while True :
connection , address = serverSocket . accept ( )
2021-04-14 16:52:47 +00:00
connection . settimeout ( timeout )
2021-04-14 16:28:54 +00:00
with connectionsLock :
clientCount = 0
ipClientCount = 0
2021-04-22 02:29:12 +00:00
for connectionId in connections :
2021-04-14 16:28:54 +00:00
clientCount + = 1
2021-04-22 02:29:12 +00:00
if connections [ connectionId ] [ " address " ] [ 0 ] == address [ 0 ] :
2021-04-14 16:28:54 +00:00
ipClientCount + = 1
2021-04-22 02:29:12 +00:00
if clientCount + 1 > maxClients :
print ( " Connection closed - too many clients. " )
closeConnection ( connectionId )
continue
if ipClientCount + 1 > maxClientsPerIP :
2021-04-21 12:02:53 +00:00
print ( " Connection closed - same IP connected too many times. " )
2021-04-22 02:29:12 +00:00
closeConnection ( connectionId )
2021-04-14 16:28:54 +00:00
continue
2021-04-13 18:33:16 +00:00
2021-04-22 02:29:12 +00:00
connectionsId + = 1
threadIn = inThread ( str ( connectionsId ) )
connections [ str ( connectionsId ) ] = {
" connection " : connection ,
" address " : address ,
" inThread " : threadIn ,
" outThread " : False ,
" action " : False ,
" user " : False
}
threadIn . start ( )
addThread ( )
2021-04-13 18:33:16 +00:00
2021-04-13 20:06:16 +00:00
if __name__ == ' __main__ ' :
main ( )