From 83ee74cfc0908d85bad80dbdb58a9bce79d9c54e Mon Sep 17 00:00:00 2001 From: markypenguin <54793197+markypenguin@users.noreply.github.com> Date: Fri, 24 Mar 2023 10:40:35 +0800 Subject: [PATCH] Add files via upload --- server_v1.py | 426 +++++++++++++++++++++++++++++++++++++++++++++++++++ users.txt | 1 + 2 files changed, 427 insertions(+) create mode 100644 server_v1.py create mode 100644 users.txt diff --git a/server_v1.py b/server_v1.py new file mode 100644 index 0000000..99ac947 --- /dev/null +++ b/server_v1.py @@ -0,0 +1,426 @@ +import math +import os +import socket +import ssl +import threading +import time +import configparser + +BUFFER_SIZE = 1024 + +class serverThread(threading.Thread): + + def __init__(self, conn, addr, usersDB, currDir, IP, port): + threading.Thread.__init__(self) + self.conn = conn + self.addr = addr + self.serverIP = IP + self.serverPort = port + self.baseWD = currDir + self.cwd = self.baseWD + self.rest = False + self.PASVmode = False + self.isLoggedIn = False + self.users = usersDB + self.validUser = False + self.isConnected = True + self.islist = False + self.mode = 'I' # Default Mode + self.allowDelete = True + + def run(self): + self.isConnected = True + # Welcome Message + resp = '220 Welcome to the FTP server.\n' + self.sendReply(resp) + # Await for connection from clients + while True: + cmd = self.conn.recv(256).decode() + if not cmd or not self.isConnected: + break + else: + print('Server Received: ', cmd) + try: + func = getattr(self, cmd[:4].strip().upper()) + func(cmd) + except Exception as err: + print('Error: ', err) + resp = '500 Syntax error, command unrecognized.' + self.sendReply(resp) + self.conn.close() + + def sendReply(self, reply): + self.conn.send((reply + '\r\n').encode()) + + def notLoggedInMSG(self): + res = '530 Please login with USER and PASS.' + self.sendReply(res) + + def paramError(self, cmd): + res = '501 \'' + cmd[:-2] + '\': parameter not understood.' + self.sendReply(res) + + def resetState(self): + + # RESET STATE of affairs + self.isLoggedIn = False + self.validUser = False + self.user = None + + def startDTPsocket(self): + try: + if self.PASVmode: + self.DTPsocket, addr = self.serverSocket.accept() + print('connect: ', addr) + except socket.error: + resp = '425 Cannot open Data Connection' + self.sendReply(resp) + + def stopDTPsocket(self): + self.DTPsocket.close() + if self.PASVmode: + self.serverSocket.close() + + def sendData(self, data): + # Mode of sending? + if not self.islist and self.mode == 'I': + self.DTPsocket.send((data)) + else: + self.DTPsocket.send((data + '\r\n').encode()) + + def LIST(self, cmd): + # Can't list if not logged in + if self.isLoggedIn: + resp = '150 File status okay; about to open data connection.' + self.sendReply(resp) + print('list: ', self.cwd) + # Ready the socket for data transfer + self.startDTPsocket() + # Get each file in the directory + for l in os.listdir(self.cwd): + ll = self.toList(os.path.join(self.cwd, l)) + # Send as str/ASCII + self.islist = True + self.sendData(ll) + self.islist = False + # Done + self.stopDTPsocket() + resp = '200 Listing completed.' + self.sendReply(resp) + else: + self.notLoggedInMSG() + + def toList(self, l): + st = os.stat(l) + fullmode = 'rwxrwxrwx' + mode = '' + # Prep the directory listing with regards to RFC959 + for i in range(9): + mode += ((st.st_mode >> (8 - i)) & 1) and fullmode[i] or '-' + + d = (os.path.isdir(l)) and 'd' or '-' + fhist = time.strftime(' %b %d %H:%M ', time.gmtime(st.st_mtime)) + return d + mode + '\t1 user' + '\t group \t\t' + str(st.st_size) + '\t' + fhist + '\t' + os.path.basename(l) + + def UPLD(self, cmd): + # need to update for same file name and file size + # Cant store files if not logged in + if self.isLoggedIn: + # Create file path + fileName = os.path.join(self.cwd, cmd[5:-2]) + print('Uploading: ', fileName) + # Upload mode? + if self.mode == 'I': + oFile = open(fileName, 'wb') + else: + oFile = open(fileName, 'w') + resp = '150 Opening data connection.' + self.sendReply(resp) + # Ready the socket for upload + self.startDTPsocket() + # Get the file + while True: + data = self.DTPsocket.recv(8192) + # print(data) + if not data: + break + oFile.write(data) + # Done + self.stopDTPsocket() + resp = '226 Transfer complete.' + self.sendReply(resp) + print('Upload success') + oFile.close() + else: + self.notLoggedInMSG() + + def RETR(self, cmd): + # Cant retrieve files if not logged in + if self.isLoggedIn: + fileName = os.path.join(self.cwd, cmd[5:-2]) + # For Filezilla + if fileName[0] == '/': + fileName = fileName[1:] + # Check if file exist + if os.path.exists(fileName): + print('Downloading :', fileName) + if self.mode == 'I': + rFile = open(fileName, 'rb') + else: + rFile = open(fileName, 'r') + # Open data connection + resp = '150 Opening file data connection.' + self.sendReply(resp) + data = rFile.read(8192) + self.startDTPsocket() + # Send the file + while data: + self.sendData(data) + data = rFile.read(8192) + rFile.close() + self.stopDTPsocket() + resp = '226 Transfer complete.' + self.sendReply(resp) + print("Download Sent") + else: + # File does not exist + resp = '550 The system cannot find the file specified.' + self.sendReply(resp) + else: + self.notLoggedInMSG() + + def DELF(self, cmd): + # Can't delete file if not logged in + # check if file name exist before deleting? + if self.isLoggedIn: + fileName = os.path.join(self.cwd, cmd[4:-2]) + os.remove(fileName) + resp = '250 Directory deleted.' + self.sendReply(resp) + else: + self.notLoggedInMSG() + + def RNFR(self, cmd): + # Can't rename file if not logged in + if self.isLoggedIn: + old_filename = cmd[1] + new_filename = cmd[2] + os.rename(old_filename, new_filename) + os.chdir("../") + resp = '250 File renamed' + self.sendReply(resp) + + def QUIT(self, cmd): + # If the user is logged in, they are logged out + if self.isLoggedIn: + self.resetState() + resp = '221 Logged out' + self.sendReply(resp) + else: + resp = '221 Service closing control connection' + self.sendReply(resp) + self.isConnected = False + + def USER(self, cmd): + # RESET STATE, Incase someone logs in while the other is still logged in + self.resetState() + # Extract username in the command + self.user = cmd[5:-2] + # Read users file + users = open(self.users, 'r').read() + # Check if user exists on the database + for u in users.split('\n'): + if self.user == u.split(' ')[0] and len(u.split(' ')[0]) != 0: + self.validUser = True + resp = '331 User name okay, need password.' + self.sendReply(resp) + break + if not self.validUser: + resp = '530 Invalid User.' + self.sendReply(resp) + self.validUser = False + + def PASS(self, cmd): + # Check if user name is entered + if self.validUser: + password = cmd[5:-2] + pws = open(self.users, 'r').read() + # Check if password matches user + for p in pws.split('\n'): + + if len(p.split(' ')[0]) != 0: + if password == p.split(' ')[1] and self.user == p.split(' ')[0]: + self.isLoggedIn = True + resp = '230 User logged in, proceed.' + self.sendReply(resp) + break + if not self.isLoggedIn: + resp = '530 Invalid password for ' + self.user + self.sendReply(resp) + else: + self.notLoggedInMSG() + + def TYPE(self, cmd): + # ASCII or Binary Mode + mode = cmd[5] + # Confirm I or A + if mode.upper() == 'I': + self.mode = mode + resp = '200 Binary mode.' + self.sendReply(resp) + elif mode.upper() == 'A': + self.mode = mode + resp = '200 ASCII mode.' + self.sendReply(resp) + else: + # Unknown parameter + self.paramError(cmd) + + def PASV(self, cmd): + # Cant't try to establish connection without logging in + if self.isLoggedIn: + self.PASVmode = True + self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.serverSocket.bind((self.serverIP, 0)) + self.serverSocket.listen(1) + ip, port = self.serverSocket.getsockname() + # Condition IP with the RFC959 standard + ip = ip.split('.') + ip = ','.join(ip) + # Condition the port with the RFC959 standard + p1 = math.floor(port / 256) + p2 = port % 256 + print('open...\nIP: ' + str(ip) + '\nPORT: ' + str(port)) + # Prepare the connection settings for take-off + resp = '227 Entering Passive Mode (' + str(ip) + ',' + str(p1) + ',' + str(p2) + ').' + self.sendReply(resp) + else: + self.notLoggedInMSG() + + def PORT(self, cmd): + # Cant't try to establish connection without logging in + if self.isLoggedIn: + # check if Passive Mode + if self.PASVmode: + self.serverSocket.close() + self.PASVmode = False + # Split the connection settings + conSettings = cmd[5:].split(',') + # Generate the IP address from the connection settings + self.DTPaddr = '.'.join(conSettings[:4]) + # Generate the PORT from the connection settings + # This is with respect to RFC959 + self.DTPport = ((int(conSettings[4]) << 8)) + int(conSettings[5]) + print('Connected to :', self.DTPaddr, self.DTPport) + # Acknowledge + resp = '200 Got it.' + self.sendReply(resp) + self.DTPsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.DTPsocket.connect((self.DTPaddr, self.DTPport)) + else: + self.notLoggedInMSG() + + def PWD(self, cmd): + # Cant't print working directory if not logged in + if self.isLoggedIn: + # The path relative to the root + tempDir = '/' + self.cwd + cwd = os.path.relpath(tempDir, '/') + if cwd == '.': + cwd = '/' + else: + cwd = '/' + cwd + resp = '257' + ' "' + cwd + '" is the current dir.' + self.sendReply(resp) + else: + self.notLoggedInMSG() + + def CWD(self, cmd): + + if self.isLoggedIn: + # Get the directory + chwd = cmd[4:-2] + # Base directory? + if chwd == '.' or chwd == '/': + self.cwd = self.baseWD + resp = '250 OK.' + self.sendReply(resp) + else: + # Consider /dir or dir + if chwd[0] == '/': + chwd = chwd[1:] + tempCwd = os.path.join(self.cwd, chwd) + # Does the path exist? + if os.path.exists(tempCwd): + self.cwd = tempCwd + resp = '250 OK.' + self.sendReply(resp) + else: + resp = '550 The system cannot find the file specified.' + self.sendReply(resp) + else: + self.notLoggedInMSG() + + def HELP(self, cmd): + if self.isLoggedIn: + resp = '\nList of executable commands:\nLIST: List files\nUPLD : ' + b'Upload file\nDWLD : Download file\nDELF : Delete file\nRNTO ' + b': Rename file\nQUIT: Exit' + self.sendReply(resp) + else: + self.notLoggedInMSG() + +class FTPserver(threading.Thread): + + # The lookout class, waits for contact from client + + def __init__(self, usersDB, homeDir, IP, Port): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.serverIP = IP + self.serverPort = Port + self.sock.bind((self.serverIP, self.serverPort)) + self.usersDB = usersDB + self.homeDir = homeDir + threading.Thread.__init__(self) + + def run(self): + self.sock.listen(5) + while True: + connectionSocket, addr = self.sock.accept() + thread = serverThread(connectionSocket, addr, self.usersDB, self.homeDir, self.serverIP, self.serverPort) + thread.daemon = True + thread.start() + + def stop(self): + self.sock.close() + + +def Main(): + config = configparser.ConfigParser() + config.read('./config/config.ini') + serverPort = int(config['FTPSERVER']['port']) + + server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + server.connect(("8.8.8.8", 80)) + + serverIP = server.getsockname()[0] + + # Database for users + users = './users.txt' + + # Default directory + homeDir = '../' + + # Make new thread for each new connection + + cThread = FTPserver(users, homeDir, serverIP, serverPort) + cThread.daemon = True + cThread.start() + + # Wait for contact + print('FTP-Application running on', serverIP, ':', serverPort) + input('Enter to end...\n') + cThread.stop() + + +Main() \ No newline at end of file diff --git a/users.txt b/users.txt new file mode 100644 index 0000000..dd72661 --- /dev/null +++ b/users.txt @@ -0,0 +1 @@ +admin admin