def run(self): '''Метод содержащий основной цикл работы транспортного потока.''' logger.debug('Запущен процесс - приёмник собщений с сервера.') while self.running: # Отдыхаем секунду и снова пробуем захватить сокет. # если не сделать тут задержку, то отправка может достаточно долго # ждать освобождения сокета. time.sleep(1) message = None with socket_lock: try: self.transport.settimeout(0.5) message = get_message(self.transport) except OSError as err: if err.errno: logger.critical(f'Потеряно соединение с сервером.') self.running = False self.connection_lost.emit() # Проблемы с соединением except (ConnectionError, ConnectionAbortedError, ConnectionResetError, json.JSONDecodeError, TypeError): logger.debug(f'Потеряно соединение с сервером.') self.running = False self.connection_lost.emit() finally: self.transport.settimeout(5) # Если сообщение получено, то вызываем функцию обработчик: if message: logger.debug(f'Принято сообщение с сервера: {message}') self.process_server_ans(message)
def remove_contact(self, contact): '''Метод отправляющий на сервер сведения о удалении контакта.''' logger.debug(f'Deleting contact {contact}') req = { ACTION: REMOVE_CONTACT, TIME: time.time(), USER: self.username, ACCOUNT_NAME: contact } with socket_lock: send_message(self.transport, req) self.process_server_ans(get_message(self.transport))
def send_message(self, to, message): '''Метод отправляющий на сервер сообщения для пользователя.''' message_dict = { ACTION: MESSAGE, SENDER: self.username, DESTINATION: to, TIME: time.time(), MESSAGE_TEXT: message } logger.debug(f'Сформирован словарь сообщения: {message_dict}') # Необходимо дождаться освобождения сокета для отправки сообщения with socket_lock: send_message(self.transport, message_dict) self.process_server_ans(get_message(self.transport)) logger.info(f'Отправлено сообщение для пользователя {to}')
def key_request(self, user): '''Метод запрашивающий с сервера публичный ключ пользователя.''' logger.debug(f'Public key request for {user}') req = { ACTION: PUBLIC_KEY_REQUEST, TIME: time.time(), ACCOUNT_NAME: user } with socket_lock: send_message(self.transport, req) ans = get_message(self.transport) if RESPONSE in ans and ans[RESPONSE] == 511: return ans[DATA] else: logger.error(f'Unable to get key for {user}.')
def user_list_update(self): '''Метод обновляющий с сервера список пользователей.''' logger.debug(f'Запрос списка известных пользователей {self.username}') req = { ACTION: USERS_REQUEST, TIME: time.time(), ACCOUNT_NAME: self.username } with socket_lock: send_message(self.transport, req) ans = get_message(self.transport) if RESPONSE in ans and ans[RESPONSE] == 202: self.database.add_users(ans[LIST_INFO]) else: logger.error('Unable to update known users list.')
def contacts_list_update(self): '''Метод обновляющий с сервера список контактов.''' self.database.contacts_clear() logger.debug(f'Запрос контакт листа для пользователся {self.name}') req = {ACTION: GET_CONTACTS, TIME: time.time(), USER: self.username} logger.debug(f'Сформирован запрос {req}') with socket_lock: send_message(self.transport, req) ans = get_message(self.transport) logger.debug(f'Получен ответ {ans}') if RESPONSE in ans and ans[RESPONSE] == 202: for contact in ans[LIST_INFO]: self.database.add_contact(contact) else: logger.error('Не удалось обновить список контактов.')
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) as err: logger.debug(f'Getting data from client exception.', exc_info=err) self.remove_client(client_with_message)
def autorize_user(self, message, sock): '''Метод реализующий авторизцию пользователей.''' # Если имя пользователя уже занято то возвращаем 400 logger.debug(f'Start auth process for {message[USER]}') if message[USER][ACCOUNT_NAME] in self.names.keys(): response = RESPONSE_400 response[ERROR] = 'Имя пользователя уже занято.' try: logger.debug(f'Username busy, sending {response}') send_message(sock, response) except OSError: logger.debug('OS Error') pass self.clients.remove(sock) sock.close() # Проверяем что пользователь зарегистрирован на сервере. elif not self.database.check_user(message[USER][ACCOUNT_NAME]): response = RESPONSE_400 response[ERROR] = 'Пользователь не зарегистрирован.' try: logger.debug(f'Unknown username, sending {response}') send_message(sock, response) except OSError: pass self.clients.remove(sock) sock.close() else: 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[DATA] = random_str.decode('ascii') # Создаём хэш пароля и связки с рандомной строкой, сохраняем # серверную версию ключа hash = hmac.new( self.database.get_hash(message[USER][ACCOUNT_NAME]), random_str, 'MD5') digest = hash.digest() logger.debug(f'Auth message = {message_auth}') try: # Обмен с клиентом send_message(sock, message_auth) ans = get_message(sock) except OSError as err: logger.debug('Error in auth, data:', exc_info=err) 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 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): logger.info(f'Попытка подключения №{i + 1}') try: self.transport.connect((ip, port)) except (OSError, ConnectionRefusedError): pass else: connected = True logger.debug("Connection established.") break time.sleep(1) # Если соединится не удалось - исключение if not connected: logger.critical('Не удалось установить соединение с сервером') raise ServerError('Не удалось установить соединение с сервером') 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) logger.debug(f'Passwd hash ready: {passwd_hash_string}') # Получаем публичный ключ и декодируем его из байтов pubkey = self.keys.publickey().export_key().decode('ascii') # Авторизируемся на сервере with socket_lock: presense = { ACTION: PRESENCE, TIME: time.time(), USER: { ACCOUNT_NAME: self.username, PUBLIC_KEY: pubkey } } logger.debug(f"Presense message = {presense}") # Отправляем серверу приветственное сообщение. try: send_message(self.transport, presense) ans = get_message(self.transport) logger.debug(f'Server response = {ans}.') # Если сервер вернул ошибку, бросаем исключение. if RESPONSE in ans: if ans[RESPONSE] == 400: raise ServerError(ans[ERROR]) elif ans[RESPONSE] == 511: # Если всё нормально, то продолжаем процедуру # авторизации. ans_data = ans[DATA] hash = hmac.new(passwd_hash_string, ans_data.encode('utf-8'), 'MD5') digest = hash.digest() my_ans = RESPONSE_511 my_ans[DATA] = binascii.b2a_base64(digest).decode( 'ascii') send_message(self.transport, my_ans) self.process_server_ans(get_message(self.transport)) except (OSError, json.JSONDecodeError) as err: logger.debug(f'Connection error.', exc_info=err) raise ServerError('Сбой соединения в процессе авторизации.')