#!/usr/bin/env python #Copyright 2006 Sebastian Hagen # This file is part of eucharis. # eucharis is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 # as published by the Free Software Foundation # eucharis is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with eucharis; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # server implementation for a simple text-based control protocol import socket import logging import socket_management import authentication_system import input_handlers from authentication_system import UnknownUserError from authentication_system import UnknownRequestError from authentication_system import UnknownMethodError from authentication_system import InsufficientPermissionsError root_logger = logging.getLogger() class control_server: def __init__(self, port, address_family=socket.AF_INET, socket_type=socket.SOCK_STREAM, host='', backlog=2): self.socket = socket_management.sock_server(bindargs=((host, port),), handler=self.connection_setup, connection_class=control_connection, address_family=address_family, socket_type=socket_type, backlog=backlog) self.logger = logging.getLogger('control_server') self.handlers = handlers_control(parent=self, parent_loggername='control_server') def connection_setup(self, connection): self.logger.log(20, 'Incoming connection from %s' % (connection.address,)) connection.parent = self def connection_clean_up(self, connection): self.logger.log(20, 'Discarding connection to %s' % (connection.address,)) class control_connection(socket_management.sock_stream_connection_linebased): def __init__(self): self.logger = logging.getLogger('control_connection') socket_management.sock_stream_connection_linebased.__init__(self) self.input_handler = self.lines_parse self.logging_handler = control_connection_logger_handler(line_output=self.send_line, level=40) root_logger.addHandler(self.logging_handler) self.auth_unit = authentication_system.authentication_unit() self.permissions = [] self.command_permissions = { 'AUTH':[], 'SET':['s'], } def lines_parse(self): for line in self.buffer_lines: line_data = { 'command':None, 'arguments':[] } line_split = line.split(None, 1) if (len(line_split) > 0): self.logger.log(10, '> ' + line) line_data['command'] = line_split.pop(0).upper() if (len(line_split) > 0): argumentstring = line_split[0] while (len(argumentstring) > 0): arguments_split = argumentstring.split(None,1) if (len(arguments_split) < 1): break elif (arguments_split[0][0] == ':'): line_data['arguments'].append(argumentstring[1:]) argumentstring = '' break elif (len(arguments_split) < 2): line_data['arguments'].append(argumentstring) argumentstring = '' break else: line_data['arguments'].append(arguments_split[0]) argumentstring = arguments_split[1] self.line_data_parse(line_data) self.buffer_lines = [] def line_data_parse(self, line_data): command = line_data['command'] arguments = line_data['arguments'] try: if (command in self.command_permissions): self.permissions_verify(self.command_permissions[command]) if (command == 'AUTH'): if (len(arguments) < 2): self.send_line(command='AUTH', arguments=['ERROR', '001', 'Insufficient arguments']) else: auth_action = arguments[0] auth_user = arguments[1] if (auth_action == 'REQUEST'): try: auth_challenge = self.auth_unit.request(user=auth_user) except UnknownUserError: self.send_line(command='AUTH', arguments=['ERROR', '002', 'Unknown user']) else: self.send_line(command='AUTH', arguments=['CHALLENGE', auth_user, auth_challenge]) elif (auth_action == 'RESPONSE'): if (len(arguments) < 4): self.send_line(command='AUTH', arguments=['ERROR', '001', 'Insufficient arguments']) else: auth_method = arguments[2] auth_response = arguments[3] try: auth_result = self.auth_unit.response(user=auth_user, method=auth_method, response=auth_response) except UnknownMethodError: self.send_line(command='AUTH', arguments=['ERROR', '004', 'Unknown Method']) except UnknownRequestError: self.send_line(command='AUTH', arguments=['ERROR', '003', 'Unknown Request']) except UnknownUserError: self.send_line(command='AUTH', arguments=['ERROR', '002', 'Unknown User']) else: if (auth_result == False): self.send_line(command='AUTH', arguments=['REJECT', auth_user, auth_method]) else: self.send_line(command='AUTH', arguments=['CONFIRM', auth_user, ','.join(auth_result)]) for permission in auth_result: if not (permission in self.permissions): self.permissions.append(permission) else: self.send_line(command='AUTH', arguments=['ERROR', '000', 'Unknown action']) elif(command == 'SET'): if (len(arguments) < 2): self.send_line(command='SET', arguments=['Error', '001', 'Insufficient arguments']) else: set_target = arguments[0].upper() set_valuestring = arguments[1] try: if (set_target in ('LOGLEVEL',)): set_value = int(set_valuestring) if (set_target == 'LOGLEVEL'): self.logging_handler.level = set_value else: raise UnknownTargetError() except ValueError: self.send_line(command='SET', arguments=['ERROR', '002', 'Invalid valuestring %s' % (set_valuestring,)]) except UnknownTargetError: self.send_line(command='SET', arguments=['ERROR', '001', 'Unknown target %s' % (set_target,)]) else: self.send_line(command='SET', arguments=['CONFIRM', set_target, str(set_value)]) elif(command == 'CLOSE'): self.clean_up() except InsufficientPermissionsError: self.send_line(command='ERROR', arguments=['010', 'Insufficient permissions']) self.parent.handlers.input_process(connection=self, output=self.send_line, command=command, arguments=arguments) def permissions_verify(self, permissions): for permission in permissions: if (not (permission in self.permissions)): raise InsufficientPermissionsError() return True def send_line(self, command, arguments): output_string = '' if (type(command) == str): output_string = output_string + command if (len(arguments) > 0): for argument in arguments[:-1]: output_string = output_string + ' ' + argument output_string = output_string + ' :' + arguments[-1] output_string = output_string + '\n' self.send_data(output_string) def clean_up(self): root_logger.removeHandler(self.logging_handler) self.logging_handler = None self.send_line = None socket_management.sock_stream_connection_linebased.clean_up(self) class UnknownTargetError(StandardError): pass class handlers_control(input_handlers.handlers_base): def __init__(self, parent, parent_loggername=''): self.parent = parent self.loggername = parent_loggername + '.handlers' self.logger = logging.getLogger(self.loggername) self.handlers = { 'command':{} } def input_process(self, connection, output, command, arguments): self.handle_event(event_type='command', event=command, data={ 'connection':connection, 'output':output, 'command':command, 'arguments':arguments }, permissions=connection.permissions) def output(self): raise NotImplementedError class control_connection_logger_handler(logging.Handler): def __init__(self, line_output, level=40): logging.Handler.__init__(self, level=level) self.line_output = line_output self.default_formatter = logging.Formatter('%(name)s %(levelno)s %(message)s','') self.setFormatter(self.default_formatter) self.addFilter(control_logloop_filter()) def emit(self, record): output_string = self.format(record) for line in output_string.split('\n'): try: self.line_output(command='LOG_EVENT', arguments=[line]) except UnicodeError: self.line_output(command='LOG_EVENT', arguments=[str(line)]) class control_logloop_filter: def filter(self, record): #This should hopefully filter out all debug info about control connections themselves, #thus preventing loops in the logging system. if ((record.levelno < 11) and (record.name in ('control_server', 'control_connection'))): return 0 else: return 1