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])
def run(self): self.print_help() while True: command = input('Enter command: ') if command == 'message': self.create_message() elif command == 'help': self.print_help() elif command == 'exit': try: send_message(self.sock, self.create_exit_message()) except BaseException: pass LOGGER.info('Connection closed.') # Задержка неоходима, чтобы успело уйти сообщение о выходе time.sleep(DEF_TIMEOUT) break elif command == 'contacts': with database_lock: contacts_list = self.database.get_contacts() for contact in contacts_list: print(contact) elif command == 'edit': self.edit_contacts() elif command == 'history': self.print_history() else: print('Undefined command.') self.print_help()
def user_list_request(sock, username): LOGGER.debug(f'Запрос списка известных пользователей {username}') req = {ACTION: USERS_REQUEST, TIME: time.time(), ACCOUNT_NAME: username} send_message(sock, req) ans = get_my_message(sock) if RESPONSE in ans and ans[RESPONSE] == 202: return ans[LIST_INFO] else: raise ServerError
def remove_contact(self, contact): logger.debug(f'Удаление контакта {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_my_message(self.transport))
def contacts_list_request(sock, name): LOGGER.debug(f'Запрос контакт листа для пользователся {name}') req = {ACTION: GET_CONTACTS, TIME: time.time(), USER: name} LOGGER.debug(f'Сформирован запрос {req}') send_message(sock, req) ans = get_my_message(sock) LOGGER.debug(f'Получен ответ {ans}') if RESPONSE in ans and ans[RESPONSE] == 202: return ans[LIST_INFO] else: raise ServerError
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 transport_shutdown(self): self.running = False message = { ACTION: EXIT, TIME: time.time(), ACCOUNT_NAME: self.username } with socket_lock: try: send_message(self.transport, message) except OSError: pass logger.debug('Транспорт завершает работу.') time.sleep(0.5)
def process_message(self, message, listen_socks): if message[DESTINATION] in self.names and self.names[ message[DESTINATION]] in listen_socks: send_message(self.names[message[DESTINATION]], message) LOGGER.info( f'Отправлено сообщение пользователю {message[DESTINATION]} от пользователя {message[SENDER]}.' ) elif message[DESTINATION] in self.names and self.names[ message[DESTINATION]] not in listen_socks: raise ConnectionError else: LOGGER.error( f'Пользователь {message[DESTINATION]} не зарегистрирован на сервере, отправка сообщения невозможна.' )
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_my_message(self.transport) if RESPONSE in ans and ans[RESPONSE] == 202: self.database.add_users(ans[LIST_INFO]) else: logger.error('Не удалось обновить список известных пользователей.')
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 remove_contact(sock, username, contact): LOGGER.debug(f'Создание контакта {contact}') req = { ACTION: REMOVE_CONTACT, TIME: time.time(), USER: username, ACCOUNT_NAME: contact } send_message(sock, req) ans = get_my_message(sock) if RESPONSE in ans and ans[RESPONSE] == 200: pass else: raise ServerError('Ошибка удаления клиента') print('Удачное удаление')
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 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_my_message(self.transport)) logger.info(f'Отправлено сообщение для пользователя {to}')
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 create_message(self): to = input('Введите получателя сообщения: ') message = input('Введите сообщение для отправки: ') message_dict = { ACTION: MESSAGE, SENDER: self.account_name, DESTINATION: to, TIME: time.time(), MESSAGE_TEXT: message } LOGGER.debug(f'Сформирован словарь сообщения: {message_dict}') with database_lock: self.database.save_message(self.account_name, to, message) try: send_message(self.sock, message_dict) LOGGER.info(f'Отправлено сообщение для пользователя {to}') except BaseException: LOGGER.critical('Потеряно соединение с сервером.') exit(1)
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(2): logger.info(f'Попытка подключения №{i + 1}') try: self.transport.connect((ip, port)) except (OSError, ConnectionRefusedError): pass else: connected = True break time.sleep(1) # Если соединится не удалось - исключение if not connected: logger.critical('Не удалось установить соединение с сервером') raise ServerError('Не удалось установить соединение с сервером') logger.debug('Установлено соединение с сервером') # Посылаем серверу приветственное сообщение и получаем ответ что всё # нормально или ловим исключение. try: with socket_lock: send_message(self.transport, self.create_presence()) self.process_server_ans(get_my_message(self.transport)) except (OSError, json.JSONDecodeError): logger.critical('Потеряно соединение с сервером!') raise ServerError('Потеряно соединение с сервером!') # Раз всё хорошо, сообщение о установке соединения. logger.info('Соединение с сервером успешно установлено.')
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]} не зарегистрирован на сервере, отправка сообщения невозможна.' )
def main(): print('Client started') server_address, server_port, client_name = get_args() if not client_name: client_name = input('Введите имя пользователя: ') else: print(f'Клиентский модуль запущен с именем: {client_name}') LOGGER.info( f'Запущен клиент с парамертами: адрес сервера: {server_address} , порт: {server_port}, имя пользователя: {client_name}' ) try: transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM) transport.settimeout(DEF_TIMEOUT) transport.connect((server_address, server_port)) send_message(transport, create_presence(client_name)) answer = process_response_ans(get_my_message(transport)) LOGGER.info( f'Установлено соединение с сервером. Ответ сервера: {answer}') print(f'Установлено соединение с сервером.') except json.JSONDecodeError: LOGGER.error('Не удалось декодировать полученную Json строку.') exit(1) except ServerError as error: LOGGER.error( f'При установке соединения сервер вернул ошибку: {error.text}') exit(1) except ReqFieldMissingError as missing_error: LOGGER.error( f'В ответе сервера отсутствует необходимое поле {missing_error.missing_field}' ) exit(1) except (ConnectionRefusedError, ConnectionError): LOGGER.critical( f'Не удалось подключиться к серверу {server_address}:{server_port}, конечный компьютер отверг запрос на подключение.' ) exit(1) else: database = ClientDB(client_name) database_load(transport, database, client_name) module_sender = ClientSender(client_name, transport, database) module_sender.daemon = True module_sender.start() LOGGER.debug('Запущены процессы') module_receiver = ClientReader(client_name, transport, database) module_receiver.daemon = True module_receiver.start() while True: time.sleep(1) if module_receiver.is_alive() and module_sender.is_alive(): continue break
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('Сбой соединения в процессе авторизации.')
def process_client_message(self, message, client): LOGGER.debug(f'Message fom client : {message}') # 1 ################################################################### if ACTION in message and message[ ACTION] == PRESENCE and TIME in message and USER in message: print( "ACTION in message and message[ACTION] == PRESENCE and TIME in message and USER in message:" ) print(f"names : {self.names}") if message[USER][ACCOUNT_NAME] not in self.names.keys(): self.names[message[USER][ACCOUNT_NAME]] = client client_ip, client_port = client.getpeername() self.database.user_login(message[USER][ACCOUNT_NAME], client_ip, client_port) send_message(client, RESPONSE_200) with conflag_lock: new_connection = True else: response = RESPONSE_400 response[ERROR] = 'Имя пользователя уже занято.' send_message(client, response) self.clients.remove(client) client.close() return # 2 ################################################################### 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.messages.append(message) self.database.process_message(message[SENDER], message[DESTINATION]) send_message(client, RESPONSE_200) else: response = RESPONSE_400 response[ERROR] = 'Пользователь не зарегистрирован на сервере.' send_message(client, response) return # 3 ################################################################ elif ACTION in message and message[ACTION] == EXIT and ACCOUNT_NAME in message \ and self.names[message[ACCOUNT_NAME]] == client: self.database.user_logout(message[ACCOUNT_NAME]) self.clients.remove(self.names[message[ACCOUNT_NAME]]) self.names[message[ACCOUNT_NAME]].close() del self.names[message[ACCOUNT_NAME]] return ############ 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]) send_message(client, response) ############ 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]) send_message(client, RESPONSE_200) ############ 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]) send_message(client, RESPONSE_200) ############ 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() ] send_message(client, response) ############ else: response = RESPONSE_400 response[ERROR] = 'Запрос некорректен.' send_message(client, response) return
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 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()