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'отправка сообщения невозможна.')
 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])
예제 #3
0
 def remove_contact(self, contact):
     """Метод отправляющий на сервер сведения о удалении контакта."""
     client_logger.debug(f'Удаление контакта {contact}')
     req = {
         CONFIGS.get('ACTION'): CONFIGS.get('REMOVE_CONTACT'),
         CONFIGS.get('TIME'): time.time(),
         CONFIGS.get('USER'): self.username,
         CONFIGS.get('ACCOUNT_NAME'): contact
     }
     with socket_lock:
         send_message(self.transport, req, CONFIGS)
         self.process_server_ans(get_message(self.transport, CONFIGS))
    def test_send_message(self):
        test_message_from_client = {
            'action': 'presence',
            'time': 123456789,
            'type': 'status',
            'user': {
                'account_name': 'Samoryad',
                'status': 'Привет, сервер!'
            }
        }

        test_socket = TestSocket(test_message_from_client)
        send_message(test_socket, test_message_from_client, self.CONFIGS)
        self.assertEqual(test_socket.encoded_message,
                         test_socket.received_message)
예제 #5
0
 def transport_shutdown(self):
     """Метод уведомляющий сервер о завершении работы клиента."""
     self.running = False
     message = {
         CONFIGS.get('ACTION'): CONFIGS.get('EXIT'),
         CONFIGS.get('TIME'): time.time(),
         CONFIGS['ACCOUNT_NAME']: self.username
     }
     with socket_lock:
         try:
             send_message(self.transport, message, CONFIGS)
         except OSError:
             pass
     client_logger.debug('Транспорт завершает работу.')
     time.sleep(0.5)
예제 #6
0
    def send_message(self, to, message):
        """Метод отправляющий на сервер сообщения для пользователя."""
        message_dict = {
            CONFIGS['ACTION']: CONFIGS['MESSAGE'],
            CONFIGS['FROM_USER']: self.username,
            CONFIGS['TO_USER']: to,
            CONFIGS['TIME']: time.time(),
            CONFIGS['MESSAGE_TEXT']: message
        }
        client_logger.debug(f'Сформирован словарь сообщения: {message_dict}')

        # Необходимо дождаться освобождения сокета для отправки сообщения
        with socket_lock:
            send_message(self.transport, message_dict, CONFIGS)
            self.process_server_ans(get_message(self.transport, CONFIGS))
            client_logger.info(f'Отправлено сообщение для пользователя {to}')
예제 #7
0
 def key_request(self, user):
     """Метод запрашивающий с сервера публичный ключ пользователя."""
     client_logger.debug(f'Запрос публичного ключа для {user}')
     req = {
         CONFIGS.get('ACTION'): CONFIGS.get('PUBLIC_KEY_REQUEST'),
         CONFIGS.get('TIME'): time.time(),
         CONFIGS.get('ACCOUNT_NAME'): user
     }
     with socket_lock:
         send_message(self.transport, req, CONFIGS)
         ans = get_message(self.transport, CONFIGS)
     if CONFIGS.get('RESPONSE') in ans and ans[CONFIGS.get(
             'RESPONSE')] == 511:
         return ans[CONFIGS.get('DATA')]
     else:
         client_logger.error(f'Не удалось получить ключ собеседника{user}.')
예제 #8
0
 def user_list_update(self):
     """Метод обновляющий с сервера список пользователей."""
     client_logger.debug(
         f'Запрос списка известных пользователей {self.username}')
     req = {
         CONFIGS.get('ACTION'): CONFIGS.get('USERS_REQUEST'),
         CONFIGS.get('TIME'): time.time(),
         CONFIGS.get('ACCOUNT_NAME'): self.username
     }
     with socket_lock:
         send_message(self.transport, req, CONFIGS)
         ans = get_message(self.transport, CONFIGS)
     if CONFIGS.get('RESPONSE') in ans and ans[CONFIGS.get(
             'RESPONSE')] == 202:
         self.database.add_users(ans[CONFIGS.get('LIST_INFO')])
     else:
         client_logger.error(
             'Не удалось обновить список известных пользователей.')
