rk/LDN10/server.py
Gašper Dobrovoljc 885ce73d7e
LDN10
2024-05-26 18:10:13 +02:00

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()