358 lines
11 KiB
Python
358 lines
11 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 colorama import init, Fore
|
|
|
|
from Server.UserList import UserList
|
|
from Server.ACL import AccessControlList
|
|
from Server.CommandException import CommandException
|
|
from Common.socketCommands import sendData, recvData
|
|
from Server.errorHandler import serverSendErrHandler, serverRecvErrHandler
|
|
from Server.miscCommands import helpController, historyController, addToHistory
|
|
from Server.prototypeVNC import startxController
|
|
from Server.fileCommands import DATA_PATH, rmController, mvController, mkdirController, cdController, lsController, cpController, vimController, findController, rightsController, chmodController, chownController
|
|
from Server.ulCommands import adduserController, removeuserController, addgroupController, delgroupController, addusergroupController, removeusergroupController, passwdController, groupsController
|
|
|
|
|
|
__authors__ = "HorlogeSkynet, Tatiyk, CaumartinYann"
|
|
__copyright__ = "Copyright 2017, ACMS"
|
|
__license__ = "GPLv3"
|
|
__status__ = "Production"
|
|
__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, serverRecvErrHandler, self.sock)
|
|
if not login:
|
|
self.stop()
|
|
return
|
|
|
|
if not login.startswith('login:'):
|
|
sendData(self.sock, 'LOGIN_ERROR', serverSendErrHandler, self.sock)
|
|
continue
|
|
|
|
login = login.split(':', 1)
|
|
if len(login) != 2:
|
|
sendData(self.sock, 'LOGIN_ERROR', serverSendErrHandler, self.sock)
|
|
continue
|
|
|
|
# Sends and ACK for the login reception
|
|
sendData(self.sock, 'LOGIN_OK', serverSendErrHandler, self.sock)
|
|
|
|
# Let's wait for the password reception
|
|
password = recvData(self.sock, serverRecvErrHandler, self.sock)
|
|
if not password:
|
|
self.stop()
|
|
return
|
|
|
|
if not password.startswith('password:'):
|
|
sendData(self.sock, 'LOGIN_ERROR', serverSendErrHandler, self.sock)
|
|
continue
|
|
|
|
password = password.split(':', 1)
|
|
if len(password) != 2:
|
|
sendData(self.sock, 'PASSWORD_ERROR', serverSendErrHandler, self.sock)
|
|
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', serverSendErrHandler, self.sock)
|
|
break
|
|
|
|
else:
|
|
sendData(self.sock, "AUTHENTICATION_ERROR", serverSendErrHandler, self.sock)
|
|
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.', serverSendErrHandler, self.sock)
|
|
|
|
else:
|
|
self.checkAndSetHomeDir()
|
|
sendData(self.sock, 'NEW_PATH:' + '/' + self.currentWorkingDir.partition(DATA_PATH)[2], serverSendErrHandler, self.sock)
|
|
|
|
# Usage procedure #
|
|
while True:
|
|
command = recvData(self.sock, serverRecvErrHandler, self.sock)
|
|
if not command or not computeCommand(command, self):
|
|
break
|
|
|
|
addToHistory(self.username, command, acl)
|
|
|
|
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(Fore.RED + "Cannot create home directory for " + self.username + Fore.RESET)
|
|
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 there are any spaces before the first word...
|
|
if not command[0].strip():
|
|
command.remove(command[0])
|
|
|
|
# Lower the first argument... Handy for one-word commands
|
|
command[0] = str.lower(command[0])
|
|
|
|
###########################
|
|
# Miscellaneous functions #
|
|
###########################
|
|
|
|
if command[0] == 'exit' and len(command) == 1:
|
|
return False
|
|
|
|
elif command[0] == 'clear' and len(command) == 1:
|
|
sendData(client.sock, 'CLEAR_SCREEN', serverSendErrHandler, client.sock)
|
|
|
|
elif (command[0] == 'help' or command[0] == '?') and len(command) == 1:
|
|
sendData(client.sock, helpController(acl.isAdministrator(client.username)), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'history' and len(command) == 1:
|
|
sendData(client.sock, historyController(client.username), serverSendErrHandler, client.sock)
|
|
|
|
####################
|
|
# Graphic function #
|
|
####################
|
|
|
|
elif command[0] == 'startx' and len(command) == 1:
|
|
startxController(client.sock, clients.index(client))
|
|
|
|
##################
|
|
# File functions #
|
|
##################
|
|
|
|
elif command[0] == 'rm':
|
|
rmController(command, client, acl)
|
|
sendData(client.sock, "Done.", serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'mv':
|
|
mvController(command, client, acl)
|
|
sendData(client.sock, "Done.", serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'mkdir':
|
|
mkdirController(command, client, acl)
|
|
sendData(client.sock, "The directory has been created.", serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'cd':
|
|
cdController(command, client, acl)
|
|
sendData(client.sock, "NEW_PATH:" + '/' + client.currentWorkingDir.partition(DATA_PATH)[2], serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'ls':
|
|
sendData(client.sock, lsController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'cp':
|
|
cpController(command, client, acl)
|
|
sendData(client.sock, "File or directory successfully copied !", serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'vim':
|
|
vimController(command, client, acl)
|
|
|
|
elif command[0] == 'view':
|
|
command[0] = 'vim'
|
|
vimController(command, client, acl, readOnly=True)
|
|
|
|
elif command[0] == 'find':
|
|
sendData(client.sock, findController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'rights':
|
|
sendData(client.sock, rightsController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'chmod':
|
|
sendData(client.sock, chmodController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'chown':
|
|
sendData(client.sock, chownController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
#######################
|
|
# User List functions #
|
|
#######################
|
|
|
|
elif command[0] == 'adduser':
|
|
sendData(client.sock, adduserController(command, client, userList, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'removeuser':
|
|
sendData(client.sock, removeuserController(command, client, userList, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'addgroup':
|
|
sendData(client.sock, addgroupController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'delgroup':
|
|
sendData(client.sock, delgroupController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'addusergroup':
|
|
sendData(client.sock, addusergroupController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'removeusergroup':
|
|
sendData(client.sock, removeusergroupController(command, client, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'passwd':
|
|
sendData(client.sock, passwdController(command, client, userList, acl), serverSendErrHandler, client.sock)
|
|
|
|
elif command[0] == 'groups' and len(command) == 1:
|
|
sendData(client.sock, groupsController(client.username, userList, acl), serverSendErrHandler, client.sock)
|
|
|
|
# Unknown command
|
|
else:
|
|
sendData(client.sock, "Unknown command (or is there too many arguments ?).\nYou should try `help` or your \'TAB\' key.", serverSendErrHandler, client.sock)
|
|
|
|
except CommandException as e:
|
|
sendData(client.sock, e.message, serverSendErrHandler, client.sock)
|
|
|
|
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()
|
|
|
|
# Colorama module initialization
|
|
init(autoreset=True)
|
|
|
|
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", ssl_version=ssl.PROTOCOL_TLSv1_2)
|
|
|
|
# Let's run a new thread for the connection with this client
|
|
ClientThread(sock, address, port, mutex).start()
|