def run(self): """ The main loop of the client's app. While the running flag is True, it locks the socket and tries to receive messages from the server. Handles various exceptions and emits the lost connection signal. """ SOCKET_LOGGER.debug('Запущен процесс приема сообщений с сервера.') while self.running: sleep(1) with socket_lock: try: self.client_socket.settimeout(0.5) server_response = receive_message(self.client_socket) except OSError as e: if e.errno: SOCKET_LOGGER.critical( 'Потеряно соединение с сервером.') self.running = False self.connection_lost.emit() except (ConnectionError, ConnectionAbortedError, ConnectionRefusedError, JSONDecodeError, TypeError): SOCKET_LOGGER.critical('Ошибка при соединении с сервером.') self.running = False self.connection_lost.emit() else: SOCKET_LOGGER.debug('Принято сообщение с сервера: %s' % server_response) self.process_answer(server_response) finally: self.client_socket.settimeout(5)
def run(self): """ The main loop of the server. Creates a listening socket, and then accepts connections while the working flag is True. If there are clients with messages, sends them. """ self.server_socket = socket(AF_INET, SOCK_STREAM) self.server_socket.bind((self.listening_address, self.listening_port)) self.server_socket.settimeout(0.5) self.server_socket.listen(MAX_NUMBER_OF_CONNECTIONS) try: while self.working: try: client_socket, client = self.server_socket.accept() addr, port = client except OSError: pass else: SERVER_LOGGER.info( 'Установлено соединение с пользователем: ' 'адрес: %s, порт: %s.' % ( addr, port, )) client_socket.settimeout(5) self.clients_list.append(client_socket) ready_to_receive = [] ready_to_send = [] exceptions_list = [] try: if self.clients_list: ready_to_send, self.sockets_list, self.exceptions_list = select( self.clients_list, self.clients_list, [], 0) except OSError as err: SERVER_LOGGER.error('Ошибка работы с сокетами: %s.' % err) if ready_to_send: for client in ready_to_send: try: self.process_client_message( receive_message(client), client) except (OSError, JSONDecodeError, TypeError) as e: SERVER_LOGGER.error( 'Ошибка при запросе ' 'информации от клиента.', exc_info=e) self.delete_client(client) except KeyboardInterrupt: SERVER_LOGGER.info('Серер остановлен пользователем.') self.server_socket.close()
def remove_contact(self, contact: str): """ Handles the adding of a new contact on the socket's side. Generates the relevant message and sends it to the server. :param contact: contact to be deleted """ SOCKET_LOGGER.debug('Удаление контакта %s для пользователя %s' % ( contact, self.client_nickname, )) request = { ACTION: REMOVE_CONTACT, TIME: time(), USER: self.client_nickname, ACCOUNT_NAME: contact } with socket_lock: send_message(self.client_socket, request) self.process_answer(receive_message(self.client_socket))
def create_message(self, recipient: str, message: str): """ Creates and sends the dictionary with the message from one client to another. :param recipient: message's recipient :param message: message text """ message_to_send_dict = { ACTION: MESSAGE, SENDER: self.client_nickname, DESTINATION: recipient, TIME: time(), MESSAGE_TEXT: message } SOCKET_LOGGER.debug('Сформирован словарь сообщения: %s' % message_to_send_dict) with socket_lock: send_message(self.client_socket, message_to_send_dict) self.process_answer(receive_message(self.client_socket)) SOCKET_LOGGER.info('Отправлено сообщение пользователю %s' % recipient)
def request_pub_key(self, user: str) -> str: """ Requests a public RSA key for a client in user's contact list. If the status code in the server's response is 511, returns the key. :param user: contact """ SOCKET_LOGGER.debug('Запрос публичного ключа для пользователя %s' % user) request = { ACTION: PUBLIC_KEY_REQUEST, TIME: time(), ACCOUNT_NAME: user } with socket_lock: send_message(self.client_socket, request) response = receive_message(self.client_socket) if RESPONSE in response and response[RESPONSE] == 511: return response[DATA] else: SOCKET_LOGGER.error('Не удалось получить публичный ключ ' 'пользователя %s' % user)
def request_user_list(self): """ Requests a list of existing users from the server. If the status code of the server's response is 202, updates the list in the client's DB. """ SOCKET_LOGGER.info( 'Пользователь %s запрашивает список всех пользователей.' % self.client_nickname) request = { ACTION: USER_REQUEST, TIME: time(), ACCOUNT_NAME: self.client_nickname } with socket_lock: send_message(self.client_socket, request) response = receive_message(self.client_socket) if RESPONSE in response and response[RESPONSE] == 202: self.database.add_existing_users(response[LIST_INFO]) else: SOCKET_LOGGER.error( 'Не удалось обновить список известных пользователей.')
def request_contacts(self): """ Requests a contact list from the server. If the status code of the server's response is 202, updates the contact list in the client's DB. """ SOCKET_LOGGER.debug('Запрос списка контактов пользователя: %s' % self.client_nickname) self.database.cleat_contact_list() request = { ACTION: GET_CONTACTS, TIME: time(), USER: self.client_nickname } SOCKET_LOGGER.debug('Сформирован запрос к серверу: %s' % request) with socket_lock: send_message(self.client_socket, request) response = receive_message(self.client_socket) SOCKET_LOGGER.debug('Получен ответ от сервера: %s' % response) if RESPONSE in response and response[RESPONSE] == 202: for contact in response[LIST_INFO]: self.database.add_user_to_contacts(contact) else: SOCKET_LOGGER.error('Не удалось обновить список контактов.')
def establish_connection(self, ip_address: str, port: int): """ Establishes connection to the server. Makes 5 attempts to do so, if unsuccessful, ends the cycle. If successful, sends the presence message to the server, and then, sends the encrypted password to the server to compare to the one stored in the server's DB. :param ip_address: server's IP address :param port: server's listening port """ self.client_socket = socket(AF_INET, SOCK_STREAM) self.client_socket.settimeout(5) connected = False for i in range(5): SOCKET_LOGGER.info('Попытка соединения №%d' % (i + 1, )) try: self.client_socket.connect((ip_address, port)) except (OSError, ConnectionRefusedError): pass else: connected = True break sleep(1) if not connected: SOCKET_LOGGER.critical( 'Не удалось установить соединение с сервером.') raise ServerError('Не удалось установить соединение с сервером.') SOCKET_LOGGER.debug('Установлено соединение с сервером. ' 'Начинаю процесс авторизации.') password_bytes = self.password.encode('utf-8') salt = self.client_nickname.lower().encode('utf-8') password_hash = pbkdf2_hmac('sha512', password_bytes, salt, 10000) password_hash_str = hexlify(password_hash) SOCKET_LOGGER.debug('Подготовлен хэш пароля: %s' % password_hash_str) self.pubkey = self.keys.publickey().export_key().decode('ascii') with socket_lock: msg = self.establish_presence() try: send_message(self.client_socket, msg) server_response = receive_message(self.client_socket) SOCKET_LOGGER.debug('Ответ сервера - %s' % server_response) if RESPONSE in server_response: if server_response[RESPONSE] == 400: raise ServerError(server_response[ERROR]) elif server_response[RESPONSE] == 511: resp_data = server_response[DATA] resp_hash = new(password_hash_str, resp_data.encode('utf-8'), 'MD5') digest = resp_hash.digest() client_response = { RESPONSE: 511, DATA: b2a_base64(digest).decode('ascii') } send_message(self.client_socket, client_response) self.process_answer(receive_message( self.client_socket)) except (OSError, JSONDecodeError): SOCKET_LOGGER.critical('В процессе авторизации потеряно ' 'соединение с сервером') raise ServerError('В процессе авторизации потеряно ' 'соединение с сервером') else: SOCKET_LOGGER.info('Соединение успешно установлено.')
def authorize_client(self, message: dict, client: socket): """ Handles the client's authorization. Checks if the username isn't already taken, then checks if the user is registered on the server. If those two checks pass, the method then initiates the exchange of encrypted passwords with the client. If the password is correct, the client is logged onto the server, otherwise the client is removed from the server and his socket gets closed. :param message: presence message :param client: client's socket """ SERVER_LOGGER.debug('Старт процесса авторизации пользователя %s' % message[USER]) if message[USER][ACCOUNT_NAME] in self.nicknames.keys(): response = {RESPONSE: 400, ERROR: 'Имя пользователя уже занято'} try: send_message(client, response) except OSError as e: SERVER_LOGGER.debug('Произошла ошибка: %s' % e) pass self.clients_list.remove(client) client.close() elif not self.server_db.check_existing_user( message[USER][ACCOUNT_NAME]): response = { RESPONSE: 400, ERROR: 'Пользователь не зарегистрирован' } try: SERVER_LOGGER.debug('Пользователь %s не зарегистрирован' % message[USER][ACCOUNT_NAME]) send_message(client, response) except OSError as e: SERVER_LOGGER.debug('Произошла ошибка: %s' % e) pass else: SERVER_LOGGER.debug('Начало проверки пароля') random_string = hexlify(urandom(64)) auth_response = { RESPONSE: 511, DATA: random_string.decode('ascii') } pwd_hash = new( self.server_db.get_user_pwd_hash(message[USER][ACCOUNT_NAME]), random_string, 'MD5') pwd_digest = pwd_hash.digest() SERVER_LOGGER.debug('Подготовлено сообщение для авторизации: %s' % auth_response) try: send_message(client, auth_response) client_response = receive_message(client) except OSError as e: SERVER_LOGGER.debug('Ошибка при авторизации: ', exc_info=e) client.close() return client_digest = a2b_base64(client_response[DATA]) if RESPONSE in client_response and \ client_response[RESPONSE] == 511 and \ compare_digest(pwd_digest, client_digest): self.nicknames[message[USER][ACCOUNT_NAME]] = client client_addr, client_port = client.getpeername() try: send_message(client, {RESPONSE: 200}) except OSError: self.delete_client(client) self.server_db.login_user(message[USER][ACCOUNT_NAME], client_addr, client_port, message[USER][PUBLIC_KEY]) else: response = {RESPONSE: 400, ERROR: 'Неверный пароль'} try: send_message(client, response) except OSError: pass self.clients_list.remove(client) client.close()