import socket import threading import time import select import struct from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.backends import default_backend from cryptography.exceptions import InvalidSignature # --- Definizione della classe Client (originariamente alla fine di servertcp_crypto_TLV.py) --- class Client: """Rappresenta un client connesso.""" def __init__(self, name, endpoint, thread, sock): self.name = name self.endpoint = endpoint self.thread = thread self.sock = sock self.keep_alive = True # --- Definizione della classe ServerTCP (originariamente in servertcp_crypto_TLV.py) --- class ServerTCP: """ Un server TCP che utilizza il protocollo TLV, gestisce la frammentazione e la crittografia asimmetrica RSA. """ MAX_MESSAGE_SIZE = 1024 # Dim. massima messaggio (prima della frammentazione) MAX_FRAME_SIZE = 256 # Dimensione massima di un singolo frammento ACCEPT_TIMEOUT = 0.5 TIMEOUT_MS = 100 # Tipi di messaggio TLV (estesi per la crittografia) TYPE_TEXT_MESSAGE = 0x01 TYPE_CLIENT_PUBLIC_KEY = 0x02 TYPE_SERVER_PUBLIC_KEY = 0x03 TYPE_ENCRYPTED_MESSAGE = 0x04 TYPE_ACK = 0x05 def __init__(self, port=5000): self.active_control = False self.listen_port = port self.listener = None self.clients = [] self.listening_thread = None self.on_msg_arrived = None self.on_msg_error = None self.on_msg_state = None self.lock = threading.Lock() self.close_event = threading.Event() self.is_closing = False self.client_buffers = {} self.exit = False # Genera le chiavi RSA del server self.private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) self.public_key = self.private_key.public_key() self.client_public_keys = {} # Mappa client -> chiave pubblica def start(self): self.active_control = True self.clients = [] self.client_buffers = {} # Inizializza il dizionario dei buffer self.client_public_keys = {} # Reset delle chiavi pubbliche self.listening_thread = threading.Thread(target=self._start_listening) self.listening_thread.start() self.close_event.clear() self.is_closing = False def close(self): self.is_closing = True self.active_control = False self.close_event.set() try: with self.lock: clients_to_close = self.clients.copy() self.clients.clear() for client in clients_to_close: client.keep_alive = False try: client.sock.shutdown(socket.SHUT_RDWR) client.sock.close() except Exception: pass if client.thread.is_alive(): client.thread.join(timeout=1) if self.listener: try: self.listener.shutdown(socket.SHUT_RDWR) self.listener.close() except Exception: pass if self.listening_thread and self.listening_thread.is_alive(): self.listening_thread.join(timeout=1) except Exception as ex: print(f"Errore durante la chiusura del server: {ex}") def _start_listening(self): try: self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.listener.bind(("", self.listen_port)) self.listener.listen() self.listener.settimeout(self.ACCEPT_TIMEOUT) while not self.is_closing: try: s, addr = self.listener.accept() # Usa addr qui se necessario client_thread = threading.Thread(target=self._service_client, args=(s,)) client_thread.start() except socket.timeout: continue except Exception as ex: if not self.is_closing: print(f"Errore nel thread di ascolto: {ex}") break except Exception as ex: if not self.is_closing: print(f"Errore nell'avvio del listener: {ex}") finally: if self.listener: try: # Shutdown non sempre necessario o possibile se già chiuso # self.listener.shutdown(socket.SHUT_RDWR) self.listener.close() except: pass def _service_client(self, client_socket): # client = client_socket # Rinominiamo per chiarezza keep_alive = True c = None # Variabile per l'oggetto Client try: remote_endpoint = client_socket.getpeername() # Crea l'oggetto Client usando la definizione di classe sopra c = Client(f"cl_{remote_endpoint[0]}_{remote_endpoint[1]}", remote_endpoint, threading.current_thread(), client_socket) with self.lock: self.clients.append(c) # Inizializza il buffer per questo client (usando l'oggetto Client 'c' come chiave) self.client_buffers[c] = {"fragments": [], "total_length": 0, "message_type": 0} if self.on_msg_state: self.on_msg_state(self, f"CONNECT from {c.endpoint}") # Più informativo while keep_alive: # Usa client_socket (il socket grezzo) per select e recv ready = select.select([client_socket], [], [], self.TIMEOUT_MS / 1000) if ready[0]: try: data = client_socket.recv(self.MAX_FRAME_SIZE) if not data: keep_alive = False if self.on_msg_state: self.on_msg_state(self, f"DISCONNECT from {c.endpoint} (no data)") break # Esce dal loop recv # Passa l'oggetto Client 'c' a _process_data self._process_data(c, data) except ConnectionResetError: keep_alive = False if self.on_msg_state: self.on_msg_state(self, f"DISCONNECT from {c.endpoint} (reset)") # Non serve break qui, l'eccezione esce dal try/except interno except socket.error as sock_ex: # Gestisce altri errori socket aaaaa keep_alive = False if self.exit == False: if self.on_msg_state: self.on_msg_state(self, f"DISCONNECT from {c.endpoint} (socket error: {sock_ex})") except Exception as ex: # Gestisce errori generici nel recv/process print(f"Errore gestendo dati da {c.endpoint}: {ex}") keep_alive = False if self.on_msg_state: self.on_msg_state(self, f"DISCONNECT from {c.endpoint} (processing error)") # Non serve break qui, l'eccezione esce dal try/except interno # Controlla keep_alive sull'oggetto Client e l'evento globale if not c.keep_alive or self.close_event.is_set(): keep_alive = False # Non mandare messaggio di disconnect qui, verrà gestito nel finally break # Esce dal loop principale while except Exception as ex: # Gestisce errori nella configurazione iniziale del client (es. getpeername) if not self.is_closing: # Potremmo non avere 'c' qui se l'errore è molto precoce endpoint_str = str(client_socket.getpeername()) if 'client_socket' in locals() and client_socket.fileno() != -1 else "unknown endpoint" print(f"Errore nel thread di servizio client ({endpoint_str}): {ex}") finally: # Pulizia garantita if c: # Assicurati che 'c' sia stato creato if self.on_msg_state and keep_alive: # Se usciamo per un motivo diverso da errore/disconnect esplicito # Questo potrebbe accadere se keep_alive diventa False nel loop # senza che sia stato mandato un messaggio DISCONNECT # E' improbabile con la logica attuale ma per sicurezza pass # Non mandare un altro disconnect se già gestito # Chiudi il socket try: c.sock.shutdown(socket.SHUT_RDWR) except (socket.error, OSError): # Ignora errori se già chiuso o non connesso pass try: c.sock.close() except (socket.error, OSError): pass # Rimuovi dalle strutture dati del server with self.lock: if c in self.clients: self.clients.remove(c) if c in self.client_buffers: del self.client_buffers[c] if c in self.client_public_keys: del self.client_public_keys[c] else: # Se 'c' non è mai stato creato ma il socket sì, prova a chiuderlo try: client_socket.shutdown(socket.SHUT_RDWR) except (socket.error, OSError): pass try: client_socket.close() except (socket.error, OSError): pass def _process_data(self, client, data): """Gestisce i dati in arrivo, assemblando i frammenti TLV.""" # Usa l'oggetto Client come chiave if client not in self.client_buffers: print(f"Attenzione: Buffer non trovato per il client {client.endpoint}. Ignoro dati.") return buffer = self.client_buffers[client] buffer["fragments"].append(data) buffer["total_length"] += len(data) while buffer["total_length"] >= 5: # Lunghezza minima header TLV (T=1byte, L=4byte) # Combina solo i frammenti necessari per leggere l'header header_data = b"".join(buffer["fragments"])[:5] if len(header_data) < 5: # Controllo di sicurezza aggiuntivo break message_type, message_length = struct.unpack('!BI', header_data) # Verifica se abbiamo ricevuto l'intero messaggio (header + payload) if buffer["total_length"] >= 5 + message_length: # Estrai i dati completi del messaggio full_message_data = b"".join(buffer["fragments"]) message_data = full_message_data[5:5 + message_length] remaining_data = full_message_data[5 + message_length:] # Aggiorna il buffer con i dati rimanenti buffer["fragments"] = [remaining_data] if remaining_data else [] buffer["total_length"] = len(remaining_data) # Non serve resettare message_type qui # Gestisci il messaggio completo self._handle_message(client, message_type, message_data) else: # Non abbiamo ancora ricevuto l'intero messaggio, aspetta altri dati break def _handle_message(self, client, message_type, message_data): """Gestisce i messaggi TLV, inclusa la logica di crittografia.""" if message_type == self.TYPE_CLIENT_PUBLIC_KEY: # Ricezione chiave pubblica del client try: client_public_key = serialization.load_pem_public_key( message_data, backend=default_backend() ) self.client_public_keys[client] = client_public_key print(f"Chiave pubblica del client {client.endpoint} ricevuta.") # Invia la chiave pubblica del server al client self._send_server_public_key(client) except Exception as e: print(f"Errore durante il caricamento della chiave pubblica del client {client.endpoint}: {e}") if self.on_msg_error: self.on_msg_error(client, "Errore nella chiave pubblica del client") # Considera di chiudere la connessione se la chiave è invalida client.keep_alive = False elif message_type == self.TYPE_SERVER_PUBLIC_KEY: # Il server NON dovrebbe ricevere questo messaggio da un client. print(f"Ricevuto TYPE_SERVER_PUBLIC_KEY dal client {client.endpoint} (inatteso). Ignoro.") elif message_type == self.TYPE_ENCRYPTED_MESSAGE: # Decifra il messaggio if client not in self.client_public_keys: print(f"Chiave pubblica del client {client.endpoint} non disponibile. Impossibile decifrare.") if self.on_msg_error: self.on_msg_error(client, "Chiave pubblica non disponibile per decifrare") # Considera di chiudere la connessione se non c'è chiave # client.keep_alive = False return # Non possiamo procedere try: plaintext = self.private_key.decrypt( message_data, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) # Decodifica in stringa prima di passarla al gestore decoded_message = plaintext.decode('utf-8') if self.on_msg_arrived: # Passa l'oggetto client e il messaggio decodificato self.on_msg_arrived(client, decoded_message) # Invia ACK criptato self.send_ack(client) except InvalidSignature: # Errore specifico di decifratura print(f"Errore di decifrazione (InvalidSignature) dal client {client.endpoint}.") if self.on_msg_error: self.on_msg_error(client, "Errore nella decifrazione (firma non valida)") # Non mandare ACK se la decifrazione fallisce except Exception as e: # Altri errori (es. padding) print(f"Errore generico durante la decifrazione da {client.endpoint}: {e}") if self.on_msg_error: self.on_msg_error(client, f"Errore nella decifrazione: {e}") # Non mandare ACK se la decifrazione fallisce elif message_type == self.TYPE_TEXT_MESSAGE: # Messaggio di testo non cifrato (potrebbe essere rimosso o loggato con warning) print(f"Warning: Ricevuto messaggio TESTO non cifrato da {client.endpoint}") try: message = message_data.decode('utf-8').rstrip() if self.on_msg_arrived: # Passa l'oggetto client e il messaggio self.on_msg_arrived(client, f"[UNENCRYPTED] {message}") except UnicodeDecodeError: print(f"Errore decodifica UTF-8 messaggio testo da {client.endpoint}") if self.on_msg_error: self.on_msg_error(client, "Errore di decodifica UTF-8 (testo)") elif message_type == self.TYPE_ACK: # Il server riceve un ACK criptato dal client (confermando ricezione messaggio server) if client not in self.client_public_keys: print(f"Ricevuto ACK da {client.endpoint}, ma chiave pubblica non disponibile per decifrare.") if self.on_msg_error: self.on_msg_error(client, "Chiave pubblica non disponibile per decifrare ACK") return try: plaintext_ack = self.private_key.decrypt( message_data, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) if plaintext_ack == b"ACK": print(f"ACK ricevuto e verificato da {client.endpoint}") # Qui potresti implementare logica addizionale basata sulla ricezione dell'ACK # (es. marcare un messaggio come consegnato) else: print(f"ACK ricevuto da {client.endpoint} con contenuto inatteso: {plaintext_ack}") if self.on_msg_error: self.on_msg_error(client, "ACK ricevuto con contenuto non valido") except Exception as e: print(f"Errore durante la decifrazione dell'ACK da {client.endpoint}: {e}") if self.on_msg_error: self.on_msg_error(client, "Errore nella decifrazione dell'ACK") else: print(f"Tipo di messaggio sconosciuto ({message_type}) ricevuto da {client.endpoint}. Ignoro.") def _send_server_public_key(self, client): """Invia la chiave pubblica del server a un client specifico.""" try: serialized_public_key = self.public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) success = self._send_to_client(client, self.TYPE_SERVER_PUBLIC_KEY, serialized_public_key) if success: print(f"Chiave pubblica del server inviata a {client.endpoint}.") else: print(f"Fallito invio chiave pubblica del server a {client.endpoint}.") except Exception as e: print(f"Errore durante la serializzazione/invio della chiave pubblica del server a {client.endpoint}: {e}") def send_ack(self, client): """Invia un messaggio di ACK criptato al client.""" if client not in self.client_public_keys: print(f"Errore: Chiave pubblica di {client.endpoint} non disponibile per inviare l'ACK.") return try: ack_message = b"ACK" # Invia come bytes # Cifra l'ACK con la chiave pubblica del client ciphertext = self.client_public_keys[client].encrypt( ack_message, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) # Usa TYPE_ACK per inviare l'ACK success = self._send_to_client(client, self.TYPE_ACK, ciphertext) # Non stampare nulla qui per non intasare i log, l'invio è implicito # if success: # print(f"ACK inviato a {client.endpoint}") except Exception as e: print(f"Errore durante l'invio dell'ACK criptato a {client.endpoint}: {e}") def _send_to_client(self, client, message_type, message_data): """Invia un messaggio TLV (frammentato se necessario). Ritorna True/False.""" if not client or not client.sock or client.sock.fileno() == -1: print(f"Tentativo di invio a client non valido o disconnesso.") return False try: header = struct.pack('!BI', message_type, len(message_data)) full_message = header + message_data bytes_sent = 0 while bytes_sent < len(full_message): # Invia in blocchi della dimensione massima del frame chunk = full_message[bytes_sent : bytes_sent + self.MAX_FRAME_SIZE] sent = client.sock.send(chunk) # Usa send, non sendall per il loop manuale if sent == 0: raise socket.error("Socket connection broken") # Connessione chiusa dall'altra parte bytes_sent += sent return True # Invio completato except (socket.error, OSError) as e: # Gestisce errori di rete specifici print(f"Errore socket durante l'invio a {client.endpoint}: {e}. Disconnessione.") # Gestisci la disconnessione del client in modo pulito client.keep_alive = False # Segnala al thread del client di terminare # La pulizia effettiva avviene nel blocco finally di _service_client # Non rimuovere il client qui per evitare race condition con il lock return False except Exception as e: # Gestisce altri errori imprevisti print(f"Errore generico durante l'invio a {client.endpoint}: {e}") client.keep_alive = False return False def send(self, message): """Invia un messaggio di testo *cifrato* a tutti i client connessi e autenticati.""" if not self.active_control: print("Invio fallito: il server non è attivo.") return message_bytes = message.encode('utf-8') clients_to_send = [] with self.lock: # Copia la lista dei client che hanno una chiave pubblica clients_to_send = [c for c in self.clients if c in self.client_public_keys] if not clients_to_send: print("Nessun client connesso o con chiave pubblica disponibile a cui inviare.") return print(f"Invio messaggio cifrato a {len(clients_to_send)} client...") successful_sends = 0 for client in clients_to_send: if not client.keep_alive: # Controlla se il client è ancora considerato attivo continue try: # Cifra il messaggio con la chiave pubblica specifica di questo client ciphertext = self.client_public_keys[client].encrypt( message_bytes, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) # Invia il messaggio cifrato if self._send_to_client(client, self.TYPE_ENCRYPTED_MESSAGE, ciphertext): successful_sends += 1 except Exception as ex: # L'errore di invio è già gestito da _send_to_client, # ma logghiamo l'errore di cifratura se avviene qui. print(f"Errore durante la cifratura per {client.endpoint}: {ex}") # Non incrementare successful_sends print(f"Messaggio inviato con successo a {successful_sends}/{len(clients_to_send)} client.") # --- Parte principale (originariamente in main_servertcp_crypto_TLV.py) --- def handle_message(sender_client, message): # sender_client è l'oggetto Client print(f"Messaggio decifrato da {sender_client.endpoint}: {message}") def handle_error(sender_client, message): # sender_client è l'oggetto Client o None se l'errore è generale endpoint_str = sender_client.endpoint if sender_client else "Server" print(f"Errore da {endpoint_str}: {message}") def handle_state(server_instance, message): # server_instance è l'oggetto ServerTCP print(f"Stato Server: {message}") if __name__ == '__main__': server = ServerTCP(port=5001) # Usa la classe ServerTCP definita sopra server.on_msg_arrived = handle_message server.on_msg_error = handle_error server.on_msg_state = handle_state print("Avvio del server sulla porta 5001...") server.start() print("Server avviato. In attesa di connessioni...") running = True try: while running: command = input("Inserisci un messaggio da inviare a tutti i client (o 'quit' per uscire): ") if command.lower() == 'quit': running = False elif command: # Invia solo se non è una stringa vuota server.send(command) except KeyboardInterrupt: print("\nInterruzione richiesta dall'utente...") running = False # Assicura che il loop finisca finally: print("Chiusura del server in corso...") server.exit = True server.close() print("Server chiuso.")