/* * lwftp.c : a lightweight FTP client using raw API of LWIP * * Copyright (c) 2014 GEZEDO * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Laurent GONZALEZ * */ #pragma GCC diagnostic error "-Wall" #pragma GCC diagnostic error "-Wextra" #include "common_config.h" #ifdef FTP_ENABLE #include #include #include "ftp.h" #include "lwip/tcp.h" #include "sockets.h" #include "stm32f4xx.h" #include "spi_flash.h" #include "web_params_api.h" #include "FreeRTOS.h" #include "task.h" #include "hal.h" #include "syslog.h" #include "log.h" /** Enable debugging for LWFTP */ #ifndef LWFTP_DEBUG #define LWFTP_DEBUG LWIP_DBG_ON #endif #define LWFTP_TRACE (LWFTP_DEBUG|LWIP_DBG_TRACE) #define LWFTP_WARNING (LWFTP_DEBUG|LWIP_DBG_LEVEL_WARNING) #define LWFTP_SERIOUS (LWFTP_DEBUG|LWIP_DBG_LEVEL_SERIOUS) #define LWFTP_SEVERE (LWFTP_DEBUG|LWIP_DBG_LEVEL_SEVERE) #define PTRNLEN(s) s,(sizeof(s)-1) #define min(x,y) ((x)<(y)?(x):(y)) lwftp_session_t ftpcfg; static unsigned received_bytes_count = 0; static char **error_ptr; /** Close control or data pcb * @param pointer to lwftp session data */ static err_t lwftp_pcb_close(struct tcp_pcb *tpcb) { err_t error; tcp_err(tpcb, NULL); tcp_recv(tpcb, NULL); tcp_sent(tpcb, NULL); error = tcp_close(tpcb); if ( error != ERR_OK ) { LWIP_DEBUGF(LWFTP_SEVERE, ("lwftp:pcb close failure, not implemented\n")); } return ERR_OK; } /** Send data * @param pointer to lwftp session data * @param pointer to PCB * @param number of bytes sent */ static err_t lwftp_send_next_data(lwftp_session_t *s) { const char *data; int len = 0; err_t error = ERR_OK; if (s->data_source) { len = s->data_source(s->data_handle, &data, s->data_pcb->mss); if (len) { LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:sending %d bytes of data\n",len)); error = tcp_write(s->data_pcb, data, len, 0); if (error!=ERR_OK) { LWIP_DEBUGF(LWFTP_SEVERE, ("lwftp:write failure (%s), not implemented\n",lwip_strerr(error))); } } } if (!len) { LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:end of file\n")); lwftp_pcb_close(s->data_pcb); s->data_pcb = NULL; } return ERR_OK; } /** Handle data connection incoming data * @param pointer to lwftp session data * @param pointer to PCB * @param pointer to incoming pbuf * @param state of incoming process */ static err_t lwftp_data_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { (void)err; lwftp_session_t *s = (lwftp_session_t*)arg; if (p) { if (s->data_sink) { struct pbuf *q; for (q=p; q; q=q->next) { s->data_sink(s->data_handle, q->payload, q->len); } } else { LWIP_DEBUGF(LWFTP_SEVERE, ("lwftp: sinking %d bytes\n",p->tot_len)); } tcp_recved(tpcb, p->tot_len); pbuf_free(p); } else { if (s->data_sink) { s->data_sink(s->data_handle, NULL, 0); } // NULL pbuf shall lead to close the pcb. if (s->data_pcb) { lwftp_pcb_close(s->data_pcb); s->data_pcb = NULL; } } return ERR_OK; } /** Handle data connection acknowledge of sent data * @param pointer to lwftp session data * @param pointer to PCB * @param number of bytes sent */ static err_t lwftp_data_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) { (void)tpcb; lwftp_session_t *s = (lwftp_session_t*)arg; if ( s->data_source ) { s->data_source(s->data_handle, NULL, len); } return lwftp_send_next_data(s); } /** Handle data connection error * @param pointer to lwftp session data * @param state of connection */ static void lwftp_data_err(void *arg, err_t err) { LWIP_UNUSED_ARG(err); if (arg != NULL) { lwftp_session_t *s = (lwftp_session_t*)arg; LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:failed/error connecting for data to server (%s)\n",lwip_strerr(err))); s->control_state = LWFTP_QUIT; // gracefully exit on data error s->data_pcb = NULL; // No need to de-allocate PCB } } /** Process newly connected PCB * @param pointer to lwftp session data * @param pointer to PCB * @param state of connection */ static err_t lwftp_data_connected(void *arg, struct tcp_pcb *tpcb, err_t err) { (void)tpcb; lwftp_session_t *s = (lwftp_session_t*)arg; if ( err == ERR_OK ) { LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:connected for data to server\n")); s->data_state = LWFTP_CONNECTED; } else { LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:err in data_connected (%s)\n",lwip_strerr(err))); } return err; } /** Open data connection for passive transfer * @param pointer to lwftp session data * @param pointer to incoming PASV response */ static err_t lwftp_data_open(lwftp_session_t *s, struct pbuf *p) { err_t error; char *ptr; ip_addr_t data_server; u16_t data_port; // Find server connection parameter ptr = strchr(p->payload, '('); if (!ptr) return ERR_BUF; do { unsigned int a = strtoul(ptr+1,&ptr,10); unsigned int b = strtoul(ptr+1,&ptr,10); unsigned int c = strtoul(ptr+1,&ptr,10); unsigned int d = strtoul(ptr+1,&ptr,10); IP4_ADDR(&data_server,a,b,c,d); } while(0); data_port = strtoul(ptr+1,&ptr,10) << 8; data_port |= strtoul(ptr+1,&ptr,10) & 255; if (*ptr!=')') return ERR_BUF; // Open data session tcp_arg(s->data_pcb, s); tcp_err(s->data_pcb, lwftp_data_err); tcp_recv(s->data_pcb, lwftp_data_recv); tcp_sent(s->data_pcb, lwftp_data_sent); error = tcp_connect(s->data_pcb, &data_server, data_port, lwftp_data_connected); return error; } /** Send a message to control connection * @param pointer to lwftp session data * @param pointer to message string */ static err_t lwftp_send_msg(lwftp_session_t *s, const char* msg, size_t len) { err_t error; LWIP_DEBUGF(LWFTP_TRACE,("lwftp:sending %s",msg)); error = tcp_write(s->control_pcb, msg, len, 0); if ( error != ERR_OK ) { LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:cannot write (%s)\n",lwip_strerr(error))); } return error; } /** Close control connection * @param pointer to lwftp session data * @param result to pass to callback fn (if called) */ static void lwftp_control_close(lwftp_session_t *s, int result) { if (s->control_pcb) { lwftp_pcb_close(s->control_pcb); s->control_pcb = NULL; } s->control_state = LWFTP_CLOSED; if ( (result >= 0) && s->done_fn ) { s->done_fn(s->data_handle, result); } } static void set_timeout(struct tcp_pcb *tpcb, unsigned seconds) { struct timeval timeout; timeout.tv_sec = seconds; timeout.tv_usec = 0; setsockopt(tpcb, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout)); } /** Main client state machine * @param pointer to lwftp session data * @param pointer to PCB * @param pointer to incoming data */ static void lwftp_control_process(lwftp_session_t *s, struct tcp_pcb *tpcb, struct pbuf *p) { (void)tpcb; unsigned response = 0; int result = LWFTP_RESULT_ERR_SRVR_RESP; // Try to get response number if (p) { response = strtoul(p->payload, NULL, 10); LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:got response %d\n",response)); } switch (s->control_state) { case LWFTP_CONNECTED: if (response>0) { if (response==220) { lwftp_send_msg(s, PTRNLEN("USER ")); lwftp_send_msg(s, s->settings->user, strlen(s->settings->user)); lwftp_send_msg(s, PTRNLEN("\r\n")); s->control_state = LWFTP_USER_SENT; } else { s->error = "The server doesn't like us"; s->control_state = LWFTP_QUIT; } } else { s->error = "The server doesn't greet us"; s->control_state = LWFTP_QUIT; } break; case LWFTP_USER_SENT: if (response>0) { if (response==331) { lwftp_send_msg(s, PTRNLEN("PASS ")); lwftp_send_msg(s, s->settings->pass, strlen(s->settings->pass)); lwftp_send_msg(s, PTRNLEN("\r\n")); s->control_state = LWFTP_PASS_SENT; } else if (response==230) { goto anonymous; } else { s->error = "Wrong user name"; s->control_state = LWFTP_QUIT; } } break; case LWFTP_PASS_SENT: if (response>0) { if (response==230) { anonymous: lwftp_send_msg(s, PTRNLEN("TYPE I\r\n")); s->control_state = LWFTP_TYPE_SENT; } else { s->error = "Wrong password"; s->control_state = LWFTP_QUIT; } } break; case LWFTP_TYPE_SENT: if (response>0) { if (response==200) { lwftp_send_msg(s, PTRNLEN("PASV\r\n")); s->control_state = LWFTP_PASV_SENT; } else { s->error = "The server doesn't support binary files"; s->control_state = LWFTP_QUIT; } } break; case LWFTP_PASV_SENT: if (response>0) { if (response==227) { lwftp_data_open(s,p); switch (s->target_state) { case LWFTP_STOR_SENT: lwftp_send_msg(s, PTRNLEN("STOR ")); break; case LWFTP_RETR_SENT: lwftp_send_msg(s, PTRNLEN("RETR ")); break; default: //LOG_ERROR("Unexpected internal state"); s->target_state = LWFTP_QUIT; } lwftp_send_msg(s, s->settings->remote_path, strlen(s->settings->remote_path)); lwftp_send_msg(s, PTRNLEN("\r\n")); s->control_state = s->target_state; } else { s->error = "The server doesn't support PASV"; s->control_state = LWFTP_QUIT; } } break; case LWFTP_RETR_SENT: if (response>0) { if (response==150) { s->control_state = LWFTP_XFERING; set_timeout(s->control_pcb, 0); } else if (response==550) { s->control_state = LWFTP_QUIT; s->error = "Failed to open file"; LWIP_DEBUGF(LWFTP_WARNING, ("lwftp: %s '%s'\n", error, s->settings->remote_path)); } else { s->error = "The server doesn't start sending the file"; s->control_state = LWFTP_QUIT; LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:expected 150, received %d\n",response)); } } break; case LWFTP_STOR_SENT: if (response>0) { if (response==150) { s->control_state = LWFTP_XFERING; lwftp_data_sent(s,NULL,0); } else { s->control_state = LWFTP_QUIT; } } break; case LWFTP_XFERING: if (response>0) { if (response==226) { s->data_state = LWFTP_XFERING; // signal transfer OK } else { LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:expected 226, received %d\n",response)); } // Quit anyway after any message received during STOR s->control_state = LWFTP_QUIT; } break; case LWFTP_QUIT_SENT: if (response>0) { if (response==221) { if (s->data_state == LWFTP_XFERING){ // check for transfer OK result = LWFTP_RESULT_OK; } } else { LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:expected 221, received %d\n",response)); } s->control_state = LWFTP_CLOSING; } break; default: LWIP_DEBUGF(LWFTP_SEVERE, ("lwftp:unhandled state (%d)\n",s->control_state)); } // Free receiving pbuf if any if (p) { pbuf_free(p); } // Handle second step in state machine switch ( s->control_state ) { case LWFTP_QUIT: lwftp_send_msg(s, PTRNLEN("QUIT\r\n")); s->control_state = LWFTP_QUIT_SENT; break; case LWFTP_CLOSING: // this function frees s, no use of s is allowed after return lwftp_control_close(s, result); default:; } } /** Handle control connection incoming data * @param pointer to lwftp session data * @param pointer to PCB * @param pointer to incoming pbuf * @param state of incoming process */ static err_t lwftp_control_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { lwftp_session_t *s = (lwftp_session_t*)arg; if ( err == ERR_OK ) { if (p) { tcp_recved(tpcb, p->tot_len); lwftp_control_process(s, tpcb, p); } else { LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:connection closed by remote host\n")); lwftp_control_close(s, LWFTP_RESULT_ERR_CLOSED); } } else { LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:failed to receive (%s)\n",lwip_strerr(err))); lwftp_control_close(s, LWFTP_RESULT_ERR_UNKNOWN); } return err; } /** Handle control connection acknowledge of sent data * @param pointer to lwftp session data * @param pointer to PCB * @param number of bytes sent */ static err_t lwftp_control_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) { (void)arg; (void)tpcb; (void)len; LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:successfully sent %d bytes\n",len)); return ERR_OK; } /** Handle control connection error * @param pointer to lwftp session data * @param state of connection */ static void lwftp_control_err(void *arg, err_t err) { LWIP_UNUSED_ARG(err); if (arg != NULL) { lwftp_session_t *s = (lwftp_session_t*)arg; int result; if( s->control_state == LWFTP_CLOSED ) { s->error = "failed to connect to server"; LWIP_DEBUGF(LWFTP_WARNING, ("lwftp: %s (%s)\n",lwip_strerr(err))); result = LWFTP_RESULT_ERR_CONNECT; } else { s->error = "connection closed by remote host"; LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:connection closed by remote host\n")); result = LWFTP_RESULT_ERR_CLOSED; } s->control_pcb = NULL; // No need to de-allocate PCB lwftp_control_close(s, result); } } /** Process newly connected PCB * @param pointer to lwftp session data * @param pointer to PCB * @param state of connection */ static err_t lwftp_control_connected(void *arg, struct tcp_pcb *tpcb, err_t err) { (void)tpcb; lwftp_session_t *s = (lwftp_session_t*)arg; if ( err == ERR_OK ) { LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:connected to server\n")); s->control_state = LWFTP_CONNECTED; } else { LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:err in control_connected (%s)\n",lwip_strerr(err))); } return err; } /** Store data to a remote file * @param Session structure */ err_t lwftp_store(lwftp_session_t *s) { err_t error; // Check user supplied data if ( (s->control_state!=LWFTP_CLOSED) || !s->settings->remote_path || s->control_pcb || s->data_pcb || !s->settings->user || !s->settings->pass ) { LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:invalid session data\n")); return ERR_ARG; } // Get sessions pcb s->control_pcb = tcp_new(); if (!s->control_pcb) { LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:cannot alloc control_pcb (low memory?)\n")); error = ERR_MEM; goto exit; } s->data_pcb = tcp_new(); if (!s->data_pcb) { LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:cannot alloc data_pcb (low memory?)\n")); error = ERR_MEM; goto close_pcb; } set_timeout(s->control_pcb, 5); set_timeout(s->data_pcb, 5); // Open control session tcp_arg(s->control_pcb, s); tcp_err(s->control_pcb, lwftp_control_err); tcp_recv(s->control_pcb, lwftp_control_recv); tcp_sent(s->control_pcb, lwftp_control_sent); error = tcp_connect(s->control_pcb, &s->settings->server_ip, s->settings->server_port, lwftp_control_connected); if ( error == ERR_OK ) goto exit; LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:cannot connect control_pcb (%s)\n", lwip_strerr(error))); close_pcb: // Release pcbs in case of failure lwftp_control_close(s, -1); exit: return error; } err_t lwftp_retr(lwftp_session_t *s) { s->target_state = LWFTP_RETR_SENT; err_t error; // Check user supplied data if (!s->settings->remote_path) { s->error = "empty remote path"; return ERR_ARG; } if (!s->settings->user) { s->error = "empty user name"; return ERR_ARG; } if (!s->settings->pass) { s->error = "empty password"; return ERR_ARG; } if (s->control_state != LWFTP_CLOSED || s->control_pcb) { // previous connection is incomplete lwftp_control_close(s, -1); } if (s->data_pcb) { lwftp_pcb_close(s->data_pcb); } // Get sessions pcb s->control_pcb = tcp_new(); if (!s->control_pcb) { s->error = "cannot alloc control_pcb (low memory?)"; error = ERR_MEM; goto exit; } s->data_pcb = tcp_new(); if (!s->data_pcb) { s->error = "cannot alloc data_pcb (low memory?)"; error = ERR_MEM; goto close_pcb; } // Open control session tcp_arg(s->control_pcb, s); tcp_err(s->control_pcb, lwftp_control_err); tcp_recv(s->control_pcb, lwftp_control_recv); tcp_sent(s->control_pcb, lwftp_control_sent); error = tcp_connect(s->control_pcb, &s->settings->server_ip, s->settings->server_port, lwftp_control_connected); if ( error == ERR_OK ) goto exit; LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:cannot connect control_pcb (%s)\n", lwip_strerr(error))); s->error = "cannot connet control_pcb"; close_pcb: // Release pcbs in case of failure lwftp_control_close(s, -1); exit: return error; } //static void ftp_retr_callback(void *arg, int result) //{ // lwftp_session_t *s = (lwftp_session_t *)arg; // // if (result != LWFTP_RESULT_OK) { // //LOG_ERROR("retr failed (%d)", result); // return lwftp_close(s); // } // lwftp_close(s); //} static bool validate_spif_firmware(uint32_t fw_len) { const uint32_t spif_firmware_offset = SPI_FLASH_SECTOR_SIZE * FIRMWARE_UPDATE_SECTOR_OFFSET; // check the firmware revision id char rev[HW_REV_LEN]; spi_flash_read(spif_firmware_offset + HW_REV_OFFSET, &rev, sizeof(rev), 0); if (strncmp(rev, HW_REV, sizeof(rev))) { printf("HW revision mismatch: expected %s, found %s\r\n", HW_REV, rev); return false; } // check if the firmware length is correct if (fw_len != MAIN_FW_SIZE) { printf("ftp: firmware size mismatch: expected %d, got %ld\r\n", MAIN_FW_SIZE, fw_len); return false; } uint32_t expected_crc; const uint32_t crc_offset = USER_FLASH_CRC_ADDRESS - USER_FLASH_FIRST_PAGE_ADDRESS; spi_flash_read(spif_firmware_offset + crc_offset, &expected_crc, sizeof(expected_crc), 0); // calculate CRC RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE); CRC->CR = 1; for (uint32_t offset = 0; offset < crc_offset; offset += 4) { uint32_t data; spi_flash_read(spif_firmware_offset + offset, &data, sizeof(data), 0); CRC->DR = data; } uint32_t calculated_crc = CRC->DR; if (expected_crc != calculated_crc) { printf("ftp: calculated CRC (%lx) doesn't match the expected CRC (%lx)!\r\n", calculated_crc, expected_crc); return false; } return true; } static void erase_spif_firmware() { printf("ftp: erasing SPI firmware partition...\r\n"); for (int sec = 0; sec < FIRMWARE_UPDATE_SECTOR_COUNT; ++sec) { spi_flash_erase_sector(SPI_FLASH_SECTOR_SIZE * (FIRMWARE_UPDATE_SECTOR_OFFSET + sec), 0); } } static unsigned data_sink(void *arg, const char* ptr, unsigned len) { (void)arg; if (ptr && len) { // we received some data from the FTP server if (received_bytes_count == 0) { // we need to erase the flash before we can write it erase_spif_firmware(); } if (received_bytes_count + len > MAIN_FW_SIZE) { syslog_str(SYSLOG_ERROR, "Файл прошивки слишком велик"); log_event_data(LOG_UPDATE_SOFT, "Файл слишком велик"); return 0; } spi_flash_write(SPI_FLASH_SECTOR_SIZE * FIRMWARE_UPDATE_SECTOR_OFFSET + received_bytes_count, ptr, len, 0); received_bytes_count += len; } else { printf("ftp: the firmware is downloaded, verifying...\r\n"); uint32_t fw_size = received_bytes_count; bool good_firmware = validate_spif_firmware(fw_size); if (good_firmware) { printf("ftp: the firmware is valid, rebooting...\r\n"); set_act_source(FTP_ACT); HTTP_StartResetTask(true); } else { syslog_str(SYSLOG_ERROR, "Некорректный файл прошивки"); log_event_data(LOG_UPDATE_SOFT, "Некорректный файл"); // erase it so the bootloader won't try to verify it every time erase_spif_firmware(); } } return len; } void start_ftp_client(lwftp_session_t *s) { received_bytes_count = 0; s->error = NULL; error_ptr = &s->error; s->data_sink = data_sink; //s->done_fn = ftp_retr_callback; lwftp_retr(s); // FTP session will continue with the connection callback } char *get_ftp_progress() { if (*error_ptr) { return *error_ptr; } else { unsigned progress = received_bytes_count * 100 / MAIN_FW_SIZE; static char progress_str_buf[4]; snprintf(progress_str_buf, sizeof(progress_str_buf), "%u", progress); return progress_str_buf; } } #endif // FTP_ENABLE