Ejemplo n.º 1
0
class MessageProcessor(threading.Thread):
    """
    Основной класс сервера. Принимает содинения, словари - пакеты
    от клиентов, обрабатывает поступающие сообщения.
    Работает в качестве отдельного потока.
    """
    port = Port()

    def __init__(self, listen_address, listen_port, database):
        self.addr = listen_address
        self.port = listen_port
        self.database = database
        self.sock = None
        self.clients = []
        self.listen_sockets = None
        self.error_sockets = None
        self.running = True
        self.names = dict()
        super().__init__()

    def run(self):
        """Метод основной цикл потока."""
        self.init_socket()

        while self.running:
            try:
                client, client_address = self.sock.accept()
            except OSError:
                pass
            else:
                logger.info(f'Установлено соедение с ПК {client_address}')
                client.settimeout(5)
                self.clients.append(client)

            recv_data_lst = []
            send_data_lst = []
            err_lst = []
            try:
                if self.clients:
                    recv_data_lst, self.listen_sockets, self.error_sockets = select.select(
                        self.clients, self.clients, [], 0)
            except OSError as err:
                logger.error(f'Ошибка работы с сокетами: {err.errno}')

            if recv_data_lst:
                for client_with_message in recv_data_lst:
                    try:
                        self.process_client_message(
                            get_message(client_with_message),
                            client_with_message)
                    except (OSError, json.JSONDecodeError, TypeError):
                        self.remove_client(client_with_message)

    def remove_client(self, client):
        """
        Метод обработчик клиента с которым прервана связь.
        Ищет клиента и удаляет его из списков и базы:
        """
        logger.info(f'Клиент {client.getpeername()} отключился от сервера.')
        for name in self.names:
            if self.names[name] == client:
                self.database.user_logout(name)
                del self.names[name]
                break
        self.clients.remove(client)
        client.close()

    def init_socket(self):
        """Метод инициализатор сокета."""
        logger.info(
            f'Запущен сервер, порт для подключений: {self.port} , адрес с которого принимаются подключения: {self.addr}. Если адрес не указан, принимаются соединения с любых адресов.'
        )
        transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        transport.bind((self.addr, self.port))
        transport.settimeout(0.5)
        self.sock = transport
        self.sock.listen(MAX_CONNECTIONS)

    def process_message(self, message):
        """
        Метод отправки сообщения клиенту.
        """
        if message[DESTINATION] in self.names and self.names[
                message[DESTINATION]] in self.listen_sockets:
            try:
                send_message(self.names[message[DESTINATION]], message)
                logger.info(
                    f'Отправлено сообщение пользователю {message[DESTINATION]} от пользователя {message[SENDER]}.'
                )
            except OSError:
                self.remove_client(message[DESTINATION])
        elif message[DESTINATION] in self.names and self.names[
                message[DESTINATION]] not in self.listen_sockets:
            logger.error(
                f'Связь с клиентом {message[DESTINATION]} была потеряна. Соединение закрыто, доставка невозможна.'
            )
            self.remove_client(self.names[message[DESTINATION]])
        else:
            logger.error(
                f'Пользователь {message[DESTINATION]} не зарегистрирован на сервере, отправка сообщения невозможна.'
            )

    @login_required
    def process_client_message(self, message, client):
        """Метод отбработчик поступающих сообщений."""
        logger.debug(f'Разбор сообщения от клиента : {message}')
        if ACTION in message and message[ACTION] == PRESENCE \
                and TIME in message and USER in message:
            self.autorize_user(message, client)

        elif ACTION in message and message[ACTION] == MESSAGE \
                and DESTINATION in message and TIME in message \
                and SENDER in message and MESSAGE_TEXT in message \
                and self.names[message[SENDER]] == client:
            if message[DESTINATION] in self.names:
                self.database.process_message(message[SENDER],
                                              message[DESTINATION])
                self.process_message(message)
                try:
                    send_message(client, RESPONSE_200)
                except OSError:
                    self.remove_client(client)
            else:
                response = RESPONSE_400
                response[ERROR] = 'Пользователь не зарегистрирован на сервере.'
                try:
                    send_message(client, response)
                except OSError:
                    pass
            return

        elif ACTION in message and message[ACTION] == EXIT \
                and ACCOUNT_NAME in message \
                and self.names[message[ACCOUNT_NAME]] == client:
            self.remove_client(client)

        elif ACTION in message and message[ACTION] == GET_CONTACTS \
                and USER in message \
                and self.names[message[USER]] == client:
            response = RESPONSE_202
            response[LIST_INFO] = self.database.get_contacts(message[USER])
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)

        elif ACTION in message and message[ACTION] == ADD_CONTACT \
                and ACCOUNT_NAME in message \
                and USER in message \
                and self.names[message[USER]] == client:
            self.database.add_contact(message[USER], message[ACCOUNT_NAME])
            try:
                send_message(client, RESPONSE_200)
            except OSError:
                self.remove_client(client)

        elif ACTION in message and message[ACTION] == REMOVE_CONTACT \
                and ACCOUNT_NAME in message \
                and USER in message \
                and self.names[message[USER]] == client:
            self.database.remove_contact(message[USER], message[ACCOUNT_NAME])
            try:
                send_message(client, RESPONSE_200)
            except OSError:
                self.remove_client(client)

        elif ACTION in message and message[ACTION] == USERS_REQUEST \
                and ACCOUNT_NAME in message \
                and self.names[message[ACCOUNT_NAME]] == client:
            response = RESPONSE_202
            response[LIST_INFO] = [
                user[0] for user in self.database.users_list()
            ]
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)

        elif ACTION in message and message[ACTION] == PUBLIC_KEY_REQUEST \
                and ACCOUNT_NAME in message:
            response = RESPONSE_511
            response[DATA] = self.database.get_pubkey(message[ACCOUNT_NAME])

            if response[DATA]:
                try:
                    send_message(client, response)
                except OSError:
                    self.remove_client(client)
            else:
                response = RESPONSE_400
                response[
                    ERROR] = 'Нет публичного ключа для данного пользователя'
                try:
                    send_message(client, response)
                except OSError:
                    self.remove_client(client)

        else:
            response = RESPONSE_400
            response[ERROR] = 'Запрос некорректен.'
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)

    def autorize_user(self, message, sock):
        """Метод реализующий авторизцию пользователей."""
        if message[USER][ACCOUNT_NAME] in self.names.keys():
            response = RESPONSE_400
            response[ERROR] = 'Имя пользователя уже занято.'
            try:
                send_message(sock, response)
            except OSError:
                pass
            self.clients.remove(sock)
            sock.close()
        elif not self.database.check_user(message[USER][ACCOUNT_NAME]):
            response = RESPONSE_400
            response[ERROR] = 'Пользователь не зарегистрирован.'
            try:
                send_message(sock, response)
            except OSError:
                pass
            self.clients.remove(sock)
            sock.close()
        else:
            message_auth = RESPONSE_511
            random_str = binascii.hexlify(os.urandom(64))
            message_auth[DATA] = random_str.decode('ascii')
            hash = hmac.new(
                self.database.get_hash(message[USER][ACCOUNT_NAME]),
                random_str)
            digest = hash.digest()
            try:
                send_message(sock, message_auth)
                ans = get_message(sock)
            except OSError:
                sock.close()
                return
            client_digest = binascii.a2b_base64(ans[DATA])
            if RESPONSE in ans and ans[RESPONSE] == 511 and hmac.compare_digest(
                    digest, client_digest):
                self.names[message[USER][ACCOUNT_NAME]] = sock
                client_ip, client_port = sock.getpeername()
                try:
                    send_message(sock, RESPONSE_200)
                except OSError:
                    self.remove_client(message[USER][ACCOUNT_NAME])
                self.database.user_login(message[USER][ACCOUNT_NAME],
                                         client_ip, client_port,
                                         message[USER][PUBLIC_KEY])
            else:
                response = RESPONSE_400
                response[ERROR] = 'Неверный пароль.'
                try:
                    send_message(sock, response)
                except OSError:
                    pass
                self.clients.remove(sock)
                sock.close()

    def service_update_lists(self):
        """Метод реализующий отправки сервисного сообщения 205 клиентам."""
        for client in self.names:
            try:
                send_message(self.names[client], RESPONSE_205)
            except OSError:
                self.remove_client(self.names[client])
