コード例 #1
0
ファイル: Term_Server.py プロジェクト: dpogue/WebCyanChat
	def __init__(self):
		self.server = HTTP_Server()
		self.server.redirects["/"] = "console.html"
		self.server.redirects["/console.html"] = {"header": "User-Agent", "value": "iPhone", "location": "textConsole.html"}
		self.sessionQueue = self.server.registerProtocol("term")
		self.connections = list()
		self.prefs = { \
			"http_port": 8080, \
			"term_proc": "/bin/bash", \
			"term_args": "", \
			"term_height": 24, \
			"term_width": 80, \
		}
コード例 #2
0
ファイル: Term_Server.py プロジェクト: NadnerbD/WebCyanChat
	def __init__(self):
		self.server = HTTP_Server()
		self.server.redirects["/"] = "console.html"
		self.server.registerAuthorizer(re.compile("^/(term-socket|console\.html).*$"), self.authorize)
		self.sessionQueue = self.server.registerProtocol("term", "/term-socket")
		self.connections = list()
		self.master = None
		self.prefs = { \
			"enable_http": 0, \
			"http_port": 8080, \
			"enable_https": 1, \
			"https_port": 443, \
			"term_proc": "/bin/bash", \
			"term_args": "", \
			"term_height": 24, \
			"term_width": 80, \
			"term_pass": "******", \
			"https_redirect": 0, \
			"https_cert": "server.crt", \
			"https_key": "server.key", \
		}
コード例 #3
0
ファイル: Term_Server.py プロジェクト: NadnerbD/WebCyanChat
class Term_Server:
	def __init__(self):
		self.server = HTTP_Server()
		self.server.redirects["/"] = "console.html"
		self.server.registerAuthorizer(re.compile("^/(term-socket|console\.html).*$"), self.authorize)
		self.sessionQueue = self.server.registerProtocol("term", "/term-socket")
		self.connections = list()
		self.master = None
		self.prefs = { \
			"enable_http": 0, \
			"http_port": 8080, \
			"enable_https": 1, \
			"https_port": 443, \
			"term_proc": "/bin/bash", \
			"term_args": "", \
			"term_height": 24, \
			"term_width": 80, \
			"term_pass": "******", \
			"https_redirect": 0, \
			"https_cert": "server.crt", \
			"https_key": "server.key", \
		}

	def readPrefs(self, filename="TermServer.conf"):
		log(self, "reading %s" % filename)
		prefsFile = file(filename, 'r')
		prefsData = prefsFile.read()
		prefsFile.close()
		newPrefs = parseToDict(prefsData, ':', '\n')
		for pref in newPrefs:
			newPrefs[pref] = newPrefs[pref].strip()
			if(newPrefs[pref].isdigit()):
				newPrefs[pref] = int(newPrefs[pref])
			if(pref == "log_level"):
				Logger.logging = int(newPrefs[pref])
		self.prefs.update(newPrefs)

	def resize(self, width, height):
		# winsize is 4 unsigned shorts: (ws_row, ws_col, ws_xpixel, ws_ypixel)
		winsize = struct.pack('HHHH', height, width, 0, 0)
		fcntl.ioctl(self.master, termios.TIOCSWINSZ, winsize)

	def authorize(self, headers):
		try:
			return base64.b64decode(headers['authorization'].split(' ')[1]) == self.prefs['term_pass']
		except:
			return False
	
	def start(self):
		# init the terminal emulator
		self.terminal = Terminal(self, self.prefs["term_width"], self.prefs["term_height"])

		(pid, self.master) = os.forkpty()
		if(pid == 0):
			# ensure that the right terminal type is set
			os.environ['TERM'] = 'xterm'
			# launch the target
			os.execv(self.prefs["term_proc"], [self.prefs["term_proc"]] + shlex.split(self.prefs["term_args"]))
		
		# set the attributes of the terminal (size, for now)
		self.resize(self.prefs["term_width"], self.prefs["term_height"])

		# open the psuedo terminal master file (this is what we read/write to)
		self.wstream = os.fdopen(self.master, "w")
		self.rstream = os.fdopen(self.master, "r")

		# this is passed locally so we can shut down only threads that were started by this invocation
		shutdown = threading.Event()

		# start a loop to accept incoming http sessions
		a = threading.Thread(target=self.sessionLoop, name="sessionLoop", args=(shutdown,))
		a.daemon = True
		a.start()

		# start a thread to read output from the shell
		o = threading.Thread(target=self.handleOutput, name="oThread", args=(shutdown,))
		o.daemon = True
		o.start()

		if(self.prefs["enable_http"]):
			# start the http listener
			s = threading.Thread(target=self.server.acceptLoop, name="httpThread", kwargs={
				'port': self.prefs["http_port"],
				'useSSL': False,
				'SSLRedirect': self.prefs["https_port"] if self.prefs["https_redirect"] else False,
			})
			s.daemon = True
			s.start()

		if(self.prefs["enable_https"]):
			# start the https listener
			s = threading.Thread(target=self.server.acceptLoop, name="httpsThread", kwargs={
				'port': self.prefs["https_port"],
				'useSSL': True,
				'SSLCert': self.prefs["https_cert"],
				'SSLKey': self.prefs["https_key"],
			})
			s.daemon = True
			s.start()

		# start a thread to push diff updates to the clients
		u = threading.Thread(target=self.updateLoop, name="updateThread", args=(shutdown,))
		u.daemon = True
		u.start()

		# start a thread to ping clients
		p = threading.Thread(target=self.pingLoop, name="pingLoop", args=(shutdown,))
		p.daemon = True
		p.start()

		# now wait for the subprocess to terminate, and for us to flush the last of it's output
		try:
			os.waitpid(pid, 0)
		except KeyboardInterrupt:
			os.kill(pid, signal.SIGKILL)
			os.waitpid(pid, 0) # wait on the process so we don't create a zombie
		finally:
			#signal the worker threads to shut down
			shutdown.set()

	def handleOutput(self, shutdown):
		parser = Parser(self.rstream)
		while not shutdown.is_set():
			command = parser.getCommand()
			if(command == None):
				return
			log(self, "cmd: %r" % command, 4)
			resp = self.terminal.handleCmd(command)
			if(resp):
				self.wstream.write(resp)
				self.wstream.flush()
			
	def handleInput(self, sock, addr):
		# the first frame sent over the socket must be the password
		log(self, "Accepted term connection from %r" % (addr,))
		self.terminal.sendInit(sock)
		while True:
			try:
				frame = sock.recvFrame()
			except Exception as error:
				# if we hit an error reading from the socket, remove it and end the thread
				log(self, "Error reading from %r: %s" % (addr, error))
				self.connections.remove(sock)
				return
			if len(frame) == 0:
				log(self, "End of stream from %r" % (addr,))
				self.connections.remove(sock)
				return
			if frame[0] == 'k': # keypress
				log(self, "recvd keypress: %r" % frame[1], 4)
				self.wstream.write(frame[1])
				self.wstream.flush()
			elif frame[0] == 'r': #resize
				if len(frame) != 5:
					log(self, "Malformed resize request: %r" % frame)
				sreq = struct.unpack('!HH', frame[1:5])
				log(self, "recvd resize req: %r" % (sreq,))
				with self.terminal.bufferLock:
					self.terminal.resize(sreq[0], sreq[1]) # this will call self.resize
					self.terminal.broadcast(self.terminal.buffer.initMsg(self.terminal.showCursor))

	def sessionLoop(self, shutdown):
		while not shutdown.is_set():
			(sock, addr) = self.sessionQueue.acceptHTTPSession()
			if(shutdown.is_set()):
				# if our primary thread has terminated, put the session back and exit
				self.sessionQueue.insert((sock, addr))
				return
			self.connections.append(sock)
			# start a thread to send input to the shell
			i = threading.Thread(target=self.handleInput, name="iThread", args=(sock, addr))
			i.daemon = True
			i.start()

	def updateLoop(self, shutdown):
		while not shutdown.is_set():
			self.terminal.updateEvent.wait()
			self.terminal.sendDiff()
			time.sleep(0.001)

	def pingLoop(self, shutdown):
		while not shutdown.is_set():
			self.terminal.sendPing()
			time.sleep(30)
