Module lib.server.ftpservercore
Ftp server implementation core class
Expand source code
# Distributed under Pycameresp License
# Copyright (c) 2023 Remi BERTHOLET
# historically based on :
# https://github.com/robert-hh/FTP-Server-for-ESP8266-ESP32-and-PYBD/blob/master/ftp.py
# but I have modified a lot, there must still be some original functions.
# pylint:disable=consider-using-f-string
# pylint:disable=unspecified-encoding
""" Ftp server implementation core class """
import socket
import os
import server.stream
import server.user
import wifi.accesspoint
import wifi.station
import tools.logger
import tools.fnmatch
import tools.filesystem
import tools.strings
import tools.date
import tools.tasking
MONTHS = [b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec"]
class FtpServerCore:
""" Ftp implementation server core """
portbase = [12345]
def __init__(self):
""" Ftp constructor method """
self.portbase[0] += 1
self.dataport = self.portbase[0]
self.pasvsocket = None
self.addr = b""
self.user = b""
self.password = b""
self.path = b""
self.cwd = b"/"
self.fromname = None
if tools.filesystem.ismicropython():
self.root = b""
self.path_length = 64
else:
self.root = tools.strings.tobytes(os.getcwd() + "/ftp")
self.path_length = 256
self.command = b""
self.payload = b""
self.datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.datasocket.bind(socket.getaddrinfo("0.0.0.0", self.dataport)[0][4])
self.datasocket.listen(1)
self.datasocket.settimeout(None)
self.data_addr = None
self.quit = None
self.received = None
self.remoteaddr = None
self.client = None
self.log(b"Open data %d"%self.dataport)
def log(self, err, msg="", write=False):
""" Log message """
if write:
tools.logger.syslog(err, msg=msg, write=write)
def get_ip(self):
""" Get the ip address of the board """
if wifi.station.Station.is_ip_on_interface(self.remoteaddr):
result = tools.strings.tobytes(wifi.station.Station.get_info()[0])
else:
result = tools.strings.tobytes(wifi.accesspoint.AccessPoint.get_info()[0])
return result
def close(self):
""" Close all ftp connections """
self.close_pasv()
if self.datasocket:
self.datasocket.close()
self.datasocket = None
def __del__(self):
""" Destroy ftp instance """
self.close()
def get_file_description(self, filename, typ, size, current_date, now, full):
""" Build list of file description """
if full:
file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--"
d = tools.date.local_time(current_date)
year,month,day,hour,minute,_,_,_ = d[:8]
if year != now[0] and month != now[1]:
file_date = b"%s %2d %4d"%(MONTHS[month-1], day, year)
else:
file_date = b"%s %2d %02d:%02d"%(MONTHS[month-1], day, hour, minute)
description = b"%s 1 owner group %10d %s %s\r\n"%(file_permissions, size, file_date, tools.strings.tobytes(filename))
else:
description = tools.strings.tobytes(filename) + b"\r\n"
return description
def send_file_list_with_pattern(self, path, stream_, full, now, pattern=None):
""" Send the list of file with pattern """
description = b""
quantity = 0
counter = 0
for fileinfo in tools.filesystem.list_directory(tools.strings.tostrings(path)):
filename = fileinfo[0]
typ = fileinfo[1]
if len(fileinfo) > 3:
size = fileinfo[3]
else:
size = 0
if pattern is None:
accepted = True
else:
accepted = tools.fnmatch.fnmatch(tools.strings.tostrings(filename), tools.strings.tostrings(pattern))
if accepted:
if quantity > 100:
current_date = 0
else:
sta = (0,0,0,0,0,0,0,0,0)
try:
# If it is a file
if not (typ & 0xF000 == 0x4000):
sta = tools.filesystem.fileinfo(tools.strings.tostrings(tools.filesystem.abspathbytes(path,tools.strings.tobytes(filename))))
except Exception:
pass
current_date = sta[8]
description += self.get_file_description(filename, typ, size, current_date, now, full)
counter += 1
if counter == 20:
counter = 0
stream_.write(description)
description = b""
quantity += 1
if description != b"":
stream_.write(description)
def send_file_list(self, path, stream_, full):
""" Send the list of file """
now = tools.date.local_time()
try:
self.send_file_list_with_pattern(path, stream_, full, now)
except Exception as err:
self.log(err, write=True)
pattern = path.split(b"/")[-1]
path = path[:-(len(pattern) + 1)]
if path == b"":
path = b"/"
self.send_file_list_with_pattern(path, stream_, full, now, pattern)
async def send_ok(self):
""" Send ok to ftp client """
await self.send_response(250,b"OK")
async def send_response(self, code, message):
""" Send response to ftp client """
self.log(b"%d %s"%(code, message))
await self.client.write(b'%d %s\r\n'%(code,message))
async def send_error(self, err):
""" Send error to ftp client """
showError = False
if type(err) != type(b""):
if tools.filesystem.ismicropython():
if type(err) != type(OSError):
showError = True
else:
if isinstance(err,FileNotFoundError) or isinstance(err,NotADirectoryError):
showError = False
else:
showError = True
if showError:
self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload))
await self.send_response(550, b"Failed")
async def USER(self):
""" Ftp command USER """
if server.user.User.get_user() == b"":
await self.send_response(230, b"User Logged In.")
else:
self.user = self.path[1:]
await self.send_response(331, b"User known, enter password")
async def PASS(self):
""" Ftp command PASS """
self.password = self.path[1:]
if server.user.User.check(self.user, self.password, False):
await self.send_response(230, b"Logged in.")
else:
await self.send_response(430, b"Invalid username or password")
async def SYST(self):
""" Ftp command SYST """
await self.send_response(215, b"UNIX Type: L8")
async def NOOP(self):
""" Ftp command NOOP """
await self.send_response(200, b"OK")
async def FEAT(self):
""" Ftp command FEAT """
await self.send_response(211, b"no-features")
async def XPWD(self):
""" Ftp command XPWD """
await self.PWD()
async def PWD(self):
""" Ftp command PWD """
await self.send_response(257,b'"%s" is current directory.'%self.cwd)
async def XCWD(self):
""" Ftp command XCWD """
await self.CWD()
async def CWD(self):
""" Ftp command CWD """
if len(self.path) <= self.path_length:
try:
dd = os.listdir(tools.strings.tostrings(self.root + self.path))
self.cwd = self.path
await self.send_response(250,b"CWD command successful.")
except Exception as err:
self.log(err, write=True)
await self.send_error(b"Path not existing")
else:
await self.send_error(b"Path too long")
async def CDUP(self):
""" Ftp command CDUP """
self.cwd = tools.filesystem.abspathbytes(self.cwd, b"..")
await self.send_ok()
async def TYPE(self):
""" Ftp command TYPE """
await self.send_response(200, b"Binary transfer mode active.")
async def SIZE(self):
""" Ftp command SIZE """
size = tools.filesystem.filesize(tools.strings.tostrings(self.root + self.path))
await self.send_response(213, b"%d"%(size))
async def PASV(self):
""" Ftp command PASV """
await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256))
self.close_pasv()
self.pasvsocket, self.data_addr = self.datasocket.accept()
self.log(b"PASV Accepted")
async def PORT(self):
""" Ftp command PORT """
items = self.payload.split(b",")
if len(items) >= 6:
self.data_addr = b'.'.join(items[:4])
if self.data_addr == b"127.0.1.1":
self.data_addr = self.remoteaddr
self.dataport = int(items[4]) * 256 + int(items[5])
self.close_pasv()
self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.pasvsocket.settimeout(1000)
self.pasvsocket.connect((self.data_addr, self.dataport))
self.log("Data connection with: %s"%tools.strings.tostrings(self.data_addr))
await self.send_response(200, b"OK")
else:
await self.send_response(504, b"Fail")
async def NLST(self):
""" Ftp command NLST """
await self.LIST()
async def LIST(self):
""" Ftp command LIST """
if not self.payload.startswith(b"-"):
place = self.path
else:
place = self.cwd
await self.send_response(150, b"Connection accepted.") # Start list files
listsocket = server.stream.Socket(self.pasvsocket)
self.log("List %s"%(tools.strings.tostrings(self.root+place)))
self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l")
listsocket.close()
await self.send_response(226, b"Transfert complete.") # End list files
self.close_pasv()
async def STAT(self):
""" Ftp command STAT """
if self.payload == b"":
await self.send_response(211, b"Connected to (%s)"%self.remoteaddr[0])
await self.send_response(211, b"Data address (%s)"%self.addr)
await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream")
else:
await self.send_response(213,b"Directory listing:")
self.log("List %s"%tools.strings.tostrings(self.root+self.path))
self.send_file_list(self.root + self.path, self.client, True)
await self.send_response(213, b"Stat end")
async def RETR(self):
""" Ftp command RETR """
await self.send_response(150, b"Start send file")
self.log("Send %s"%tools.strings.tostrings(self.root+self.path), write=True)
filename = self.root + self.path
if tools.filesystem.ismicropython():
buffer_size = 1440
chunk = bytearray(buffer_size)
with open(tools.strings.tostrings(filename), "r") as file:
length = file.readinto(chunk)
while length > 0:
# pylint: disable=no-member
sent = self.pasvsocket.write(chunk[:length])
length = file.readinto(chunk)
else:
with open(tools.strings.tostrings(filename), "rb") as file:
self.pasvsocket.sendall(file.read())
await self.send_response(226, b"End send file")
self.close_pasv()
def close_pasv(self):
""" Close PASV connection """
if self.pasvsocket is not None:
self.log(b"Close PASV")
self.pasvsocket.close()
self.pasvsocket = None
def write_file(self, path, dataclient):
""" Write ftp received """
chunk = bytearray(1440)
with open(tools.strings.tostrings(path), "wb") as file:
length = dataclient.readinto(chunk)
while length > 0:
file.write(chunk, length)
length = dataclient.readinto(chunk)
async def STOR(self):
""" Ftp command STOR """
await self.send_response(150, b"Start receive file")
self.log("Receive %s"%tools.strings.tostrings(self.root + self.path), write=True)
filename = self.root + self.path
if tools.filesystem.ismicropython():
try:
self.write_file(filename, self.pasvsocket)
except Exception as err:
self.log(err, write=True)
directory, file = tools.filesystem.split(tools.strings.tostrings(filename))
tools.filesystem.makedir(directory, True)
self.write_file(filename, self.pasvsocket)
else:
with open(filename, "wb") as file:
data = b" "
while len(data) > 0:
data = self.pasvsocket.recv(1440)
file.write(data)
data = b""
await self.send_response(226, b"End receive file")
self.close_pasv()
async def DELE(self):
""" Ftp command DELE """
self.log("Delete %s"%tools.strings.tostrings(self.root + self.path), write=True)
os.remove(tools.strings.tostrings(self.root + self.path))
await self.send_ok()
async def XRMD(self):
""" Ftp command XRMD """
await self.RMD()
async def RMD(self):
""" Ftp command RMD """
os.rmdir(tools.strings.tostrings(self.root + self.path))
await self.send_ok()
async def XMKD(self):
""" Ftp command XMKD """
await self.MKD()
async def MKD(self):
""" Ftp command MKD """
os.mkdir(tools.strings.tostrings(self.root + self.path))
await self.send_ok()
async def RNFR(self):
""" Ftp command RNFR """
self.fromname = self.path
await self.send_response(350, b"Rename from")
async def RNTO(self):
""" Ftp command RNTO """
if self.fromname is not None:
self.log("Rename %s to %s"%(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path)), write=True)
os.rename(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path))
await self.send_ok()
else:
await self.send_error(self.fromname)
self.fromname = None
async def QUIT(self):
""" Ftp command QUIT """
self.quit = True
await self.send_response(221, b"Bye.")
async def unsupported_command(self):
""" Ftp unknown command """
await self.send_response(502, b"Unsupported command")
async def receive_command(self):
""" Ftp command reception """
tools.tasking.Tasks.slow_down()
try:
self.received = await self.client.readline()
except Exception as err:
self.log(err, write=True)
self.log(b"Reset connection")
self.quit = True
if len(self.received) <= 0:
self.quit = True
else:
self.received = self.received.rstrip(b"\r\n")
if tools.strings.tobytes(self.received[:4]) == b"PASS":
message = b"PASS ????"
else:
message = self.received
self.command = self.received.split(b" ")[0].upper()
self.payload = self.received[len(self.command):].lstrip()
self.path = tools.filesystem.abspathbytes(self.cwd, self.payload)
self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path))
async def treat_command(self):
""" Treat ftp command """
tools.tasking.Tasks.slow_down()
if self.quit is False:
try:
command = tools.strings.tostrings(self.command)
if hasattr(self, command):
callback = getattr(self, command)
if self.command not in [b"USER",b"PASS"]:
if server.user.User.check(self.user, self.password):
await callback()
else:
await self.send_response(430, b"Invalid username or password")
else:
await callback()
else:
await self.unsupported_command()
except Exception as err:
self.log(err, write=True)
await self.send_error(err)
async def on_connection(self, reader, writer):
""" Asyncio on ftp connection method """
tools.tasking.Tasks.slow_down()
self.remoteaddr = tools.strings.tobytes(writer.get_extra_info('peername')[0])
self.addr = self.get_ip()
self.log("Connected from %s"%tools.strings.tostrings(self.remoteaddr), write=True)
self.client = server.stream.Stream(reader, writer)
try:
await self.send_response(220, b"Ftp " + tools.strings.tobytes(os.uname()[4]) + b".")
self.quit = False
while self.quit is False:
await self.receive_command()
await self.treat_command()
except Exception as err:
self.log(err, write=True)
await self.send_error(err)
finally:
self.close_pasv()
await self.client.close()
self.log("Disconnected", write=True)
Classes
class FtpServerCore-
Ftp implementation server core
Ftp constructor method
Expand source code
class FtpServerCore: """ Ftp implementation server core """ portbase = [12345] def __init__(self): """ Ftp constructor method """ self.portbase[0] += 1 self.dataport = self.portbase[0] self.pasvsocket = None self.addr = b"" self.user = b"" self.password = b"" self.path = b"" self.cwd = b"/" self.fromname = None if tools.filesystem.ismicropython(): self.root = b"" self.path_length = 64 else: self.root = tools.strings.tobytes(os.getcwd() + "/ftp") self.path_length = 256 self.command = b"" self.payload = b"" self.datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.datasocket.bind(socket.getaddrinfo("0.0.0.0", self.dataport)[0][4]) self.datasocket.listen(1) self.datasocket.settimeout(None) self.data_addr = None self.quit = None self.received = None self.remoteaddr = None self.client = None self.log(b"Open data %d"%self.dataport) def log(self, err, msg="", write=False): """ Log message """ if write: tools.logger.syslog(err, msg=msg, write=write) def get_ip(self): """ Get the ip address of the board """ if wifi.station.Station.is_ip_on_interface(self.remoteaddr): result = tools.strings.tobytes(wifi.station.Station.get_info()[0]) else: result = tools.strings.tobytes(wifi.accesspoint.AccessPoint.get_info()[0]) return result def close(self): """ Close all ftp connections """ self.close_pasv() if self.datasocket: self.datasocket.close() self.datasocket = None def __del__(self): """ Destroy ftp instance """ self.close() def get_file_description(self, filename, typ, size, current_date, now, full): """ Build list of file description """ if full: file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--" d = tools.date.local_time(current_date) year,month,day,hour,minute,_,_,_ = d[:8] if year != now[0] and month != now[1]: file_date = b"%s %2d %4d"%(MONTHS[month-1], day, year) else: file_date = b"%s %2d %02d:%02d"%(MONTHS[month-1], day, hour, minute) description = b"%s 1 owner group %10d %s %s\r\n"%(file_permissions, size, file_date, tools.strings.tobytes(filename)) else: description = tools.strings.tobytes(filename) + b"\r\n" return description def send_file_list_with_pattern(self, path, stream_, full, now, pattern=None): """ Send the list of file with pattern """ description = b"" quantity = 0 counter = 0 for fileinfo in tools.filesystem.list_directory(tools.strings.tostrings(path)): filename = fileinfo[0] typ = fileinfo[1] if len(fileinfo) > 3: size = fileinfo[3] else: size = 0 if pattern is None: accepted = True else: accepted = tools.fnmatch.fnmatch(tools.strings.tostrings(filename), tools.strings.tostrings(pattern)) if accepted: if quantity > 100: current_date = 0 else: sta = (0,0,0,0,0,0,0,0,0) try: # If it is a file if not (typ & 0xF000 == 0x4000): sta = tools.filesystem.fileinfo(tools.strings.tostrings(tools.filesystem.abspathbytes(path,tools.strings.tobytes(filename)))) except Exception: pass current_date = sta[8] description += self.get_file_description(filename, typ, size, current_date, now, full) counter += 1 if counter == 20: counter = 0 stream_.write(description) description = b"" quantity += 1 if description != b"": stream_.write(description) def send_file_list(self, path, stream_, full): """ Send the list of file """ now = tools.date.local_time() try: self.send_file_list_with_pattern(path, stream_, full, now) except Exception as err: self.log(err, write=True) pattern = path.split(b"/")[-1] path = path[:-(len(pattern) + 1)] if path == b"": path = b"/" self.send_file_list_with_pattern(path, stream_, full, now, pattern) async def send_ok(self): """ Send ok to ftp client """ await self.send_response(250,b"OK") async def send_response(self, code, message): """ Send response to ftp client """ self.log(b"%d %s"%(code, message)) await self.client.write(b'%d %s\r\n'%(code,message)) async def send_error(self, err): """ Send error to ftp client """ showError = False if type(err) != type(b""): if tools.filesystem.ismicropython(): if type(err) != type(OSError): showError = True else: if isinstance(err,FileNotFoundError) or isinstance(err,NotADirectoryError): showError = False else: showError = True if showError: self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload)) await self.send_response(550, b"Failed") async def USER(self): """ Ftp command USER """ if server.user.User.get_user() == b"": await self.send_response(230, b"User Logged In.") else: self.user = self.path[1:] await self.send_response(331, b"User known, enter password") async def PASS(self): """ Ftp command PASS """ self.password = self.path[1:] if server.user.User.check(self.user, self.password, False): await self.send_response(230, b"Logged in.") else: await self.send_response(430, b"Invalid username or password") async def SYST(self): """ Ftp command SYST """ await self.send_response(215, b"UNIX Type: L8") async def NOOP(self): """ Ftp command NOOP """ await self.send_response(200, b"OK") async def FEAT(self): """ Ftp command FEAT """ await self.send_response(211, b"no-features") async def XPWD(self): """ Ftp command XPWD """ await self.PWD() async def PWD(self): """ Ftp command PWD """ await self.send_response(257,b'"%s" is current directory.'%self.cwd) async def XCWD(self): """ Ftp command XCWD """ await self.CWD() async def CWD(self): """ Ftp command CWD """ if len(self.path) <= self.path_length: try: dd = os.listdir(tools.strings.tostrings(self.root + self.path)) self.cwd = self.path await self.send_response(250,b"CWD command successful.") except Exception as err: self.log(err, write=True) await self.send_error(b"Path not existing") else: await self.send_error(b"Path too long") async def CDUP(self): """ Ftp command CDUP """ self.cwd = tools.filesystem.abspathbytes(self.cwd, b"..") await self.send_ok() async def TYPE(self): """ Ftp command TYPE """ await self.send_response(200, b"Binary transfer mode active.") async def SIZE(self): """ Ftp command SIZE """ size = tools.filesystem.filesize(tools.strings.tostrings(self.root + self.path)) await self.send_response(213, b"%d"%(size)) async def PASV(self): """ Ftp command PASV """ await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256)) self.close_pasv() self.pasvsocket, self.data_addr = self.datasocket.accept() self.log(b"PASV Accepted") async def PORT(self): """ Ftp command PORT """ items = self.payload.split(b",") if len(items) >= 6: self.data_addr = b'.'.join(items[:4]) if self.data_addr == b"127.0.1.1": self.data_addr = self.remoteaddr self.dataport = int(items[4]) * 256 + int(items[5]) self.close_pasv() self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.pasvsocket.settimeout(1000) self.pasvsocket.connect((self.data_addr, self.dataport)) self.log("Data connection with: %s"%tools.strings.tostrings(self.data_addr)) await self.send_response(200, b"OK") else: await self.send_response(504, b"Fail") async def NLST(self): """ Ftp command NLST """ await self.LIST() async def LIST(self): """ Ftp command LIST """ if not self.payload.startswith(b"-"): place = self.path else: place = self.cwd await self.send_response(150, b"Connection accepted.") # Start list files listsocket = server.stream.Socket(self.pasvsocket) self.log("List %s"%(tools.strings.tostrings(self.root+place))) self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l") listsocket.close() await self.send_response(226, b"Transfert complete.") # End list files self.close_pasv() async def STAT(self): """ Ftp command STAT """ if self.payload == b"": await self.send_response(211, b"Connected to (%s)"%self.remoteaddr[0]) await self.send_response(211, b"Data address (%s)"%self.addr) await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream") else: await self.send_response(213,b"Directory listing:") self.log("List %s"%tools.strings.tostrings(self.root+self.path)) self.send_file_list(self.root + self.path, self.client, True) await self.send_response(213, b"Stat end") async def RETR(self): """ Ftp command RETR """ await self.send_response(150, b"Start send file") self.log("Send %s"%tools.strings.tostrings(self.root+self.path), write=True) filename = self.root + self.path if tools.filesystem.ismicropython(): buffer_size = 1440 chunk = bytearray(buffer_size) with open(tools.strings.tostrings(filename), "r") as file: length = file.readinto(chunk) while length > 0: # pylint: disable=no-member sent = self.pasvsocket.write(chunk[:length]) length = file.readinto(chunk) else: with open(tools.strings.tostrings(filename), "rb") as file: self.pasvsocket.sendall(file.read()) await self.send_response(226, b"End send file") self.close_pasv() def close_pasv(self): """ Close PASV connection """ if self.pasvsocket is not None: self.log(b"Close PASV") self.pasvsocket.close() self.pasvsocket = None def write_file(self, path, dataclient): """ Write ftp received """ chunk = bytearray(1440) with open(tools.strings.tostrings(path), "wb") as file: length = dataclient.readinto(chunk) while length > 0: file.write(chunk, length) length = dataclient.readinto(chunk) async def STOR(self): """ Ftp command STOR """ await self.send_response(150, b"Start receive file") self.log("Receive %s"%tools.strings.tostrings(self.root + self.path), write=True) filename = self.root + self.path if tools.filesystem.ismicropython(): try: self.write_file(filename, self.pasvsocket) except Exception as err: self.log(err, write=True) directory, file = tools.filesystem.split(tools.strings.tostrings(filename)) tools.filesystem.makedir(directory, True) self.write_file(filename, self.pasvsocket) else: with open(filename, "wb") as file: data = b" " while len(data) > 0: data = self.pasvsocket.recv(1440) file.write(data) data = b"" await self.send_response(226, b"End receive file") self.close_pasv() async def DELE(self): """ Ftp command DELE """ self.log("Delete %s"%tools.strings.tostrings(self.root + self.path), write=True) os.remove(tools.strings.tostrings(self.root + self.path)) await self.send_ok() async def XRMD(self): """ Ftp command XRMD """ await self.RMD() async def RMD(self): """ Ftp command RMD """ os.rmdir(tools.strings.tostrings(self.root + self.path)) await self.send_ok() async def XMKD(self): """ Ftp command XMKD """ await self.MKD() async def MKD(self): """ Ftp command MKD """ os.mkdir(tools.strings.tostrings(self.root + self.path)) await self.send_ok() async def RNFR(self): """ Ftp command RNFR """ self.fromname = self.path await self.send_response(350, b"Rename from") async def RNTO(self): """ Ftp command RNTO """ if self.fromname is not None: self.log("Rename %s to %s"%(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path)), write=True) os.rename(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path)) await self.send_ok() else: await self.send_error(self.fromname) self.fromname = None async def QUIT(self): """ Ftp command QUIT """ self.quit = True await self.send_response(221, b"Bye.") async def unsupported_command(self): """ Ftp unknown command """ await self.send_response(502, b"Unsupported command") async def receive_command(self): """ Ftp command reception """ tools.tasking.Tasks.slow_down() try: self.received = await self.client.readline() except Exception as err: self.log(err, write=True) self.log(b"Reset connection") self.quit = True if len(self.received) <= 0: self.quit = True else: self.received = self.received.rstrip(b"\r\n") if tools.strings.tobytes(self.received[:4]) == b"PASS": message = b"PASS ????" else: message = self.received self.command = self.received.split(b" ")[0].upper() self.payload = self.received[len(self.command):].lstrip() self.path = tools.filesystem.abspathbytes(self.cwd, self.payload) self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path)) async def treat_command(self): """ Treat ftp command """ tools.tasking.Tasks.slow_down() if self.quit is False: try: command = tools.strings.tostrings(self.command) if hasattr(self, command): callback = getattr(self, command) if self.command not in [b"USER",b"PASS"]: if server.user.User.check(self.user, self.password): await callback() else: await self.send_response(430, b"Invalid username or password") else: await callback() else: await self.unsupported_command() except Exception as err: self.log(err, write=True) await self.send_error(err) async def on_connection(self, reader, writer): """ Asyncio on ftp connection method """ tools.tasking.Tasks.slow_down() self.remoteaddr = tools.strings.tobytes(writer.get_extra_info('peername')[0]) self.addr = self.get_ip() self.log("Connected from %s"%tools.strings.tostrings(self.remoteaddr), write=True) self.client = server.stream.Stream(reader, writer) try: await self.send_response(220, b"Ftp " + tools.strings.tobytes(os.uname()[4]) + b".") self.quit = False while self.quit is False: await self.receive_command() await self.treat_command() except Exception as err: self.log(err, write=True) await self.send_error(err) finally: self.close_pasv() await self.client.close() self.log("Disconnected", write=True)Class variables
var portbase
Methods
async def CDUP(self)-
Ftp command CDUP
Expand source code
async def CDUP(self): """ Ftp command CDUP """ self.cwd = tools.filesystem.abspathbytes(self.cwd, b"..") await self.send_ok() async def CWD(self)-
Ftp command CWD
Expand source code
async def CWD(self): """ Ftp command CWD """ if len(self.path) <= self.path_length: try: dd = os.listdir(tools.strings.tostrings(self.root + self.path)) self.cwd = self.path await self.send_response(250,b"CWD command successful.") except Exception as err: self.log(err, write=True) await self.send_error(b"Path not existing") else: await self.send_error(b"Path too long") async def DELE(self)-
Ftp command DELE
Expand source code
async def DELE(self): """ Ftp command DELE """ self.log("Delete %s"%tools.strings.tostrings(self.root + self.path), write=True) os.remove(tools.strings.tostrings(self.root + self.path)) await self.send_ok() async def FEAT(self)-
Ftp command FEAT
Expand source code
async def FEAT(self): """ Ftp command FEAT """ await self.send_response(211, b"no-features") async def LIST(self)-
Ftp command LIST
Expand source code
async def LIST(self): """ Ftp command LIST """ if not self.payload.startswith(b"-"): place = self.path else: place = self.cwd await self.send_response(150, b"Connection accepted.") # Start list files listsocket = server.stream.Socket(self.pasvsocket) self.log("List %s"%(tools.strings.tostrings(self.root+place))) self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l") listsocket.close() await self.send_response(226, b"Transfert complete.") # End list files self.close_pasv() async def MKD(self)-
Ftp command MKD
Expand source code
async def MKD(self): """ Ftp command MKD """ os.mkdir(tools.strings.tostrings(self.root + self.path)) await self.send_ok() async def NLST(self)-
Ftp command NLST
Expand source code
async def NLST(self): """ Ftp command NLST """ await self.LIST() async def NOOP(self)-
Ftp command NOOP
Expand source code
async def NOOP(self): """ Ftp command NOOP """ await self.send_response(200, b"OK") async def PASS(self)-
Ftp command PASS
Expand source code
async def PASS(self): """ Ftp command PASS """ self.password = self.path[1:] if server.user.User.check(self.user, self.password, False): await self.send_response(230, b"Logged in.") else: await self.send_response(430, b"Invalid username or password") async def PASV(self)-
Ftp command PASV
Expand source code
async def PASV(self): """ Ftp command PASV """ await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256)) self.close_pasv() self.pasvsocket, self.data_addr = self.datasocket.accept() self.log(b"PASV Accepted") async def PORT(self)-
Ftp command PORT
Expand source code
async def PORT(self): """ Ftp command PORT """ items = self.payload.split(b",") if len(items) >= 6: self.data_addr = b'.'.join(items[:4]) if self.data_addr == b"127.0.1.1": self.data_addr = self.remoteaddr self.dataport = int(items[4]) * 256 + int(items[5]) self.close_pasv() self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.pasvsocket.settimeout(1000) self.pasvsocket.connect((self.data_addr, self.dataport)) self.log("Data connection with: %s"%tools.strings.tostrings(self.data_addr)) await self.send_response(200, b"OK") else: await self.send_response(504, b"Fail") async def PWD(self)-
Ftp command PWD
Expand source code
async def PWD(self): """ Ftp command PWD """ await self.send_response(257,b'"%s" is current directory.'%self.cwd) async def QUIT(self)-
Ftp command QUIT
Expand source code
async def QUIT(self): """ Ftp command QUIT """ self.quit = True await self.send_response(221, b"Bye.") async def RETR(self)-
Ftp command RETR
Expand source code
async def RETR(self): """ Ftp command RETR """ await self.send_response(150, b"Start send file") self.log("Send %s"%tools.strings.tostrings(self.root+self.path), write=True) filename = self.root + self.path if tools.filesystem.ismicropython(): buffer_size = 1440 chunk = bytearray(buffer_size) with open(tools.strings.tostrings(filename), "r") as file: length = file.readinto(chunk) while length > 0: # pylint: disable=no-member sent = self.pasvsocket.write(chunk[:length]) length = file.readinto(chunk) else: with open(tools.strings.tostrings(filename), "rb") as file: self.pasvsocket.sendall(file.read()) await self.send_response(226, b"End send file") self.close_pasv() async def RMD(self)-
Ftp command RMD
Expand source code
async def RMD(self): """ Ftp command RMD """ os.rmdir(tools.strings.tostrings(self.root + self.path)) await self.send_ok() async def RNFR(self)-
Ftp command RNFR
Expand source code
async def RNFR(self): """ Ftp command RNFR """ self.fromname = self.path await self.send_response(350, b"Rename from") async def RNTO(self)-
Ftp command RNTO
Expand source code
async def RNTO(self): """ Ftp command RNTO """ if self.fromname is not None: self.log("Rename %s to %s"%(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path)), write=True) os.rename(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path)) await self.send_ok() else: await self.send_error(self.fromname) self.fromname = None async def SIZE(self)-
Ftp command SIZE
Expand source code
async def SIZE(self): """ Ftp command SIZE """ size = tools.filesystem.filesize(tools.strings.tostrings(self.root + self.path)) await self.send_response(213, b"%d"%(size)) async def STAT(self)-
Ftp command STAT
Expand source code
async def STAT(self): """ Ftp command STAT """ if self.payload == b"": await self.send_response(211, b"Connected to (%s)"%self.remoteaddr[0]) await self.send_response(211, b"Data address (%s)"%self.addr) await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream") else: await self.send_response(213,b"Directory listing:") self.log("List %s"%tools.strings.tostrings(self.root+self.path)) self.send_file_list(self.root + self.path, self.client, True) await self.send_response(213, b"Stat end") async def STOR(self)-
Ftp command STOR
Expand source code
async def STOR(self): """ Ftp command STOR """ await self.send_response(150, b"Start receive file") self.log("Receive %s"%tools.strings.tostrings(self.root + self.path), write=True) filename = self.root + self.path if tools.filesystem.ismicropython(): try: self.write_file(filename, self.pasvsocket) except Exception as err: self.log(err, write=True) directory, file = tools.filesystem.split(tools.strings.tostrings(filename)) tools.filesystem.makedir(directory, True) self.write_file(filename, self.pasvsocket) else: with open(filename, "wb") as file: data = b" " while len(data) > 0: data = self.pasvsocket.recv(1440) file.write(data) data = b"" await self.send_response(226, b"End receive file") self.close_pasv() async def SYST(self)-
Ftp command SYST
Expand source code
async def SYST(self): """ Ftp command SYST """ await self.send_response(215, b"UNIX Type: L8") async def TYPE(self)-
Ftp command TYPE
Expand source code
async def TYPE(self): """ Ftp command TYPE """ await self.send_response(200, b"Binary transfer mode active.") async def USER(self)-
Ftp command USER
Expand source code
async def USER(self): """ Ftp command USER """ if server.user.User.get_user() == b"": await self.send_response(230, b"User Logged In.") else: self.user = self.path[1:] await self.send_response(331, b"User known, enter password") async def XCWD(self)-
Ftp command XCWD
Expand source code
async def XCWD(self): """ Ftp command XCWD """ await self.CWD() async def XMKD(self)-
Ftp command XMKD
Expand source code
async def XMKD(self): """ Ftp command XMKD """ await self.MKD() async def XPWD(self)-
Ftp command XPWD
Expand source code
async def XPWD(self): """ Ftp command XPWD """ await self.PWD() async def XRMD(self)-
Ftp command XRMD
Expand source code
async def XRMD(self): """ Ftp command XRMD """ await self.RMD() def close(self)-
Close all ftp connections
Expand source code
def close(self): """ Close all ftp connections """ self.close_pasv() if self.datasocket: self.datasocket.close() self.datasocket = None def close_pasv(self)-
Close PASV connection
Expand source code
def close_pasv(self): """ Close PASV connection """ if self.pasvsocket is not None: self.log(b"Close PASV") self.pasvsocket.close() self.pasvsocket = None def get_file_description(self, filename, typ, size, current_date, now, full)-
Build list of file description
Expand source code
def get_file_description(self, filename, typ, size, current_date, now, full): """ Build list of file description """ if full: file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--" d = tools.date.local_time(current_date) year,month,day,hour,minute,_,_,_ = d[:8] if year != now[0] and month != now[1]: file_date = b"%s %2d %4d"%(MONTHS[month-1], day, year) else: file_date = b"%s %2d %02d:%02d"%(MONTHS[month-1], day, hour, minute) description = b"%s 1 owner group %10d %s %s\r\n"%(file_permissions, size, file_date, tools.strings.tobytes(filename)) else: description = tools.strings.tobytes(filename) + b"\r\n" return description def get_ip(self)-
Get the ip address of the board
Expand source code
def get_ip(self): """ Get the ip address of the board """ if wifi.station.Station.is_ip_on_interface(self.remoteaddr): result = tools.strings.tobytes(wifi.station.Station.get_info()[0]) else: result = tools.strings.tobytes(wifi.accesspoint.AccessPoint.get_info()[0]) return result def log(self, err, msg='', write=False)-
Log message
Expand source code
def log(self, err, msg="", write=False): """ Log message """ if write: tools.logger.syslog(err, msg=msg, write=write) async def on_connection(self, reader, writer)-
Asyncio on ftp connection method
Expand source code
async def on_connection(self, reader, writer): """ Asyncio on ftp connection method """ tools.tasking.Tasks.slow_down() self.remoteaddr = tools.strings.tobytes(writer.get_extra_info('peername')[0]) self.addr = self.get_ip() self.log("Connected from %s"%tools.strings.tostrings(self.remoteaddr), write=True) self.client = server.stream.Stream(reader, writer) try: await self.send_response(220, b"Ftp " + tools.strings.tobytes(os.uname()[4]) + b".") self.quit = False while self.quit is False: await self.receive_command() await self.treat_command() except Exception as err: self.log(err, write=True) await self.send_error(err) finally: self.close_pasv() await self.client.close() self.log("Disconnected", write=True) async def receive_command(self)-
Ftp command reception
Expand source code
async def receive_command(self): """ Ftp command reception """ tools.tasking.Tasks.slow_down() try: self.received = await self.client.readline() except Exception as err: self.log(err, write=True) self.log(b"Reset connection") self.quit = True if len(self.received) <= 0: self.quit = True else: self.received = self.received.rstrip(b"\r\n") if tools.strings.tobytes(self.received[:4]) == b"PASS": message = b"PASS ????" else: message = self.received self.command = self.received.split(b" ")[0].upper() self.payload = self.received[len(self.command):].lstrip() self.path = tools.filesystem.abspathbytes(self.cwd, self.payload) self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path)) async def send_error(self, err)-
Send error to ftp client
Expand source code
async def send_error(self, err): """ Send error to ftp client """ showError = False if type(err) != type(b""): if tools.filesystem.ismicropython(): if type(err) != type(OSError): showError = True else: if isinstance(err,FileNotFoundError) or isinstance(err,NotADirectoryError): showError = False else: showError = True if showError: self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload)) await self.send_response(550, b"Failed") def send_file_list(self, path, stream_, full)-
Send the list of file
Expand source code
def send_file_list(self, path, stream_, full): """ Send the list of file """ now = tools.date.local_time() try: self.send_file_list_with_pattern(path, stream_, full, now) except Exception as err: self.log(err, write=True) pattern = path.split(b"/")[-1] path = path[:-(len(pattern) + 1)] if path == b"": path = b"/" self.send_file_list_with_pattern(path, stream_, full, now, pattern) def send_file_list_with_pattern(self, path, stream_, full, now, pattern=None)-
Send the list of file with pattern
Expand source code
def send_file_list_with_pattern(self, path, stream_, full, now, pattern=None): """ Send the list of file with pattern """ description = b"" quantity = 0 counter = 0 for fileinfo in tools.filesystem.list_directory(tools.strings.tostrings(path)): filename = fileinfo[0] typ = fileinfo[1] if len(fileinfo) > 3: size = fileinfo[3] else: size = 0 if pattern is None: accepted = True else: accepted = tools.fnmatch.fnmatch(tools.strings.tostrings(filename), tools.strings.tostrings(pattern)) if accepted: if quantity > 100: current_date = 0 else: sta = (0,0,0,0,0,0,0,0,0) try: # If it is a file if not (typ & 0xF000 == 0x4000): sta = tools.filesystem.fileinfo(tools.strings.tostrings(tools.filesystem.abspathbytes(path,tools.strings.tobytes(filename)))) except Exception: pass current_date = sta[8] description += self.get_file_description(filename, typ, size, current_date, now, full) counter += 1 if counter == 20: counter = 0 stream_.write(description) description = b"" quantity += 1 if description != b"": stream_.write(description) async def send_ok(self)-
Send ok to ftp client
Expand source code
async def send_ok(self): """ Send ok to ftp client """ await self.send_response(250,b"OK") async def send_response(self, code, message)-
Send response to ftp client
Expand source code
async def send_response(self, code, message): """ Send response to ftp client """ self.log(b"%d %s"%(code, message)) await self.client.write(b'%d %s\r\n'%(code,message)) async def treat_command(self)-
Treat ftp command
Expand source code
async def treat_command(self): """ Treat ftp command """ tools.tasking.Tasks.slow_down() if self.quit is False: try: command = tools.strings.tostrings(self.command) if hasattr(self, command): callback = getattr(self, command) if self.command not in [b"USER",b"PASS"]: if server.user.User.check(self.user, self.password): await callback() else: await self.send_response(430, b"Invalid username or password") else: await callback() else: await self.unsupported_command() except Exception as err: self.log(err, write=True) await self.send_error(err) async def unsupported_command(self)-
Ftp unknown command
Expand source code
async def unsupported_command(self): """ Ftp unknown command """ await self.send_response(502, b"Unsupported command") def write_file(self, path, dataclient)-
Write ftp received
Expand source code
def write_file(self, path, dataclient): """ Write ftp received """ chunk = bytearray(1440) with open(tools.strings.tostrings(path), "wb") as file: length = dataclient.readinto(chunk) while length > 0: file.write(chunk, length) length = dataclient.readinto(chunk)