import json import signal import socket import struct import ssl import threading from datetime import datetime signal.signal(signal.SIGINT, signal.SIG_DFL) PORT = 1234 HEADER_LENGTH = 2 def setup_SSL_context(): #uporabi samo TLS, ne SSL context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) # certifikat je obvezen context.verify_mode = ssl.CERT_REQUIRED #nalozi svoje certifikate context.load_cert_chain(certfile="certs/server_cert.crt", keyfile="certs/server_private.key") # nalozi certifikate CAjev, ki jim zaupas # (samopodp. cert. = svoja CA!) context.load_verify_locations('certs/clients.pem') # nastavi SSL CipherSuites (nacin kriptiranja) context.set_ciphers('ECDHE-RSA-AES128-GCM-SHA256') return context def receive_fixed_length_msg(sock, msglen): message = b'' while len(message) < msglen: chunk = sock.recv(msglen - len(message)) # preberi nekaj bajtov if chunk == b'': raise RuntimeError("socket connection broken") message = message + chunk # pripni prebrane bajte sporocilu return message def receive_json(sock) -> dict: # preberi glavo sporocila (v prvih 2 bytih je dolzina sporocila) header = receive_fixed_length_msg(sock, HEADER_LENGTH) message_length = struct.unpack("!H", header)[0] # pretvori dolzino sporocila v int message = None if message_length > 0: # ce je vse OK message = receive_fixed_length_msg(sock, message_length) # preberi sporocilo message = message.decode("utf-8") message = json.loads(message) return message def send_json(sock, message): message["time"] = datetime.now().strftime("%c") encoded_message = json.dumps(message).encode("utf-8") # pretvori sporocilo v niz bajtov, uporabi UTF-8 kodno tabelo # ustvari glavo v prvih 2 bytih je dolzina sporocila (HEADER_LENGTH) # metoda pack "!H" : !=network byte order, H=unsigned short header = struct.pack("!H", len(encoded_message)) message = header + encoded_message # najprj posljemo dolzino sporocilo, slee nato sporocilo samo sock.sendall(message) def send_error(sock, message): send_json(sock, { "type": "error", "data": message, }) # funkcija za komunikacijo z odjemalcem (tece v loceni niti za vsakega odjemalca) def client_thread(client_sock, client_addr): global clients global usernames cert = client_sock.getpeercert() for sub in cert['subject']: for key, value in sub: if key == 'commonName': usernames[client_sock] = value print("[system] connected with " + client_addr[0] + ":" + str(client_addr[1])) print("[system] we now have " + str(len(clients)) + " clients") try: while True: # neskoncna zanka msg_received = receive_json(client_sock) msg_type = str(msg_received["type"]) data = str(msg_received["data"]) time = str(msg_received["time"]) if not msg_received: # ce obstaja sporocilo break match msg_type: case "message": username = usernames[client_sock] if username is None: username = client_addr[0] + ":" + str(client_addr[1]) msg_received["username"] = username print(f'[{time}] [{username}] : {data}') if data.startswith("@"): [username, *data] = data.split() username = str(username).replace("@", "") cl = [c for c, u in usernames.items() if u == username] if len(cl) == 0: send_error(client_sock, "Client with this username does not exist.") continue client = cl[0] msg_received["data"] = "".join(data) send_json(client, msg_received) continue for client in clients: if client == client_sock: continue send_json(client, msg_received) except: # tule bi lahko bolj elegantno reagirali, npr. na posamezne izjeme. Trenutno kar pozremo izjemo pass # prisli smo iz neskoncne zanke with clients_lock: clients.remove(client_sock) if client_sock in usernames: usernames.pop(client_sock) print("[system] we now have " + str(len(clients)) + " clients") client_sock.close() # kreiraj socket my_ssl_ctx = setup_SSL_context() server_socket = my_ssl_ctx.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) server_socket.bind(("localhost", PORT)) server_socket.listen(1) # cakaj na nove odjemalce print("[system] listening ...") clients = set() usernames = dict() clients_lock = threading.Lock() while True: try: # pocakaj na novo povezavo - blokirajoc klic client_sock, client_addr = server_socket.accept() with clients_lock: clients.add(client_sock) thread = threading.Thread(target=client_thread, args=(client_sock, client_addr)) thread.daemon = True thread.start() except KeyboardInterrupt: break print("[system] closing server socket ...") server_socket.close()