コード例 #4
0
class Term_Server:
	def __init__(self):
		self.server = HTTP_Server()
		self.server.redirects["/"] = "console.html"
		self.server.redirects["/console.html"] = {"header": "User-Agent", "value": "iPhone", "location": "textConsole.html"}
		self.sessionQueue = self.server.registerProtocol("term")
		self.connections = list()
		self.master = None
		self.prefs = { \
			"enable_http": 0, \
			"http_port": 8080, \
			"enable_https": 1, \
			"https_port": 443, \
			"term_proc": "/bin/bash", \
			"term_args": "", \
			"term_height": 24, \
			"term_width": 80, \
			"term_pass": "******", \
		}

	def readPrefs(self, filename="TermServer.conf"):
		log(self, "reading %s" % filename)
		prefsFile = file(filename, 'r')
		prefsData = prefsFile.read()
		prefsFile.close()
		newPrefs = parseToDict(prefsData, ':', '\n')
		for pref in newPrefs:
			newPrefs[pref] = newPrefs[pref].strip()
			if(newPrefs[pref].isdigit()):
				newPrefs[pref] = int(newPrefs[pref])
			if(pref == "log_level"):
				Logger.logging = int(newPrefs[pref])
		self.prefs.update(newPrefs)

	def resize(self, width, height):
		# winsize is 4 unsigned shorts: (ws_row, ws_col, ws_xpixel, ws_ypixel)
		winsize = struct.pack('HHHH', height, width, 0, 0)
		fcntl.ioctl(self.master, termios.TIOCSWINSZ, winsize)
		
	
	def start(self):
		# init the terminal emulator
		self.terminal = Terminal(self, self.prefs["term_width"], self.prefs["term_height"])

		(pid, self.master) = os.forkpty()
		if(pid == 0):
			# ensure that the right terminal type is set
			os.environ['TERM'] = 'xterm'
			# launch the target
			os.execv(self.prefs["term_proc"], [self.prefs["term_proc"]] + shlex.split(self.prefs["term_args"]))
		
		# set the attributes of the terminal (size, for now)
		self.resize(self.prefs["term_width"], self.prefs["term_height"])

		# open the psuedo terminal master file (this is what we read/write to)
		self.wstream = os.fdopen(self.master, "w")
		self.rstream = os.fdopen(self.master, "r")

		# this is passed locally so we can shut down only threads that were started by this invocation
		shutdown = threading.Event()

		# start a loop to accept incoming http sessions
		a = threading.Thread(target=self.sessionLoop, name="sessionLoop", args=(shutdown,))
		a.daemon = True
		a.start()

		# start a thread to read output from the shell
		o = threading.Thread(target=self.handleOutput, name="oThread", args=(shutdown,))
		o.daemon = True
		o.start()

		if(self.prefs["enable_http"]):
			# start the http listener
			s = threading.Thread(target=self.server.acceptLoop, name="httpThread", args=(self.prefs["http_port"], False))
			s.daemon = True
			s.start()

		if(self.prefs["enable_https"]):
			# start the https listener
			s = threading.Thread(target=self.server.acceptLoop, name="httpsThread", args=(self.prefs["https_port"], True))
			s.daemon = True
			s.start()

		# start a thread to push diff updates to the clients
		u = threading.Thread(target=self.updateLoop, name="updateThread", args=(shutdown,))
		u.daemon = True
		u.start()

		# now wait for the subprocess to terminate, and for us to flush the last of it's output
		try:
			os.waitpid(pid, 0)
		except KeyboardInterrupt:
			os.kill(pid, signal.SIGKILL)
			os.waitpid(pid, 0) # wait on the process so we don't create a zombie
		finally:
			#signal the worker threads to shut down
			shutdown.set()

	def handleOutput(self, shutdown):
		parser = CommandParser(self.rstream)
		while not shutdown.is_set():
			command = parser.getCommand()
			if(command == None):
				return
			log(self, "cmd: %r" % command, 4)
			self.terminal.handleCmd(command)
			
	def handleInput(self, sock, addr):
		# the first frame sent over the socket must be the password
		passwd = sock.recvFrame()
		if(passwd != self.prefs["term_pass"]):
			log(self, "Incorrect password attempt from %s: %r" % (addr, passwd))
			sock.send("{\"cmd\": \"badpass\"}")
			sock.close()
			return
		log(self, "Accepted password from %r" % (addr,))
		self.terminal.sendInit(sock)
		while True:
			try:
				char = chr(int(sock.recvFrame()))
			except Exception as error:
				# if we hit an error reading from the socket, remove it and end the thread
				log(self, "Error reading from %r: %s" % (addr, error))
				self.connections.remove(sock)
				return
			if(char == '\r'):
				char = '\n'
			log(self, "recvd: %r" % char, 4)
			self.wstream.write(char)
			self.wstream.flush()

	def sessionLoop(self, shutdown):
		while not shutdown.is_set():
			(sock, addr) = self.sessionQueue.acceptHTTPSession()
			if(shutdown.is_set()):
				# if our primary thread has terminated, put the session back and exit
				self.sessionQueue.insert((sock, addr))
				return
			self.connections.append(sock)
			# start a thread to send input to the shell
			i = threading.Thread(target=self.handleInput, name="iThread", args=(sock, addr))
			i.daemon = True
			i.start()

	def updateLoop(self, shutdown):
		# condenses rapid updates into a single message
		while not shutdown.is_set():
			self.terminal.updateEvent.wait()
			prev = self.terminal.lastUpdate
			while True:
				time.sleep(0.001)
				if(prev != self.terminal.lastUpdate):
					prev = self.terminal.lastUpdate
				else:
					break
			self.terminal.sendDiff()
			self.terminal.updateEvent.clear()