Ejemplo n.º 2
0
class MessageProcessor(threading.Thread):
    """
    Основной класс сервера. Принимает содинения, словари - пакеты
    от клиентов, обрабатывает поступающие сообщения.
    Работает в качестве отдельного потока.
    """
    port = Port()

    def __init__(self, listen_address, listen_port, database):
        # параментры подключения
        self.addr = listen_address
        self.port = listen_port

        # база данных сервера
        self.database = database

        # Сокет, через который будет осуществляться работа
        self.sock = None

        # список подключённых клиентов.
        self.clients = []

        # Сокеты
        self.listen_sockets = None
        self.error_sockets = None

        # Флаг продолжения работы
        self.running = True

        # словарь, содержащий сопоставленные имена и соответствующие им сокеты.
        self.names = dict()

        # конструктор предка
        super().__init__()

    def run(self):
        """Метод - основной цикл потока."""
        # инициализируем сокет
        self.init_socket()

        # основной цикл программы сервера
        while self.running:
            # ждём подключения, если таймаут вышел, ловим исключение
            try:
                # принимает запрос на установку соединения
                client, client_address = self.sock.accept()
            except OSError:
                pass  # timeout вышел
            else:
                server_logger.info(
                    f'Установлено соединение с: {str(client_address)}')
                client.settimeout(5)
                self.clients.append(client)

            recv_data_lst = []
            send_data_lst = []
            err_lst = []
            # проверяем на наличие ждущих клиентов
            try:
                if self.clients:
                    recv_data_lst, self.listen_sockets, self.error_sockets = \
                        select.select(self.clients, self.clients, [], 0)
            except OSError as err:
                server_logger.error(f'Ошибка работы с сокетами: {err.errno}')

            # принимаем сообщения и если ошибка, исключаем клиента
            if recv_data_lst:
                for client_with_message in recv_data_lst:
                    try:
                        self.process_client_message(
                            get_message(
                                client_with_message,
                                CONFIGS),
                            client_with_message,
                            CONFIGS)
                    except (OSError, json.JSONDecodeError, TypeError) as err:
                        server_logger.debug(
                            f'Getting data from client exception',
                            exc_info=err
                        )
                        self.remove_client(client_with_message)

    def remove_client(self, client):
        """
        Метод обработчик клиента с которым прервана связь.
        Ищет клиента и удаляет его из списков и базы:
        """
        server_logger.info(
            f'Клиент {client.getpeername()} отключился от сервера')
        for name in self.names:
            if self.names[name] == client:
                self.database.user_logout(name)
                del self.names[name]
                break
        self.clients.remove(client)
        client.close()

    def init_socket(self):
        """Метод инициализатор сокета."""
        server_logger.info(
            f'Запущен сервер, порт для подключений: {self.port} ,'
            f' адрес с которого принимаются подключения: {self.addr}.'
            f' Если адрес не указан, принимаются соединения с любых адресов.')

        # сервер создаёт сокет
        transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # привязывает сокет к IP-адресу и порту машины
        transport.bind((self.addr, self.port))
        # Таймаут для операций с сокетом (0.5 секунды)
        transport.settimeout(0.5)

        self.sock = transport
        # готов принимать соединения
        self.sock.listen(CONFIGS.get('MAX_CONNECTIONS'))

    @log
    def process_message(self, message):
        """
        Метод отправки сообщения клиенту.
        """
        if message[CONFIGS.get('TO_USER')] in self.names and self.names[
                message[CONFIGS.get('TO_USER')]] in self.listen_sockets:
            try:
                send_message(
                    self.names[message[CONFIGS.get('TO_USER')]],
                    message,
                    CONFIGS
                )
                server_logger.info(
                    f'Отправлено сообщение пользователю '
                    f'{message[CONFIGS.get("TO_USER")]} '
                    f'от пользователя {message[CONFIGS.get("FROM_USER")]}.')
            except OSError:
                self.remove_client(message[CONFIGS.get('TO_USER')])
        elif message[CONFIGS.get('TO_USER')] in self.names and self.names[
                message[CONFIGS.get('TO_USER')]] not in self.listen_sockets:
            server_logger.error(
                f'Связь с клиентом {message[CONFIGS.get("TO_USER")]} '
                f'была потеряна. '
                f'Соединение закрыто, доставка невозможна.')
            self.remove_client(self.names[message[CONFIGS.get('TO_USER')]])
        else:
            server_logger.error(
                f'Пользователь {message[CONFIGS.get("TO_USER")]} '
                f'не зарегистрирован на сервере, '
                f'отправка сообщения невозможна.')

    @login_required
    # метод проверки сообщения клиента
    def process_client_message(self, message, client, CONFIGS):
        """Метод - отбработчик поступающих сообщений."""
        server_logger.debug(f'Обработка сообщения от клиента: {message}')

        # если это сообщение о присутствии, принимаем и отвечаем
        if CONFIGS.get('ACTION') in message and \
                message[CONFIGS.get('ACTION')] == CONFIGS.get('PRESENCE') and \
                CONFIGS.get('TIME') in message and \
                CONFIGS.get('USER') in message:
            # Если сообщение о присутствии то вызываем функцию авторизации.
            self.authorize_user(message, client)

        # Если это сообщение, то отправляем его получателю.
        elif CONFIGS.get('ACTION') in message and \
                message[CONFIGS.get('ACTION')] == CONFIGS.get('MESSAGE') and \
                CONFIGS.get('TO_USER') in message and \
                CONFIGS.get('TIME') in message and \
                CONFIGS.get('FROM_USER') in message and \
                CONFIGS.get('MESSAGE_TEXT') in message and \
                self.names[message[CONFIGS.get('FROM_USER')]] == client:
            if message[CONFIGS.get('TO_USER')] in self.names:
                self.database.process_message(message[CONFIGS.get(
                    'FROM_USER')], message[CONFIGS.get('TO_USER')])
                self.process_message(message)
                try:
                    send_message(client, RESPONSE_200, CONFIGS)
                except OSError:
                    self.remove_client(client)
            else:
                response = RESPONSE_400
                response[CONFIGS.get(
                    'ERROR')] = 'Пользователь не зарегистрирован на сервере.'
                try:
                    send_message(client, response, CONFIGS)
                except OSError:
                    pass
            return

        # если клиент выходит
        elif CONFIGS.get('ACTION') in message and \
                message[CONFIGS.get('ACTION')] == CONFIGS.get('EXIT') and \
                CONFIGS.get('ACCOUNT_NAME') in message and \
                self.names[message[CONFIGS.get('ACCOUNT_NAME')]] == client:
            self.remove_client(client)

        # если это запрос контакт-листа
        elif CONFIGS.get('ACTION') in message and \
                message[CONFIGS.get('ACTION')] == CONFIGS.get('GET_CONTACTS') \
                and CONFIGS.get('USER') in message and \
                self.names[message[CONFIGS.get('USER')]] == client:
            response = RESPONSE_202
            response[CONFIGS.get('LIST_INFO')] = self.database.get_contacts(
                message[CONFIGS.get('USER')])
            try:
                send_message(client, response, CONFIGS)
            except OSError:
                self.remove_client(client)

        # если это добавление контакта
        elif CONFIGS.get('ACTION') in message and \
                message[CONFIGS.get('ACTION')] == CONFIGS.get('ADD_CONTACT') \
                and CONFIGS.get("ACCOUNT_NAME") in message and \
                CONFIGS.get('USER') in message and \
                self.names[message[CONFIGS.get('USER')]] == client:
            self.database.add_contact(message[CONFIGS.get(
                'USER')], message[CONFIGS.get("ACCOUNT_NAME")])
            try:
                send_message(client, RESPONSE_200, CONFIGS)
            except OSError:
                self.remove_client(client)

        # если это удаление контакта
        elif CONFIGS.get('ACTION') in message and \
                message[CONFIGS.get('ACTION')] == \
                CONFIGS.get('REMOVE_CONTACT') and \
                CONFIGS.get('ACCOUNT_NAME') in message and \
                CONFIGS.get('USER') in message and \
                self.names[message[CONFIGS.get('USER')]] == client:
            self.database.remove_contact(message[CONFIGS.get(
                'USER')], message[CONFIGS.get('ACCOUNT_NAME')])
            try:
                send_message(client, RESPONSE_200, CONFIGS)
            except OSError:
                self.remove_client(client)

        # если это запрос известных пользователей
        elif CONFIGS.get('ACTION') in message and \
                message[CONFIGS.get('ACTION')] == CONFIGS.get('USERS_REQUEST')\
                and CONFIGS.get('ACCOUNT_NAME') in message and \
                self.names[message[CONFIGS.get('ACCOUNT_NAME')]] == client:
            response = RESPONSE_202
            response[CONFIGS.get('LIST_INFO')] = \
                [user[0] for user in self.database.users_list()]
            try:
                send_message(client, response, CONFIGS)
            except OSError:
                self.remove_client(client)

        # Если это запрос публичного ключа пользователя
        elif CONFIGS.get('ACTION') in message and \
                message[CONFIGS.get('ACTION')] == \
                CONFIGS.get('PUBLIC_KEY_REQUEST') and \
                CONFIGS.get('ACCOUNT_NAME') in message:
            response = RESPONSE_511
            response[CONFIGS.get('DATA')] = self.database.get_pubkey(
                message[CONFIGS.get('ACCOUNT_NAME')])
            # может быть, что ключа ещё нет (пользователь никогда не логинился,
            # тогда шлём 400)
            if response[CONFIGS.get('DATA')]:
                try:
                    send_message(client, response, CONFIGS)
                except OSError:
                    self.remove_client(client)
            else:
                response = RESPONSE_400
                response[CONFIGS.get('ERROR')] = \
                    'Нет публичного ключа для данного пользователя'
                try:
                    send_message(client, response, CONFIGS)
                except OSError:
                    self.remove_client(client)

        # иначе отдаём Bad request
        else:
            response = RESPONSE_400
            response[CONFIGS.get('ERROR')] = 'Запрос некорректен.'
            try:
                send_message(client, response, CONFIGS)
            except OSError:
                self.remove_client(client)

    def authorize_user(self, message, sock):
        """Метод реализующий авторизцию пользователей."""
        # Если имя пользователя уже занято то возвращаем 400
        server_logger.debug(
            f'Start auth process for {message[CONFIGS.get("USER")]}')
        if message[CONFIGS.get('USER')][CONFIGS.get(
                'ACCOUNT_NAME')] in self.names.keys():
            response = RESPONSE_400
            response[CONFIGS.get('ERROR')] = 'Имя пользователя уже занято.'
            try:
                server_logger.debug(f'Username busy, sending {response}')
                send_message(sock, response, CONFIGS)
            except OSError:
                server_logger.debug('OS Error')
                pass
            self.clients.remove(sock)
            sock.close()

        # Проверяем что пользователь зарегистрирован на сервере.
        elif not self.database.check_user(message[CONFIGS.get('USER')]
                                          [CONFIGS.get('ACCOUNT_NAME')]):
            response = RESPONSE_400
            response[CONFIGS.get('ERROR')] = 'Пользователь не зарегистрирован.'
            try:
                server_logger.debug(f'Unknown username, sending {response}')
                send_message(sock, response, CONFIGS)
            except OSError:
                pass
            self.clients.remove(sock)
            sock.close()
        else:
            server_logger.debug('Correct username, starting passwd check.')
            # Иначе отвечаем 511 и проводим процедуру авторизации
            # Словарь - заготовка
            message_auth = RESPONSE_511
            # Набор байтов в hex представлении
            random_str = binascii.hexlify(os.urandom(64))
            # В словарь байты нельзя, декодируем (json.dumps -> TypeError)
            message_auth[CONFIGS.get('DATA')] = random_str.decode('ascii')
            # Создаём хэш пароля и связки с рандомной строкой, сохраняем
            # серверную версию ключа
            hash = hmac.new(self.database.get_hash(message[CONFIGS.get(
                'USER')][CONFIGS.get('ACCOUNT_NAME')]), random_str, 'MD5')
            digest = hash.digest()
            server_logger.debug(f'Auth message = {message_auth}')
            try:
                # Обмен с клиентом
                send_message(sock, message_auth, CONFIGS)
                ans = get_message(sock, CONFIGS)
            except OSError as err:
                server_logger.debug('Error in auth, data:', exc_info=err)
                sock.close()
                return
            client_digest = binascii.a2b_base64(ans[CONFIGS.get('DATA')])
            # Если ответ клиента корректный, то сохраняем его в список
            # пользователей.
            if CONFIGS.get('RESPONSE') in ans and \
                    ans[CONFIGS.get('RESPONSE')] == 511 and \
                    hmac.compare_digest(digest, client_digest):
                self.names[message[CONFIGS.get(
                    'USER')][CONFIGS.get('ACCOUNT_NAME')]] = sock
                client_ip, client_port = sock.getpeername()
                try:
                    send_message(sock, RESPONSE_200, CONFIGS)
                except OSError:
                    self.remove_client(message[CONFIGS.get(
                        'USER')][CONFIGS.get('ACCOUNT_NAME')])
                # добавляем пользователя в список активных и если у него
                # изменился открытый ключ сохраняем новый
                self.database.user_login(
                    message[CONFIGS.get('USER')][CONFIGS.get('ACCOUNT_NAME')],
                    client_ip,
                    client_port,
                    message[CONFIGS.get('USER')][CONFIGS.get('PUBLIC_KEY')])
            else:
                response = RESPONSE_400
                response[CONFIGS.get('ERROR')] = 'Неверный пароль.'
                try:
                    send_message(sock, response, CONFIGS)
                except OSError:
                    pass
                self.clients.remove(sock)
                sock.close()

    def service_update_lists(self):
        """Метод реализующий отправки сервисного сообщения 205 клиентам."""
        for client in self.names:
            try:
                send_message(self.names[client], RESPONSE_205, CONFIGS)
            except OSError:
                self.remove_client(self.names[client])
