295 lines
7.1 KiB
Python
295 lines
7.1 KiB
Python
#!/usr/bin/env python3.5
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
A simple server for ACMS. Description to be improved.
|
|
"""
|
|
|
|
|
|
import os
|
|
import re
|
|
import ssl
|
|
import socket
|
|
import atexit
|
|
import threading
|
|
|
|
from UserList import UserList
|
|
from ACL import AccessControlList
|
|
from socketCommands import sendData, recvData
|
|
from fileCommands import DATA_PATH, listFiles, changeDirectory, makeDirectory, removeFile, copyFile, textEditor, moveFile
|
|
from CommandException import CommandException
|
|
|
|
|
|
__authors__ = "HorlogeSkynet, Tatiyk, CaumartinYann"
|
|
__copyright__ = "Copyright 2017, ACMS"
|
|
__license__ = "GPLv3"
|
|
__status__ = "Development"
|
|
__date__ = "02/22/2017"
|
|
|
|
|
|
MAX_SESSIONS_PER_USER = 2
|
|
MAX_NUMBER_CLIENTS_PER_IP = 2
|
|
|
|
|
|
################
|
|
# Client class #
|
|
################
|
|
|
|
class ClientThread(threading.Thread):
|
|
def __init__(self, sock, address, port, mutex):
|
|
super(ClientThread, self).__init__()
|
|
|
|
self.sock = sock
|
|
self.address = address
|
|
self.port = port
|
|
|
|
self.mutex = mutex
|
|
|
|
self.HOME_DIR = DATA_PATH
|
|
self.currentWorkingDir = ''
|
|
|
|
self.username = ''
|
|
|
|
def run(self):
|
|
clients.append(self)
|
|
|
|
print("\nA new client thread has just started for \"" + self.address + ':' + str(self.port) + "\".")
|
|
displayStatus()
|
|
|
|
# Log in procedure #
|
|
while True:
|
|
# ATM, the client should sends the login followed by the password !
|
|
login = recvData(self.sock)
|
|
if not login:
|
|
self.stop()
|
|
return
|
|
|
|
if not login.startswith('login:'):
|
|
sendData(self.sock, 'LOGIN_ERROR')
|
|
continue
|
|
|
|
login = login.split(':', 1)
|
|
if len(login) != 2:
|
|
sendData(self.sock, 'LOGIN_ERROR')
|
|
continue
|
|
|
|
# Sends and ACK for the login reception
|
|
sendData(self.sock, 'LOGIN_OK')
|
|
|
|
# Let's wait for the password reception
|
|
password = recvData(self.sock)
|
|
if not password:
|
|
self.stop()
|
|
return
|
|
|
|
if not password.startswith('password:'):
|
|
sendData(self.sock, 'LOGIN_ERROR')
|
|
continue
|
|
|
|
password = password.split(':', 1)
|
|
if len(password) != 2:
|
|
sendData(self.sock, 'PASSWORD_ERROR')
|
|
continue
|
|
|
|
# We have just received both username and password, let's check them and send an answer !
|
|
if userList.checkUserPassword(login[1], password[1]):
|
|
sendData(self.sock, 'PASSWORD_OK')
|
|
break
|
|
|
|
else:
|
|
sendData(self.sock, "AUTHENTICATION_ERROR")
|
|
continue
|
|
|
|
self.username = login[1]
|
|
|
|
# Contain session number here
|
|
if [client.username for client in clients].count(self.username) > MAX_SESSIONS_PER_USER:
|
|
sendData(self.sock, 'ERROR: Too many sessions are already opened for this account.')
|
|
|
|
else:
|
|
self.checkAndSetHomeDir()
|
|
sendData(self.sock, 'NEW_PATH:' + '/' + self.currentWorkingDir.partition(DATA_PATH)[2])
|
|
|
|
# Usage procedure #
|
|
while True:
|
|
command = recvData(self.sock)
|
|
if not command or not computeCommand(command, self):
|
|
break
|
|
|
|
self.stop()
|
|
|
|
print("\nThe thread for \"" + self.address + ':' + str(self.port) + "\" has just terminated.")
|
|
displayStatus()
|
|
|
|
def stop(self):
|
|
self.sock.close()
|
|
|
|
clients.remove(self)
|
|
|
|
def checkAndSetHomeDir(self):
|
|
tmp = self.HOME_DIR
|
|
self.HOME_DIR += self.username + '/'
|
|
if not os.path.exists(self.HOME_DIR):
|
|
try:
|
|
os.mkdir(self.HOME_DIR)
|
|
acl.addFile(self.username, self.HOME_DIR)
|
|
|
|
except:
|
|
print("Cannot create home directory for " + self.username)
|
|
self.currentWorkingDir = tmp
|
|
return
|
|
|
|
if acl.isAllowedToOn(self.username, 'execute', self.HOME_DIR):
|
|
self.currentWorkingDir = self.HOME_DIR
|
|
|
|
else:
|
|
self.currentWorkingDir = tmp
|
|
|
|
|
|
#####################
|
|
# Display functions #
|
|
#####################
|
|
|
|
def displayStatus():
|
|
print("There is now " + str(len(clients)) + " client" + ('s' if len(clients) > 1 else '') + " connected.\n")
|
|
|
|
|
|
def clearScreen():
|
|
os.system('cls' if os.name == 'nt' else 'clear')
|
|
|
|
|
|
#####################
|
|
# atexit() function #
|
|
#####################
|
|
|
|
def cleaningFunction():
|
|
for client in clients:
|
|
client.stop()
|
|
|
|
listenSock.close()
|
|
|
|
|
|
######################
|
|
# Commands functions #
|
|
######################
|
|
|
|
def computeCommand(command, client):
|
|
|
|
# Let's split this command around ' ' and '\t'
|
|
command = re.split('\s+', command)
|
|
|
|
try:
|
|
if command[0] == 'exit' and len(command) == 1:
|
|
return False
|
|
|
|
elif command[0] == 'ls':
|
|
sendData(client.sock, listFiles(command, client, acl))
|
|
|
|
elif command[0] == 'cd':
|
|
changeDirectory(command, client, acl)
|
|
sendData(client.sock, "NEW_PATH:" + '/' + client.currentWorkingDir.partition(DATA_PATH)[2])
|
|
|
|
elif command[0] == 'mkdir':
|
|
makeDirectory(command, client, acl)
|
|
sendData(client.sock, "The directory has been created.")
|
|
|
|
elif command[0] == 'mv':
|
|
moveFile(command, client, acl)
|
|
sendData(client.sock, "Done")
|
|
|
|
elif command[0] == 'rm':
|
|
removeFile(command, client, acl)
|
|
sendData(client.sock, "Done.")
|
|
|
|
elif command[0] == 'cp':
|
|
copyFile(command, client, acl)
|
|
sendData(client.sock, "File or directory successfully copied !")
|
|
|
|
elif command[0] == 'clear':
|
|
sendData(client.sock, 'CLEAR_SCREEN')
|
|
|
|
elif command[0] == 'vim':
|
|
textEditor(command, client, acl)
|
|
|
|
elif command[0] == 'view':
|
|
command[0] = 'vim'
|
|
textEditor(command, client, acl, readOnly=True)
|
|
|
|
elif command[0] == 'startx' and len(command) == 1:
|
|
# TO DO in a future feature
|
|
sendData(client.sock, "There is not any GUI implemented yet :/")
|
|
|
|
#######################
|
|
# User List functions #
|
|
#######################
|
|
|
|
elif command[0] == 'adduser' and len(command) == 3:
|
|
userList.addUser(command[1], command[2], acl.isAdministrator(client.username))
|
|
sendData(client.sock, "New user " + command[1] + " added")
|
|
|
|
elif command[0] == 'removeuser' and len(command) == 2:
|
|
userList.removeUser(command[1], acl.isAdministrator(client.username))
|
|
sendData(client.sock, "User " + command[1] + " removed")
|
|
|
|
# Unknown command
|
|
else:
|
|
sendData(client.sock, "An unknown command has been received by server.")
|
|
|
|
except CommandException as e:
|
|
sendData(client.sock, e.message)
|
|
|
|
return True
|
|
|
|
|
|
##########
|
|
# main() #
|
|
##########
|
|
|
|
if __name__ == '__main__':
|
|
|
|
# Socket settings
|
|
TCP_IP = "0.0.0.0"
|
|
TCP_PORT = 4242
|
|
|
|
# Displays some stuff
|
|
clearScreen()
|
|
print("\n\tWelcome on the ACMS\'s Server !")
|
|
|
|
# An instance for our user list
|
|
userList = UserList()
|
|
|
|
# An instance for our ACL
|
|
acl = AccessControlList()
|
|
|
|
# Server socket initialization
|
|
listenSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
listenSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
|
|
listenSock.bind((TCP_IP, TCP_PORT))
|
|
listenSock.listen()
|
|
|
|
print("Listening for clients connection on \"" + str(TCP_IP) + ':' + str(TCP_PORT) + "\"...\n")
|
|
|
|
# Client threads container
|
|
clients = []
|
|
|
|
# The clients' list exists now, let's add a special cleaning function
|
|
atexit.register(cleaningFunction)
|
|
|
|
# Our mutex for the clients
|
|
mutex = threading.RLock()
|
|
|
|
while True:
|
|
# We wait and accept each entering connection
|
|
sock, (address, port) = listenSock.accept()
|
|
|
|
# Limit DDoS here
|
|
if [client.address for client in clients].count(address) + 1 > MAX_NUMBER_CLIENTS_PER_IP:
|
|
sock.close()
|
|
continue
|
|
|
|
# Creates a SSL socket from the one that has just been set up above
|
|
sock = ssl.wrap_socket(sock, server_side=True, certfile="Server/Credentials/server.crt", keyfile="Server/Credentials/server.key")
|
|
|
|
# Let's run a new thread for the connection with this client
|
|
ClientThread(sock, address, port, mutex).start()
|