コード例 #5
0
ファイル: Term_Server.py プロジェクト: dpogue/WebCyanChat
class Term_Server:
	def __init__(self):
		self.server = HTTP_Server()
		self.server.redirects["/"] = "console.html"
		self.server.redirects["/console.html"] = {"header": "User-Agent", "value": "iPhone", "location": "textConsole.html"}
		self.sessionQueue = self.server.registerProtocol("term")
		self.connections = list()
		self.prefs = { \
			"http_port": 8080, \
			"term_proc": "/bin/bash", \
			"term_args": "", \
			"term_height": 24, \
			"term_width": 80, \
		}

	def readPrefs(self, filename="TermServer.conf"):
		global logging
		log(self, "reading %s" % filename)
		prefsFile = file(filename, 'r')
		prefsData = prefsFile.read()
		prefsFile.close()
		newPrefs = parseToDict(prefsData, ':', '\n')
		for pref in newPrefs:
			newPrefs[pref] = newPrefs[pref].strip()
			if(newPrefs[pref].isdigit()):
				newPrefs[pref] = int(newPrefs[pref])
			if(pref == "log_level"):
				logging = int(newPrefs[pref])
		self.prefs.update(newPrefs)
	
	def start(self):
		# init the terminal emulator
		self.terminal = Terminal(self.connections, self.prefs["term_width"], self.prefs["term_height"])

		(pid, master) = os.forkpty()
		if(pid == 0):
			# ensure that the right terminal type is set
			os.environ['TERM'] = 'xterm'
			# launch the target
			os.execv(self.prefs["term_proc"], [self.prefs["term_proc"]] + shlex.split(self.prefs["term_args"]))

		# set the attributes of the terminal (size, for now)
		# winsize is 4 unsigned shorts: (ws_row, ws_col, ws_xpixel, ws_ypixel)
		winsize = struct.pack('HHHH', self.prefs["term_height"], self.prefs["term_width"], 0, 0)
		fcntl.ioctl(master, termios.TIOCSWINSZ, winsize)

		# open the psuedo terminal master file (this is what we read/write to)
		wstream = os.fdopen(master, "w")
		rstream = os.fdopen(master, "r")

		# start a loop to accept incoming http sessions
		a = threading.Thread(target=self.sessionLoop, name="sessionLoop", args=(wstream,))
		a.daemon = True
		a.start()

		# start a thread to read output from the shell
		o = threading.Thread(target=self.handleOutput, name="oThread", args=(rstream,))
		o.daemon = True
		o.start()

		# start the http server
		s = threading.Thread(target=self.server.acceptLoop, name="httpThread", args=(self.prefs["http_port"],))
		s.daemon = True
		s.start()

		# start a thrad to push diff updates to the clients
		u = threading.Thread(target=self.updateLoop, name="updateThread", args=())
		u.daemon = True
		u.start()

		# now wait for the subprocess to terminate, and for us to flush the last of it's output
		try:
			os.waitpid(pid, 0)
		except KeyboardInterrupt:
			os.kill(pid, signal.SIGKILL)
			os.waitpid(pid, 0) # wait on the process so we don't create a zombie

	def handleOutput(self, stream):
		parser = CommandParser(stream)
		while True:
			command = parser.getCommand()
			if(command == None):
				return
			log(self, "cmd: %r" % command, 4)
			self.terminal.handleCmd(command)
			
	def handleInput(self, sock, stream):
		self.terminal.sendInit(sock)
		while True:
			char = chr(int(sock.recvFrame()))
			if(char == '\r'):
				char = '\n'
			log(self, "recvd: %r" % char, 4)
			stream.write(char)
			stream.flush()

	def sessionLoop(self, stream):
		while True:
			(sock, addr) = self.sessionQueue.acceptHTTPSession()
			self.connections.append(sock)
			# start a thread to send input to the shell
			i = threading.Thread(target=self.handleInput, name="iThread", args=(sock, stream))
			i.daemon = True
			i.start()

	def updateLoop(self):
		# condenses rapid updates into a single message
		while True:
			self.terminal.updateEvent.wait()
			prev = self.terminal.lastUpdate
			while True:
				time.sleep(0.001)
				if(prev != self.terminal.lastUpdate):
					prev = self.terminal.lastUpdate
				else:
					break
			self.terminal.sendDiff()
			self.terminal.updateEvent.clear()
