This repository has been archived on 2023-11-03. You can view files and clone it, but cannot push or open issues or pull requests.
ACMS/Client/client.py

317 lines
8.3 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
A simple client for ACMS. Description to be improved.
"""
import os
import sys
import ssl
import tty
import time
import random
import socket
import atexit
import tkinter
import termios
from colorama import init, Fore
from tkinter.messagebox import showwarning, showinfo
from threading import Thread
__authors__ = "HorlogeSkynet, CaumartinYann"
__copyright__ = "Copyright 2017, ACMS"
__license__ = "GPLv3"
__status__ = "Development"
__date__ = "02/22/2017"
class SendThread(Thread):
def __init__(self):
Thread.__init__(self)
self.keepGoing = True
def run(self):
while True:
c = sys.stdin.buffer.read(1)
if not c:
break
else:
sock.send(c)
if not self.keepGoing:
break
def stop(self):
self.keepGoing = False
self.join()
#####################
# Display functions #
#####################
def clearScreen():
os.system('cls' if os.name == 'nt' else 'clear')
def setUpMasterWindow():
# Tkinter root application
master = tkinter.Tk()
# A title and an icon
master.title("A Cooperative Medical System")
master.tk.call('wm', 'iconphoto', master._w, tkinter.PhotoImage(file="Client/Resources/ACMS_Logo.png"))
# Manage the window closing
master.protocol('WM_DELETE_WINDOW', quit)
# Let's return the object created
return master
def loadDrTuxOnWindow(master):
# Let's load (and resize) the ACMS's logo into a canvas on the window
logo = tkinter.PhotoImage(file="Client/Resources/ACMS_Logo.png").subsample(15, 15)
canvas = tkinter.Canvas(master, width=logo.width() + 4, height=logo.height() + 4)
canvas.create_image(3, 3, anchor=tkinter.NW, image=logo)
canvas.image = logo # F**k the garbage collector
canvas.grid(row=0, column=1)
# An oval around the logo #swag
canvas.create_rectangle(1, 1, logo.width() + 4, logo.height() + 4)
def promptServerSettings(serverIP_default, serverPort_default):
master = setUpMasterWindow()
loadDrTuxOnWindow(master)
# A label and an entry for the IP Address
tkinter.Label(master, text='IP Address').grid(row=1, column=0)
serverIP = tkinter.StringVar()
serverIP.set(serverIP_default)
tkinter.Entry(master, textvariable=serverIP, width=30, bd=5, bg=('dark orange' if serverIP_default != TCP_IP or serverPort_default != TCP_PORT else 'white')).grid(row=1, column=1)
# A label and an entry for the port
tkinter.Label(master, text='Port').grid(row=2, column=0)
serverPort = tkinter.IntVar()
serverPort.set(serverPort_default if serverPort_default != 0 else '')
tkinter.Entry(master, textvariable=serverPort, width=30, bd=5, bg=('dark orange' if serverIP_default != TCP_IP or serverPort_default != TCP_PORT else 'white')).grid(row=2, column=1)
# A button for the connection
tkinter.Button(master, text='Connect', command=master.destroy, bg='forest green').grid(row=3, column=1)
# while(1)...
master.mainloop()
try:
return serverIP.get(), serverPort.get()
except:
return '', 0
def promptUserCredentials(login_default):
master = setUpMasterWindow()
loadDrTuxOnWindow(master)
# A label and an entry for the IP Address
tkinter.Label(master, text='Login').grid(row=1, column=0)
login = tkinter.StringVar()
login.set(login_default)
tkinter.Entry(master, textvariable=login, width=30, bd=5, bg=('dark orange' if login_default != '' else 'white')).grid(row=1, column=1)
# A label and an entry for the port
tkinter.Label(master, text='Password').grid(row=2, column=0)
password = tkinter.StringVar()
password.set('')
tkinter.Entry(master, textvariable=password, show='', width=30, bd=5, bg=('dark orange' if login_default != '' else 'white')).grid(row=2, column=1)
# A button for the connection
tkinter.Button(master, text='Log in', command=master.destroy, bg='forest green').grid(row=3, column=1)
# while(1)...
master.mainloop()
return login.get(), password.get()
#####################
# Sockets functions #
#####################
def sendData(data):
try:
sock.send(data.encode())
except:
print("An error occurred while sending your command to the server. Closing client now.\n")
quit()
def recvData():
try:
return sock.recv(BUFFER_SIZE).decode().strip()
except:
print("An error occurred while receiving your command from the server. Closing client now.\n")
quit()
def cleaningFunction():
sock.close()
print("Bye !\n")
##########
# main() #
##########
if __name__ == '__main__':
# Socket DEFAULT settings
TCP_IP = "127.0.0.1"
TCP_PORT = 4242
BUFFER_SIZE = 2048
# Client socket initialization
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Let's add a SSL layer above the socket created previously
sock = ssl.wrap_socket(sock, server_side=False, ca_certs="Client/Credentials/server.crt", cert_reqs=ssl.CERT_REQUIRED)
# The socket has been created, we'll have to close it on program's ending
atexit.register(cleaningFunction)
# Colorama module initialization
init(autoreset=True)
# Displays some stuff
clearScreen()
print("\n\tWelcome on the ACMS\'s Client !\n")
# Connection procedure #
# Simple affectations in order to 'remember' user entries
serverIP, serverPort = TCP_IP, TCP_PORT
while True:
# Let's ask and get the (IP, Port) in order to connect to the server !
serverIP, serverPort = promptServerSettings(serverIP, serverPort)
# An invisible window to open up a message box without bug
messageBox = tkinter.Tk()
messageBox.withdraw()
try:
# Let's connect the client to the server
sock.connect((serverIP, serverPort))
showinfo("Successfully connected !", "You\'re now connected to \"" + serverIP + ':' + str(serverPort) + "\" !")
break
except ConnectionRefusedError:
showwarning("Connection has failed !", "No server seems running on " + serverIP + ':' + str(serverPort) + "...")
except (ConnectionResetError, OSError):
showwarning("Your IP is blacklisted !", "Too many connections have been received from your IP on " + serverIP + ':' + str(serverPort) + "...")
quit()
finally:
messageBox.destroy()
# Login procedure #
login = ''
while True:
login, password = promptUserCredentials(login)
# An invisible window to open up a message box without a bug afterwards
messageBox = tkinter.Tk()
messageBox.withdraw()
sendData('login:' + login)
if recvData() == 'LOGIN_OK':
sendData('password:' + password)
if recvData() == 'PASSWORD_OK':
print("Congratulations, you\'re now authenticated on an ACMS server !\n")
break
time.sleep(2 + random.random() * 0.5)
showwarning("Authentication failure !", "These credentials are invalid.")
messageBox.destroy()
continue
# At the login, the user will be in the root directory
currentWorkingDir = recvData()
if currentWorkingDir.startswith('NEW_PATH:'):
currentWorkingDir = currentWorkingDir.split(':', 1)[1]
else:
print("Received from server: \"" + currentWorkingDir + '\"\n')
quit()
# Usage procedure
while True:
command = ''
try:
command = input('[' + Fore.RED + login + Fore.RESET + '@' + Fore.MAGENTA + serverIP + Fore.RESET + ':' + Fore.BLUE + currentWorkingDir + Fore.RESET + ']% ')
except EOFError:
# Force disconnection by server on EOF
sendData('exit')
quit()
if not command or not command.strip():
continue
else:
# The command is sent to the server...
sendData(command)
# ... and the answer is caught here
data = recvData()
# If the socket has been closed by server, let's shutdown the client
if not data:
print("\nThe connection with the server has been closed, let\'s close the client !\n")
break
elif data.startswith('NEW_PATH:'):
currentWorkingDir = data.split(':', 1)[1]
elif data == 'CLEAR_SCREEN':
clearScreen()
elif data == 'REDIRECT':
old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin)
sendThread = SendThread()
sendThread.start()
while True:
c = recvData()
if c == "VIM_STOP_REDIRECT_ACMS_2017_TOKEN:$}a*M-2i5<9Ù3(+$":
sendThread.stop()
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
print('\n', end='')
break
elif c.startswith("Vim : Alerte : La sortie ne s'effectue pas sur un terminal") \
or c.startswith("Vim : Alerte : L'entrée ne se fait pas sur un terminal") \
or c.startswith("Vim: Warning: Output is not to a terminal") \
or c.startswith("Vim: Warning: Input is not from a terminal"):
pass
else:
sys.stdout.buffer.write(c.encode())
sys.stdout.flush()
else:
print('\n' + data + '\n')