317 lines
8.3 KiB
Python
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')
|