コード例 #6
0
	def __init__(self):
		self.starttime = time.strftime('%a %b %d %H:%M:%S %Z %Y')
		self.connections = self.connectionList(self)
		self.quit = threading.Event()
		self.maxconnections = 0
		self.maxlogins = 0
		self.totallogins = 0
		self.banlist = []
		self.authDict = dict()
		self.HTTPServ = HTTP_Server()
		self.HTTPServ.redirects['/'] = "/ChatClient.html"
		self.HTTPServ.redirects["/ChatClient.html"] = {"header": "User-Agent", "value": "iPhone", "location": "MobileClient.html"}
		self.welcomeMessage = """
			Welcome to %(server_version)s
			Using protocol versions 0, 1 and 2
			Written by 'Nadnerb'
			There are only a few rules:
			    Don't send invalid commands. ;)
			    Language filter is currently %(censor_level)s
			    Bans are currently %(enable_bans)s
			Comments can be sent to [email protected]
			Server commands now available, type !\? at the beginning of a line."""
		self.welcomeParams = { \
			"server_version": ["value"], \
			"censor_level": ["disabled", "replace", "warn", "kick"], \
			"enable_bans": ["disabled", "enabled"], \
		}
		self.prefs = { \
			"server_version": "CyanChat (Py CC Server 2.1)", \
			"enable_http": 1, \
			"enable_https": 0, \
			"enable_cc": 1, \
			"cc_port": 1812, \
			"http_port": 81, \
			"https_port": 443, \
			"welcome_file": "CCWelcome.conf", \
			"word_file": "BadWords.conf", \
			"enable_bans": 0, \
			"censor_level": 1, \
			"debug_mode": 0, \
			"use_reverse_dns": 1, \
			"enable_admin_extensions": 1, \
			"enable_protocol_extensions": 1, \
			"auth_1": "huru", \
			"auth_2": "tia", \
			"enable_bouncer": 0, \
			"bounce_buffer_size": 40, \
		}
		# dict levels
		# 0 - still bad with * inserted and leaded and ended by letters ("f**k", "fluck", "pairofducks")
		# 1 - bad if standalone with no inserted chars, warnable if led or ended with letters ("shit" kills, but "shits" warns)
		# 2 - bad if standalone with no inserted chars (ie "c**t" but not "count")
		# 3 - warnable if standalone with no inserted chars (ie "hell" but not "hello")
		self.badWords = {\
			"f**k": 0, \
			"shit": 1, \
			"bitch": 0, \
			"c**t": 1, \
			"anus": 2, \
			"penis": 0, \
			"v****a": 0, \
			"t**s": 0, \
			"asshole": 0, \
			"bastard": 0, \
			"hell": 3, \
			"s**t": 0, \
			"w***e": 0, \
			"nigger": 0, \
			"pussy": 0, \
			"dickhead": 1, \
		}