Ejemplo n.º 3
0
class MessageProcessor(threading.Thread, metaclass=ServerMaker):
    port = Port()

    def __init__(self, listen_address, listen_port, database):
        # Параментры подключения
        self.addr = listen_address
        self.port = listen_port

        # База данных сервера
        self.database = database

        # Сокет, через который будет осуществляться работа
        self.sock = None

        # Список подключённых клиентов.
        self.clients = []

        # Сокеты
        self.listen_sockets = None
        self.error_sockets = None

        # Флаг продолжения работы
        self.running = True

        # Словарь содержащий сопоставленные имена и соответствующие им сокеты.
        self.names = dict()

        # Конструктор предка
        super().__init__()

    def run(self):
        # Инициализация Сокета
        self.init_socket()

        # Основной цикл программы сервера
        while self.running:
            # Ждём подключения, если таймаут вышел, ловим исключение.
            try:
                client, client_address = self.sock.accept()
            except OSError:
                pass
            else:
                logger.info(f'Установлено соедение с ПК {client_address}')
                client.settimeout(5)
                self.clients.append(client)

            recv_data_lst = []
            send_data_lst = []
            err_lst = []
            # Проверяем на наличие ждущих клиентов
            try:
                if self.clients:
                    recv_data_lst, self.listen_sockets, self.error_sockets = select.select(
                        self.clients, self.clients, [], 0)
            except OSError as err:
                logger.error(f'Ошибка работы с сокетами: {err.errno}')

            # принимаем сообщения и если ошибка, исключаем клиента.
            if recv_data_lst:
                for client_with_message in recv_data_lst:
                    try:
                        self.process_client_message(get_message(client_with_message), client_with_message)
                    except (OSError, json.JSONDecodeError, TypeError):
                        self.remove_client(client_with_message)

    # Функция обработчик клиента с которым потеряна связь
    # ищет клиента в словаре клиентов и удаляет его со списков и базы:
    def remove_client(self, client):
        logger.info(f'Клиент {client.getpeername()} отключился от сервера.')
        for name in self.names:
            if self.names[name] == client:
                self.database.user_logout(name)
                del self.names[name]
                break
        self.clients.remove(client)
        client.close()

    # Функция-инициализатор сокета
    def init_socket(self):
        logger.info(
            f'Запущен сервер, порт для подключений: {self.port} , адрес с которого принимаются подключения: {self.addr}. Если адрес не указан, принимаются соединения с любых адресов.')
        # Готовим сокет
        transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        transport.bind((self.addr, self.port))
        transport.settimeout(0.5)

        # Начинаем слушать сокет.
        self.sock = transport
        self.sock.listen(MAX_CONNECTIONS)

    # Функция адресной отправки сообщения определённому клиенту. Принимает словарь сообщение, список зарегистрированых
    # пользователей и слушающие сокеты. Ничего не возвращает.
    def process_message(self, message):
        if message[DESTINATION] in self.names and self.names[message[DESTINATION]] in self.listen_sockets:
            try:
                send_message(self.names[message[DESTINATION]], message)
                logger.info(
                    f'Отправлено сообщение пользователю {message[DESTINATION]} от пользователя {message[SENDER]}.')
            except OSError:
                self.remove_client(message[DESTINATION])
        elif message[DESTINATION] in self.names and self.names[message[DESTINATION]] not in self.listen_sockets:
            logger.error(
                f'Связь с клиентом {message[DESTINATION]} была потеряна. Соединение закрыто, доставка невозможна.')
            self.remove_client(self.names[message[DESTINATION]])
        else:
            logger.error(
                f'Пользователь {message[DESTINATION]} не зарегистрирован на сервере, отправка сообщения невозможна.')

    # Обработчик сообщений от клиентов, принимает словарь - сообщение от клиента, проверяет корректность, отправляет
    #     словарь-ответ в случае необходимости.
    @login_required
    def process_client_message(self, message, client):
        logger.debug(f'Разбор сообщения от клиента : {message}')
        # Если это сообщение о присутствии, принимаем и отвечаем
        if ACTION in message and message[ACTION] == PRESENCE and TIME in message and USER in message:
            # Если сообщение о присутствии то вызываем функцию авторизации.
            self.autorize_user(message, client)

        # Если это сообщение, то отправляем его получателю.
        elif ACTION in message and message[ACTION] == MESSAGE and DESTINATION in message and TIME in message \
                and SENDER in message and MESSAGE_TEXT in message and self.names[message[SENDER]] == client:
            if message[DESTINATION] in self.names:
                self.database.process_message(message[SENDER], message[DESTINATION])
                self.process_message(message)
                try:
                    send_message(client, RESPONSE_200)
                except OSError:
                    self.remove_client(client)
            else:
                response = RESPONSE_400
                response[ERROR] = 'Пользователь не зарегистрирован на сервере.'
                try:
                    send_message(client, response)
                except OSError:
                    pass
            return

        # Если клиент выходит
        elif ACTION in message and message[ACTION] == EXIT and ACCOUNT_NAME in message \
                and self.names[message[ACCOUNT_NAME]] == client:
            self.remove_client(client)

        # Если это запрос контакт-листа
        elif ACTION in message and message[ACTION] == GET_CONTACTS and USER in message and \
                self.names[message[USER]] == client:
            response = RESPONSE_202
            response[LIST_INFO] = self.database.get_contacts(message[USER])
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)

        # Если это добавление контакта
        elif ACTION in message and message[ACTION] == ADD_CONTACT and ACCOUNT_NAME in message and USER in message \
                and self.names[message[USER]] == client:
            self.database.add_contact(message[USER], message[ACCOUNT_NAME])
            try:
                send_message(client, RESPONSE_200)
            except OSError:
                self.remove_client(client)

        # Если это удаление контакта
        elif ACTION in message and message[ACTION] == REMOVE_CONTACT and ACCOUNT_NAME in message and USER in message \
                and self.names[message[USER]] == client:
            self.database.remove_contact(message[USER], message[ACCOUNT_NAME])
            try:
                send_message(client, RESPONSE_200)
            except OSError:
                self.remove_client(client)

        # Если это запрос известных пользователей
        elif ACTION in message and message[ACTION] == USERS_REQUEST and ACCOUNT_NAME in message \
                and self.names[message[ACCOUNT_NAME]] == client:
            response = RESPONSE_202
            response[LIST_INFO] = [user[0] for user in self.database.users_list()]
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)

        # Если это запрос публичного ключа пользователя
        elif ACTION in message and message[ACTION] == PUBLIC_KEY_REQUEST and ACCOUNT_NAME in message:
            response = RESPONSE_511
            response[DATA] = self.database.get_pubkey(message[ACCOUNT_NAME])
            # может быть, что ключа ещё нет (пользователь никогда не логинился, тогда шлём 400)
            if response[DATA]:
                try:
                    send_message(client, response)
                except OSError:
                    self.remove_client(client)
            else:
                response = RESPONSE_400
                response[ERROR] = 'Нет публичного ключа для данного пользователя'
                try:
                    send_message(client, response)
                except OSError:
                    self.remove_client(client)

        # Иначе отдаём Bad request
        else:
            response = RESPONSE_400
            response[ERROR] = 'Запрос некорректен.'
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)

    # Функция авторизации пользователя на сервере
    def autorize_user(self, message, sock):
        # Если имя пользователя уже занято то возвращаем 400
        if message[USER][ACCOUNT_NAME] in self.names.keys():
            response = RESPONSE_400
            response[ERROR] = 'Имя пользователя уже занято.'
            try:
                send_message(sock, response)
            except OSError:
                pass
            self.clients.remove(sock)
            sock.close()
        # Проверяем что пользователь зарегистрирован на сервере.
        elif not self.database.check_user(message[USER][ACCOUNT_NAME]):
            response = RESPONSE_400
            response[ERROR] = 'Пользователь не зарегистрирован.'
            try:
                send_message(sock, response)
            except OSError:
                pass
            self.clients.remove(sock)
            sock.close()
        else:
            # Иначе отвечаем 501 и проводим процедуру авторизации
            # Словарь - заготовка
            message_auth = RESPONSE_511
            # Набор байтов в hex представлении
            random_str = binascii.hexlify(os.urandom(64))
            # В словарь байты нельзя, декодируем (json.dumps -> TypeError)
            message_auth[DATA] = random_str.decode('ascii')
            # Создаём хэш пароля и связки с рандомной строкой, сохраняем серверную версию ключа
            hash = hmac.new(self.database.get_hash(message[USER][ACCOUNT_NAME]), random_str)
            digest = hash.digest()
            try:
                # Обмен с клиентом
                send_message(sock, message_auth)
                ans = get_message(sock)
            except OSError:
                sock.close()
                return
            client_digest = binascii.a2b_base64(ans[DATA])
            # Если ответ клиента корректный, то сохраняем его в список пользователей.
            if RESPONSE in ans and ans[RESPONSE] == 511 and hmac.compare_digest(digest, client_digest):
                self.names[message[USER][ACCOUNT_NAME]] = sock
                client_ip, client_port = sock.getpeername()
                try:
                    send_message(sock, RESPONSE_200)
                except OSError:
                    self.remove_client(message[USER][ACCOUNT_NAME])
                # добавляем пользователя в список активных и если у него изменился открытый ключ
                # сохраняем новый
                self.database.user_login(message[USER][ACCOUNT_NAME],
                                         client_ip, client_port, message[USER][PUBLIC_KEY])
            else:
                response = RESPONSE_400
                response[ERROR] = 'Неверный пароль.'
                try:
                    send_message(sock, response)
                except OSError:
                    pass
                self.clients.remove(sock)
                sock.close()

    # Функция - отправляет сервисное сообщение 205 с требованием клиентам обновить списки
    def service_update_lists(self):
        for client in self.names:
            try:
                send_message(self.names[client], RESPONSE_205)
            except OSError:
                self.remove_client(self.names[client])
