#!/usr/bin/python3 import os import re import sys import socket import json import base64 from common_comm import send_dict, recv_dict, sendrecv_dict from Crypto.Cipher import AES from Crypto.Hash import SHA256 class Tcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' # Function to encript values for sending in json format # return int data encrypted in a 16 bytes binary string coded in base64 def encrypt_intvalue(cipherkey, data): key = base64.b64decode(cipherkey) cipher = AES.new(key, AES.MODE_ECB) data = cipher.encrypt(bytes("%16d" % data, "utf8")) return str(base64.b64encode(data), "utf8") # Function to decript values received in json format # return int data decrypted from a 16 bytes binary strings coded in base64 def decrypt_intvalue(cipherkey, data_arg): cipher = AES.new(base64.b64decode(cipherkey), AES.MODE_ECB) data = base64.b64decode(data_arg) data = cipher.decrypt(data) return int(str(data, "utf8")) # verify if response from server is valid or is an error message and act accordingly - já está implementada def validate_response(client_sock, response): if not response["status"]: print(f"{Tcolors.FAIL}Error: {response['error']}{Tcolors.ENDC}") client_sock.close() sys.exit(3) # process QUIT operation def quit_action(client_sock, has_started): print(f"{Tcolors.ENDC}Quitting...") if has_started: senddata = {"op": "QUIT"} recvdata = sendrecv_dict(client_sock, senddata) try: # status = False if not recvdata["status"]: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: {recvdata['error']}{Tcolors.ENDC}") return except TypeError: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: an error occurred with the server.{Tcolors.ENDC}") print(f"{Tcolors.ENDC}{Tcolors.WARNING}Client not removed from server, quitting...{Tcolors.ENDC}") client_sock.close() exit(1) # status = True print(f"{Tcolors.OKGREEN}Client quit with success") client_sock.close() exit(0) # Outcomming message structure: # { op = "START", client_id, [cipher] } # { op = "QUIT" } # { op = "NUMBER", number } # { op = "STOP", [shasum] } # { op = "GUESS", choice } # # Incomming message structure: # { op = "START", status } # { op = "QUIT" , status } # { op = "NUMBER", status } # { op = "STOP", status, value } # { op = "GUESS", status, result } # # Suport for executing the client pretended behaviour # # returns a valid number def returnValidNum(): while 1: try: num = int(input(f"\n{Tcolors.ENDC}{Tcolors.BOLD}Number > {Tcolors.UNDERLINE}")) except ValueError: print(f"{Tcolors.ENDC}{Tcolors.WARNING}Invalid input{Tcolors.ENDC}") continue break return num # verify if port is valid def verify_port(port): # verify if port is a number if not port.isdigit(): return {"status": False, "error": "Port must be an integer"} # verify if port is between 1024 and 65535 if not (1024 <= int(port) <= 65535): return {"status": False, "error": "Port number must be between 1024 and 65535"} return {"status": True, "port": int(port)} # verify if hostname is valid def verify_hostname(hostname): if hostname == "localhost": return {"status": True} if not (re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', hostname) and all(0 <= int(n) < 256 for n in hostname.split('.'))): return {"status": False, "error": "Invalid DNS address"} return {"status": True} def run_client(client_sock, client_id): # Print the welcome message print(f"{Tcolors.OKCYAN}{Tcolors.BOLD}{Tcolors.UNDERLINE}Number characteristics guesser game!{Tcolors.ENDC}\n") # client runtime global variables has_stopped = False has_started = False cipherkey = None numbers = [] while 1: option = input(f"{Tcolors.ENDC}\nOperation? (START, QUIT, NUMBER, STOP, GUESS)\n{Tcolors.BOLD}> {Tcolors.UNDERLINE}") # start option if option.upper() == "START": if has_started: print(f"{Tcolors.ENDC}{Tcolors.WARNING}Client already started\n{Tcolors.ENDC}") continue while 1: # ask user if cipher is needed choice = input(f"\n{Tcolors.ENDC}Do you wish to use a cipher? {Tcolors.BOLD}(Y/N)\n> {Tcolors.UNDERLINE}") if choice.upper() == "Y": # create cipher key for server cipherkey = base64.b64encode(os.urandom(16)).decode() break elif choice.upper() == "N": # do nothing since cipher will be None break else: # loop if invalid option print(f"{Tcolors.ENDC}{Tcolors.WARNING}Invalid input{Tcolors.ENDC}") continue # send dict and receive response senddata = {"op": "START", "client_id": client_id, "cipher": cipherkey} recvdata = sendrecv_dict(client_sock, senddata) try: # status = False if not recvdata["status"]: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: {recvdata['error']}{Tcolors.ENDC}") print(f"{Tcolors.ENDC}{Tcolors.WARNING}Client not added, quitting...{Tcolors.ENDC}") client_sock.close() exit(1) except TypeError: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: an error occurred with the server, try again later{Tcolors.ENDC}") print(f"{Tcolors.ENDC}{Tcolors.WARNING}Client not added, quitting...{Tcolors.ENDC}") client_sock.close() exit(1) # status = True has_started = True print(f"{Tcolors.ENDC}{Tcolors.OKGREEN}\nClient added with success{Tcolors.ENDC}\n") elif option.upper() == "QUIT": quit_action(client_sock, has_started) continue elif option.upper() == "NUMBER": if not has_started: print(f"{Tcolors.ENDC}{Tcolors.WARNING}You must start the game first\n{Tcolors.ENDC}") continue if has_stopped: print(f"{Tcolors.ENDC}{Tcolors.WARNING}You can't add more numbers\n{Tcolors.ENDC}") continue # verify if number is int num = returnValidNum() # encrypt the number is a cipher is being used if cipherkey is not None: encrypted_num = encrypt_intvalue(cipherkey, num) else: encrypted_num = num # send dict and receive response senddata = {"op": "NUMBER", "number": encrypted_num} recvdata = sendrecv_dict(client_sock, senddata) try: # status = False if not recvdata["status"]: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: {recvdata['error']}{Tcolors.ENDC}") client_sock.close() continue # status = True numbers.append(num) print(f"{Tcolors.ENDC}{Tcolors.OKGREEN}Number added with success{Tcolors.ENDC}\n") except TypeError: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: an error occurred with the server\nSocket has been closed, try to start again{Tcolors.ENDC}") client_sock.close() has_started = False cipherkey = None numbers = [] continue elif option.upper() == "STOP": # check if client has started the game if not has_started: print(f"{Tcolors.ENDC}{Tcolors.WARNING}You must start the game first\n{Tcolors.ENDC}") continue # check if client has stopped adding numbers if has_stopped: print(f"{Tcolors.ENDC}{Tcolors.WARNING}You can't stop the game again\n{Tcolors.ENDC}") continue # creates the synthesis for the number list hasher = SHA256.new() for number in numbers: hasher.update(bytes(str(number), "utf8")) # send dict and receive response senddata = {"op": "STOP", "shasum": hasher.hexdigest()} recvdata = sendrecv_dict(client_sock, senddata) try: # status = False if not recvdata["status"]: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: {recvdata['error']}{Tcolors.ENDC}") continue except TypeError: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: an error occurred with the server\nSocket has been closed, try to start again{Tcolors.ENDC}") client_sock.close() has_started = False cipherkey = None numbers = [] continue # decipher data if using encryption data = recvdata["value"] if cipherkey is not None: data = decrypt_intvalue(cipherkey, data) has_stopped = True # status = True print(f"{Tcolors.ENDC}{Tcolors.OKGREEN}\nChosen number: {Tcolors.UNDERLINE}{data}{Tcolors.ENDC}\n") elif option.upper() == "GUESS": # check if client has started the game if not has_started: print(f"{Tcolors.ENDC}{Tcolors.WARNING}You must start the game first\n{Tcolors.ENDC}") continue # check if client has stopped adding numbers if not has_stopped: print(f"{Tcolors.ENDC}{Tcolors.WARNING}You can't guess before stopping the game\n{Tcolors.ENDC}") continue # print the possible choices print(f""" {Tcolors.ENDC}Choose one of the following options: 1 - first 2 - last 3 - min 4 - max 5 - median 6 - min, first 7 - max, first 8 - min, last 9 - max, last 10 - median, first 11 - median, last """) while True: try: choice_num = int(input(f"{Tcolors.BOLD}\n> {Tcolors.UNDERLINE}")) if choice_num == 1: choice = ["first"] elif choice_num == 2: choice = ["last"] elif choice_num == 3: choice = ["min"] elif choice_num == 4: choice = ["max"] elif choice_num == 5: choice = ["median"] elif choice_num == 6: choice = ["min", "first"] elif choice_num == 7: choice = ["max", "first"] elif choice_num == 8: choice = ["min", "last"] elif choice_num == 9: choice = ["max", "last"] elif choice_num == 10: choice = ["median", "first"] elif choice_num == 11: choice = ["median", "last"] else: print(f"{Tcolors.ENDC}{Tcolors.WARNING}Invalid input{Tcolors.ENDC}") continue break except ValueError: print(f"{Tcolors.ENDC}{Tcolors.WARNING}Invalid input{Tcolors.ENDC}") continue # send dict and receive response senddata = {"op": "GUESS", "choice": choice} recvdata = sendrecv_dict(client_sock, senddata) try: # status = False if not recvdata["status"]: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: {recvdata['error']}{Tcolors.ENDC}") continue except TypeError: print(f"{Tcolors.ENDC}{Tcolors.FAIL}Error: an error occurred with the server\nSocket has been closed, try to start again{Tcolors.ENDC}") client_sock.close() has_started = False cipherkey = None numbers = [] continue # status = True print(f"\n\n{Tcolors.ENDC}{Tcolors.BOLD}{Tcolors.OKBLUE}{'='*15}\n\n{Tcolors.UNDERLINE}{Tcolors.OKCYAN}" + ("You are right!" if recvdata["result"] else "You are wrong!") + f"{Tcolors.ENDC}{Tcolors.BOLD}{Tcolors.OKBLUE}\n\n{'='*15}{Tcolors.ENDC}\n\n") quit_action(client_sock, has_started) else: print(f"{Tcolors.ENDC}{Tcolors.WARNING}Invalid option!\n{Tcolors.ENDC}") return None def main(): # validate the number of arguments and eventually print error message and exit with error # verify type of arguments and eventually print error message and exit with error if len(sys.argv) not in [3, 4]: print(f"{Tcolors.WARNING}Usage: python3 client.py client_id port DNS{Tcolors.ENDC}") sys.exit(1) # check if indicated port is valid and get its value port = sys.argv[2] verified = verify_port(port) if not verified["status"]: print(f"{Tcolors.WARNING}{verified['error']}{Tcolors.ENDC}") sys.exit(1) port = verified["port"] # get the ip address of the DNS and get its value hostname = sys.argv[3] if len(sys.argv) == 4 else socket.gethostbyname(socket.gethostname()) verified = verify_hostname(hostname) if not verified["status"]: print(f"{Tcolors.WARNING}{verified['error']}{Tcolors.ENDC}") sys.exit(1) # create the socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.bind(("0.0.0.0", 0)) # catch error message if server does not exist in those specifications print(f"{Tcolors.WARNING}Connecting to {Tcolors.UNDERLINE}{hostname}:{port}{Tcolors.ENDC}{Tcolors.WARNING}...{Tcolors.ENDC}") try: client_socket.connect((hostname, port)) except OSError: print(f"{Tcolors.FAIL}Error: connection to server failed{Tcolors.ENDC}") sys.exit(1) # send confirmation about the connection print(f"{Tcolors.OKGREEN}Connected to {Tcolors.UNDERLINE}{hostname}:{port}{Tcolors.ENDC}{Tcolors.OKGREEN} as client {Tcolors.UNDERLINE}{sys.argv[1]}\n{Tcolors.ENDC}") # run the client run_client(client_socket, sys.argv[1]) client_socket.close() sys.exit(0) if __name__ == "__main__": main()