166 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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()
 |