예제 #9
0
 def contacts_list_update(self):
     """Метод обновляющий с сервера список контактов."""
     self.database.contacts_clear()
     client_logger.debug(
         f'Запрос контакт листа для пользователся {self.name}')
     req = {
         CONFIGS.get('ACTION'): CONFIGS.get('GET_CONTACTS'),
         CONFIGS.get('TIME'): time.time(),
         CONFIGS.get('USER'): self.username
     }
     client_logger.debug(f'Сформирован запрос {req}')
     with socket_lock:
         send_message(self.transport, req, CONFIGS)
         ans = get_message(self.transport, CONFIGS)
     client_logger.debug(f'Получен ответ {ans}')
     if CONFIGS.get('RESPONSE') in ans and ans[CONFIGS.get(
             'RESPONSE')] == 202:
         for contact in ans[CONFIGS.get('LIST_INFO')]:
             self.database.add_contact(contact)
     else:
         client_logger.error('Не удалось обновить список контактов.')
    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 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)
예제 #12
0
    def connection_init(self, port, ip):
        """Метод отвечающий за устанновку соединения с сервером."""
        # Инициализация сокета и сообщение серверу о нашем появлении
        self.transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # Таймаут необходим для освобождения сокета.
        self.transport.settimeout(5)

        # Соединяемся, 5 попыток соединения, флаг успеха ставим в True если
        # удалось
        connected = False
        for i in range(5):
            client_logger.info(f'Попытка подключения №{i + 1}')
            try:
                self.transport.connect((ip, port))
            except (OSError, ConnectionRefusedError):
                pass
            else:
                connected = True
                client_logger.debug("Connection established.")
                break
            time.sleep(1)

        # Если соединится не удалось - исключение
        if not connected:
            client_logger.critical(
                'Не удалось установить соединение с сервером')
            raise ServerError('Не удалось установить соединение с сервером')

        client_logger.debug('Starting auth dialog.')

        # Запускаем процедуру авторизации
        # Получаем хэш пароля
        passwd_bytes = self.password.encode('utf-8')
        salt = self.username.lower().encode('utf-8')
        passwd_hash = hashlib.pbkdf2_hmac('sha512', passwd_bytes, salt, 10000)
        passwd_hash_string = binascii.hexlify(passwd_hash)

        client_logger.debug(f'Passwd hash ready: {passwd_hash_string}')

        # Получаем публичный ключ и декодируем его из байтов
        pubkey = self.keys.publickey().export_key().decode('ascii')

        # Авторизируемся на сервере
        with socket_lock:
            presense = {
                CONFIGS.get('ACTION'): CONFIGS.get('PRESENCE'),
                CONFIGS.get('TIME'): time.time(),
                CONFIGS.get('USER'): {
                    CONFIGS.get('ACCOUNT_NAME'): self.username,
                    CONFIGS.get('PUBLIC_KEY'): pubkey
                }
            }
            client_logger.debug(f"Presence message = {presense}")
            # Отправляем серверу приветственное сообщение.
            try:
                send_message(self.transport, presense, CONFIGS)
                ans = get_message(self.transport, CONFIGS)
                client_logger.debug(f'Server response = {ans}.')
                # Если сервер вернул ошибку, бросаем исключение.
                if CONFIGS.get('RESPONSE') in ans:
                    if ans[CONFIGS.get('RESPONSE')] == 400:
                        raise ServerError(ans[CONFIGS.get('ERROR')])
                    elif ans[CONFIGS.get('RESPONSE')] == 511:
                        # Если всё нормально, то продолжаем процедуру
                        # авторизации.
                        ans_data = ans[CONFIGS.get('DATA')]
                        hash = hmac.new(passwd_hash_string,
                                        ans_data.encode('utf-8'), 'MD5')
                        digest = hash.digest()
                        my_ans = RESPONSE_511
                        my_ans[CONFIGS.get('DATA')] = binascii.b2a_base64(
                            digest).decode('ascii')
                        send_message(self.transport, my_ans, CONFIGS)
                        self.process_server_ans(
                            get_message(self.transport, CONFIGS))
            except (OSError, json.JSONDecodeError) as err:
                client_logger.debug(f'Connection error.', exc_info=err)
                raise ServerError('Сбой соединения в процессе авторизации.')