commit 1ef959b6064ce27f100e141d7ace3d189a1c0bbc Author: stirelshka8_vivo Date: Fri Mar 28 18:32:31 2025 +0300 INIT diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..da112a1 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Файлы, игнорируемые по умолчанию +/shelf/ +/workspace.xml +# Запросы HTTP-клиента в редакторе +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/ChatSockerRSA.iml b/.idea/ChatSockerRSA.iml new file mode 100644 index 0000000..2037897 --- /dev/null +++ b/.idea/ChatSockerRSA.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..0728eae --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,619 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..5f3d93f --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7a8d696 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f78b857 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CSRSA_client.py b/CSRSA_client.py new file mode 100644 index 0000000..7cf096d --- /dev/null +++ b/CSRSA_client.py @@ -0,0 +1,396 @@ +import socket +import threading +import rsa +import os +from pathlib import Path +import time +from datetime import datetime +import urwid +import sys +import locale + + +class ChatClient: + def __init__(self, host='127.0.0.1', port=5555): + # Настройка локализации + locale.setlocale(locale.LC_ALL, '') + os.environ['LANG'] = 'ru_RU.UTF-8' + + self.host = host + self.port = port + self.nickname = None + self.client_public_key = None + self.client_private_key = None + self.server_public_key = None + self.current_chat = [] + self.file_transfers = {} + self.current_file = None + self.input_history = [] + self.history_index = -1 + self.last_update = time.time() + self.update_interval = 5 # Интервал обновления в секундах + + # Цветовая схема + self.palette = [ + ('header', 'white', 'dark blue'), + ('chat', 'light gray', 'black'), + ('input', 'white', 'black'), + ('status', 'white', 'dark blue'), + ('nick', 'light cyan', 'black'), + ('server', 'light green', 'black'), + ('file', 'yellow', 'black'), + ('error', 'light red', 'black'), + ('own', 'light magenta', 'black'), + ('highlight', 'black', 'light gray'), + ] + + self.generate_keys() + self.setup_client() + self.setup_ui() + + def generate_keys(self): + (self.client_public_key, self.client_private_key) = rsa.newkeys(2048) + + def setup_client(self): + self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + self.client.connect((self.host, self.port)) + except Exception as e: + print(f"Не удалось подключиться к серверу: {e}") + sys.exit(1) + + def setup_ui(self): + # Создаем виджеты чата + self.chat_content = urwid.SimpleListWalker([]) + self.chat_list = urwid.ListBox(self.chat_content) + + # Исправленная строка - правильно закрываем скобки Frame + chat_frame = urwid.Frame( + urwid.AttrMap(self.chat_list, 'chat'), + header=urwid.AttrMap(urwid.Text(" ЧАТ "), 'header')) + + # Поле ввода + self.input_edit = urwid.Edit(caption="Сообщение: ") + input_pile = urwid.Pile([ + urwid.AttrMap(self.input_edit, 'input'), + urwid.Divider('-') + ]) + + # Статус бар + self.status_text = urwid.Text("") + status_bar = urwid.AttrMap(self.status_text, 'status') + + # Главный layout + self.main_frame = urwid.Frame( + body=chat_frame, + footer=urwid.Pile([ + input_pile, + status_bar + ]) + ) + + # Главный цикл + self.loop = urwid.MainLoop( + self.main_frame, + palette=self.palette, + unhandled_input=self.handle_input + ) + + def handle_input(self, key): + if key == 'enter': + self.process_command(self.input_edit.get_edit_text()) + self.input_edit.set_edit_text("") + self.history_index = -1 + elif key == 'up': + self.navigate_history(-1) + elif key == 'down': + self.navigate_history(1) + elif key == 'f1': + self.show_help() + elif key == 'ctrl u': + self.input_edit.set_edit_text("") + + def navigate_history(self, direction): + if not self.input_history: + return + + if direction == -1 and abs(self.history_index) < len(self.input_history): + self.history_index -= 1 + elif direction == 1 and self.history_index < -1: + self.history_index += 1 + + if -len(self.input_history) <= self.history_index <= -1: + self.input_edit.set_edit_text(self.input_history[self.history_index]) + + def process_command(self, msg): + if not msg.strip(): + return + + # Добавляем в историю + self.input_history.append(msg) + if len(self.input_history) > 100: + self.input_history.pop(0) + + if msg.lower() == '/exit': + self.client.close() + raise urwid.ExitMainLoop() + elif msg.lower() == '/help': + self.show_help() + elif msg.lower() in ('y', 'n') and hasattr(self, 'current_file') and self.current_file.get('awaiting_response'): + response = '/accept_file' if msg.lower() == 'y' else '/reject_file' + encrypted_response = rsa.encrypt(response.encode('utf-8'), self.server_public_key) + self.client.send(encrypted_response) + + if msg.lower() == 'y': + self.add_message(f"[ФАЙЛ] Ожидайте получения файла {self.current_file['file_name']}...", 'file') + else: + self.add_message(f"[ФАЙЛ] Вы отказались от получения файла {self.current_file['file_name']}", 'file') + + del self.current_file + self.set_status("") + elif msg.startswith('/file'): + self.send_file(msg) + else: + # Отображаем свое сообщение + self.add_message(f"{self.nickname}: {msg}", 'own') + + # Отправляем на сервер + try: + encrypted_msg = rsa.encrypt(msg.encode('utf-8'), self.server_public_key) + self.client.send(encrypted_msg) + except Exception as e: + self.add_message(f"[ОШИБКА] Не удалось отправить сообщение: {str(e)}", 'error') + + def send_file(self, command): + try: + parts = command.split(' ', 2) + if len(parts) < 3: + self.add_message("[ОШИБКА] Использование: /file <получатель> <путь_к_файлу>", 'error') + self.set_status("Используйте: /file <ник> <путь_к_файлу>") + return + + recipient = parts[1] + file_path = parts[2] + + if not os.path.exists(file_path): + self.add_message(f"[ОШИБКА] Файл {file_path} не найден", 'error') + self.set_status(f"Файл не найден: {file_path}") + return + + file_size = os.path.getsize(file_path) + if file_size > 1024 * 1024: # 1MB лимит + self.add_message("[ОШИБКА] Файл слишком большой (макс. 1MB)", 'error') + self.set_status("Файл слишком большой (макс. 1MB)") + return + + with open(file_path, 'rb') as f: + file_data = f.read() + + file_name = os.path.basename(file_path) + full_command = f"/file {recipient} {file_name} {file_data.decode('latin1')}" + + encrypted_msg = rsa.encrypt(full_command.encode('latin1'), self.server_public_key) + self.client.send(encrypted_msg) + + self.add_message(f"[ФАЙЛ] Запрос на отправку {file_name} пользователю {recipient} отправлен", 'file') + self.set_status(f"Запрос на отправку файла {file_name} отправлен") + + except Exception as e: + self.add_message(f"[ОШИБКА при отправке файла] {str(e)}", 'error') + self.set_status("Ошибка при отправке файла") + + def receive(self): + try: + # Получаем публичный ключ сервера + self.server_public_key = rsa.PublicKey.load_pkcs1(self.client.recv(2048)) + + # Отправляем свой публичный ключ + self.client.send(self.client_public_key.save_pkcs1()) + + # Получаем никнейм + self.set_status("Введите ваш никнейм и нажмите Enter") + while not self.nickname: + time.sleep(0.1) + + # Отправляем никнейм на сервер + encrypted_nickname = rsa.encrypt(self.nickname.encode('utf-8'), self.server_public_key) + self.client.send(encrypted_nickname) + self.set_status(f"Подключено как {self.nickname}") + + while True: + try: + encrypted_msg = self.client.recv(4096) + if not encrypted_msg: + break + + # Расшифровываем сообщение + msg = rsa.decrypt(encrypted_msg, self.client_private_key).decode('utf-8') + + if msg.startswith('/file_notification'): + self.handle_file_notification(msg) + elif msg.startswith('/file_data'): + self.handle_file_data(msg) + else: + self.add_message(msg, 'server' if msg.startswith('[СЕРВЕР]') else 'nick') + + except Exception as e: + self.add_message(f"[ОШИБКА] {str(e)}", 'error') + break + except Exception as e: + self.add_message(f"[ОШИБКА] {str(e)}", 'error') + finally: + self.client.close() + raise urwid.ExitMainLoop() + + def handle_file_notification(self, notification): + parts = notification.split(' ', 2) + if len(parts) < 3: + return + + sender = parts[1] + file_name = parts[2] + + self.add_message(f"[ФАЙЛ] {sender} хочет отправить вам файл: {file_name}", 'file') + self.set_status(f"Принять файл {file_name} от {sender}? (y/n)") + + self.current_file = { + 'sender': sender, + 'file_name': file_name, + 'awaiting_response': True + } + + def handle_file_data(self, file_data): + if not hasattr(self, 'current_file'): + return + + try: + downloads_dir = Path("downloads") + downloads_dir.mkdir(exist_ok=True) + + file_path = downloads_dir / self.current_file['file_name'] + + with open(file_path, 'wb') as f: + f.write(file_data[len('/file_data '):].encode('latin1')) + + self.add_message(f"[ФАЙЛ] Файл {self.current_file['file_name']} сохранен в {file_path}", 'file') + self.set_status(f"Файл получен: {self.current_file['file_name']}") + + except Exception as e: + self.add_message(f"[ОШИБКА] Не удалось сохранить файл: {str(e)}", 'error') + finally: + if hasattr(self, 'current_file'): + del self.current_file + + def add_message(self, text, style=None): + if style: + self.chat_content.append(urwid.AttrMap(urwid.Text(text), style)) + else: + self.chat_content.append(urwid.Text(text)) + + # Автопрокрутка вниз + if len(self.chat_content) > 0: + self.chat_list.set_focus(len(self.chat_content) - 1) + + def set_status(self, message, timeout=3): + self.status_text.set_text(f" {message} ") + if timeout > 0: + threading.Timer(timeout, lambda: self.set_status("")).start() + + def update_ui(self, loop=None, user_data=None): + """Функция для обновления интерфейса""" + current_time = time.time() + if current_time - self.last_update >= self.update_interval: + self.last_update = current_time + # Здесь можно добавить любые обновления интерфейса + # Например, обновление времени в статус баре + self.loop.draw_screen() + + # Планируем следующее обновление + self.loop.set_alarm_in(self.update_interval, self.update_ui) + + def show_help(self): + help_text = [ + "СПРАВКА ПО МЕССЕНДЖЕРУ", + "", + "Основные команды:", + "/exit - Выйти из программы", + "/help - Показать эту справку", + "", + "Отправка файлов:", + "/file <ник> <путь> - Отправить файл указанному пользователю", + "Пример: /file user2 /home/user/image.jpg", + "", + "При получении файла:", + "1. Вы получите уведомление о входящем файле", + "2. Введите 'y' для принятия или 'n' для отказа", + "", + "Горячие клавиши:", + "F1 - Показать/скрыть справку", + "Стрелки вверх/вниз - История сообщений", + "Ctrl+U - Очистить поле ввода" + ] + + help_box = urwid.ListBox(urwid.SimpleListWalker([ + urwid.AttrMap(urwid.Text(line), 'highlight') for line in help_text + ])) + + overlay = urwid.Overlay( + urwid.LineBox(help_box, title="Помощь"), + self.main_frame, + align='center', + width=('relative', 80), + valign='middle', + height=('relative', 80) + ) + + def exit_help(key): + if key in ('f1', 'esc', 'enter'): + self.loop.widget = self.main_frame + + self.loop.widget = overlay + self.loop.unhandled_input = exit_help + + def run(self): + # Получаем никнейм + self.set_status("Введите ваш никнейм и нажмите Enter") + + def set_nickname(edit, new_edit_text): + if new_edit_text.strip(): + self.nickname = new_edit_text.strip() + self.input_edit.set_edit_text("") + self.loop.unhandled_input = self.handle_input + self.set_status(f"Подключено как {self.nickname}") + self.input_edit.set_caption("Сообщение: ") + # Запускаем периодическое обновление интерфейса + self.loop.set_alarm_in(self.update_interval, self.update_ui) + + self.input_edit.set_caption("Никнейм: ") + self.loop.unhandled_input = lambda key: ( + key == 'enter' and set_nickname(None, self.input_edit.get_edit_text()) + ) + + # Запускаем поток для получения сообщений + receive_thread = threading.Thread(target=self.receive, daemon=True) + receive_thread.start() + + # Запускаем главный цикл + self.loop.run() + + +if __name__ == "__main__": + try: + if len(sys.argv) > 1: + host = sys.argv[1] + port = int(sys.argv[2]) if len(sys.argv) > 2 else 5555 + client = ChatClient(host, port) + else: + client = ChatClient() + + client.run() + except KeyboardInterrupt: + if hasattr(client, 'client'): + client.client.close() + sys.exit(0) + except Exception as e: + print(f"Ошибка: {str(e)}") + sys.exit(1) \ No newline at end of file diff --git a/CSRSA_server.py b/CSRSA_server.py new file mode 100644 index 0000000..bc9e80f --- /dev/null +++ b/CSRSA_server.py @@ -0,0 +1,143 @@ +import socket +import threading +import rsa +from datetime import datetime + + +class Server: + def __init__(self, host='0.0.0.0', port=5555): + self.host = host + self.port = port + self.clients = {} + self.public_keys = {} + self.server_public_key = None + self.server_private_key = None + self.generate_keys() + self.setup_server() + + def generate_keys(self): + (self.server_public_key, self.server_private_key) = rsa.newkeys(2048) + + def setup_server(self): + self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server.bind((self.host, self.port)) + self.server.listen() + + def broadcast(self, message, sender=None): + for client in self.clients: + if client != sender: + try: + client_public_key = self.public_keys[client] + encrypted_msg = rsa.encrypt(message.encode(), client_public_key) + client.send(encrypted_msg) + except: + self.remove_client(client) + + def handle_client(self, client): + try: + # Отправляем клиенту публичный ключ сервера + client.send(self.server_public_key.save_pkcs1()) + + # Получаем публичный ключ клиента + client_public_key = rsa.PublicKey.load_pkcs1(client.recv(2048)) + self.public_keys[client] = client_public_key + + # Получаем имя клиента + encrypted_name = client.recv(1024) + client_name = rsa.decrypt(encrypted_name, self.server_private_key).decode() + self.clients[client] = client_name + + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {client_name} подключился.") + self.broadcast(f"[СЕРВЕР] {client_name} присоединился к чату!", client) + + while True: + try: + encrypted_msg = client.recv(4096) + if not encrypted_msg: + break + + msg = rsa.decrypt(encrypted_msg, self.server_private_key).decode() + + if msg.startswith('/file'): + self.handle_file_transfer(client, msg) + else: + self.broadcast(f"{self.clients[client]}: {msg}", client) + + except Exception as e: + print(f"[ОШИБКА] {e}") + break + + except Exception as e: + print(f"[ОШИБКА] {e}") + finally: + self.remove_client(client) + + def handle_file_transfer(self, sender, command): + try: + parts = command.split(' ', 3) + if len(parts) < 4: + return + + recipient_name = parts[1] + file_name = parts[2] + file_data = parts[3].encode() + + # Находим получателя + recipient = None + for client in self.clients: + if self.clients[client] == recipient_name: + recipient = client + break + + if recipient: + # Отправляем уведомление получателю + notification = f"/file_notification {self.clients[sender]} {file_name}" + encrypted_notification = rsa.encrypt(notification.encode(), self.public_keys[recipient]) + recipient.send(encrypted_notification) + + # Ждем подтверждения + response = rsa.decrypt(recipient.recv(1024), self.server_private_key).decode() + if response == "/accept_file": + # Отправляем файл + encrypted_file_data = rsa.encrypt(file_data, self.public_keys[recipient]) + recipient.send(encrypted_file_data) + sender.send(rsa.encrypt(f"[СЕРВЕР] Файл {file_name} отправлен {recipient_name}".encode(), + self.public_keys[sender])) + else: + sender.send(rsa.encrypt(f"[СЕРВЕР] {recipient_name} отказался принять файл".encode(), + self.public_keys[sender])) + else: + sender.send( + rsa.encrypt(f"[СЕРВЕР] Пользователь {recipient_name} не найден".encode(), self.public_keys[sender])) + + except Exception as e: + print(f"[ОШИБКА при передаче файла] {e}") + + def remove_client(self, client): + if client in self.clients: + name = self.clients[client] + del self.clients[client] + if client in self.public_keys: + del self.public_keys[client] + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {name} отключился.") + self.broadcast(f"[СЕРВЕР] {name} покинул чат.", client) + client.close() + + def run(self): + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Сервер запущен на {self.host}:{self.port}") + try: + while True: + client, address = self.server.accept() + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Новое соединение с {address[0]}:{address[1]}") + + thread = threading.Thread(target=self.handle_client, args=(client,)) + thread.start() + except KeyboardInterrupt: + print("\nСервер останавливается...") + finally: + self.server.close() + + +if __name__ == "__main__": + server = Server() + server.run() \ No newline at end of file