Ejemplo n.º 4
0
class MessageProcessor(threading.Thread, metaclass=ServerMaker):

    port = Port()

    def __init__(self, listen_address, listen_port, database):
        self.addr = listen_address
        self.port = listen_port
        self.database = database
        self.sock = None

        self.clients = []
        self.listen_sockets = None
        self.error_sockets = None
        self.running = True

        self.names = dict()
        super().__init__()

    def run(self):
        """Basic function to run server."""
        self.init_socket()
        while self.running:
            try:
                client, client_address = self.sock.accept()
            except OSError:
                pass
            else:
                logger.info(f'Applied connection from {client_address}')
                client.settimeout(5)
                self.clients.append(client)
            recv_data_lst = []
            send_data_lst = []
            err_lst = []
            try:
                if self.clients:
                    recv_data_lst, self.listen_sockets, self.error_sockets = select.select(
                        self.clients, self.clients, [], 0)
            except OSError as err:
                logger.error(f'Socket Error: {err.errno}')
            if recv_data_lst:
                for client_with_message in recv_data_lst:
                    try:
                        self.process_client_message(
                            get_message(client_with_message),
                            client_with_message)
                    except (OSError, json.JSONDecodeError, TypeError):
                        self.remove_client(client_with_message)

    def remove_client(self, client):
        """Delete client from server."""
        logger.info(
            f'Client {client.getpeername()} has been disconnected from server')
        for name in self.names:
            if self.names[name] == client:
                self.database.user_logout(name)
                del self.names[name]
                break
        self.clients.remove(client)
        client.close()

    def init_socket(self):
        """Create socket object."""
        logger.info(
            f'Server is running {self.port}, connection address: {self.addr}.\nIf address is empty all connection applied'
        )
        transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        transport.bind((self.addr, self.port))
        transport.settimeout(0.5)
        self.sock = transport
        self.sock.listen(MAX_CONNECTIONS)

    def process_message(self, message):
        """Sending message function. Receive message fom user
        and tries to send it to another user.
        """

        if message[DESTINATION] in self.names and self.names[
                message[DESTINATION]] in self.listen_sockets:
            try:
                send_message(self.names[message[DESTINATION]], message)
                logger.info(
                    f'Message has been send to {message[DESTINATION]} from user {message[SENDER]}.'
                )
            except OSError:
                self.remove_client(message[DESTINATION])
        elif message[DESTINATION] in self.names and self.names[
                message[DESTINATION]] not in self.listen_sockets:
            logger.error(
                f'Connection with client {message[DESTINATION]} has been lost. Connection closed.'
            )
            self.remove_client(self.names[message[DESTINATION]])
        else:
            logger.error(
                f"User {message[DESTINATION]} is not online or not registered, failed to send message"
            )

    @login_required
    def process_client_message(self, message, client):
        """Client's message parser. Tries to receive message from client,
        parse it and creating an answer.
        """

        logger.debug(f'Check user message : {message}')
        if ACTION in message and message[
                ACTION] == PRESENCE and TIME in message and USER in message:
            self.autorize_user(message, client)
        elif ACTION in message and message[ACTION] == MESSAGE and DESTINATION in message and TIME in message \
                and SENDER in message and MESSAGE_TEXT in message and self.names[message[SENDER]] == client:
            if message[DESTINATION] in self.names:
                self.database.process_message(message[SENDER],
                                              message[DESTINATION])
                self.process_message(message)
                try:
                    send_message(client, RESPONSE_200)
                except OSError:
                    self.remove_client(client)
            else:
                response = RESPONSE_400
                response[ERROR] = "'User is not online or not registered"
                try:
                    send_message(client, response)
                except OSError:
                    pass
            return
        elif ACTION in message and message[ACTION] == EXIT and ACCOUNT_NAME in message \
                and self.names[message[ACCOUNT_NAME]] == client:
            self.remove_client(client)
        elif ACTION in message and message[ACTION] == GET_CONTACTS and USER in message and \
                self.names[message[USER]] == client:
            response = RESPONSE_202
            response[LIST_INFO] = self.database.get_contacts(message[USER])
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)
        elif ACTION in message and message[ACTION] == ADD_CONTACT and ACCOUNT_NAME in message and USER in message \
                and self.names[message[USER]] == client:
            self.database.add_contact(message[USER], message[ACCOUNT_NAME])
            try:
                send_message(client, RESPONSE_200)
            except OSError:
                self.remove_client(client)
        elif ACTION in message and message[ACTION] == REMOVE_CONTACT and ACCOUNT_NAME in message and USER in message \
                and self.names[message[USER]] == client:
            self.database.remove_contact(message[USER], message[ACCOUNT_NAME])
            try:
                send_message(client, RESPONSE_200)
            except OSError:
                self.remove_client(client)
        elif ACTION in message and message[ACTION] == USERS_REQUEST and ACCOUNT_NAME in message \
                and self.names[message[ACCOUNT_NAME]] == client:
            response = RESPONSE_202
            response[LIST_INFO] = [
                user[0] for user in self.database.users_list()
            ]
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)
        elif ACTION in message and message[
                ACTION] == PUBLIC_KEY_REQUEST and ACCOUNT_NAME in message:
            response = RESPONSE_511
            response[DATA] = self.database.get_pubkey(message[ACCOUNT_NAME])
            if response[DATA]:
                try:
                    send_message(client, response)
                except OSError:
                    self.remove_client(client)
            else:
                response = RESPONSE_400
                response[ERROR] = 'No public key for this user'
                try:
                    send_message(client, response)
                except OSError:
                    self.remove_client(client)
        else:
            response = RESPONSE_400
            response[ERROR] = 'Incorrect request'
            try:
                send_message(client, response)
            except OSError:
                self.remove_client(client)

    def autorize_user(self, message, sock):
        """Authorize function. Receive a values from client.
        Send a result of authorizing.
        """

        if message[USER][ACCOUNT_NAME] in self.names.keys():
            response = RESPONSE_400
            response[ERROR] = 'Username is already exists'
            try:
                send_message(sock, response)
            except OSError:
                pass
            self.clients.remove(sock)
            sock.close()
        elif not self.database.check_user(message[USER][ACCOUNT_NAME]):
            response = RESPONSE_400
            response[ERROR] = "User is not registered"
            try:
                send_message(sock, response)
            except OSError:
                pass
            self.clients.remove(sock)
            sock.close()
        else:
            message_auth = RESPONSE_511
            random_str = binascii.hexlify(os.urandom(64))
            message_auth[DATA] = random_str.decode('ascii')
            hash = hmac.new(
                self.database.get_hash(message[USER][ACCOUNT_NAME]),
                random_str)
            digest = hash.digest()
            try:
                send_message(sock, message_auth)
                ans = get_message(sock)
            except OSError:
                sock.close()
                return
            client_digest = binascii.a2b_base64(ans[DATA])
            if RESPONSE in ans and ans[RESPONSE] == 511 and hmac.compare_digest(
                    digest, client_digest):
                self.names[message[USER][ACCOUNT_NAME]] = sock
                client_ip, client_port = sock.getpeername()
                try:
                    send_message(sock, RESPONSE_200)
                except OSError:
                    self.remove_client(message[USER][ACCOUNT_NAME])
                self.database.user_login(message[USER][ACCOUNT_NAME],
                                         client_ip, client_port,
                                         message[USER][PUBLIC_KEY])
            else:
                response = RESPONSE_400
                response[ERROR] = 'Incorrect password'
                try:
                    send_message(sock, response)
                except OSError:
                    pass
                self.clients.remove(sock)
                sock.close()

    def service_update_lists(self):
        """Updating clients list function."""
        for client in self.names:
            try:
                send_message(self.names[client], RESPONSE_205)
            except OSError:
                self.remove_client(self.names[client])