def shutdown(self): # Cleanup operations self.shutting_down = True # Cancel further polling for district/master server if self.local_connection: self.local_connection.cancel() self.loop.run_until_complete(self.tasker.await_cancellation(self.local_connection)) if self.district_connection: self.district_connection.cancel() self.loop.run_until_complete(self.tasker.await_cancellation(self.district_connection)) if self.masterserver_connection: self.masterserver_connection.cancel() self.loop.run_until_complete(self.tasker.await_cancellation(self.masterserver_connection)) self.loop.run_until_complete(self.tasker.await_cancellation(self.ms_client.shutdown())) # Cancel pending client tasks and cleanly remove them from the areas players = self.get_player_count() logger.log_print('Kicking {} remaining client{}.' .format(players, 's' if players != 1 else '')) for client in self.client_manager.clients: client.disconnect()
def _upcoming_python_version_check(): current_python_tuple = sys.version_info current_python_simple = 'Python {}.{}.{}'.format(*current_python_tuple[:3]) if current_python_tuple < (3, 9): msg = ( f'WARNING: The upcoming major release of TsuserverDR (4.4.0) will be requiring ' f'at least Python 3.9. You currently have {current_python_simple}. ' f'Please consider upgrading to at least Python 3.9 soon. You may find ' f'additional instructions on updating in README.md') logger.log_print(msg)
def send_error_report(self, client: ClientManager.Client, cmd: str, args: List[str], ex: Exception): """ In case of an error caused by a client packet, send error report to user, notify moderators and have full traceback available on console and through /lasterror """ # Send basic logging information to user info = ( '=========\nThe server ran into a Python issue. Please contact the server owner ' 'and send them the following logging information:') etype, evalue, etraceback = sys.exc_info() tb = traceback.extract_tb(tb=etraceback) current_time = Constants.get_time() file, line_num, module, func = tb[-1] file = file[file.rfind('\\') + 1:] # Remove unnecessary directories version = self.version info += '\r\n*Server version: {}'.format(version) info += '\r\n*Server time: {}'.format(current_time) info += '\r\n*Packet details: {} {}'.format(cmd, args) info += '\r\n*Client status: {}'.format(client) info += '\r\n*Area status: {}'.format(client.area) info += '\r\n*File: {}'.format(file) info += '\r\n*Line number: {}'.format(line_num) info += '\r\n*Module: {}'.format(module) info += '\r\n*Function: {}'.format(func) info += '\r\n*Error: {}: {}'.format(type(ex).__name__, ex) info += '\r\nYour help would be much appreciated.' info += '\r\n=========' client.send_ooc(info) client.send_ooc_others( 'Client {} triggered a Python error through a client packet. ' 'Do /lasterror to take a look at it.'.format(client.id), pred=lambda c: c.is_mod) # Print complete traceback to console info = 'TSUSERVERDR HAS ENCOUNTERED AN ERROR HANDLING A CLIENT PACKET' info += '\r\n*Server time: {}'.format(current_time) info += '\r\n*Packet details: {} {}'.format(cmd, args) info += '\r\n*Client status: {}'.format(client) info += '\r\n*Area status: {}'.format(client.area) info += '\r\n\r\n{}'.format("".join( traceback.format_exception(etype, evalue, etraceback))) logger.log_print(info) self.last_error = [info, etype, evalue, etraceback] # Log error to file logger.log_error(info, server=self, errortype='C') if self.in_test: raise ex
async def _abnormal_shutdown(exception, server=None): # Print complete traceback to console etype, evalue, etraceback = (type(exception), exception, exception.__traceback__) info = 'TSUSERVERDR HAS ENCOUNTERED A FATAL PYTHON ERROR.' info += "\r\n" + "".join( traceback.format_exception(etype, evalue, etraceback)) logger.log_print(info) logger.log_error(info, server=server, errortype='P') logger.log_server('Server is shutting down due to an unhandled exception.') logger.log_print('Attempting a graceful shutdown.') if not server: logger.log_pserver('Server has successfully shut down.') return try: await server.normal_shutdown() except Exception as exception2: logger.log_print('Unable to gracefully shut down: Forcing a shutdown.') etype, evalue, etraceback = (type(exception2), exception2, exception2.__traceback__) info = "\r\n" + "".join( traceback.format_exception(etype, evalue, etraceback)) logger.log_print(info) logger.log_error(info, server=server, errortype='P')
def __init__(self): self.release = 3 self.major_version = 'DR' self.minor_version = '190622b' self.software = 'tsuserver{}'.format(self.get_version_string()) self.version = 'tsuserver{}dev'.format(self.get_version_string()) logger.log_print('Launching {}...'.format(self.software)) logger.log_print('Loading server configurations...') self.config = None self.global_connection = None self.shutting_down = False self.loop = None self.allowed_iniswaps = None self.default_area = 0 self.load_config() self.load_iniswaps() self.char_list = list() self.load_characters() self.client_manager = ClientManager(self) self.area_manager = AreaManager(self) self.ban_manager = BanManager(self) self.ipid_list = {} self.hdid_list = {} self.char_pages_ao1 = None self.music_list = None self.music_list_ao2 = None self.music_pages_ao1 = None self.backgrounds = None self.load_music() self.load_backgrounds() self.load_ids() self.district_client = None self.ms_client = None self.rp_mode = False self.user_auth_req = False self.client_tasks = dict() self.active_timers = dict() self.showname_freeze = False self.commands = importlib.import_module('server.commands') logger.setup_logger(debug=self.config['debug'])
def shutdown(self): # Cleanup operations self.shutting_down = True # Cancel further polling for district/master server if self.global_connection: self.global_connection.cancel() self.loop.run_until_complete( self.await_cancellation(self.global_connection)) # Cancel pending client tasks and cleanly remove them from the areas logger.log_print('Kicking {} remaining clients.'.format( self.get_player_count())) for area in self.area_manager.areas: while area.clients: client = next(iter(area.clients)) area.remove_client(client) for task_id in self.client_tasks[client.id].keys(): task = self.get_task(client, [task_id]) self.loop.run_until_complete(self.await_cancellation(task))
def __init__(self, server: TsuserverDR): super().__init__() self.server = server self.client = None self.buffer = '' self.ping_timeout = None logger.log_print = logger.log_print2 if self.server.in_test else logger.log_print # Determine whether /exec is active or not and warn server owner if so. if getattr(self.server.commands, "ooc_cmd_exec")(self.client, "is_exec_active") == 1: logger.log_print(""" WARNING THE /exec COMMAND IN commands.py IS ACTIVE. UNLESS YOU ABSOLUTELY MEANT IT AND KNOW WHAT YOU ARE DOING, PLEASE STOP YOUR SERVER RIGHT NOW AND DEACTIVATE IT BY GOING TO THE commands.py FILE AND FOLLOWING THE INSTRUCTIONS UNDER ooc_cmd_exec.\n BAD THINGS CAN AND WILL HAPPEN OTHERWISE. """)
def net_cmd_ct(self, args): """ OOC Message CT#<name:string>#<message:string>#% """ if self.client.is_ooc_muted: # Checks to see if the client has been muted by a mod self.client.send_ooc("You have been muted by a moderator.") return if not self.validate_net_cmd( args, ArgType.STR, ArgType.STR, needs_auth=False): return if self.client.name != args[0] and self.client.fake_name != args[0]: if self.client.is_valid_name(args[0]): self.client.name = args[0] self.client.fake_name = args[0] else: self.client.fake_name = args[0] self.client.name = '' if self.client.name == '': self.client.send_ooc( 'You must insert a name with at least one letter.') return if self.client.name.startswith(' '): self.client.send_ooc( 'You must insert a name that starts with a letter.') return if self.server.config[ 'hostname'] in self.client.name or '<dollar>G' in self.client.name: self.client.send_ooc('That name is reserved.') return if args[1].startswith('/'): spl = args[1][1:].split(' ', 1) cmd = spl[0] arg = '' if len(spl) == 2: arg = spl[1][:1024] try: called_function = 'ooc_cmd_{}'.format(cmd) function = None # Double assignment to check if it matched to a function later function = getattr(self.server.commands, called_function) except AttributeError: try: function = getattr(self.server.commands_alt, called_function) except AttributeError: logger.log_print('Attribute error with ' + called_function) self.client.send_ooc('Invalid command.') if function: try: function(self.client, arg) except TsuserverException as ex: self.client.send_ooc(ex) else: # Censor passwords if accidentally said without a slash in OOC for password in self.server.all_passwords: for login in ['login ', 'logincm ', 'loginrp ', 'logingm ']: if login + password in args[1]: args[1] = args[1].replace(password, '[CENSORED]') if self.client.disemvowel: #If you are disemvoweled, replace string. args[1] = Constants.disemvowel_message(args[1]) if self.client.disemconsonant: #If you are disemconsonanted, replace string. args[1] = Constants.disemconsonant_message(args[1]) if self.client.remove_h: #If h is removed, replace string. args[1] = Constants.remove_h_message(args[1]) self.client.area.send_command('CT', self.client.name, args[1]) self.client.last_ooc_message = args[1] logger.log_server( '[OOC][{}][{}][{}]{}'.format(self.client.area.id, self.client.get_char_name(), self.client.name, args[1]), self.client) self.client.last_active = Constants.get_time()
def data_received(self, data): """ Handles any data received from the network. Receives data, parses them into a command and passes it to the command handler. :param data: bytes of data """ # try to decode as utf-8, ignore any erroneous characters self.buffer += data.decode('utf-8', 'ignore') if len(self.buffer) > 8192: self.client.disconnect() for msg in self.get_messages(): if len(msg) < 2: self.client.disconnect() return # general netcode structure is not great if msg[0] in ('#', '3', '4'): if msg[0] == '#': msg = msg[1:] spl = msg.split('#', 1) msg = '#'.join([fanta_decrypt(spl[0])] + spl[1:]) logger.log_debug('[INC][RAW]{}'.format(msg), self.client) try: cmd, *args = msg.split('#') self.net_cmd_dispatcher[cmd](self, args) except Exception as ex: # Send basic logging information to user info = '=========\nThe server ran into a Python issue. Please contact the server owner and send them the following logging information:' etype, evalue, etraceback = sys.exc_info() tb = traceback.extract_tb(tb=etraceback) current_time = asctime(localtime(time())) file, line_num, module, func = tb[-1] file = file[file.rfind('\\') + 1:] # Remove unnecessary directories info += '\r\n*Server time: {}'.format(current_time) info += '\r\n*Packet details: {} {}'.format(cmd, args) info += '\r\n*Client status: {}, {}, {}'.format( self.client.id, self.client.get_char_name(), self.client.is_staff()) info += '\r\n*Area status: {}, {}'.format( self.client.area.id, len(self.client.area.clients)) info += '\r\n*File: {}'.format(file) info += '\r\n*Line number: {}'.format(line_num) info += '\r\n*Module: {}'.format(module) info += '\r\n*Function: {}'.format(func) info += '\r\n*Error: {}: {}'.format(type(ex).__name__, ex) info += '\r\nYour help would be much appreciated.' info += '\r\n=========' self.client.send_host_message(info) # Print complete traceback to console info = 'TSUSERVER HAS ENCOUNTERED AN ERROR HANDLING A CLIENT PACKET' info += '\r\n*Server time: {}'.format(current_time) info += '\r\n*Packet details: {} {}'.format(cmd, args) info += '\r\n*Client status: {}, {}, {}'.format( self.client.id, self.client.get_char_name(), self.client.is_staff()) info += '\r\n*Area status: {}, {}'.format( self.client.area.id, len(self.client.area.clients)) logger.log_print(info) traceback.print_exception(etype, evalue, etraceback)
def net_cmd_ct(self, args): """ OOC Message CT#<name:string>#<message:string>#% """ if self.client.is_ooc_muted: # Checks to see if the client has been muted by a mod self.client.send_host_message( "You have been muted by a moderator.") return if not self.validate_net_cmd( args, self.ArgType.STR, self.ArgType.STR, needs_auth=False): return if self.client.name != args[0] and self.client.fake_name != args[0]: if self.client.is_valid_name(args[0]): self.client.name = args[0] self.client.fake_name = args[0] else: self.client.fake_name = args[0] self.client.name = '' if self.client.name == '': self.client.send_host_message( 'You must insert a name with at least one letter.') return if self.client.name.startswith(' '): self.client.send_host_message( 'You must insert a name that starts with a letter.') return if self.server.config[ 'hostname'] in self.client.name or '<dollar>G' in self.client.name: self.client.send_host_message('That name is reserved!') return if args[1].startswith('/'): spl = args[1][1:].split(' ', 1) cmd = spl[0] arg = '' if len(spl) == 2: arg = spl[1][:256] try: called_function = 'ooc_cmd_{}'.format(cmd) function = getattr(self.server.commands, called_function) except AttributeError: logger.log_print('Attribute error with ' + called_function) self.client.send_host_message('Invalid command.') else: try: function(self.client, arg) except (ClientError, AreaError, ArgumentError, ServerError) as ex: self.client.send_host_message(ex) except Exception: raise # Explicit raising, even though not needed else: if self.client.disemvowel: #If you are disemvoweled, replace string. args[1] = self.client.disemvowel_message(args[1]) if self.client.disemconsonant: #If you are disemconsonanted, replace string. args[1] = self.client.disemconsonant_message(args[1]) if self.client.remove_h: #If h is removed, replace string. args[1] = self.client.remove_h_message(args[1]) self.client.area.send_command('CT', self.client.name, args[1]) logger.log_server( '[OOC][{}][{}][{}]{}'.format(self.client.area.id, self.client.get_char_name(), self.client.name, args[1]), self.client)
def __init__(self, protocol=None, client_manager=None, in_test=False): self.release = 4 self.major_version = 2 self.minor_version = 5 self.segment_version = 'post9' self.internal_version = '210325a' version_string = self.get_version_string() self.software = 'TsuserverDR {}'.format(version_string) self.version = 'TsuserverDR {} ({})'.format(version_string, self.internal_version) self.in_test = in_test self.protocol = AOProtocol if protocol is None else protocol client_manager = ClientManager if client_manager is None else client_manager logger.log_print = logger.log_print2 if self.in_test else logger.log_print logger.log_server = logger.log_server2 if self.in_test else logger.log_server self.random = importlib.reload(random) logger.log_print('Launching {}...'.format(self.version)) logger.log_print('Loading server configurations...') self.config = None self.local_connection = None self.district_connection = None self.masterserver_connection = None self.shutting_down = False self.loop = None self.last_error = None self.allowed_iniswaps = None self.area_list = None self.old_area_list = None self.default_area = 0 self.all_passwords = list() self.load_config() self.load_iniswaps() self.char_list = list() self.char_pages_ao1 = None self.load_characters() self.load_commandhelp() self.client_manager = client_manager(self) self.zone_manager = ZoneManager(self) self.area_manager = AreaManager(self) self.ban_manager = BanManager(self) self.party_manager = PartyManager(self) self.ipid_list = {} self.hdid_list = {} self.music_list = None self._music_list_ao2 = None # Pending deprecation in 4.3 self.music_pages_ao1 = None self.backgrounds = None self.load_music() self.load_backgrounds() self.load_ids() self.district_client = None self.ms_client = None self.rp_mode = False self.user_auth_req = False # self.client_tasks = dict() # KEPT FOR BACKWARDS COMPATIBILITY # self.active_timers = dict() # KEPT FOR BACKWARDS COMPATIBILITY self.showname_freeze = False self.commands = importlib.import_module('server.commands') self.commands_alt = importlib.import_module('server.commands_alt') self.logger_handlers = logger.setup_logger(debug=self.config['debug']) logger.log_print('Server configurations loaded successfully!')
def start(self): try: self.loop = asyncio.get_event_loop() except RuntimeError: self.loop = asyncio.new_event_loop() self.tasker = Tasker(self, self.loop) bound_ip = '0.0.0.0' if self.config['local']: bound_ip = '127.0.0.1' server_name = 'localhost' logger.log_print('Starting a local server...') else: server_name = self.config['masterserver_name'] logger.log_print('Starting a nonlocal server...') ao_server_crt = self.loop.create_server(lambda: self.protocol(self), bound_ip, self.config['port']) ao_server = self.loop.run_until_complete(ao_server_crt) logger.log_pserver('Server started successfully!') if self.config['local']: host_ip = '127.0.0.1' else: try: host_ip = (urllib.request.urlopen('https://api.ipify.org', context=ssl.SSLContext()) .read().decode('utf8')) except urllib.error.URLError as ex: host_ip = None logger.log_pdebug('Unable to obtain personal IP from https://api.ipify.org\n' '{}: {}\n' 'Players may be unable to join.' .format(type(ex).__name__, ex.reason)) if host_ip is not None: logger.log_pdebug('Server should be now accessible from {}:{}:{}' .format(host_ip, self.config['port'], server_name)) if not self.config['local']: logger.log_pdebug('If you want to join your server from this device, you may need to ' 'join with this IP instead: 127.0.0.1:{}:localhost' .format(self.config['port'])) if self.config['local']: self.local_connection = asyncio.ensure_future(self.tasker.do_nothing(), loop=self.loop) if self.config['use_district']: self.district_client = DistrictClient(self) self.district_connection = asyncio.ensure_future(self.district_client.connect(), loop=self.loop) print(' ') logger.log_print('Attempting to connect to district at {}:{}.' .format(self.config['district_ip'], self.config['district_port'])) if self.config['use_masterserver']: self.ms_client = MasterServerClient(self) self.masterserver_connection = asyncio.ensure_future(self.ms_client.connect(), loop=self.loop) print(' ') logger.log_print('Attempting to connect to the master server at {}:{} with the ' 'following details:'.format(self.config['masterserver_ip'], self.config['masterserver_port'])) logger.log_print('*Server name: {}'.format(self.config['masterserver_name'])) logger.log_print('*Server description: {}' .format(self.config['masterserver_description'])) try: self.loop.run_forever() except KeyboardInterrupt: pass print('') # Lame logger.log_pdebug('You have initiated a server shut down.') self.shutdown() ao_server.close() self.loop.run_until_complete(ao_server.wait_closed()) self.loop.close() logger.log_pserver('Server has successfully shut down.')
def start(self): self.loop = asyncio.get_event_loop() bound_ip = '0.0.0.0' if self.config['local']: bound_ip = '127.0.0.1' logger.log_print( 'Starting a local server. Ignore outbound connection attempts.' ) ao_server_crt = self.loop.create_server(lambda: AOProtocol(self), bound_ip, self.config['port']) ao_server = self.loop.run_until_complete(ao_server_crt) logger.log_pdebug('Server started successfully!\n') if self.config['use_district']: self.district_client = DistrictClient(self) self.global_connection = asyncio.ensure_future( self.district_client.connect(), loop=self.loop) logger.log_print( 'Attempting to connect to district at {}:{}.'.format( self.config['district_ip'], self.config['district_port'])) if self.config['use_masterserver']: self.ms_client = MasterServerClient(self) self.global_connection = asyncio.ensure_future( self.ms_client.connect(), loop=self.loop) logger.log_print( 'Attempting to connect to the master server at {}:{} with the following details:' .format(self.config['masterserver_ip'], self.config['masterserver_port'])) logger.log_print('*Server name: {}'.format( self.config['masterserver_name'])) logger.log_print('*Server description: {}'.format( self.config['masterserver_description'])) try: self.loop.run_forever() except KeyboardInterrupt: pass print('') # Lame logger.log_pdebug('You have initiated a server shut down.') self.shutdown() ao_server.close() self.loop.run_until_complete(ao_server.wait_closed()) self.loop.close() logger.log_print('Server has successfully shut down.')
def __init__(self, protocol: AOProtocol = None, client_manager: ClientManager = None, in_test: bool = False): self.logged_packet_limit = 100 # Arbitrary self.logged_packets = [] self.print_packets = False # For debugging purposes self._server = None # Internal server object, changed to proper object later self.release = 4 self.major_version = 3 self.minor_version = 0 self.segment_version = 'post5' self.internal_version = '220304a' version_string = self.get_version_string() self.software = 'TsuserverDR {}'.format(version_string) self.version = 'TsuserverDR {} ({})'.format(version_string, self.internal_version) self.in_test = in_test self.protocol = AOProtocol if protocol is None else protocol client_manager = ClientManager if client_manager is None else client_manager logger.log_print = logger.log_print2 if self.in_test else logger.log_print logger.log_server = logger.log_server2 if self.in_test else logger.log_server self.random = importlib.reload(random) logger.log_print('Launching {}...'.format(self.version)) logger.log_print('Loading server configurations...') self.config = None self.local_connection = None self.district_connection = None self.masterserver_connection = None self.shutting_down = False self.loop = None self.last_error = None self.allowed_iniswaps = None self.area_list = None self.old_area_list = None self.default_area = 0 self.all_passwords = list() self.global_allowed = True self.server_select_name = 'SERVER_SELECT' self.load_config() self.client_manager: ClientManager = client_manager(self) self.char_list = list() self.load_iniswaps() self.load_characters() self.game_manager = GameManager(self) self.trial_manager = TrialManager(self) self.zone_manager = ZoneManager(self) self.area_manager = AreaManager(self) self.ban_manager = BanManager(self) self.party_manager = PartyManager(self) self.ipid_list = {} self.hdid_list = {} self.music_list = None self.backgrounds = None self.gimp_list = list() self.load_commandhelp() self.load_music() self.load_backgrounds() self.load_ids() self.load_gimp() self.district_client = None self.ms_client = None self.rp_mode = False self.user_auth_req = False self.showname_freeze = False self.commands = importlib.import_module('server.commands') self.commands_alt = importlib.import_module('server.commands_alt') self.logger_handlers = logger.setup_logger(debug=self.config['debug']) logger.log_print('Server configurations loaded successfully!') self.error_queue = None self._server = None
async def start(self): self.loop = asyncio.get_event_loop() self.error_queue = asyncio.Queue() self.tasker = Tasker(self) bound_ip = '0.0.0.0' if self.config['local']: bound_ip = '127.0.0.1' server_name = 'localhost' logger.log_print('Starting a local server...') else: server_name = self.config['masterserver_name'] logger.log_print('Starting a nonlocal server...') # Check if port is available port = self.config['port'] with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: try: s.bind((bound_ip, port)) except socket.error as exc: if exc.errno == errno.EADDRINUSE: msg = ( f'Port {port} is in use by another application. Make sure to close any ' f'conflicting applications (even another instance of this server) and ' f'try again.') raise ServerError(msg) raise exc except OverflowError as exc: msg = str(exc).replace('bind(): ', '').capitalize() msg += ' Make sure to set your port number to an appropriate value and try again.' raise ServerError(msg) # Yes there is a race condition here (between checking if port is available, and actually # using it). The only side effect of a race condition is a slightly less nice error # message, so it's not that big of a deal. self._server = await self.loop.create_server( lambda: self.protocol(self), bound_ip, port, start_serving=False) asyncio.create_task(self._server.serve_forever()) logger.log_pserver('Server started successfully!') if self.config['local']: host_ip = '127.0.0.1' else: try: host_ip = (urllib.request.urlopen( 'https://api.ipify.org', context=ssl.SSLContext()).read().decode('utf8')) except urllib.error.URLError as ex: host_ip = None logger.log_pdebug( 'Unable to obtain personal IP from https://api.ipify.org\n' '{}: {}\n' 'Players may be unable to join.'.format( type(ex).__name__, ex.reason)) if host_ip is not None: logger.log_pdebug( 'Server should be now accessible from {}:{}:{}'.format( host_ip, self.config['port'], server_name)) if not self.config['local']: logger.log_pdebug( 'If you want to join your server from this device, you may need to ' 'join with this IP instead: 127.0.0.1:{}:localhost'.format( self.config['port'])) if self.config['local']: self.local_connection = asyncio.create_task( self.tasker.do_nothing()) if self.config['use_district']: self.district_client = DistrictClient(self) self.district_connection = asyncio.create_task( self.district_client.connect()) print(' ') logger.log_print( 'Attempting to connect to district at {}:{}.'.format( self.config['district_ip'], self.config['district_port'])) if self.config['use_masterserver']: self.ms_client = MasterServerClient(self) self.masterserver_connection = asyncio.create_task( self.ms_client.connect()) print(' ') logger.log_print( 'Attempting to connect to the master server at {}:{} with the ' 'following details:'.format(self.config['masterserver_ip'], self.config['masterserver_port'])) logger.log_print('*Server name: {}'.format( self.config['masterserver_name'])) logger.log_print('*Server description: {}'.format( self.config['masterserver_description'])) raise await self.error_queue.get()