Add files via upload
This commit is contained in:
parent
57f37c5561
commit
83ee74cfc0
|
@ -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 <file_name>: '
|
||||
b'Upload file\nDWLD <file_name>: Download file\nDELF <file_name>: Delete file\nRNTO <old_name> '
|
||||
b'<new_name>: 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()
|
Loading…
Reference in New Issue