コード例 #7
0
class CC_Server:
	class connectionList:
		def __init__(self, parent):
			self.broadcastLock = threading.Lock()
			self.accessLock = threading.Lock()
			self.connections = list()
			self.parent = parent
		
		def broadcast(self, message, toAll=0):
			self.broadcastLock.acquire()
			for connection in self.connections:
				if(toAll or connection.named):
					connection.send(message)
			self.broadcastLock.release()
		
		def sendUserList(self):
			log(self, "sending userlist", 2)
			message = "35"
			for connection in self.connections:
				if(connection.named):
					message += "|%s" % connection.msg()
			self.broadcast(message, 1)
		
		def findByName(self, name):
			for user in self.connections:
				if(user.name == name):
					return user
			return None

		def findByBounceKey(self, key):
			for user in self.connections:
				if(user.bounceKey == key):
					return user
			return None
		
		def debugMsg(self, connection, message):
			if(self.parent.prefs["debug_mode"]):
				self.sendPM(CC_Server.chatServer(2), connection, message, 1)
		
		def debugOrKick(self, connection, message):
			if(self.parent.prefs["debug_mode"]):
				self.sendPM(CC_Server.chatServer(3), connection, message, 1)
			else:
				self.kick(connection)
		
		def sendChat(self, sender, msg, flag=1):
			if(len(msg.strip()) > 0):
				message = "31|%s|^%d%s" % (sender.msg(), flag, msg)
				self.broadcast(message)
		
		def sendPM(self, sender, target, msg, flag=0):
			if(len(msg.strip()) > 0):
				message = "21|%s|^%d%s" % (sender.msg(), flag, msg)
				target.send(message)
		
		def changeName(self, connection, name):
			self.accessLock.acquire()
			connectMessages = { \
				1: "<links in from Cyan Worlds Age>", \
				4: "<links in from Cyan Guest Age>", \
			}
			if(len(name) < 20 and len(name) > 1 and self.findByName(connection.name[0] + name) == None):
				self.debugMsg(connection, "Setting name to '%s' - login - successful" % name)
				oldname = connection.name[1:]
				wasnamed = connection.named
				connection.name = connection.name[0] + name
				connection.named = 1
				connection.send("11")
				if(wasnamed and self.parent.prefs["enable_protocol_extensions"]):
					self.sendChat(connection, "<[%s] is now known as [%s]>" % (oldname, name), 2)
				elif(connectMessages.has_key(connection.level())):
					self.sendChat(connection, connectMessages[connection.level()], 2)
				elif(HAS_DNSPYTHON and self.parent.prefs["use_reverse_dns"]):
					try:
						addr = reversename.from_address(connection.addr[0])
						host = '.'.join(str(resolver.query(addr, "PTR")[0]).split('.')[-3:-1])
					except:
						host = "somewhere on the internet"
					self.sendChat(connection, "<links in from %s Age>" % host, 2)
				else:
					self.sendChat(connection, "<links in from somewhere on the internet Age>", 2)
				self.sendUserList()
				self.parent.totallogins += 1
				if(self.currentlogins() > self.parent.maxlogins):
					self.parent.maxlogins = self.currentlogins()
			else:
				log(self, "rejecting name: %s" % name, 2)
				connection.send("10")
			self.accessLock.release()
		
		def removeName(self, connection, cause=0):
			self.accessLock.acquire()
			if(connection.named):
				self.debugMsg(connection, "Removing name - logout - successful")
				if(cause > 0):
					connection.named = 0
				if(cause == 2 and self.parent.prefs["enable_protocol_extensions"]):
					self.sendChat(connection, "<was kicked by the server *ZZZZZWHAP*>", 3)
				elif(cause == 1):
					self.sendChat(connection, "<mistakenly used an unsafe linking book without a maintainer's suit *ZZZZZWHAP*>", 3)
				else:
					self.sendChat(connection, "<links safely back to their home Age>", 3)
				connection.named = 0
				connection.name = connection.name[0] + str(connection.addr[1])
				self.sendUserList()
			self.accessLock.release()
		
		def sendWelcome(self, target, message):
			for line in message:
				if(len(line) > 0):
					target.send("40|%d%s" % (target.version, line))
		
		def sendIgnore(self, sender, target):
			self.accessLock.acquire()
			target.send("70|%s" % sender.msg())
			# cho broadcasts the userlist immediately following an ignore forward
			# this is to refresh the client userlist
			self.sendUserList()
			self.accessLock.release()
		
		def setLevel(self, connection, level):
			self.accessLock.acquire()
			connection.name = str(level) + connection.name[1:]
			self.sendUserList()
			self.accessLock.release()
		
		def checkAuthKey(self, authKey):
			log(self, "checking authkey: %s" % authKey, 3)
			for connection in self.connections:
				if(connection.authKey != "" and connection.authKey == authKey):
					return connection.authLevel
			return 0
		
		def kick(self, target):
			# disconnected admins may be kicked, as they may be ghosting
			if(target.authLevel < 2 or target.bounceDisconnected.isSet()):
				# prevent the bouncer from holding on to the connection if we're kicking it
				target.bounceEnable = False
				if(target.bounceDisconnected.isSet()):
					# restart the target's sock loop so it will be cleaned up and not wait forever
					target.bounceConnect.set()
				else:
					# only send this PM if there's a connected client to see it
					self.sendPM(CC_Server.chatServer(3), target, "An oscillating inharmonic interference error(OIIE) was detected. Please close and restart your browser.", 1)
				self.remove(target, 2)
				if(self.parent.prefs["enable_bans"]):
					self.parent.banlist.append(target.addr[0])
		
		def insert(self, connection):
			self.accessLock.acquire()
			# give the connection a random (unique) bounceKey
			while(connection.bounceKey == "" or self.findByBounceKey(connection.bounceKey) != None):
				connection.bounceKey = hex(random.randint(0, 0x7FFFFFFE))[2:]
			self.connections.append(connection)
			if(len(self.connections) > self.parent.maxconnections):
				self.parent.maxconnections = len(self.connections)
			self.sendUserList()
			self.accessLock.release()
		
		def remove(self, connection, cause=1):
			try:
				connection.sock.close()
			except:
				pass
			connection.status = 0
			self.removeName(connection, cause)
			self.accessLock.acquire()
			if(connection in self.connections):
				self.connections.remove(connection)
			self.accessLock.release()
		
		def currentlogins(self):
			total = 0
			for connection in self.connections:
				if(connection.named):
					total += 1
			return total
	
	class CC_Connection:
		def __init__(self, sock, addr):
			self.comLock = threading.Lock()
			self.name = "0%s" % addr[1]
			self.addr = addr
			self.sock = sock
			self.ipHash = addr[0].replace('.', '')
			self.status = 1 # 0 disconnected, 1 connected, 2 banned
			self.named = 0
			self.version = 0 # client version
			self.authLevel = 0
			self.authKey = str()
			# bouncer data
			self.bounceEnable = 0
			self.bounceBufferSize = 0
			self.bounceKey = ""
			self.bounceDisconnected = threading.Event()
			self.bounceBursting = 0
			self.bounceConnect = threading.Event()
			self.bounceBuffer = []
		
		def __repr__(self):
			return "[%s] %r" % (self.name, self.addr)
		
		def msg(self):
			if(self.ipHash):
				return "%s,%s" % (self.name, self.ipHash)
			else:
				return self.name
		
		def level(self):
			return int(self.name[0])
		
		def send(self, message):
			self.comLock.acquire()
			if(self.bounceEnable and not self.bounceBursting):
				self.bounceBuffer.append(message)
				# this naively buffers all messages, probably non-optimal, but simple
				while(len(self.bounceBuffer) > self.bounceBufferSize):
					self.bounceBuffer.remove(self.bounceBuffer[0])
			if(not self.bounceDisconnected.isSet() or self.bounceBursting):
				try:
					log(self, "sending: %s to %s" % (repr(message), self), 2)
					self.sock.send(message + "\r\n")
				except Exception as error:
					log(self, "send error to %s: %s" % (self, error), 2)
			self.comLock.release()
	
	def __init__(self):
		self.starttime = time.strftime('%a %b %d %H:%M:%S %Z %Y')
		self.connections = self.connectionList(self)
		self.quit = threading.Event()
		self.maxconnections = 0
		self.maxlogins = 0
		self.totallogins = 0
		self.banlist = []
		self.authDict = dict()
		self.HTTPServ = HTTP_Server()
		self.HTTPServ.redirects['/'] = "/ChatClient.html"
		self.HTTPServ.redirects["/ChatClient.html"] = {"header": "User-Agent", "value": "iPhone", "location": "MobileClient.html"}
		self.welcomeMessage = """
			Welcome to %(server_version)s
			Using protocol versions 0, 1 and 2
			Written by 'Nadnerb'
			There are only a few rules:
			    Don't send invalid commands. ;)
			    Language filter is currently %(censor_level)s
			    Bans are currently %(enable_bans)s
			Comments can be sent to [email protected]
			Server commands now available, type !\? at the beginning of a line."""
		self.welcomeParams = { \
			"server_version": ["value"], \
			"censor_level": ["disabled", "replace", "warn", "kick"], \
			"enable_bans": ["disabled", "enabled"], \
		}
		self.prefs = { \
			"server_version": "CyanChat (Py CC Server 2.1)", \
			"enable_http": 1, \
			"enable_https": 0, \
			"enable_cc": 1, \
			"cc_port": 1812, \
			"http_port": 81, \
			"https_port": 443, \
			"welcome_file": "CCWelcome.conf", \
			"word_file": "BadWords.conf", \
			"enable_bans": 0, \
			"censor_level": 1, \
			"debug_mode": 0, \
			"use_reverse_dns": 1, \
			"enable_admin_extensions": 1, \
			"enable_protocol_extensions": 1, \
			"auth_1": "huru", \
			"auth_2": "tia", \
			"enable_bouncer": 0, \
			"bounce_buffer_size": 40, \
		}
		# dict levels
		# 0 - still bad with * inserted and leaded and ended by letters ("f**k", "fluck", "pairofducks")
		# 1 - bad if standalone with no inserted chars, warnable if led or ended with letters ("shit" kills, but "shits" warns)
		# 2 - bad if standalone with no inserted chars (ie "c**t" but not "count")
		# 3 - warnable if standalone with no inserted chars (ie "hell" but not "hello")
		self.badWords = {\
			"f**k": 0, \
			"shit": 1, \
			"bitch": 0, \
			"c**t": 1, \
			"anus": 2, \
			"penis": 0, \
			"v****a": 0, \
			"t**s": 0, \
			"asshole": 0, \
			"bastard": 0, \
			"hell": 3, \
			"s**t": 0, \
			"w***e": 0, \
			"nigger": 0, \
			"pussy": 0, \
			"dickhead": 1, \
		}
	
	
	def chatServer(level=2):
		server = CC_Server.CC_Connection(None, ('', ''))
		server.name = "%dChatServer" % level
		return server
	chatServer = staticmethod(chatServer)
	
	def readPrefs(self, filename="CCServer.conf"):
		log(self, "reading %s" % filename)
		prefsFile = file(filename, 'r')
		prefsData = prefsFile.read()
		prefsFile.close()
		newPrefs = parseToDict(prefsData, ':', '\n')
		for pref in newPrefs:
			newPrefs[pref] = newPrefs[pref].strip()
			if(newPrefs[pref].isdigit()):
				newPrefs[pref] = int(newPrefs[pref])
			if(pref == "log_level"):
				Logger.logging = int(newPrefs[pref])
		self.prefs.update(newPrefs)
	
	def readWelcome(self, filename=None):
		if(not filename):
			filename = self.prefs["welcome_file"]
		welcomeFile = file(filename, 'r')
		self.welcomeMessage = readTo(welcomeFile, "\n\n", ['\r'])
		welcomeParams = welcomeFile.read()
		welcomeFile.close()
		welcomeParams = parseToDict(welcomeParams, ':', '\n')
		for param in welcomeParams:
			parts = welcomeParams[param].split(',')
			for part in range(len(parts)):
				parts[part] = parts[part].strip()
			welcomeParams[param] = parts
		self.welcomeParams = welcomeParams

	def readWordList(self, filename=None):
		if(not filename):
			filename = self.prefs["word_file"]
		wordFile = file(filename, 'r')
		wordData = wordFile.read()
		wordFile.close()
		newWords = parseToDict(wordData, ':', '\n')
		for word in newWords:
			newWords[word] = int(newWords[word])
		self.badWords.update(newWords)
	
	def parseAuth(self):
		authLevel = 1
		while(self.prefs.has_key("auth_%d" % authLevel)):
			self.authDict[str(self.prefs["auth_%d" % authLevel])] = authLevel
			log(self, "set auth password for level %d: %s" % (authLevel, self.prefs["auth_%d" % authLevel]), 3)
			authLevel += 1
	
	def parseWelcome(self):
		insertValues = dict()
		for paramKey in self.welcomeParams:
			if(self.prefs.has_key(paramKey)):
				if(len(self.welcomeParams[paramKey]) > 1):
					insertValues[paramKey] = self.welcomeParams[paramKey][self.prefs[paramKey]]
				else:
					insertValues[paramKey] = self.prefs[paramKey]
			else:
				insertValues[paramKey] = "<%s>" % paramKey
		welcomeList = (self.welcomeMessage.replace('\t', '') % insertValues).splitlines()
		welcomeList.reverse()
		return welcomeList
	
	def start(self):
		self.parseAuth()
		self.readWelcome()
		self.readWordList()
		if(self.prefs["enable_cc"]):
			acceptThread = threading.Thread(None, self.acceptLoop, "acceptLoop", (self.prefs["cc_port"],))
			acceptThread.setDaemon(1)
			acceptThread.start()
		if(self.prefs["enable_http"]):
			# start the http server's thread
			HTTPServThread = threading.Thread(None, self.HTTPServ.acceptLoop, "HTTPServThread", (self.prefs["http_port"],))
			HTTPServThread.setDaemon(1)
			HTTPServThread.start()
			HTTPWatchdogThread = threading.Thread(None, self.watchThread, "HTTPWatchdogThread", (HTTPServThread,))
			HTTPWatchdogThread.setDaemon(1)
			HTTPWatchdogThread.start()
		if(self.prefs["enable_https"]):
			# start the http server's https thread
			HTTPSServThread = threading.Thread(None, self.HTTPServ.acceptLoop, "HTTPSServThread", (self.prefs["https_port"], True))
			HTTPSServThread.setDaemon(1)
			HTTPSServThread.start()
			HTTPSWatchdogThread = threading.Thread(None, self.watchThread, "HTTPSWatchdogThread", (HTTPSServThread,))
			HTTPSWatchdogThread.setDaemon(1)
			HTTPSWatchdogThread.start()
		if(self.prefs["enable_http"] or self.prefs["enable_https"]):
			# accept websockets from the http server
			acceptThread = threading.Thread(None, self.acceptHTTP, "acceptHttpLoop", ())
			acceptThread.setDaemon(1)
			acceptThread.start()
		self.run()
	
	def watchThread(self, servThread):
		servThread.join()
		log(self, "%s terminated" % servThread.getName())
		self.quit.set() #Terminate the server if a server thread dies

	def run(self):
		while(not self.quit.isSet()):
			time.sleep(10)
		self.quit.wait()
	
	def addConnection(self, sock, addr):
		newConnection = self.CC_Connection(sock, addr)
		if(addr[0] in self.banlist):
			newConnection.status = 2
			log(self, "banned connection attempt from: %s" % newConnection.addr[0], 2)
		else:
			self.connections.insert(newConnection)
			log(self, "added new connection %s" % newConnection, 2)
		sockThread = threading.Thread(None, self.sockLoop, "sockLoop", (newConnection,))
		sockThread.setDaemon(1)
		sockThread.start()
		cleanupThread = threading.Thread(None, self.cleanSock, "cleanSock", (newConnection, sockThread))
		cleanupThread.setDaemon(1)
		cleanupThread.start()
	
	def acceptLoop(self, port=1812): #Threaded per-server
		listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		try:
			listener.bind(('', port))
		except:
			log(self, "failed to bind port %d" % port)
			self.quit.set()
			return
		log(self, "listening on port %d" % port)
		while 1:
			listener.listen(1)
			(sock, addr) = listener.accept()
			self.addConnection(sock, addr)
	
	def acceptHTTP(self): #Threaded per-server
		acceptQueue = self.HTTPServ.registerProtocol("cyanchat")
		while 1:
			(sock, addr) = acceptQueue.acceptHTTPSession()
			self.addConnection(sock, addr)
	
	def cleanSock(self, connection, sockThread):
		sockThread.join()
		self.connections.remove(connection)
		log(self, "connection socket removed: %r" % (connection.addr,), 2)
	
	def sockLoop(self, connection): #Threaded per-socket
		while 1:
			try:
				line = readTo(connection.sock, '\n', ['\r'])
			except Exception as error:
				log(self, "error reading from socket on %s: %s" % (connection, error), 2)
				line = None
			if(not line or connection.status == 0):
				if(connection.bounceEnable and connection.named and connection.status != 0):
					log(self, "bounced session %s disconnected" % connection, 2)
					connection.bounceDisconnected.set()
					connection.bounceConnect.wait()
					# when the session is reconnected, bounceConnect will be set
					connection.bounceConnect.clear()
					connection.bounceDisconnected.clear()
					continue
				else:
					log(self, "lost connection to %s" % connection, 2)
					return
			#line = line.rstrip("\r\n")
			line = line.strip()
			log(self, "received: %r from %s" % (line, connection), 2) 
			line = self.censor(line)
			if(type(line) == int):
				if(line == 1): # warn
					self.connections.sendPM(self.chatServer(2), connection, "I can't repeat that!", 1)
					continue
				elif(line == 2): # kick/ban
					self.connections.kick(connection)
			msg = line.split('|', 1)
			cmd = msg[0]
			if(len(msg) == 2):
				msg = msg[1]
			else:
				msg = ''
			if(not cmd.isdigit()):
				log(self, "invalid command: %s from %s" % (cmd, connection), 2)
				self.connections.debugOrKick(connection, "ERROR>Unknown command. Line sent=%s" % line)
				continue
			self.handleMsg(connection, int(cmd), msg)
	
	def handleMsg(self, connection, cmd, msg):
		log(self, "command: %s from %s" % (cmd, connection), 2)
		if(connection.status == 2 and cmd != 40):
			self.connections.sendPM(self.chatServer(3), connection, "Access denied.", 1)
		elif(cmd == 40): # announce
			connection.version = int(msg)
			if(connection.status == 2):
				banMsg = ["This ip address[/%s] is blocked from using CyanChat until tomorrow." % connection.addr[0]]
				self.connections.sendWelcome(connection, banMsg)
			else:
				self.connections.sendWelcome(connection, self.parseWelcome())
		elif(cmd == 10): # set name
			self.connections.changeName(connection, msg)
		elif(cmd == 15): # logout
			self.connections.removeName(connection)
		elif(cmd == 30): # chat
			if(msg[2:].startswith("!\\")):
				if(self.handleServCmd(connection, msg[4:])):
					return
			if(connection.named):
				self.connections.debugMsg(connection, "Send broadcast message - successful")
				self.connections.sendChat(connection, msg[2:])
			else:
				self.connections.kick(connection)
		elif(cmd == 20): # pm
			if(connection.named):
				(user, msg) = msg.split("|", 1)
				user = user.split(",", 1)
				target = self.connections.findByName(user[0])
				if(target):
					self.connections.debugMsg(connection, "Send private message - successful")
					self.connections.sendPM(connection, target, msg[2:])
			else:
				self.connections.kick(connection)
		elif(cmd == 70): # ignore
			target = self.connections.findByName(msg)
			if(target):
				self.connections.debugMsg(connection, "Ignore user successful.")
				self.connections.sendIgnore(connection, target)
		else:
			if(self.prefs["enable_admin_extensions"]):
				self.handleExt(connection, cmd, msg)
			if(self.prefs["enable_bouncer"]):
				self.handleBounce(connection, cmd, msg)
	
	def handleExt(self, connection, cmd, msg):
		if(cmd == 12): # auth
			if(self.authDict.has_key(msg)):
				connection.authLevel = self.authDict[msg]
				connection.authKey = hex(random.randint(0, 0x7FFFFFFE))[2:]
				connection.send("13|%d|%s" % (connection.authLevel, connection.authKey))
			else:
				log(self, "invalid password %s attempted by %s" % (msg, connection), 2)
				self.connections.kick(connection)
		elif(cmd == 50): # change level
			if(connection.authLevel > 0):
				msg = msg.split("|")
				target = self.connections.findByName(msg[0])
				if(target):
					self.connections.setLevel(target, int(msg[1]))
		elif(cmd == 51): # kick/ban
			if(connection.authLevel > 0):
				target = self.connections.findByName(msg)
				if(target):
					self.connections.kick(target)
		elif(cmd == 52): # clear bans
			if(connection.authLevel > 0):
				self.banlist = list()
		elif(cmd == 53): # change self level
			if(connection.authLevel > 0):
				self.connections.setLevel(connection, int(msg))
		elif(cmd == 60): # ChatServer message
			if(connection.authLevel > 1):
				self.connections.sendChat(self.chatServer(2), msg)
		elif(cmd == 80): # remote shutdown
			if(connection.authLevel > 1):
				self.connections.sendChat(self.chatServer(2), "Warning: Server is now shutting down")
				self.quit.set()
		elif(cmd == 90): # reload config
			if(connection.authLevel > 1):
				self.readPrefs()
				self.parseAuth()
				self.readWelcome()
				self.readWordList()
	
	def handleBounce(self, connection, cmd, msg):
		if(cmd == 100): # bounce key request
			connection.send("101|%s" % connection.bounceKey)
			connection.bounceEnable = 1
			connection.bounceBufferSize = self.prefs["bounce_buffer_size"]
		elif(cmd == 102): # bounce connect request
			bounceTarget = self.connections.findByBounceKey(msg)
			if(bounceTarget):
				if(not bounceTarget.bounceDisconnected.isSet()):
					# forcibly disconnect the client
					bounceTarget.sock.close()
					# and wait for sockLoop to notice
					bounceTarget.bounceDisconnected.wait()
				bounceTarget.sock = connection.sock
				connection.status = 0 # mark this session to be removed
				connection.sock = None # remove the sock from the original session
				bounceTarget.bounceBursting = 1
				bounceTarget.send("103|%s" % bounceTarget.name) # bounce connect accept
				for line in bounceTarget.bounceBuffer:
					bounceTarget.send(line)
				bounceTarget.send("104") # end of bounce init burst
				bounceTarget.bounceBursting = 0
				bounceTarget.bounceConnect.set() # resume the sock recv thread
			else:
				connection.send("102") # bounce connect reject
		elif(cmd == 104): # disable bounce request
			connection.bounceEnable = 0
			connection.bounceBuffer = []
	
	def handleServCmd(self, connection, msg):
		if(msg == "?"):
			self.showHelp(connection)
		elif(msg == "time"):
			self.showTime(connection)
		elif(msg == "stats"):
			self.showStats(connection)
		# extended chat commands for server administration
		elif(connection.authLevel > 1 and self.prefs["enable_admin_extensions"]):
			if(msg == "reload"):
				self.readPrefs()
			elif(msg.startswith("set ")):
				args = msg[4:].split(" ", 1)
				if(len(args) == 2):
					self.prefs[args[0]] = int(args[1]) if args[1].isdigit() else args[1]
				self.connections.sendPM(self.chatServer(2), connection, "%s=%r" % (args[0], self.prefs[args[0]]), 1)
			elif(msg.startswith("get ")):
				args = msg[4:].split(" ")
				for arg in args:
					if(self.prefs.has_key(arg)):
						self.connections.sendPM(self.chatServer(2), connection, "%s=%r" % (arg, self.prefs[arg]), 1)
			else:
				return 0
			# do these in case a relevant pref was changed
			self.parseAuth()
			self.readWelcome()
			self.readWordList()
		else:
			return 0
		return 1
	
	def showHelp(self, connection):
		commandsmessage = [
			'Server commands:', \
			'!\\stats	(displays server stats)', \
			'!\\time	(displays server current time)' \
		]
		if(connection.authLevel > 1 and self.prefs["enable_admin_extensions"]):
			commandsmessage += [ \
				'!\\reload	(reloads server config file)', \
				'!\\set <pref> <value>	(sets a pref value)', \
				'!\\get <pref>	(displays a pref value)' \
			]
		commandsmessage.reverse()
		for line in commandsmessage:
			self.connections.sendPM(self.chatServer(2), connection, line, 1)
		
	def showTime(self, connection):
		self.connections.sendPM(self.chatServer(2), connection, "Current local server time is %s" % time.strftime('%a %b %d %H:%M:%S %Z %Y'), 1)
		
	def showStats(self, connection):
		statsmessage = [ \
			"The highest number of logins at one time is %d and highest number of connections is %d." % (self.maxlogins, self.maxconnections), \
			"Currently, there are %d people logged in. And %d connections." % (self.connections.currentlogins(), len(self.connections.connections)), \
			"There have been %d logins since CyanChat was started on %s" % (self.totallogins, self.starttime), \
			self.prefs["server_version"], \
		]
		for line in statsmessage:
			self.connections.sendPM(self.chatServer(2), connection, line, 1)
	
	def censor(self, line):
		# dict levels
		# 0 - still bad with * inserted and leaded and ended by letters ("f**k", "fluck", "pairofducks")
		# 1 - bad if standalone with no inserted chars, warnable if led or ended with letters ("shit" kills, but "shits" warns)
		# 2 - bad if standalone with no inserted chars (ie "c**t" but not "count")
		# 3 - warnable if standalone with no inserted chars (ie "hell" but not "hello")
		matches = []
		for key in self.badWords:
			for startIndex in range(len(line)):
				nonMatchChars = 0
				matchChars = 0
				endIndex = startIndex
				while nonMatchChars < 2 and matchChars < len(key) and endIndex < len(line):
					if(line[endIndex].lower() == key[matchChars].lower()):
						matchChars += 1
					elif(startIndex == endIndex):
						break
					else:
						nonMatchChars += 1
					endIndex += 1
				if(matchChars == len(key)):
					# we have a match, find out if it has leading or trailing chars
					lead = ' '
					if(startIndex - 1 >= 0):
						lead = line[startIndex - 1]
					trail = ' '
					if(endIndex < len(line)):
						trail = line[endIndex]
					ltchars = False
					if(lead != ' ' or trail != ' '):
						ltchars = True
					# now we have all our data, let's determine what to do about it
					# matches format: [start, end, warn]
					if(self.badWords[key] == 0):
						matches.append([startIndex, endIndex, 0])
					elif(self.badWords[key] == 1):
						if(nonMatchChars == 0 and ltchars == False):
							matches.append([startIndex, endIndex, 0])
						elif(nonMatchChars == 0):
							matches.append([startIndex, endIndex, 1])
					elif(self.badWords[key] == 2):
						if(nonMatchChars == 0 and ltchars == False):
							matches.append([startIndex, endIndex, 0])
					elif(self.badWords[key] == 3):
						if(nonMatchChars == 0 and ltchars == False):
							matches.append([startIndex, endIndex, 1])
		# censor levels
		# 0 - no censor (unless in relay mode, then switch to 1)
		# 1 - replace occurances with ****
		# 2 - warn "I can't repeat that"
		# 3 - kick/ban (unless in relay mode, then switch to 2)
		if(self.prefs["censor_level"] == 1):
			for match in matches:
				fill = ''
				for i in range(match[1] - match[0]):
					fill += '*'
				line = line[0:match[0]] + fill + line[match[1]:]
		elif(self.prefs["censor_level"] == 2):
			if(len(matches) > 0):
				return 1
		elif(self.prefs["censor_level"] == 3):
			if(len(matches) > 0):
				for match in matches:
					if(match[2] == 0):
						return 2
				return 1
		return line