diff options
Diffstat (limited to 'cc3200/ftp/ftp.c')
-rw-r--r-- | cc3200/ftp/ftp.c | 1062 |
1 files changed, 1062 insertions, 0 deletions
diff --git a/cc3200/ftp/ftp.c b/cc3200/ftp/ftp.c new file mode 100644 index 0000000000..79bf08c2e7 --- /dev/null +++ b/cc3200/ftp/ftp.c @@ -0,0 +1,1062 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Daniel Campora + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdint.h> +#include <ctype.h> +#include <std.h> + +#include "mpconfig.h" +#include MICROPY_HAL_H +#include "misc.h" +#include "nlr.h" +#include "qstr.h" +#include "obj.h" +#include "osi.h" +#include "inc/hw_types.h" +#include "inc/hw_ints.h" +#include "inc/hw_memmap.h" +#include "rom_map.h" +#include "prcm.h" +#include "pybrtc.h" +#include "ftp.h" +#include "simplelink.h" +#include "modwlan.h" +#include "modutime.h" +#include "debug.h" +#include "serverstask.h" +#include "ff.h" +#include "fifo.h" +#include "socketfifo.h" +#include "diskio.h" +#include "sd_diskio.h" +#include "updater.h" + + +/****************************************************************************** + DEFINE PRIVATE CONSTANTS + ******************************************************************************/ +#define FTP_CMD_PORT 21 +#define FTP_ACTIVE_DATA_PORT 20 +#define FTP_PASIVE_DATA_PORT 2024 +#define FTP_BUFFER_SIZE 512 +#define FTP_TX_RETRIES_MAX 25 +#define FTP_CMD_SIZE_MAX 6 +#define FTP_CMD_CLIENTS_MAX 1 +#define FTP_DATA_CLIENTS_MAX 1 +#define FTP_MAX_PARAM_SIZE (MICROPY_ALLOC_PATH_MAX + 1) +#define FTP_UNIX_TIME_20000101 946684800 +#define FTP_UNIX_TIME_20150101 1420070400 +#define FTP_UNIX_SECONDS_180_DAYS 15552000 +#define FTP_DATA_TIMEOUT_MS 5000 // 5 seconds +#define FTP_CMD_TIMEOUT_MS 120000 // 2 minutes +#define FTP_SOCKETFIFO_ELEMENTS_MAX 4 +#define FTP_CYCLE_TIME_MS (SERVERS_CYCLE_TIME_MS * 2) + +/****************************************************************************** + DEFINE PRIVATE TYPES + ******************************************************************************/ +typedef enum { + E_FTP_RESULT_OK = 0, + E_FTP_RESULT_CONTINUE, + E_FTP_RESULT_FAILED +} ftp_result_t; + +typedef enum { + E_FTP_STE_DISABLED = 0, + E_FTP_STE_START, + E_FTP_STE_READY, + E_FTP_STE_END_TRANSFER, + E_FTP_STE_CONTINUE_LISTING, + E_FTP_STE_CONTINUE_FILE_TX, + E_FTP_STE_CONTINUE_FILE_RX +} ftp_state_t; + +typedef enum { + E_FTP_STE_SUB_DISCONNECTED = 0, + E_FTP_STE_SUB_LISTEN_FOR_DATA, + E_FTP_STE_SUB_DATA_CONNECTED +} ftp_data_substate_t; + +typedef union { + ftp_data_substate_t data; +} ftp_substate_t; + +typedef struct { + bool uservalid : 1; + bool passvalid : 1; +}ftp_loggin_t; + +typedef enum { + E_FTP_NOTHING_OPEN = 0, + E_FTP_FILE_OPEN, + E_FTP_DIR_OPEN +} ftp_e_open_t; + +typedef enum { + E_FTP_CLOSE_NONE = 0, + E_FTP_CLOSE_DATA, + E_FTP_CLOSE_CMD_AND_DATA, +} ftp_e_closesocket_t; + +typedef struct { + uint8_t *dBuffer; + union { + DIR dp; + FIL fp; + }; + int16_t lc_sd; + int16_t ld_sd; + int16_t c_sd; + int16_t ctimeout; + int16_t d_sd; + int16_t dtimeout; + ftp_state_t state; + ftp_substate_t substate; + uint8_t txRetries; + uint8_t logginRetries; + ftp_loggin_t loggin; + uint8_t e_open; + bool closechild; + bool enabled; + bool swupdating; + +} ftp_data_t; + +typedef struct { + char * cmd; +}ftp_cmd_t; + +typedef struct { + char * month; +}ftp_month_t; + +typedef enum { + E_FTP_CMD_NOT_SUPPORTED = -1, + E_FTP_CMD_FEAT = 0, + E_FTP_CMD_SYST, + E_FTP_CMD_CDUP, + E_FTP_CMD_CWD, + E_FTP_CMD_PWD, + E_FTP_CMD_XPWD, + E_FTP_CMD_SIZE, + E_FTP_CMD_MDTM, + E_FTP_CMD_TYPE, + E_FTP_CMD_USER, + E_FTP_CMD_PASS, + E_FTP_CMD_PASV, + E_FTP_CMD_LIST, + E_FTP_CMD_RETR, + E_FTP_CMD_STOR, + E_FTP_CMD_DELE, + E_FTP_CMD_RMD, + E_FTP_CMD_MKD, + E_FTP_CMD_RNFR, + E_FTP_CMD_RNTO, + E_FTP_CMD_NOOP, + E_FTP_CMD_QUIT, + E_FTP_NUM_FTP_CMDS +}ftp_cmd_index_t; + +/****************************************************************************** + DECLARE PRIVATE DATA + ******************************************************************************/ +static ftp_data_t ftp_data; +static char *ftp_path; +static char *ftp_scratch_buffer; +static char *ftp_cmd_buffer; +static const ftp_cmd_t ftp_cmd_table[] = { { "FEAT" }, { "SYST" }, { "CDUP" }, { "CWD" }, + { "PWD" }, { "XPWD" }, { "SIZE" }, { "MDTM" }, + { "TYPE" }, { "USER" }, { "PASS" }, { "PASV" }, + { "LIST" }, { "RETR" }, { "STOR" }, { "DELE" }, + { "RMD" }, { "MKD" }, { "RNFR" }, { "RNTO" }, + { "NOOP" }, { "QUIT" } }; + +static const ftp_month_t ftp_month[] = { { "Jan" }, { "Feb" }, { "Mar" }, { "Apr" }, + { "May" }, { "Jun" }, { "Jul" }, { "Ago" }, + { "Sep" }, { "Oct" }, { "Nov" }, { "Dec" } }; + +static SocketFifoElement_t *ftp_fifoelements; +static FIFO_t ftp_socketfifo; + +/****************************************************************************** + DECLARE PRIVATE FUNCTIONS + ******************************************************************************/ +static void ftp_wait_for_enabled (void); +static bool ftp_create_listening_socket (_i16 *sd, _u16 port, _u8 backlog); +static ftp_result_t ftp_wait_for_connection (_i16 l_sd, _i16 *n_sd); +static ftp_result_t ftp_send_non_blocking (_i16 sd, void *data, _i16 Len); +static void ftp_send_reply (_u16 status, char *message); +static void ftp_send_data (_u32 datasize); +static void ftp_send_from_fifo (void); +static ftp_result_t ftp_recv_non_blocking (_i16 sd, void *buff, _i16 Maxlen, _i32 *rxLen); +static void ftp_process_cmd (void); +static void ftp_close_files (void); +static void ftp_close_filesystem_on_error (void); +static void ftp_close_cmd_data (void); +static void ftp_reset (void); +static ftp_cmd_index_t ftp_pop_command (char **str); +static void ftp_pop_param (char **str, char *param); +static int ftp_print_eplf_item (char *dest, uint32_t destsize, FILINFO *fno); +static int ftp_print_eplf_drive (char *dest, uint32_t destsize, char *name); +static bool ftp_open_file (const char *path, int mode); +static ftp_result_t ftp_read_file (char *filebuf, uint32_t desiredsize, uint32_t *actualsize); +static ftp_result_t ftp_write_file (char *filebuf, uint32_t size); +static ftp_result_t ftp_open_dir_for_listing (const char *path, char *list, uint32_t maxlistsize, uint32_t *listsize); +static ftp_result_t ftp_list_dir (char *list, uint32_t maxlistsize, uint32_t *listsize); +static void ftp_open_child (char *pwd, char *dir); +static void ftp_close_child (char *pwd); +static void ftp_return_to_previous_path (char *pwd, char *dir); + +/****************************************************************************** + DEFINE PUBLIC FUNCTIONS + ******************************************************************************/ +void ftp_init (void) { + // Allocate memory for the data buffer, and the file system structs (from the RTOS heap) + ASSERT ((ftp_data.dBuffer = mem_Malloc(FTP_BUFFER_SIZE)) != NULL); + ASSERT ((ftp_path = mem_Malloc(FTP_MAX_PARAM_SIZE)) != NULL); + ASSERT ((ftp_scratch_buffer = mem_Malloc(FTP_MAX_PARAM_SIZE)) != NULL); + ASSERT ((ftp_cmd_buffer = mem_Malloc(FTP_MAX_PARAM_SIZE + FTP_CMD_SIZE_MAX)) != NULL); + ASSERT ((ftp_fifoelements = mem_Malloc(FTP_SOCKETFIFO_ELEMENTS_MAX * sizeof(SocketFifoElement_t))) != NULL); + SOCKETFIFO_Init (&ftp_socketfifo, (void *)ftp_fifoelements, FTP_SOCKETFIFO_ELEMENTS_MAX); + ftp_data.c_sd = -1; + ftp_data.d_sd = -1; + ftp_data.lc_sd = -1; + ftp_data.ld_sd = -1; + ftp_data.e_open = E_FTP_NOTHING_OPEN; + ftp_data.state = E_FTP_STE_DISABLED; + ftp_data.substate.data = E_FTP_STE_SUB_DISCONNECTED; + ftp_data.swupdating = false; +} + +void ftp_run (void) { + switch (ftp_data.state) { + case E_FTP_STE_DISABLED: + ftp_wait_for_enabled(); + break; + case E_FTP_STE_START: + if (ftp_create_listening_socket(&ftp_data.lc_sd, FTP_CMD_PORT, FTP_CMD_CLIENTS_MAX )) { + ftp_data.state = E_FTP_STE_READY; + } + break; + case E_FTP_STE_READY: + if (ftp_data.c_sd < 0 && ftp_data.substate.data == E_FTP_STE_SUB_DISCONNECTED) { + if (E_FTP_RESULT_OK == ftp_wait_for_connection(ftp_data.lc_sd, &ftp_data.c_sd)) { + ftp_data.txRetries = 0; + ftp_data.logginRetries = 0; + ftp_data.ctimeout = 0; + ftp_data.loggin.uservalid = false; + ftp_data.loggin.passvalid = false; + strcpy (ftp_path, "/"); + ftp_send_reply (220, "Micropython FTP Server"); + break; + } + } + if (SOCKETFIFO_IsEmpty()) { + if (ftp_data.c_sd > 0 && ftp_data.substate.data != E_FTP_STE_SUB_LISTEN_FOR_DATA) { + ftp_process_cmd(); + if (ftp_data.state != E_FTP_STE_READY) { + break; + } + } + } + break; + case E_FTP_STE_END_TRANSFER: + break; + case E_FTP_STE_CONTINUE_LISTING: + // go on with listing only if the transmit buffer is empty + if (SOCKETFIFO_IsEmpty()) { + uint32_t listsize; + ftp_list_dir((char *)ftp_data.dBuffer, FTP_BUFFER_SIZE, &listsize); + if (listsize > 0) { + ftp_send_data(listsize); + } + else { + ftp_send_reply(226, NULL); + ftp_data.state = E_FTP_STE_END_TRANSFER; + } + ftp_data.ctimeout = 0; + } + break; + case E_FTP_STE_CONTINUE_FILE_TX: + // read the next block from the file only if the previous one has been sent + if (SOCKETFIFO_IsEmpty()) { + uint32_t readsize; + ftp_result_t result; + result = ftp_read_file ((char *)ftp_data.dBuffer, FTP_BUFFER_SIZE, &readsize); + if (readsize > 0 && result != E_FTP_RESULT_FAILED) { + ftp_send_data(readsize); + ftp_data.ctimeout = 0; + if (result == E_FTP_RESULT_OK) { + ftp_send_reply(226, NULL); + ftp_data.state = E_FTP_STE_END_TRANSFER; + } + } + else { + ftp_send_reply(451, NULL); + ftp_data.state = E_FTP_STE_END_TRANSFER; + } + } + break; + case E_FTP_STE_CONTINUE_FILE_RX: + if (SOCKETFIFO_IsEmpty()) { + _i32 len; + ftp_result_t result; + if (E_FTP_RESULT_OK == (result = ftp_recv_non_blocking(ftp_data.d_sd, ftp_data.dBuffer, FTP_BUFFER_SIZE, &len))) { + ftp_data.dtimeout = 0; + ftp_data.ctimeout = 0; + // its a software update + if (ftp_data.swupdating) { + if (updater_write(ftp_data.dBuffer, len)) { + break; + } + } + // user file being received + else if (E_FTP_RESULT_OK == ftp_write_file ((char *)ftp_data.dBuffer, len)) { + break; + } + ftp_send_reply(451, NULL); + ftp_data.state = E_FTP_STE_END_TRANSFER; + } + else if (result == E_FTP_RESULT_CONTINUE) { + if (ftp_data.dtimeout++ > FTP_DATA_TIMEOUT_MS / FTP_CYCLE_TIME_MS) { + ftp_close_files(); + ftp_send_reply(426, NULL); + ftp_data.state = E_FTP_STE_END_TRANSFER; + } + } + else { + if (ftp_data.swupdating) { + ftp_data.swupdating = false; + updater_finnish(); + } + ftp_close_files(); + ftp_send_reply(226, NULL); + ftp_data.state = E_FTP_STE_END_TRANSFER; + } + } + break; + default: + break; + } + + switch (ftp_data.substate.data) { + case E_FTP_STE_SUB_DISCONNECTED: + break; + case E_FTP_STE_SUB_LISTEN_FOR_DATA: + if (E_FTP_RESULT_OK == ftp_wait_for_connection(ftp_data.ld_sd, &ftp_data.d_sd)) { + ftp_data.dtimeout = 0; + ftp_data.substate.data = E_FTP_STE_SUB_DATA_CONNECTED; + } + else if (ftp_data.dtimeout++ > FTP_DATA_TIMEOUT_MS / FTP_CYCLE_TIME_MS) { + ftp_data.dtimeout = 0; + // close the listening socket + servers_close_socket(&ftp_data.ld_sd); + ftp_data.substate.data = E_FTP_STE_SUB_DISCONNECTED; + } + break; + case E_FTP_STE_SUB_DATA_CONNECTED: + if (ftp_data.state == E_FTP_STE_READY && ftp_data.dtimeout++ > FTP_DATA_TIMEOUT_MS / FTP_CYCLE_TIME_MS) { + // close the listening and the data socket + servers_close_socket(&ftp_data.ld_sd); + servers_close_socket(&ftp_data.d_sd); + ftp_close_filesystem_on_error (); + ftp_data.substate.data = E_FTP_STE_SUB_DISCONNECTED; + } + break; + default: + break; + } + + // send data pending in the queue + ftp_send_from_fifo(); + + // check the state of the data sockets + if (ftp_data.d_sd < 0 && (ftp_data.state > E_FTP_STE_READY)) { + ftp_data.substate.data = E_FTP_STE_SUB_DISCONNECTED; + ftp_data.state = E_FTP_STE_READY; + } +} + +void ftp_enable (void) { + ftp_data.enabled = true; +} + +void ftp_disable (void) { + ftp_reset(); + ftp_data.enabled = false; + ftp_data.state = E_FTP_STE_DISABLED; +} + +/****************************************************************************** + DEFINE PRIVATE FUNCTIONS + ******************************************************************************/ +static void ftp_wait_for_enabled (void) { + // Check if the telnet service has been enabled + if (ftp_data.enabled) { + ftp_data.state = E_FTP_STE_START; + } +} + +static bool ftp_create_listening_socket (_i16 *sd, _u16 port, _u8 backlog) { + SlSockNonblocking_t nonBlockingOption; + sockaddr_in sServerAddress; + _i16 _sd; + _i16 result; + + // Open a socket for ftp data listen + ASSERT ((*sd = sl_Socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) > 0); + _sd = *sd; + + if (_sd > 0) { + // Enable non-blocking mode + nonBlockingOption.NonblockingEnabled = 1; + ASSERT (sl_SetSockOpt(_sd, SOL_SOCKET, SL_SO_NONBLOCKING, &nonBlockingOption, sizeof(nonBlockingOption)) == SL_SOC_OK); + + // Bind the socket to a port number + sServerAddress.sin_family = AF_INET; + sServerAddress.sin_addr.s_addr = htonl(INADDR_ANY); + sServerAddress.sin_port = htons(port); + + ASSERT (sl_Bind(_sd, (const SlSockAddr_t *)&sServerAddress, sizeof(sServerAddress)) == SL_SOC_OK); + + // Start listening + ASSERT ((result = sl_Listen (_sd, backlog)) == SL_SOC_OK); + + return (result == SL_SOC_OK) ? true : false; + } + return false; +} + +static ftp_result_t ftp_wait_for_connection (_i16 l_sd, _i16 *n_sd) { + sockaddr_in sClientAddress; + SlSocklen_t in_addrSize; + + // accepts a connection from a TCP client, if there is any, otherwise returns SL_EAGAIN + *n_sd = sl_Accept(l_sd, (SlSockAddr_t *)&sClientAddress, (SlSocklen_t *)&in_addrSize); + _i16 _sd = *n_sd; + if (_sd == SL_EAGAIN) { + return E_FTP_RESULT_CONTINUE; + } + else if (_sd < 0) { + // error + ftp_reset(); + return E_FTP_RESULT_FAILED; + } + + // client connected, so go on + return E_FTP_RESULT_OK; +} + +static ftp_result_t ftp_send_non_blocking (_i16 sd, void *data, _i16 Len) { + int16_t result = sl_Send(sd, data, Len, 0); + + if (result > 0) { + ftp_data.txRetries = 0; + return E_FTP_RESULT_OK; + } + else if ((FTP_TX_RETRIES_MAX >= ++ftp_data.txRetries) && (result == SL_EAGAIN)) { + return E_FTP_RESULT_CONTINUE; + } + else { + // error + ftp_reset(); + return E_FTP_RESULT_FAILED; + } +} + +static void ftp_send_reply (_u16 status, char *message) { + SocketFifoElement_t fifoelement; + if (!message) { + message = ""; + } + snprintf((char *)ftp_cmd_buffer, 4, "%u", status); + strcat ((char *)ftp_cmd_buffer, " "); + strcat ((char *)ftp_cmd_buffer, message); + strcat ((char *)ftp_cmd_buffer, "\r\n"); + fifoelement.sd = &ftp_data.c_sd; + fifoelement.datasize = strlen((char *)ftp_cmd_buffer); + fifoelement.data = mem_Malloc(fifoelement.datasize); + if (status == 221) { + fifoelement.closesockets = E_FTP_CLOSE_CMD_AND_DATA; + } + else if (status == 426 || status == 451 || status == 550) { + fifoelement.closesockets = E_FTP_CLOSE_DATA; + } + else { + fifoelement.closesockets = E_FTP_CLOSE_NONE; + } + fifoelement.freedata = true; + if (fifoelement.data) { + memcpy (fifoelement.data, ftp_cmd_buffer, fifoelement.datasize); + if (!SOCKETFIFO_Push (&fifoelement)) { + mem_Free(fifoelement.data); + } + } +} + +static void ftp_send_data (_u32 datasize) { + SocketFifoElement_t fifoelement; + + fifoelement.data = ftp_data.dBuffer; + fifoelement.datasize = datasize; + fifoelement.sd = &ftp_data.d_sd; + fifoelement.closesockets = E_FTP_CLOSE_NONE; + fifoelement.freedata = false; + SOCKETFIFO_Push (&fifoelement); +} + +static void ftp_send_from_fifo (void) { + SocketFifoElement_t fifoelement; + if (SOCKETFIFO_Peek (&fifoelement)) { + _i16 _sd = *fifoelement.sd; + if (_sd > 0) { + if (E_FTP_RESULT_OK == ftp_send_non_blocking (_sd, fifoelement.data, fifoelement.datasize)) { + SOCKETFIFO_Pop (&fifoelement); + if (fifoelement.closesockets != E_FTP_CLOSE_NONE) { + servers_close_socket(&ftp_data.d_sd); + if (fifoelement.closesockets == E_FTP_CLOSE_CMD_AND_DATA) { + servers_close_socket(&ftp_data.ld_sd); + // this one is the command socket + servers_close_socket(fifoelement.sd); + ftp_data.substate.data = E_FTP_STE_SUB_DISCONNECTED; + } + ftp_close_filesystem_on_error(); + } + if (fifoelement.freedata) { + mem_Free(fifoelement.data); + } + } + } + // socket closed, remove from the queue + else { + SOCKETFIFO_Pop (&fifoelement); + if (fifoelement.freedata) { + mem_Free(fifoelement.data); + } + } + } + else if (ftp_data.state == E_FTP_STE_END_TRANSFER && (ftp_data.d_sd > 0)) { + // close the listening and the data sockets + servers_close_socket(&ftp_data.ld_sd); + servers_close_socket(&ftp_data.d_sd); + if (ftp_data.swupdating) { + ftp_data.swupdating = false; + } + } +} + +static ftp_result_t ftp_recv_non_blocking (_i16 sd, void *buff, _i16 Maxlen, _i32 *rxLen) { + *rxLen = sl_Recv(sd, buff, Maxlen, 0); + + if (*rxLen > 0) { + return E_FTP_RESULT_OK; + } + else if (*rxLen != SL_EAGAIN) { + // error + return E_FTP_RESULT_FAILED; + } + return E_FTP_RESULT_CONTINUE; +} + +static void ftp_get_param_and_open_child (char **bufptr) { + ftp_pop_param (bufptr, ftp_scratch_buffer); + ftp_open_child (ftp_path, ftp_scratch_buffer); + ftp_data.closechild = true; +} + +static void ftp_process_cmd (void) { + _i32 len; + char *bufptr = (char *)ftp_cmd_buffer; + ftp_result_t result; + uint32_t listsize; + FILINFO fno; + FRESULT fres; + + ftp_data.closechild = false; + // also use the reply buffer to receive new commands + if (E_FTP_RESULT_OK == (result = ftp_recv_non_blocking(ftp_data.c_sd, ftp_cmd_buffer, FTP_BUFFER_SIZE, &len))) { + // bufptr is moved as commands are being popped + ftp_cmd_index_t cmd = ftp_pop_command(&bufptr); + if (!ftp_data.loggin.passvalid && (cmd != E_FTP_CMD_USER && cmd != E_FTP_CMD_PASS && cmd != E_FTP_CMD_QUIT)) { + ftp_send_reply(332, NULL); + return; + } + switch (cmd) { + case E_FTP_CMD_FEAT: + ftp_send_reply(211, "no-features"); + break; + case E_FTP_CMD_SYST: + ftp_send_reply(215, "UNIX Type: L8"); + break; + case E_FTP_CMD_CDUP: + ftp_close_child(ftp_path); + ftp_send_reply(250, NULL); + break; + case E_FTP_CMD_CWD: + { + fres = FR_NO_PATH; + ftp_pop_param (&bufptr, ftp_scratch_buffer); + ftp_open_child (ftp_path, ftp_scratch_buffer); + if ((ftp_path[0] == '/' && ftp_path[1] == '\0') || ((fres = f_opendir (&ftp_data.dp, ftp_path)) == FR_OK)) { + if (fres == FR_OK) { + f_closedir(&ftp_data.dp); + } + ftp_send_reply(250, NULL); + } + else { + ftp_close_child (ftp_path); + ftp_send_reply(550, NULL); + } + } + break; + case E_FTP_CMD_PWD: + case E_FTP_CMD_XPWD: + ftp_send_reply(257, ftp_path); + break; + case E_FTP_CMD_SIZE: + { + ftp_get_param_and_open_child (&bufptr); + if (FR_OK == f_stat (ftp_path, &fno)) { + // send the size + snprintf((char *)ftp_data.dBuffer, FTP_BUFFER_SIZE, "%u", (_u32)fno.fsize); + ftp_send_reply(213, (char *)ftp_data.dBuffer); + } + else { + ftp_send_reply(550, NULL); + } + } + break; + case E_FTP_CMD_MDTM: + ftp_get_param_and_open_child (&bufptr); + if (FR_OK == f_stat (ftp_path, &fno)) { + // send the last modified time + snprintf((char *)ftp_data.dBuffer, FTP_BUFFER_SIZE, "%u%02u%02u%02u%02u%02u", + 1980 + ((fno.fdate >> 9) & 0x7f), (fno.fdate >> 5) & 0x0f, + fno.fdate & 0x1f, (fno.ftime >> 11) & 0x1f, + (fno.ftime >> 5) & 0x3f, 2 * (fno.ftime & 0x1f)); + ftp_send_reply(213, (char *)ftp_data.dBuffer); + } + else { + ftp_send_reply(550, NULL); + } + break; + case E_FTP_CMD_TYPE: + ftp_send_reply(200, NULL); + break; + case E_FTP_CMD_USER: + ftp_pop_param (&bufptr, ftp_scratch_buffer); + if (!memcmp(ftp_scratch_buffer, servers_user, MAX(strlen(ftp_scratch_buffer), strlen(servers_user)))) { + ftp_data.loggin.uservalid = true; + } + ftp_send_reply(331, NULL); + break; + case E_FTP_CMD_PASS: + ftp_pop_param (&bufptr, ftp_scratch_buffer); + if (!memcmp(ftp_scratch_buffer, servers_pass, MAX(strlen(ftp_scratch_buffer), strlen(servers_pass))) && + ftp_data.loggin.uservalid) { + ftp_data.loggin.passvalid = true; + ftp_send_reply(230, NULL); + } + else { + ftp_send_reply(530, NULL); + } + break; + case E_FTP_CMD_PASV: + { + // some servers (e.g. google chrome) send PASV several times very quickly + servers_close_socket(&ftp_data.d_sd); + ftp_data.substate.data = E_FTP_STE_SUB_DISCONNECTED; + bool socketcreated = true; + if (ftp_data.ld_sd < 0) { + socketcreated = ftp_create_listening_socket(&ftp_data.ld_sd, FTP_PASIVE_DATA_PORT, FTP_DATA_CLIENTS_MAX); + } + if (socketcreated) { + uint32_t ip; + uint8_t *pip = (uint8_t *)&ip; + ftp_data.dtimeout = 0; + wlan_get_ip(&ip); + snprintf((char *)ftp_data.dBuffer, FTP_BUFFER_SIZE, "(%u,%u,%u,%u,%u,%u)", + pip[0], pip[1], pip[2], pip[3], (FTP_PASIVE_DATA_PORT >> 8), (FTP_PASIVE_DATA_PORT & 0xFF)); + ftp_data.substate.data = E_FTP_STE_SUB_LISTEN_FOR_DATA; + ftp_send_reply(227, (char *)ftp_data.dBuffer); + } + else { + ftp_send_reply(425, NULL); + } + } + break; + case E_FTP_CMD_LIST: + if ((result = ftp_open_dir_for_listing(ftp_path, (char *)ftp_data.dBuffer, FTP_BUFFER_SIZE, &listsize)) == E_FTP_RESULT_OK) { + ftp_data.state = E_FTP_STE_END_TRANSFER; + ftp_send_reply(150, NULL); + ftp_send_data(listsize); + ftp_send_reply(226, NULL); + } + else if (result == E_FTP_RESULT_CONTINUE) { + ftp_data.state = E_FTP_STE_CONTINUE_LISTING; + ftp_send_reply(150, NULL); + } + else { + ftp_send_reply(550, NULL); + } + break; + case E_FTP_CMD_RETR: + ftp_get_param_and_open_child (&bufptr); + if (ftp_open_file (ftp_path, FA_READ)) { + ftp_data.state = E_FTP_STE_CONTINUE_FILE_TX; + ftp_send_reply(150, NULL); + } + else { + ftp_data.state = E_FTP_STE_END_TRANSFER; + ftp_send_reply(550, NULL); + } + break; + case E_FTP_CMD_STOR: + ftp_get_param_and_open_child (&bufptr); + // first check if a software update is being requested + if (updater_check_path (ftp_path)) { + // start by erasing the previous status file + // must be done before starting the updater + f_unlink(ftp_path); + if (updater_start()) { + ftp_data.swupdating = true; + ftp_data.state = E_FTP_STE_CONTINUE_FILE_RX; + ftp_send_reply(150, NULL); + } + else { + ftp_data.state = E_FTP_STE_END_TRANSFER; + ftp_send_reply(550, NULL); + } + } + else { + if (ftp_open_file (ftp_path, FA_WRITE | FA_CREATE_ALWAYS)) { + ftp_data.state = E_FTP_STE_CONTINUE_FILE_RX; + ftp_send_reply(150, NULL); + } + else { + ftp_data.state = E_FTP_STE_END_TRANSFER; + ftp_send_reply(550, NULL); + } + } + break; + case E_FTP_CMD_DELE: + case E_FTP_CMD_RMD: + ftp_get_param_and_open_child (&bufptr); + if (FR_OK == f_unlink(ftp_path)) { + ftp_send_reply(250, NULL); + } + else { + ftp_send_reply(550, NULL); + } + break; + case E_FTP_CMD_MKD: + ftp_get_param_and_open_child (&bufptr); + if (FR_OK == f_mkdir(ftp_path)) { + ftp_send_reply(250, NULL); + } + else { + ftp_send_reply(550, NULL); + } + break; + case E_FTP_CMD_RNFR: + ftp_get_param_and_open_child (&bufptr); + if (FR_OK == f_stat (ftp_path, &fno)) { + ftp_send_reply(350, NULL); + // save the current path + strcpy ((char *)ftp_data.dBuffer, ftp_path); + } + else { + ftp_send_reply(550, NULL); + } + break; + case E_FTP_CMD_RNTO: + ftp_get_param_and_open_child (&bufptr); + // old path was saved in the data buffer + if (FR_OK == (fres = f_rename ((char *)ftp_data.dBuffer, ftp_path))) { + ftp_send_reply(250, NULL); + } + else { + ftp_send_reply(550, NULL); + } + break; + case E_FTP_CMD_NOOP: + ftp_send_reply(200, NULL); + break; + case E_FTP_CMD_QUIT: + ftp_send_reply(221, NULL); + break; + default: + // command not implemented + ftp_send_reply(502, NULL); + break; + } + + if (ftp_data.closechild) { + ftp_return_to_previous_path(ftp_path, ftp_scratch_buffer); + } + } + else if (result == E_FTP_RESULT_CONTINUE) { + if (ftp_data.ctimeout++ > (FTP_CMD_TIMEOUT_MS / FTP_CYCLE_TIME_MS)) { + ftp_send_reply(221, NULL); + } + } + else { + ftp_close_cmd_data (); + } +} + +static void ftp_close_files (void) { + if (ftp_data.e_open == E_FTP_FILE_OPEN) { + f_close(&ftp_data.fp); + } + else if (ftp_data.e_open == E_FTP_DIR_OPEN) { + f_closedir(&ftp_data.dp); + } + ftp_data.e_open = E_FTP_NOTHING_OPEN; +} + +static void ftp_close_filesystem_on_error (void) { + ftp_close_files(); + if (ftp_data.swupdating) { + updater_finnish (); + ftp_data.swupdating = false; + } +} + +static void ftp_close_cmd_data (void) { + servers_close_socket(&ftp_data.c_sd); + servers_close_socket(&ftp_data.d_sd); + ftp_close_filesystem_on_error (); +} + +static void ftp_reset (void) { + // close all connections and start all over again + servers_close_socket(&ftp_data.lc_sd); + servers_close_socket(&ftp_data.ld_sd); + ftp_close_cmd_data(); + ftp_data.state = E_FTP_STE_START; + ftp_data.substate.data = E_FTP_STE_SUB_DISCONNECTED; + SOCKETFIFO_Flush(); +} + +static ftp_cmd_index_t ftp_pop_command (char **str) { + char _cmd[FTP_CMD_SIZE_MAX]; + ftp_pop_param (str, _cmd); + stoupper (_cmd); + for (ftp_cmd_index_t i = 0; i < E_FTP_NUM_FTP_CMDS; i++) { + if (!strcmp (_cmd, ftp_cmd_table[i].cmd)) { + // move one step further to skip the space + (*str)++; + return i; + } + } + return E_FTP_CMD_NOT_SUPPORTED; +} + +static void ftp_pop_param (char **str, char *param) { + while (**str != ' ' && **str != '\r' && **str != '\n' && **str != '\0') { + *param++ = **str; + (*str)++; + } + *param = '\0'; +} + +static int ftp_print_eplf_item (char *dest, uint32_t destsize, FILINFO *fno) { + + char *type = (fno->fattrib & AM_DIR) ? "d" : "-"; + uint32_t tseconds; + uint16_t mseconds; + uint mindex = (((fno->fdate >> 5) & 0x0f) > 0) ? (((fno->fdate >> 5) & 0x0f) - 1) : 0; + uint day = ((fno->fdate & 0x1f) > 0) ? (fno->fdate & 0x1f) : 1; + uint fseconds = mod_time_seconds_since_2000(1980 + ((fno->fdate >> 9) & 0x7f), + (fno->fdate >> 5) & 0x0f, + fno->fdate & 0x1f, + (fno->ftime >> 11) & 0x1f, + (fno->ftime >> 5) & 0x3f, + 2 * (fno->ftime & 0x1f)); + MAP_PRCMRTCGet(&tseconds, &mseconds); + if (FTP_UNIX_SECONDS_180_DAYS < tseconds - fseconds) { + return snprintf(dest, destsize, "%srw-rw-r-- 1 root root %9u %s %2u %5u %s\r\n", + type, (_u32)fno->fsize, ftp_month[mindex].month, day, + 1980 + ((fno->fdate >> 9) & 0x7f), fno->fname); + } + else { + return snprintf(dest, destsize, "%srw-rw-r-- 1 root root %9u %s %2u %02u:%02u %s\r\n", + type, (_u32)fno->fsize, ftp_month[mindex].month, day, + (fno->ftime >> 11) & 0x1f, (fno->ftime >> 5) & 0x3f, fno->fname); + } +} + +static int ftp_print_eplf_drive (char *dest, uint32_t destsize, char *name) { + mod_struct_time tm; + uint32_t tseconds; + uint16_t mseconds; + char *type = "d"; + + mod_time_seconds_since_2000_to_struct_time((FTP_UNIX_TIME_20150101 - FTP_UNIX_TIME_20000101), &tm); + + MAP_PRCMRTCGet(&tseconds, &mseconds); + if (FTP_UNIX_SECONDS_180_DAYS < tseconds - (FTP_UNIX_TIME_20150101 - FTP_UNIX_TIME_20000101)) { + return snprintf(dest, destsize, "%srw-rw-r-- 1 root root %9u %s %2u %5u %s\r\n", + type, 0, ftp_month[(tm.tm_mon - 1)].month, tm.tm_mday, tm.tm_year, name); + } + else { + return snprintf(dest, destsize, "%srw-rw-r-- 1 root root %9u %s %2u %02u:%02u %s\r\n", + type, 0, ftp_month[(tm.tm_mon - 1)].month, tm.tm_mday, tm.tm_hour, tm.tm_min, name); + } +} + +static bool ftp_open_file (const char *path, int mode) { + FRESULT res = f_open(&ftp_data.fp, path, mode); + if (res != FR_OK) { + return false; + } + ftp_data.e_open = E_FTP_FILE_OPEN; + return true; +} + +static ftp_result_t ftp_read_file (char *filebuf, uint32_t desiredsize, uint32_t *actualsize) { + ftp_result_t result = E_FTP_RESULT_CONTINUE; + FRESULT res = f_read(&ftp_data.fp, filebuf, desiredsize, (UINT *)actualsize); + if (res != FR_OK) { + ftp_close_files(); + result = E_FTP_RESULT_FAILED; + *actualsize = 0; + } + else if (*actualsize < desiredsize) { + ftp_close_files(); + result = E_FTP_RESULT_OK; + } + return result; +} + +static ftp_result_t ftp_write_file (char *filebuf, uint32_t size) { + ftp_result_t result = E_FTP_RESULT_FAILED; + uint32_t actualsize; + FRESULT res = f_write(&ftp_data.fp, filebuf, size, (UINT *)&actualsize); + if ((actualsize == size) && (FR_OK == res)) { + result = E_FTP_RESULT_OK; + } + else { + ftp_close_files(); + } + return result; +} + +static ftp_result_t ftp_open_dir_for_listing (const char *path, char *list, uint32_t maxlistsize, uint32_t *listsize) { + uint next = 0; + // "hack" to list root directory + if (path[0] == '/' && path[1] == '\0') { + next += ftp_print_eplf_drive((list + next), (maxlistsize - next), "SFLASH"); +#if MICROPY_HW_HAS_SDCARD + if (sd_disk_ready()) { + next += ftp_print_eplf_drive((list + next), (maxlistsize - next), "SD"); + } +#endif + *listsize = next; + return E_FTP_RESULT_OK; + } + + FRESULT res; + res = f_opendir(&ftp_data.dp, path); /* Open the directory */ + if (res != FR_OK) { + return E_FTP_RESULT_FAILED; + } + ftp_data.e_open = E_FTP_DIR_OPEN; + return E_FTP_RESULT_CONTINUE; +} + +static ftp_result_t ftp_list_dir (char *list, uint32_t maxlistsize, uint32_t *listsize) { + uint next = 0; + uint count = 0; + FRESULT res; + FILINFO fno; + ftp_result_t result = E_FTP_RESULT_CONTINUE; + + /* read up to 4 directory items */ + while (count++ < 4) { + res = f_readdir(&ftp_data.dp, &fno); /* Read a directory item */ + if (res != FR_OK || fno.fname[0] == 0) { + result = E_FTP_RESULT_OK; + break; /* Break on error or end of dp */ + } + if (fno.fname[0] == '.' && fno.fname[1] == 0) continue; /* Ignore . entry */ + if (fno.fname[0] == '.' && fno.fname[1] == '.' && fno.fname[2] == 0) continue; /* Ignore .. entry */ + + // Add the entry to the list + next += ftp_print_eplf_item((list + next), (maxlistsize - next), &fno); + } + if (result == E_FTP_RESULT_OK) { + ftp_close_files(); + } + *listsize = next; + return result; +} + +static void ftp_open_child (char *pwd, char *dir) { + if (dir[0] == '/') { + strcpy (pwd, dir); + } + else { + if (strlen(pwd) > 1) { + strcat (pwd, "/"); + } + strcat (pwd, dir); + } + + uint len = strlen(pwd); + if ((len > 1) && (pwd[len - 1] == '/')) { + pwd[len - 1] = '\0'; + } +} + +static void ftp_close_child (char *pwd) { + uint len = strlen(pwd); + while (len && (pwd[len] != '/')) { + len--; + } + if (len == 0) { + strcpy (pwd, "/"); + } + else { + pwd[len] = '\0'; + } +} + +static void ftp_return_to_previous_path (char *pwd, char *dir) { + uint newlen = strlen(pwd) - strlen(dir); + if ((newlen > 2) && (pwd[newlen - 1] == '/')) { + pwd[newlen - 1] = '\0'; + } + else { + if (newlen == 0) { + strcpy (pwd, "/"); + } + else { + pwd[newlen] = '\0'; + } + } +} |