import socket import network import os import _thread DATA_PORT = 13333 ftpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ftpsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ftpsocket.bind(socket.getaddrinfo("0.0.0.0", 21)[0][4]) datasocket.bind(socket.getaddrinfo("0.0.0.0", DATA_PORT)[0][4]) ftpsocket.listen(1) datasocket.listen(1) datasocket.settimeout(10) dataclient = None def send_list_data(cwd, dataclient): for file in os.listdir(cwd): stat = os.stat(get_absolute_path(cwd, file)) file_permissions = "drwxr-xr-x" if (stat[0] & 0o170000 == 0o040000) else "-rw-r--r--" file_size = stat[6] description = "{} 1 owner group {:>13} Jan 1 1980 {}\r\n".format( file_permissions, file_size, file ) dataclient.sendall(description) def send_file_data(path, dataclient): with open(path) as file: chunk = file.read(128) while len(chunk) > 0: dataclient.sendall(chunk) chunk = file.read(128) def save_file_data(path, dataclient): with open(path, "w") as file: chunk = dataclient.read(128) while len(chunk) > 0: file.write(chunk) chunk = dataclient.read(128) def get_absolute_path(cwd, payload): # if it doesn't start with / consider # it a relative path if not payload.startswith("/"): payload = cwd + "/" + payload # and don't leave any trailing / return payload.rstrip("/") def ftp(): try: dataclient = None while True: cwd = "/" cl, remote_addr = ftpsocket.accept() cl.settimeout(300) try: print("FTP connection from:", remote_addr) cl.sendall("220 Hello, this is the ESP8266.\r\n") while True: data = cl.readline().decode("utf-8").replace("\r\n", "") if len(data) <= 0: print("Client is dead") break command, payload = (data.split(" ") + [""])[:2] command = command.upper() print("Command={}, Payload={}".format(command, payload)) if command == "USER": cl.sendall("230 Logged in.\r\n") elif command == "SYST": cl.sendall("215 ESP8266 MicroPython\r\n") elif command == "SYST": cl.sendall("502\r\n") elif command == "PWD": cl.sendall('257 "{}"\r\n'.format(cwd)) elif command == "CWD": path = get_absolute_path(cwd, payload) try: files = os.listdir(path) cwd = path cl.sendall('250 Directory changed successfully\r\n') except: cl.sendall('550 Failed to change directory\r\n') elif command == "EPSV": cl.sendall('502\r\n') elif command == "TYPE": # probably should switch between binary and not cl.sendall('200 Transfer mode set\r\n') elif command == "SIZE": path = get_absolute_path(cwd, payload) try: size = os.stat(path)[6] cl.sendall('213 {}\r\n'.format(size)) except: cl.sendall('550 Could not get file size\r\n') elif command == "QUIT": cl.sendall('221 Bye.\r\n') elif command == "PASV": addr = network.WLAN().ifconfig()[0] cl.sendall( '227 Entering Passive Mode ({},{},{}).\r\n'.format( addr.replace('.', ','), DATA_PORT >> 8, DATA_PORT % 256 ) ) dataclient, data_addr = datasocket.accept() print("FTP Data connection from:", data_addr) elif command == "LIST": try: send_list_data(cwd, dataclient) dataclient.close() cl.sendall("150 Here comes the directory listing.\r\n") cl.sendall("226 Listed.\r\n") except: cl.sendall('550 Failed to list directory\r\n') finally: dataclient.close() elif command == "RETR": try: send_file_data(get_absolute_path(cwd, payload), dataclient) dataclient.close() cl.sendall("150 Opening data connection.\r\n") cl.sendall("226 Transfer complete.\r\n") except: cl.sendall('550 Failed to send file\r\n') finally: dataclient.close() elif command == "STOR": try: cl.sendall("150 Ok to send data.\r\n") save_file_data(get_absolute_path(cwd, payload), dataclient) dataclient.close() cl.sendall("226 Transfer complete.\r\n") except: cl.sendall('550 Failed to send file\r\n') finally: dataclient.close() else: cl.sendall("502 Unsupported command.\r\n") print("Unsupported command {} with payload {}".format(command, payload)) finally: cl.close() finally: datasocket.close() ftpsocket.close() if dataclient is not None: dataclient.close() _thread.start_new_thread(ftp, ())