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