本程序在LwIP 2.1.2協議棧上用raw API實現了一個FTP服務器。文件存儲在Winbond的W25Q128 SPI Flash中,通過FatFs讀寫文件,建立了FAT文件系統,容量爲16MB。程序只有1700多行代碼,由頭文件ftp.h和源文件ftpd.c組成。
主要特點:
1. 採用lwip的raw API實現,可以在裸機環境下運行,支持IPv6
2. 兼容Windows文件管理器和FileZilla FTP客戶端
3. 實現了文件瀏覽、上傳、下載、重命名、新建文件夾、刪除文件夾和文件等基本FTP功能
4. 支持IPv4和IPv6的主動模式和被動模式。可以在FileZilla客戶端中強制使用主動模式,主動模式下電腦必須要關閉防火牆才能訪問FTP服務器
5. nettime.c實現了從互聯網獲取北京時間的功能,並保存到單片機的RTC實時時鐘,不用擔心通過FTP上傳的文件沒有日期和時間信息。時間服務器選擇的是utcnist.colorado.edu,端口號爲37
不足之處:
1. 沒有用戶權限配置功能,目前所有的用戶(包括匿名用戶)都能上傳和下載文件
2. 暫不支持FileZilla中的斷點續傳功能
3. 上傳文件時,不支持某些中文文件名(這是因爲Flash空間不夠,無法在ffconf.h中將FF_CODE_PAGE配置爲936 - Simplified Chinese (DBCS))
4. FileZilla無法自動檢測編碼,必須改爲強制UTF-8,文件名纔不會亂碼
【STM32F107VC+DP83848+W25Q128+FTP程序】
程序下載地址:https://pan.baidu.com/s/1KECwLCFMJIj4wO_WDz0o-A(提取碼:gk5t)
DP83848.c中的三個重要配置項:
(1)ETH_REMAP:若ETH部分引腳被重映射到了PD口,則應配置爲1,否則爲0
(2)USE_MII:如果DP83848以太網收發芯片使用的是MII接口則應配置爲1,使用RMII接口則應配置爲0
(3)RESET_N引腳:DP83848芯片的復位引腳,本例程中接的是PB15,可以改成其他引腳
請根據開發板的情況,正確配置DP83848.c中的這三個項目。
DP83848芯片的RESET引腳最好接上外部下拉電阻,避免單片機PA8沒有輸出時鐘時DP83848未處於復位狀態,干擾電路正常運行。
啓動文件startup_stm32f107xc.s中應該設置合適的棧大小Stack_Size,供FatFs使用。
HSE晶振的大小在項目屬性的C/C++選項卡中的Preprocessor Symbols中的HSE_VALUE上設置。
本例程設置的是25000000,即25MHz。若修改了晶振大小,則還需修改common.c的clock_init時鐘初始化函數。
【STM32F103RE+88W8801+W25Q128+FTP程序】
請參閱:https://blog.csdn.net/ZLK1214/article/details/107117885
【LwIP 2.0.3版本兼容性】
若要在2.0.3版本的lwip中運行,需要將2.1.2版本的pbuf_free_header和pbuf_remove_header函數複製過來,然後在ftpd.c頂部包含頭文件<ctype.h>。
【程序運行截圖】
1. 使用Windows文件管理器通過計算機名訪問FTP服務器(已登錄admin用戶,密碼爲123456)
2. FTP登錄界面
3. FileZilla訪問FTP服務器(必須要開啓強制UTF-8編碼才能防止中文文件名亂碼)
4. FileZilla上傳文件
6. 通過板子的IPv6地址訪問FTP服務器
【程序運行結果】
STM32F107VC ETH
SystemCoreClock=72000000
LSI is ready!
Failed to start ETH!
MAC address: 00:80:E1:CD:9E:1A
IPv6 link-local address: FE80::280:E1FF:FECD:9E1A
SPI Flash: M=0xef, ID=0x17
SPI Flash: M=0xef, ID=0x4018
Disk is mounted!
DP83848 interrupt occurred! status=0x2c20
Link is up!
ETH is restarted!
[Send] len=350
[Send] len=86
[Send] len=78
[Send] len=86
[Recv] len=60
[Recv] len=60
[Recv] len=60
[Send] len=350
[Recv] len=60
[Recv] len=60
[Send] len=86
[Recv] len=208
[Send] len=70
[Recv] len=142
[Send] len=86
[Recv] len=86
[Send] len=78
[Recv] len=590
[Send] len=350
[Recv] len=590
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=86
DHCP supplied address!
IP address: 192.168.1.2
Subnet mask: 255.255.255.0
Default gateway: 192.168.1.1
IPv6 address 1: 2409:8A62:362:1050:280:E1FF:FECD:9E1A
DNS Server: 192.168.1.1
[Send] len=42
Time server IP: not in cache!
[Recv] len=60
[Send] len=80
[Recv] len=116
Time server IP: 128.138.140.44
Connecting to 128.138.140.44...
[Send] len=58
[Recv] len=60
Connected!
[Send] len=54
[Recv] len=208
[Recv] len=60
[Send] len=54
[Recv] len=60
Time from network: 2020-07-03 21:17:57
RTC is not updated!
Connection to the time server is closed!
[Send] len=54
[Recv] len=60
[Send] len=42
[Send] len=42
[Recv] len=60
[Send] len=42
[Recv] len=208
[Recv] len=60
[Recv] len=208
[Recv] len=208
[Recv] len=60
[Recv] len=140
[Recv] len=208
[Recv] len=140
[Recv] len=60
[Recv] len=208
[Recv] len=140
[Recv] len=208
[Recv] len=60
[Recv] len=208
[Recv] len=140
[Recv] len=140
[Recv] len=60
[Recv] len=208
[Recv] len=86
[Send] len=86
[Recv] len=86
[Send] len=78
[Recv] len=74
FTPD accepted [2409:8A62:362:1050:883B:55B0:939C:EE8]:54466!
220 LwIP FTP Service
[Send] len=96
[Recv] len=208
[Send] len=96
[Recv] len=74
ftpd_sent: 22 bytes of response sent
[Recv] len=90
ftpd_recv: received 16 bytes
USER anonymous
331 Anonymous access allowed, send identity (e-mail name) as password.
[Send] len=146
[Recv] len=74
ftpd_sent: 72 bytes of response sent
ftpd_sent: processed 16 bytes
[Recv] len=88
ftpd_recv: received 14 bytes
PASS IEUser@
230 Login successful.
[Send] len=97
[Recv] len=88
[Send] len=74
[Recv] len=60
[Send] len=97
[Recv] len=74
ftpd_sent: 23 bytes of response sent
ftpd_sent: processed 14 bytes
[Recv] len=88
ftpd_recv: received 14 bytes
opts utf8 on
200 Always in UTF8 mode.
[Send] len=100
[Recv] len=74
ftpd_sent: 26 bytes of response sent
ftpd_sent: processed 14 bytes
[Recv] len=80
ftpd_recv: received 6 bytes
syst
500 Unknown command.
[Send] len=96
[Recv] len=80
[Send] len=74
[Recv] len=208
[Send] len=96
[Recv] len=74
ftpd_sent: 22 bytes of response sent
ftpd_sent: processed 6 bytes
[Recv] len=85
ftpd_recv: received 11 bytes
site help
500 Unknown command.
[Send] len=96
[Recv] len=85
[Send] len=74
[Recv] len=92
[Send] len=96
[Recv] len=74
ftpd_sent: 22 bytes of response sent
ftpd_sent: processed 11 bytes
[Recv] len=79
ftpd_recv: received 5 bytes
PWD
257 "/" is the current directory.
[Send] len=109
[Recv] len=74
ftpd_sent: 35 bytes of response sent
ftpd_sent: processed 5 bytes
[Recv] len=82
ftpd_recv: received 8 bytes
TYPE A
200 Switching to ASCII mode.
[Send] len=104
[Recv] len=82
[Send] len=74
[Recv] len=92
[Recv] len=60
[Recv] len=208
[Recv] len=92
[Send] len=104
[Recv] len=86
[Send] len=86
[Recv] len=208
[Recv] len=140
[Send] len=104
[Recv] len=74
ftpd_sent: 30 bytes of response sent
ftpd_sent: processed 8 bytes
[Recv] len=80
ftpd_recv: received 6 bytes
EPSV
229 Entering Extended Passive Mode (|||57805|).
[Send] len=123
[Recv] len=80
[Send] len=74
[Recv] len=140
[Recv] len=60
[Send] len=123
[Recv] len=74
ftpd_sent: 49 bytes of response sent
ftpd_sent: processed 6 bytes
[Recv] len=86
[Send] len=78
[Recv] len=74
FTPD data connection to [2409:8A62:362:1050:883B:55B0:939C:EE8]:54468 is established!
[Recv] len=80
ftpd_recv: received 6 bytes
LIST
150 Here comes the directory listing.
[Send] len=113
[Recv] len=80
[Send] len=74
[Send] len=113
[Recv] len=74
ftpd_sent: 39 bytes of response sent
ftpd_sent: processed 6 bytes
04-04-2020 08:18PM 9471 111110.xlsx
04-01-2020 11:26PM 934844 HeartOfCat_20200401.zip
05-18-2020 09:13PM 9236 pt32.xlsx
04-27-2020 09:57PM 917 test.c
FTPD data connection [2409:8A62:362:1050:883B:55B0:939C:EE8]:54468 is shutdown by the server!
[Send] len=295
226 Directory send OK.
[Send] len=98
[Recv] len=74
ftpd_sent: 24 bytes of response sent
[Recv] len=208
[Send] len=295
[Recv] len=74
[Recv] len=74
FTPD data connection [2409:8A62:362:1050:883B:55B0:939C:EE8]:54468 is closed by the client!
[Send] len=74
[Recv] len=74
[Send] len=74
[Recv] len=60
[Recv] len=208
【主要代碼】
ftpd.h:
#ifndef _FTPD_H
#define _FTPD_H
#ifndef FTPD_DEBUG
#define FTPD_DEBUG LWIP_DBG_OFF
#endif
#define FTPD_PORT 21 // FTP服務器端口號
#define FTPD_PASV 1 // 是否允許使用PASV命令
// 命令處理過程中的異常情況
#define FTPD_CMDSTEP_CONNFAILED 0x1000 // 連接建立失敗
#define FTPD_CMDSTEP_CONNABORTED 0x2000 // 連接建立成功但異常中止
#define FTPD_CMDSTEP_CONNSHUTDOWN 0x4000 // 連接建立成功後被客戶端關閉
// FTP客戶端狀態位
#if FTPD_PASV
#define FTPD_FLAG_PASSIVE 0x01 // 當前是否爲被動模式
#endif
#define FTPD_FLAG_CLOSE 0x02 // 收到客戶端的TCP第一次揮手後, 請求發送第三次揮手
#define FTPD_FLAG_SHUTDOWN 0x04 // 請求發送TCP第一次揮手, 然後接收客戶端第三次揮手
#define FTPD_FLAG_RENAME 0x08 // 是否正在重命名文件
#define FTPD_FLAG_AGAIN 0x10 // 當前FTP命令還沒有執行完畢, 控制連接上的數據發送完畢後應繼續回來處理
#define FTPD_FLAG_NEWDATACONN 0x20 // 數據連接已創建但還未連接上
#define FTPD_FLAG_TCPERROR 0x40 // TCP發送數據出錯
// 數據連接關閉方式
#define FTPD_FREEDATA_ABORT 0 // 強行中止數據連接
#define FTPD_FREEDATA_CLOSE 1 // 關閉數據連接 (客戶端已關閉)
#define FTPD_FREEDATA_SHUTDOWN 2 // 關閉數據連接 (客戶端未關閉)
#ifndef MAX_PATH
#define MAX_PATH 260
#endif
struct ftpd_user
{
char *name;
char *password;
};
struct ftpd_account
{
struct ftpd_user user;
char *rootpath;
};
#ifdef FF_DEFINED
struct ftpd_state
{
struct tcp_pcb *ctrlconn;
struct tcp_pcb *dataconn;
int dataport;
char cmd[MAX_PATH + 20];
int cmdlen;
char *cmdarg;
int cmdstep;
char last;
char type;
char path[MAX_PATH];
char rename[MAX_PATH];
struct ftpd_user user;
int userid;
int flags;
int sent; // 未收到確認的已發送字節數
struct pbuf *queue; // 數據接收隊列
void *dataout;
int dataout_len;
DIR *dp;
FIL *fp;
FILINFO *finfo;
};
#else
struct ftpd_state;
#endif
int ftpd_concat_path(char *buffer, int bufsize, const char *filename);
int ftpd_file_exists(const char *path);
#ifdef FF_DEFINED
time_t ftpd_filetime(WORD fdate, WORD ftime, struct tm *ptm);
#endif
int ftpd_fullpath(const struct ftpd_state *state, char *buffer, int bufsize, const char *filename, char **puserpath);
int ftpd_init(void);
void *ftpd_memrchr(const void *s, int c, size_t n);
int ftpd_simplify_path(char *path, int basepos);
char *ftpd_strdup(const char *s);
#endif
ftpd.c:
/*************************** 基於LwIP raw API的FTP服務器*****************************
** 注意事項:
** 1. 使用FileZilla客戶端連接FTP服務器時, 字符集應該選擇"強制UTF-8"
** 不能選擇"自動檢測", 這樣文件名纔不會亂碼
** 2. 若想要移動文件, 可在文件管理器中使用"xxx/", "../"這樣的語法重命名文件
** 例如想要把"abc.txt"移動到當前目錄的123目錄下, 則可以將文件重命名爲"123/abc.txt"
** 將"def.doc"移動到父目錄的456文件夾下, 則應該重命名爲"../456/def.doc"
** 3. 服務器使用FatFs讀寫磁盤文件
** 如果出現HardFault錯誤, 則可能是startup_stm32*.s啓動文件裏面的Stack_Size值太小
** 將其改大就可以解決問題
************************************************************************************/
#include <ff.h>
#include <lwip/tcp.h>
#include <string.h>
#include <time.h>
#include "ftpd.h"
static err_t ftpd_accept(void *arg, struct tcp_pcb *newpcb, err_t err);
static void ftpd_change_user(struct ftpd_state *state, const char *newuser);
static int ftpd_copy_cmd(struct ftpd_state *state);
#if FTPD_PASV
static err_t ftpd_data_accept(void *arg, struct tcp_pcb *newpcb, err_t err);
#endif
static void ftpd_data_check(struct ftpd_state *state);
static err_t ftpd_data_connected(void *arg, struct tcp_pcb *tpcb, err_t err);
static void ftpd_data_err(void *arg, err_t err);
static err_t ftpd_data_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
static err_t ftpd_data_sent(void *arg, struct tcp_pcb *tpcb, u16_t len);
static err_t ftpd_data_sent_list(void *arg, struct tcp_pcb *tpcb, u16_t len);
static err_t ftpd_data_sent_retr(void *arg, struct tcp_pcb *tpcb, u16_t len);
static void ftpd_err(void *arg, err_t err);
static void ftpd_free(struct ftpd_state *state);
static err_t ftpd_free_data(struct ftpd_state *state, int option);
static int ftpd_is_valid_user(struct ftpd_user *user, int *pid);
static int ftpd_prepare_data(struct ftpd_state *state);
static void ftpd_process_cmd(struct ftpd_state *state);
static int ftpd_process_data_cmd(struct ftpd_state *state);
static int ftpd_process_directory_cmd(struct ftpd_state *state);
static int ftpd_process_file_cmd(struct ftpd_state *state);
static int ftpd_process_opt_cmd(struct ftpd_state *state);
static int ftpd_process_user_cmd(struct ftpd_state *state);
static err_t ftpd_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
static int ftpd_send_msg(struct ftpd_state *state, const char *s);
static err_t ftpd_sent(void *arg, struct tcp_pcb *tpcb, u16_t len);
// 用戶列表以及對應的根目錄
static const struct ftpd_account ftpd_users[] = {
{{"anonymous", NULL}, "C:/public"}, // 匿名用戶
{{"admin", "123456"}, "C:/"},
{{"test", "789123"}, "C:/test"}
};
// 盤符可在ffconf.h中的FF_VOLUME_STRS處指定
static struct tcp_pcb *ftpd_tpcb;
/* 控制連接收到新請求 */
static err_t ftpd_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
struct ftpd_state *state;
state = mem_malloc(sizeof(struct ftpd_state));
if (state == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("FTPD failed to accept [%s]:%d!\n", ipaddr_ntoa(&newpcb->remote_ip), newpcb->remote_port));
tcp_abort(newpcb);
return ERR_ABRT;
}
memset(state, 0, sizeof(struct ftpd_state));
state->ctrlconn = newpcb;
state->dataport = -1;
state->type = 'A';
strcpy(state->path, "/");
state->userid = -1;
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD accepted [%s]:%d!\n", ipaddr_ntoa(&newpcb->remote_ip), newpcb->remote_port));
ftpd_send_msg(state, "220 LwIP FTP Service\r\n");
tcp_arg(newpcb, state);
tcp_err(newpcb, ftpd_err);
tcp_recv(newpcb, ftpd_recv);
tcp_sent(newpcb, ftpd_sent);
return ERR_OK;
}
/* 改變用戶名並清空密碼 */
static void ftpd_change_user(struct ftpd_state *state, const char *newuser)
{
if (state->user.name != NULL)
{
mem_free(state->user.name);
state->user.name = NULL;
}
if (state->user.password != NULL)
{
mem_free(state->user.password);
state->user.password = NULL;
}
if (newuser != NULL)
state->user.name = ftpd_strdup(newuser);
}
/* 將文件夾和文件名連接在一起形成新路徑 */
// 將buffer和filename連接起來, 保存到buffer中, 同時保證字符串末尾不帶斜槓(根目錄除外); buffer的最大容量爲bufsize
// 成功時返回字符串的長度; 失敗時返回-1且buffer中的內容不變
int ftpd_concat_path(char *buffer, int bufsize, const char *filename)
{
char *p;
int addslash, fileabs, folderlen, namelen, len;
// 找出字符串的連接位置, 並去掉buffer的尾斜槓和filename的首斜槓
if (filename != NULL && filename[0] == '/')
{
// 如果文件名是絕對路徑, 則需要把文件夾路徑改爲根目錄
fileabs = 1;
filename++; // 去掉首斜槓
if (buffer[0] == '/')
folderlen = 1; // 文件夾路徑不帶盤符時只保留根目錄符號 (首斜槓)
else
{
p = strchr(buffer, ':');
if (p != NULL)
folderlen = p + 1 - buffer; // 文件夾路徑帶盤符時只保留盤符
else
folderlen = 0; // 如果buffer是相對路徑, 清空字符串
}
}
else
{
// 如果文件名不是絕對路徑, 則可以直接在文件夾路徑末尾連接上文件名
fileabs = 0;
folderlen = strlen(buffer);
if (folderlen > 1 && buffer[folderlen - 1] == '/')
folderlen--;
}
// 去掉filename的尾斜槓
if (filename != NULL)
namelen = strlen(filename);
else
namelen = 0;
if (namelen != 0 && filename[namelen - 1] == '/')
namelen--;
// 計算字符串連接在一起後需要的緩衝區大小
if (folderlen == 0)
addslash = fileabs; // 路徑爲空時加不加斜槓取決於文件名是不是絕對路徑
else if (folderlen == 1 && buffer[0] == '/')
addslash = 0; // 路徑爲斜槓時不加斜槓
else if (folderlen != 0 && buffer[folderlen - 1] == ':')
addslash = 1; // 路徑最後一個字符爲冒號時要加斜槓
else if (namelen == 0)
addslash = 0; // 文件名爲空時不加斜槓
else
addslash = 1; // 其他情況都要加斜槓
len = folderlen + addslash + namelen; // 連接後的長度
if (len >= bufsize)
return -1; // 緩衝區不夠
// 連接字符串
if (addslash)
buffer[folderlen] = '/';
if (namelen != 0)
memcpy(buffer + folderlen + addslash, filename, namelen);
buffer[len] = '\0';
return len;
}
/* 將數據接收隊列queue中的FTP命令字符串提取到state->cmd中, 並釋放佔用的pbuf內存 */
// 返回值: 0表示還沒有收到完整命令; 1表示收到了完整命令; 2表示收到了完整命令, 但超過了緩衝區最大長度
// state->cmdlen表示已收到了當前命令多少個字符 (包括\r\n)
static int ftpd_copy_cmd(struct ftpd_state *state)
{
char *c;
int complete = 0; // 是否收到完整命令
int cnt = 0; // 本次複製的字符數
int i;
struct pbuf *p;
for (p = state->queue; p != NULL && complete == 0; p = p->next)
{
c = p->payload;
for (i = 0; i < p->len && complete == 0; i++)
{
if (state->last == '\r' && *c == '\n')
{
if (state->cmdlen <= sizeof(state->cmd))
{
state->cmd[state->cmdlen - 1] = '\0'; // 把\r替換成\0
complete = 1;
}
else
complete = 2;
}
else
{
if (state->cmdlen < sizeof(state->cmd))
state->cmd[state->cmdlen] = *c;
}
state->cmdlen++;
state->last = *c;
c++;
cnt++;
}
}
state->queue = pbuf_free_header(state->queue, cnt);
return complete;
}
#if FTPD_PASV
/* 數據連接被動模式連接建立成功 */
static err_t ftpd_data_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
struct ftpd_state *state = arg;
if (!ip_addr_cmp(&newpcb->remote_ip, &state->ctrlconn->remote_ip))
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("%s: IP address mismatch!\n", __FUNCTION__));
tcp_abort(newpcb);
return ERR_ABRT;
}
tcp_close(state->dataconn); // 關閉端口監聽
state->dataconn = newpcb;
tcp_err(newpcb, ftpd_data_err);
return ftpd_data_connected(arg, newpcb, err);
}
#endif
/* 檢查數據連接是否未開始發送數據 */
// 這個函數應該在控制連接發送完開始信息後調用一次
static void ftpd_data_check(struct ftpd_state *state)
{
#if FTPD_PASV
if (state->flags & FTPD_FLAG_PASSIVE)
{
// 在PASV模式下, 連接可能會在PASV命令執行完畢的時候就建立成功
// 但必須要等到數據傳輸命令(如LIST命令)的響應(如150響應)發送完畢後, 才能開始發送數據
if ((state->flags & FTPD_FLAG_NEWDATACONN) == 0)
ftpd_data_sent(state, state->dataconn, 0);
// PORT模式下不存在這個問題, 因爲連接建立後就可以立即開始發送數據
}
#endif
}
/* 數據連接主動模式建立成功 */
static err_t ftpd_data_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
struct ftpd_state *state = arg;
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection to [%s]:%d is established!\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
state->flags &= ~FTPD_FLAG_NEWDATACONN;
tcp_recv(tpcb, ftpd_data_recv);
tcp_sent(tpcb, ftpd_data_sent);
return ftpd_data_sent(arg, tpcb, 0);
}
/* 數據連接出錯 */
static void ftpd_data_err(void *arg, err_t err)
{
struct ftpd_state *state = arg;
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("FTPD data error! err=%d\n", err));
if (state != NULL)
{
state->dataconn = NULL; // 調用err回調函數時, tpcb已經被LwIP釋放了, 所以不需要再次釋放
ftpd_free_data(state, FTPD_FREEDATA_ABORT);
if (state->flags & FTPD_FLAG_NEWDATACONN)
{
state->flags &= ~FTPD_FLAG_NEWDATACONN;
state->cmdstep |= FTPD_CMDSTEP_CONNFAILED;
ftpd_send_msg(state, "425 Failed to establish connection.\r\n");
}
else
{
state->cmdstep |= FTPD_CMDSTEP_CONNABORTED;
ftpd_process_cmd(state);
}
}
}
/* 數據連接收到數據 */
static err_t ftpd_data_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
struct ftpd_state *state = arg;
struct pbuf *q;
FRESULT fr;
UINT bw;
if (p != NULL)
{
if (state != NULL)
{
if (strcasecmp(state->cmd, "STOR") == 0)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s: %d bytes received\n", __FUNCTION__, p->tot_len));
for (q = p; q != NULL; q = q->next)
{
fr = f_write(state->fp, q->payload, q->len, &bw);
if (bw != q->len)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_write() failed! fr=%d, q->len=%u, bw=%u\n", __FUNCTION__, fr, q->len, bw));
pbuf_free(p);
err = ftpd_free_data(state, FTPD_FREEDATA_ABORT);
state->cmdstep = FTPD_CMDSTEP_CONNABORTED;
ftpd_process_cmd(state);
return err;
}
}
}
}
tcp_recved(tpcb, p->tot_len);
pbuf_free(p);
}
else
{
if (state != NULL)
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is shutdown by the client!\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
ftpd_free_data(state, FTPD_FREEDATA_CLOSE);
// 通知命令處理函數, 數據連接已被客戶端關閉
state->cmdstep |= FTPD_CMDSTEP_CONNSHUTDOWN;
ftpd_process_cmd(state);
}
else
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is closed by the client!\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
}
return ERR_OK;
}
static err_t ftpd_data_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
err_t err = ERR_OK;
struct ftpd_state *state = arg;
if (state != NULL)
{
if (strcasecmp(state->cmd, "LIST") == 0)
err = ftpd_data_sent_list(arg, tpcb, len);
else if (strcasecmp(state->cmd, "RETR") == 0)
err = ftpd_data_sent_retr(arg, tpcb, len);
}
return err;
}
/* 發送文件列表 */
static err_t ftpd_data_sent_list(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
char buffer[MAX_PATH + 100];
err_t err;
int bufsize, loop, slen;
struct ftpd_state *state = arg;
struct tm tm;
FRESULT fr;
if (state->finfo == NULL)
{
loop = 2;
state->finfo = mem_malloc(sizeof(FILINFO));
if (state->finfo == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc() failed!\n", __FUNCTION__));
goto err;
}
}
else
loop = 1;
while (loop)
{
if (loop == 2)
{
// 讀取下一個文件的信息
fr = f_readdir(state->dp, state->finfo);
if (fr != FR_OK || state->finfo->fname[0] == '\0')
{
if (fr != FR_OK)
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_readdir() failed! fr=%d\n", __FUNCTION__, fr)); // 讀取文件信息失敗
// 列表發送完畢
ftpd_free_data(state, FTPD_FREEDATA_SHUTDOWN);
state->cmdstep = 2;
ftpd_process_cmd(state);
break;
}
}
if (strcmp(state->finfo->fname, ".") == 0 || strcmp(state->finfo->fname, "..") == 0)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s: jumping over \"%s\"\n", __FUNCTION__, state->finfo->fname));
continue;
}
ftpd_filetime(state->finfo->fdate, state->finfo->ftime, &tm);
slen = strftime(buffer, sizeof(buffer), "%m-%d-%Y %I:%M%p ", &tm);
if (state->finfo->fattrib & AM_DIR)
strcpy(buffer + slen, "<DIR> ");
else
sprintf(buffer + slen, "%14u ", state->finfo->fsize);
slen += 15;
slen += sprintf(buffer + slen, "%s\r\n", state->finfo->fname);
LWIP_ASSERT("slen < sizeof(buffer)", slen < sizeof(buffer));
bufsize = tcp_sndbuf(tpcb);
if (bufsize >= slen)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s", buffer));
err = tcp_write(tpcb, buffer, slen, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_write() failed! err=%d\n", __FUNCTION__, err));
goto err;
}
loop = 2;
}
else
{
// TCP滑動窗口不夠了, 暫時退出, 等待前面的數據發送完畢
LWIP_DEBUGF(FTPD_DEBUG, ("%s: paused! sndbuf=%d, slen=%d\n", __FUNCTION__, bufsize, slen));
loop = 0;
}
}
return ERR_OK;
err:
err = ftpd_free_data(state, FTPD_FREEDATA_ABORT);
state->cmdstep = FTPD_CMDSTEP_CONNABORTED;
ftpd_process_cmd(state);
return err;
}
/* 發送文件內容 */
static err_t ftpd_data_sent_retr(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
char buffer[30];
err_t err;
struct ftpd_state *state = arg;
unsigned int size;
FRESULT fr;
UINT br;
// 等待上一段數據發送完畢
state->dataout_len -= len;
if (state->dataout_len != 0)
return ERR_OK;
// 釋放上一段數據佔用的內存
if (state->dataout != NULL)
{
mem_free(state->dataout);
state->dataout = NULL;
}
size = tcp_sndbuf(tpcb);
LWIP_ASSERT("sndbuf != 0", size != 0);
state->dataout = mem_malloc(size);
if (state->dataout == NULL)
{
// 內存分配失敗時改用buffer緩衝區
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc() failed!\n", __FUNCTION__));
if (size > sizeof(buffer))
size = sizeof(buffer);
}
if (state->dataout != NULL)
fr = f_read(state->fp, state->dataout, size, &br);
else
fr = f_read(state->fp, buffer, size, &br);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: failed to read file! fr=%d\n", __FUNCTION__, fr));
goto err;
}
if (br < size)
size = br;
if (size > 0)
{
state->dataout_len = size;
if (state->dataout != NULL)
err = tcp_write(tpcb, state->dataout, size, 0);
else
err = tcp_write(tpcb, buffer, size, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_write() failed! err=%d\n", __FUNCTION__, err));
goto err;
}
}
if (f_eof(state->fp))
{
// 文件發送完畢
ftpd_free_data(state, FTPD_FREEDATA_SHUTDOWN);
state->cmdstep = 2;
ftpd_process_cmd(state);
}
return ERR_OK;
err:
err = ftpd_free_data(state, FTPD_FREEDATA_ABORT);
state->cmdstep = FTPD_CMDSTEP_CONNABORTED;
ftpd_process_cmd(state);
return err;
}
/* 控制連接出錯 */
static void ftpd_err(void *arg, err_t err)
{
struct ftpd_state *state = arg;
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("FTPD error! err=%d\n", err));
if (state != NULL)
{
state->ctrlconn = NULL;
ftpd_free(state);
}
}
/* 判斷指定文件是否存在 */
// 如果是判斷文件夾是否存在, 則字符串末尾不能有斜槓
int ftpd_file_exists(const char *path)
{
FRESULT fr;
if (strcmp(path + 1, ":") == 0 || strcmp(path + 1, ":/") == 0)
return 1;
fr = f_stat(path, NULL);
return fr == FR_OK;
}
/* 將文件時間轉換爲C標準格式 */
// 在STM32中, time_t是32位的無符號整數 (unsigned int)
// 因爲沒有符號位, 所以time_t支持超過2038年的年份, 可以放心使用
time_t ftpd_filetime(WORD fdate, WORD ftime, struct tm *ptm)
{
memset(ptm, 0, sizeof(struct tm));
ptm->tm_year = ((fdate >> 9) & 0x7f) + 80;
ptm->tm_mon = ((fdate >> 5) & 0x0f) - 1;
ptm->tm_mday = fdate & 0x1f;
ptm->tm_hour = (ftime >> 11) & 0x1f;
ptm->tm_min = (ftime >> 5) & 0x3f;
ptm->tm_sec = (ftime & 0x1f) << 1;
// 如果ptm中的日期有誤, 則這個函數能自動修正
// 否則如果輸出了錯誤的日期, 那麼Windows的文件管理器會錯誤地顯示快捷方式圖標
return mktime(ptm);
}
/* 關閉FTP控制連接和數據連接, 釋放state結構體以及裏面的成員佔用的內存 */
static void ftpd_free(struct ftpd_state *state)
{
if (state == NULL)
return;
ftpd_free_data(state, FTPD_FREEDATA_ABORT); // 如果數據連接尚未關閉, 則強行中止
if (state->ctrlconn != NULL)
{
if (state->flags & FTPD_FLAG_CLOSE)
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD connection [%s]:%d is closed by the server!\n", ipaddr_ntoa(&state->ctrlconn->remote_ip), state->ctrlconn->remote_port));
else
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD connection [%s]:%d is shutdown by the server!\n", ipaddr_ntoa(&state->ctrlconn->remote_ip), state->ctrlconn->remote_port));
tcp_arg(state->ctrlconn, NULL);
tcp_close(state->ctrlconn);
state->ctrlconn = NULL;
}
ftpd_change_user(state, NULL);
mem_free(state);
}
/* 關閉FTP數據連接並釋放相關內存 */
// 關閉連接時通常將option設爲FTPD_FREEDATA_SHUTDOWN
// 只有在ftpd_data_recv(p=NULL)中才使用FTPD_FREEDATA_CLOSE
static err_t ftpd_free_data(struct ftpd_state *state, int option)
{
err_t err = ERR_OK;
if (state == NULL)
return err;
state->dataport = -1;
if (state->dataconn != NULL)
{
tcp_arg(state->dataconn, NULL); // 連接關閉後, 回調函數仍有可能觸發, 所以必須和state徹底脫離關係
#if FTPD_PASV
if ((state->flags & FTPD_FLAG_PASSIVE) && (state->flags & FTPD_FLAG_NEWDATACONN))
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data pcb is removed!\n"));
tcp_close(state->dataconn);
}
else
{
#endif
if (option == FTPD_FREEDATA_ABORT)
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is aborted!\n", ipaddr_ntoa(&state->dataconn->remote_ip), state->dataconn->remote_port));
tcp_abort(state->dataconn);
err = ERR_ABRT;
}
else
{
if (option == FTPD_FREEDATA_CLOSE)
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is closed by the server!\n", ipaddr_ntoa(&state->dataconn->remote_ip), state->dataconn->remote_port));
else
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD data connection [%s]:%d is shutdown by the server!\n", ipaddr_ntoa(&state->dataconn->remote_ip), state->dataconn->remote_port));
tcp_close(state->dataconn);
}
#if FTPD_PASV
}
#endif
state->dataconn = NULL;
}
if (state->dataout != NULL)
{
mem_free(state->dataout);
state->dataout = NULL;
state->dataout_len = 0;
}
// 只有文件夾成功打開了之後, 纔可以將指針賦給state->dp
if (state->dp != NULL)
{
f_closedir(state->dp);
mem_free(state->dp);
state->dp = NULL;
}
// 只有文件成功打開了之後, 纔可以將指針賦給state->fp
if (state->fp != NULL)
{
f_close(state->fp);
mem_free(state->fp);
state->fp = NULL;
}
if (state->finfo != NULL)
{
mem_free(state->finfo);
state->finfo = NULL;
}
return err;
}
/* 將用戶根文件夾路徑(rootpath)、當前文件夾路徑(state->path)和文件名(filename)連接起來, 放入buffer緩衝區中 */
// buffer的原有內容會被忽略並清空, bufsize爲緩衝區的大小
// puserpath爲輸出參數, 其內容是以用戶文件夾爲根目錄的文件路徑
int ftpd_fullpath(const struct ftpd_state *state, char *buffer, int bufsize, const char *filename, char **puserpath)
{
int basepos, ret;
// 在緩衝區中準備好用戶文件夾的路徑
if (state->userid == -1)
return -1; // 未登錄, 連接失敗
basepos = strlen(ftpd_users[state->userid].rootpath);
if (basepos + 1 > bufsize)
return -1; // 緩衝區不夠
strcpy(buffer, ftpd_users[state->userid].rootpath);
// 獲取相對於用戶文件夾的文件路徑
if (buffer[basepos - 1] == '/')
basepos--; // 使userpath的第一個字符爲斜槓
// 如果沒有斜槓, 則userpath指向\0, 下面連接路徑後可能會變成斜槓
if (puserpath != NULL)
*puserpath = buffer + basepos;
// 連接state->path字符串
if (filename == NULL || filename[0] != '/')
{
// filename不是以用戶文件夾爲根目錄的絕對路徑, 而是相對於state->path的相對路徑
// 需要將rootpath, state->path和filename這三個字符串連在一起
// filename == NULL的情況可視爲空字符串, 是相對路徑
LWIP_ASSERT("state->path[0] == '/'", state->path[0] == '/'); // state->path的首字符始終爲斜槓
ret = ftpd_concat_path(buffer, bufsize, state->path + 1);
if (ret == -1)
return -1;
}
else
{
// filename是以用戶文件夾爲根目錄的絕對路徑
// 跳過斜槓字符, 只將rootpath和不帶首斜槓的filename連起來
filename++;
}
// 連接filename字符串
ret = ftpd_concat_path(buffer, bufsize, filename);
if (ret == -1)
return -1;
ret = ftpd_simplify_path(buffer, basepos);
if (puserpath != NULL && **puserpath == '\0')
*puserpath = "/"; // 如果最終結果就是用戶根目錄, 那麼應該用正斜槓表示, 而不是空字符串
return ret;
}
/* 判斷輸入的用戶名和密碼是否正確 */
static int ftpd_is_valid_user(struct ftpd_user *user, int *pid)
{
int i;
int n = LWIP_ARRAYSIZE(ftpd_users);
if (user->name == NULL)
return 0; // 未輸入用戶名
for (i = 0; i < n; i++)
{
if (strcasecmp(user->name, ftpd_users[i].user.name) == 0)
{
if (pid != NULL)
*pid = i;
if (ftpd_users[i].user.password == NULL)
return 1; // 任何密碼都可以
else if (user->password != NULL && strcmp(user->password, ftpd_users[i].user.password) == 0)
return 1; // 密碼正確
else
return 0; // 密碼錯誤
}
}
return 0; // 用戶名不存在
}
/* 啓動ftpd服務器 */
int ftpd_init(void)
{
err_t err;
struct tcp_pcb *temp;
if (ftpd_tpcb != NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("%s: FTPD server is already started!\n", __FUNCTION__));
return -1;
}
temp = tcp_new();
if (temp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_new() failed!\n", __FUNCTION__));
return -1;
}
err = tcp_bind(temp, IP_ANY_TYPE, FTPD_PORT);
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_bind() failed! err=%d\n", __FUNCTION__, err));
tcp_close(temp);
return -1;
}
ftpd_tpcb = tcp_listen(temp);
if (ftpd_tpcb == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_listen() failed!\n", __FUNCTION__));
tcp_close(temp);
return -1;
}
temp = NULL;
tcp_accept(ftpd_tpcb, ftpd_accept);
return 0;
}
/* memchr的反向版本 */
void *ftpd_memrchr(const void *s, int c, size_t n)
{
const char *p = s;
int i;
for (i = n - 1; i >= 0; i--)
{
if (p[i] == c)
return (void *)&p[i];
}
return NULL;
}
/* 準備好數據連接 */
// 若函數返回-1, 則表示連接建立失敗, 此時已發送了425消息, 不用再發送其他錯誤消息
static int ftpd_prepare_data(struct ftpd_state *state)
{
err_t err;
int ret = -1;
if (state->dataport == -1)
{
ftpd_send_msg(state, "425 Use PORT or PASV first.\r\n");
return -1;
}
#if FTPD_PASV
if (state->flags & FTPD_FLAG_PASSIVE)
{
LWIP_ASSERT("state->dataconn != NULL", state->dataconn != NULL);
ret = 0;
}
else
{
#endif
LWIP_ASSERT("state->dataconn == NULL", state->dataconn == NULL);
state->dataconn = tcp_new();
if (state->dataconn == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_new() failed!\n", __FUNCTION__));
goto end;
}
tcp_arg(state->dataconn, state);
err = tcp_connect(state->dataconn, &state->ctrlconn->remote_ip, state->dataport, ftpd_data_connected);
if (err == ERR_OK)
{
// 使用PORT模式時, 最好將電腦的防火牆關閉, 以免板子連不上電腦而出錯
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD is connecting to [%s]:%d...\n", ipaddr_ntoa(&state->ctrlconn->remote_ip), state->dataport));
tcp_err(state->dataconn, ftpd_data_err);
state->flags |= FTPD_FLAG_NEWDATACONN;
ret = 0;
}
else
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_connect() failed! err=%d\n", __FUNCTION__, err));
#if FTPD_PASV
}
#endif
end:
if (ret == -1)
{
ftpd_send_msg(state, "425 Failed to establish connection.\r\n");
if (state->dataconn != NULL)
{
tcp_arg(state->dataconn, NULL);
tcp_close(state->dataconn);
state->dataconn = NULL;
state->dataport = -1;
}
}
return ret;
}
/* 處理命令 */
static void ftpd_process_cmd(struct ftpd_state *state)
{
int ret;
// 只有當上一個命令的所有迴應都發送完畢時, 纔開始處理下一條命令
if (state->sent != 0)
return;
else if (state->flags & FTPD_FLAG_TCPERROR)
goto end;
if ((state->flags & FTPD_FLAG_AGAIN) == 0)
{
// 上一條命令已執行完畢
if (state->flags & (FTPD_FLAG_CLOSE | FTPD_FLAG_SHUTDOWN))
{
ftpd_free(state);
return;
}
// 從接收隊列中取出一條新命令
ret = ftpd_copy_cmd(state);
if (ret == 0)
return; // 命令不完整
LWIP_DEBUGF(FTPD_DEBUG, ("%s\n", state->cmd));
state->cmdstep = 0; // 當前是命令的第一步操作
if (ret == 2)
{
ftpd_send_msg(state, "500 Syntax error, command unrecognized.\r\n");
goto end;
}
// 提取出命令參數
state->cmdarg = strchr(state->cmd, ' ');
if (state->cmdarg != NULL)
*state->cmdarg++ = '\0';
else
state->cmdarg = "";
}
else
{
// 上一條命令未執行完畢, 雖然state->sent==0, 但還需要繼續發送更多數據
// cmd和cmdarg不變, 命令可根據cmdstep的值決定當前是第幾步操作
state->flags &= ~FTPD_FLAG_AGAIN;
}
// 處理各種命令
if (ftpd_process_user_cmd(state)) // 這個必須第一個處理
;
else if (ftpd_process_data_cmd(state))
;
else if (ftpd_process_directory_cmd(state))
;
else if (ftpd_process_file_cmd(state))
;
else if (ftpd_process_opt_cmd(state))
;
else
ftpd_send_msg(state, "500 Unknown command.\r\n");
end:
// TCP無法發送數據時, 強制關閉連接
if (state->sent == 0 && state->flags & FTPD_FLAG_TCPERROR)
{
state->flags = (state->flags & ~FTPD_FLAG_AGAIN) | FTPD_FLAG_SHUTDOWN;
ftpd_free(state);
}
}
/* 處理與數據連接有關的命令 */
static int ftpd_process_data_cmd(struct ftpd_state *state)
{
char ip[IPADDR_STRLEN_MAX];
int i, j, ret;
int isport = 0;
ip_addr_t ipaddr;
#if FTPD_PASV
char buffer[100];
err_t err;
int ispasv = 0;
struct tcp_pcb *newpcb;
#endif
#if LWIP_IPV6
if (IP_IS_V4_VAL(state->ctrlconn->remote_ip))
{
#endif
if (strcasecmp(state->cmd, "PORT") == 0)
isport = 4;
#if FTPD_PASV
else if (strcasecmp(state->cmd, "PASV") == 0)
ispasv = 4;
#endif
#if LWIP_IPV6
}
else if (IP_IS_V6_VAL(state->ctrlconn->remote_ip))
{
if (strcasecmp(state->cmd, "EPRT") == 0)
isport = 6;
#if FTPD_PASV
else if (strcasecmp(state->cmd, "EPSV") == 0)
ispasv = 6;
#endif
}
#endif
if (isport)
{
// 如果之前啓動了PASV模式, 則關閉創建的監聽連接
state->dataport = -1;
#if FTPD_PASV
if (state->flags & FTPD_FLAG_PASSIVE)
{
state->flags &= ~FTPD_FLAG_PASSIVE;
if (state->dataconn != NULL)
{
tcp_close(state->dataconn);
state->dataconn = NULL;
}
}
#endif
// 提取出IP地址
#if LWIP_IPV6
if (isport == 4)
{
#endif
for (i = j = 0; i < sizeof(ip) && j < 4; i++)
{
if (isdigit(state->cmdarg[i]))
ip[i] = state->cmdarg[i];
else if (state->cmdarg[i] == ',')
{
ip[i] = '.';
j++;
}
else
break;
}
if (j != 4)
goto porterr;
ip[i - 1] = '\0';
#if LWIP_IPV6
}
else
{
if (memcmp(state->cmdarg, "|2|", 3) != 0)
goto porterr;
for (i = 0; i < sizeof(ip); i++)
{
if (state->cmdarg[3 + i] == '|')
break;
ip[i] = state->cmdarg[3 + i];
}
if (i == sizeof(ip))
goto porterr;
ip[i] = '\0';
}
#endif
ret = ipaddr_aton(ip, &ipaddr);
if (ret == 0 || !ip_addr_cmp(&ipaddr, &state->ctrlconn->remote_ip))
goto porterr;
// 提取出端口號
#if LWIP_IPV6
if (isport == 4)
{
#endif
ret = sscanf(state->cmdarg + i, "%d,%d", &i, &j);
if (ret != 2)
goto porterr;
ret = i * 256 + j;
#if LWIP_IPV6
}
else
{
i = sscanf(state->cmdarg + 4 + i, "%d", &ret);
if (i != 1)
goto porterr;
}
#endif
if (ret != 0 && ret < 65536)
{
state->dataport = ret;
#if LWIP_IPV6
if (isport == 4)
{
#endif
#if FTPD_PASV
ftpd_send_msg(state, "200 PORT command successful. Consider using PASV.\r\n");
#else
ftpd_send_msg(state, "200 PORT command successful.\r\n");
#endif
#if LWIP_IPV6
}
else
ftpd_send_msg(state, "200 EPRT command successful.\r\n");
#endif
return 1;
}
porterr:
#if LWIP_IPV6
if (isport == 4)
#endif
ftpd_send_msg(state, "500 Illegal PORT command.\r\n");
#if LWIP_IPV6
else
ftpd_send_msg(state, "500 Illegal EPRT command.\r\n");
#endif
}
#if FTPD_PASV
else if (ispasv)
{
if (state->dataconn == NULL)
{
state->dataconn = tcp_new();
if (state->dataconn == NULL)
goto pasverr;
#if LWIP_IPV6
if (ispasv == 4)
#endif
err = tcp_bind(state->dataconn, IP_ADDR_ANY, 0);
#if LWIP_IPV6
else
err = tcp_bind(state->dataconn, IP6_ADDR_ANY, 0);
#endif
if (err != ERR_OK)
goto pasverr;
newpcb = tcp_listen(state->dataconn);
if (newpcb == NULL)
goto pasverr;
state->dataconn = newpcb;
tcp_arg(state->dataconn, state);
tcp_accept(state->dataconn, ftpd_data_accept);
state->dataport = state->dataconn->local_port;
state->flags |= FTPD_FLAG_NEWDATACONN | FTPD_FLAG_PASSIVE;
}
#if LWIP_IPV6
if (ispasv == 4)
{
#endif
ipaddr_ntoa_r(&state->ctrlconn->local_ip, ip, sizeof(ip));
for (i = 0; ip[i] != '\0'; i++)
{
if (ip[i] == '.')
ip[i] = ',';
}
sprintf(buffer, "227 Entering Passive Mode (%s,%d,%d).\r\n", ip, (state->dataport >> 8) & 0xff, state->dataport & 0xff);
#if LWIP_IPV6
}
else
sprintf(buffer, "229 Entering Extended Passive Mode (|||%d|).\r\n", state->dataport);
#endif
ftpd_send_msg(state, buffer);
return 1;
pasverr:
#if LWIP_IPV6
if (ispasv == 4)
#endif
ftpd_send_msg(state, "500 PASV command failed.\r\n");
#if LWIP_IPV6
else
ftpd_send_msg(state, "500 EPSV command failed.\r\n");
#endif
if (state->dataconn != NULL)
{
tcp_close(state->dataconn);
state->dataconn = NULL;
}
}
#endif
else
return 0;
return 1;
}
static int ftpd_process_directory_cmd(struct ftpd_state *state)
{
char buffer[MAX_PATH];
char *path;
int ret;
DIR *dp = NULL;
FRESULT fr;
if (strcasecmp(state->cmd, "PWD") == 0)
{
ftpd_send_msg(state, "257 \"");
ftpd_send_msg(state, state->path);
ftpd_send_msg(state, "\" is the current directory.\r\n");
}
else if (strcasecmp(state->cmd, "CWD") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, &path);
if (ret == -1)
goto cwderr;
else if (!ftpd_file_exists(buffer))
goto cwderr;
strcpy(state->path, path);
ftpd_send_msg(state, "250 Directory successfully changed.\r\n");
return 1;
cwderr:
ftpd_send_msg(state, "550 Failed to change directory.\r\n");
}
else if (strcasecmp(state->cmd, "LIST") == 0)
{
if (state->cmdstep == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, NULL, NULL);
if (ret == -1)
goto listerr;
LWIP_ASSERT("state->dp == NULL", state->dp == NULL);
dp = mem_malloc(sizeof(DIR));
if (dp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(DIR)) failed!\n", __FUNCTION__));
goto listerr;
}
fr = f_opendir(dp, buffer);
if (fr != FR_OK)
goto listerr;
state->dp = dp; // 文件夾打開了之後才能賦給state->dp
ret = ftpd_prepare_data(state);
if (ret == -1)
goto listerr2;
state->cmdstep = 1;
state->flags |= FTPD_FLAG_AGAIN;
ftpd_send_msg(state, "150 Here comes the directory listing.\r\n");
return 1;
listerr:
state->cmdstep = FTPD_CMDSTEP_CONNABORTED;
listerr2:
// 這裏涉及到兩個不同的操作
// 一個是關閉文件夾, 另一個是釋放存儲文件夾信息的內存
if (state->dp != NULL)
{
f_closedir(state->dp);
state->dp = NULL;
}
if (dp != NULL)
{
mem_free(dp);
dp = NULL;
}
}
else if (state->cmdstep == 1)
{
state->flags |= FTPD_FLAG_AGAIN;
ftpd_data_check(state);
}
else if (state->cmdstep == 2)
ftpd_send_msg(state, "226 Directory send OK.\r\n");
if (state->cmdstep & (FTPD_CMDSTEP_CONNABORTED | FTPD_CMDSTEP_CONNSHUTDOWN))
ftpd_send_msg(state, "450 Failed to list the folder.\r\n");
}
else if (strcasecmp(state->cmd, "MKD") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, &path);
if (ret != -1)
{
fr = f_mkdir(buffer);
if (fr == FR_OK)
{
ftpd_send_msg(state, "257 \"");
ftpd_send_msg(state, path);
ftpd_send_msg(state, "\" created.\r\n");
return 1;
}
}
ftpd_send_msg(state, "550 Create directory operation failed.\r\n");
}
else if (strcasecmp(state->cmd, "RMD") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret != -1)
{
fr = f_unlink(buffer);
if (fr == FR_OK)
{
ftpd_send_msg(state, "250 Remove directory operation successful.\r\n");
return 1;
}
}
ftpd_send_msg(state, "550 Remove directory operation failed.\r\n");
}
else
return 0;
return 1;
}
/* 處理與文件有關的命令 */
static int ftpd_process_file_cmd(struct ftpd_state *state)
{
char buffer[MAX_PATH];
int ret;
long size;
FIL *fp = NULL;
FRESULT fr;
if (strcasecmp(state->cmd, "SIZE") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret == -1)
goto sizeerr;
fp = mem_malloc(sizeof(FIL));
if (fp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(FIL)) failed!\n", __FUNCTION__));
goto sizeerr;
}
fr = f_open(fp, buffer, FA_READ);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_open() failed! fr=%d\n", __FUNCTION__, fr));
goto sizeerr;
}
size = f_size(fp);
f_close(fp);
mem_free(fp);
sprintf(buffer, "213 %ld\r\n", size);
ftpd_send_msg(state, buffer);
return 1;
sizeerr:
ftpd_send_msg(state, "550 Could not get file size.\r\n");
if (fp != NULL)
mem_free(fp);
}
else if (strcasecmp(state->cmd, "RETR") == 0)
{
if (state->cmdstep == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret == -1)
goto retrerr;
LWIP_ASSERT("state->fp == NULL", state->fp == NULL);
fp = mem_malloc(sizeof(FIL));
if (fp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(FIL)) failed!\n", __FUNCTION__));
goto retrerr;
}
fr = f_open(fp, buffer, FA_READ);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_open() failed! fr=%d\n", __FUNCTION__, fr));
goto retrerr;
}
state->fp = fp; // 文件打開了之後才能賦給state->fp
ret = ftpd_prepare_data(state);
if (ret == -1)
goto retrerr2;
state->cmdstep = 1;
state->flags |= FTPD_FLAG_AGAIN;
sprintf(buffer, "150 Opening %s mode data connection for ", (state->type == 'I') ? "BINARY" : "ASCII");
ftpd_send_msg(state, buffer);
ftpd_send_msg(state, state->cmdarg);
size = f_size(state->fp);
sprintf(buffer, " (%ld bytes).\r\n", size);
ftpd_send_msg(state, buffer);
return 1;
retrerr:
ftpd_send_msg(state, "550 Failed to open file.\r\n");
retrerr2:
if (state->fp != NULL)
{
f_close(state->fp);
state->fp = NULL;
}
if (fp != NULL)
{
mem_free(fp);
fp = NULL;
}
}
else if (state->cmdstep == 1)
{
state->flags |= FTPD_FLAG_AGAIN;
ftpd_data_check(state);
}
else if (state->cmdstep == 2)
ftpd_send_msg(state, "226 Transfer complete.\r\n");
else if (state->cmdstep & (FTPD_CMDSTEP_CONNSHUTDOWN | FTPD_CMDSTEP_CONNABORTED))
ftpd_send_msg(state, "451 Requested action aborted: local error in processing.\r\n");
}
else if (strcasecmp(state->cmd, "STOR") == 0)
{
if (state->cmdstep == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret == -1)
goto storerr;
LWIP_ASSERT("state->fp == NULL", state->fp == NULL);
fp = mem_malloc(sizeof(FIL));
if (fp == NULL)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: mem_malloc(sizeof(FIL)) failed!\n", __FUNCTION__));
goto storerr;
}
fr = f_open(fp, buffer, FA_CREATE_ALWAYS | FA_WRITE);
if (fr != FR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: f_open() failed! fr=%d, path=\"%s\"\n", __FUNCTION__, fr, buffer));
goto storerr;
}
state->fp = fp; // 文件打開了之後才能賦給state->fp
ret = ftpd_prepare_data(state);
if (ret == -1)
goto storerr2;
state->cmdstep = 1;
state->flags |= FTPD_FLAG_AGAIN;
ftpd_send_msg(state, "150 Ok to send data.\r\n");
return 1;
storerr:
ftpd_send_msg(state, "550 Failed to open file.\r\n");
storerr2:
if (state->fp != NULL)
{
f_close(state->fp);
state->fp = NULL;
}
if (fp != NULL)
{
mem_free(fp);
fp = NULL;
}
}
else if (state->cmdstep == 1)
{
state->flags |= FTPD_FLAG_AGAIN;
ftpd_data_check(state);
}
else if (state->cmdstep & FTPD_CMDSTEP_CONNSHUTDOWN)
ftpd_send_msg(state, "226 Transfer complete.\r\n");
else if (state->cmdstep & FTPD_CMDSTEP_CONNABORTED)
ftpd_send_msg(state, "451 Requested action aborted: local error in processing.\r\n");
}
else if (strcasecmp(state->cmd, "DELE") == 0)
{
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret != -1)
{
ret = f_unlink(buffer);
if (ret == 0)
{
ftpd_send_msg(state, "250 Delete operation successful.\r\n");
return 1;
}
}
ftpd_send_msg(state, "550 Delete operation failed.\r\n");
}
else if (strcasecmp(state->cmd, "RNFR") == 0)
{
ret = ftpd_fullpath(state, state->rename, sizeof(state->rename), state->cmdarg, NULL);
if (ret != -1)
{
state->flags |= FTPD_FLAG_RENAME;
ftpd_send_msg(state, "350 Ready for RNTO.\r\n");
}
else
{
state->flags &= ~FTPD_FLAG_RENAME;
ftpd_send_msg(state, "550 RNFR command failed.\r\n");
}
}
else if (strcasecmp(state->cmd, "RNTO") == 0)
{
if ((state->flags & FTPD_FLAG_RENAME) == 0)
{
ftpd_send_msg(state, "503 RNFR required first.\r\n");
return 1;
}
state->flags &= ~FTPD_FLAG_RENAME;
ret = ftpd_fullpath(state, buffer, MAX_PATH, state->cmdarg, NULL);
if (ret != -1)
{
ret = f_rename(state->rename, buffer);
if (ret == 0)
{
ftpd_send_msg(state, "250 Rename successful.\r\n");
return 1;
}
}
ftpd_send_msg(state, "550 Rename failed.\r\n");
}
else
return 0;
return 1;
}
/* 處理與服務器選項有關的命令 */
static int ftpd_process_opt_cmd(struct ftpd_state *state)
{
if (strcasecmp(state->cmd, "opts") == 0)
{
if (strcasecmp(state->cmdarg, "utf8 on") == 0)
ftpd_send_msg(state, "200 Always in UTF8 mode.\r\n");
else
ftpd_send_msg(state, "501 Option not understood.\r\n");
}
else if (strcasecmp(state->cmd, "TYPE") == 0)
{
if (strcasecmp(state->cmdarg, "A") == 0)
ftpd_send_msg(state, "200 Switching to ASCII mode.\r\n");
else if (strcasecmp(state->cmdarg, "I") == 0)
ftpd_send_msg(state, "200 Switching to Binary mode.\r\n");
else
{
ftpd_send_msg(state, "500 Unrecognised TYPE command.\r\n");
return 1;
}
state->type = state->cmdarg[0];
}
else if (strcasecmp(state->cmd, "noop") == 0)
ftpd_send_msg(state, "200 NOOP ok.\r\n");
else
return 0;
return 1;
}
/* 處理與用戶有關的命令 */
static int ftpd_process_user_cmd(struct ftpd_state *state)
{
int userid;
if (strcasecmp(state->cmd, "USER") == 0)
{
if (state->userid != -1)
ftpd_send_msg(state, "530 Can't change to another user.\r\n");
else
{
ftpd_change_user(state, state->cmdarg);
if (strcasecmp(state->cmdarg, "ANONYMOUS") == 0 && ftpd_is_valid_user(&state->user, NULL))
ftpd_send_msg(state, "331 Anonymous access allowed, send identity (e-mail name) as password.\r\n");
else
ftpd_send_msg(state, "331 Please specify the password.\r\n");
}
}
else if (strcasecmp(state->cmd, "PASS") == 0)
{
if (state->userid != -1)
ftpd_send_msg(state, "230 Already logged in.\r\n");
else if (state->user.name == NULL)
ftpd_send_msg(state, "503 Login with USER first.\r\n");
else
{
state->user.password = ftpd_strdup(state->cmdarg);
if (ftpd_is_valid_user(&state->user, &userid))
{
if (ftpd_file_exists(ftpd_users[userid].rootpath))
{
state->userid = userid;
ftpd_send_msg(state, "230 Login successful.\r\n");
}
else
{
ftpd_change_user(state, NULL);
ftpd_send_msg(state, "530 Please create the home directory \"");
ftpd_send_msg(state, ftpd_users[userid].rootpath);
ftpd_send_msg(state, "\" before logging in.\r\n");
}
}
else
{
ftpd_change_user(state, NULL);
ftpd_send_msg(state, "530 Login incorrect.\r\n");
}
}
}
else if (strcasecmp(state->cmd, "QUIT") == 0)
{
ftpd_send_msg(state, "221 Goodbye.\r\n");
state->flags |= FTPD_FLAG_SHUTDOWN;
}
else if (state->userid == -1)
ftpd_send_msg(state, "530 Please login with USER and PASS.\r\n");
else
return 0;
return 1;
}
static err_t ftpd_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
struct ftpd_state *state = arg;
if (p != NULL)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s: received %d bytes\n", __FUNCTION__, p->tot_len));
if (state->queue == NULL)
state->queue = p;
else
pbuf_cat(state->queue, p);
}
else
{
if (state != NULL)
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD connection [%s]:%d is shutdown by the client!\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
state->flags |= FTPD_FLAG_CLOSE;
}
else
{
LWIP_DEBUGF(FTPD_DEBUG, ("FTPD connection [%s]:%d is closed by the client!\n", ipaddr_ntoa(&tpcb->remote_ip), tpcb->remote_port));
return ERR_OK;
}
}
ftpd_process_cmd(state);
return ERR_OK;
}
/* 發送迴應 */
static int ftpd_send_msg(struct ftpd_state *state, const char *s)
{
err_t err;
int len;
if (state->flags & FTPD_FLAG_TCPERROR)
return -1;
len = strlen(s);
LWIP_DEBUGF(FTPD_DEBUG, ("%s", s));
LWIP_ASSERT("sndbuf >= len", tcp_sndbuf(state->ctrlconn) >= len);
err = tcp_write(state->ctrlconn, s, len, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
state->flags |= FTPD_FLAG_TCPERROR;
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("%s: tcp_write() failed! err=%d\n", __FUNCTION__, err));
return -1;
}
state->sent += len;
return len;
}
static err_t ftpd_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
struct ftpd_state *state = arg;
LWIP_DEBUGF(FTPD_DEBUG, ("%s: %d bytes of response sent\n", __FUNCTION__, len));
if (state != NULL)
{
state->sent -= len;
if (state->sent == 0)
{
if (state->cmdlen != 0)
{
LWIP_DEBUGF(FTPD_DEBUG, ("%s: processed %d bytes\n", __FUNCTION__, state->cmdlen));
tcp_recved(state->ctrlconn, state->cmdlen);
state->cmdlen = 0;
}
ftpd_process_cmd(state);
}
}
return ERR_OK;
}
/* 去除路徑中的"./"和"../"以及"//" */
// path必須爲絕對路徑 (可以帶盤符也可以不帶盤符), 不允許爲相對路徑
// basepos是字符串中用戶根目錄末尾的斜槓的位置, 用於保證"../"在後退時不會退到用戶根目錄以外
// 比如"C:/foo/bar", 如果用戶根目錄是C:/foo, 那麼basepos應該爲6
int ftpd_simplify_path(char *path, int basepos)
{
char *base, *p, *pp, *q;
int len;
// 檢查path是否爲絕對路徑
if (*path != '/')
{
p = strchr(path, '/');
if (p == NULL || *(p - 1) != ':')
return -1; // path不允許爲相對路徑
}
// 檢查並修正basepos參數
len = strlen(path);
if (basepos < 0)
basepos = 0;
else if (basepos > len)
basepos = len;
base = path + basepos;
if (*base != '/' && *base != '\0')
{
base = strchr(base, '/');
if (base == NULL)
base = path + len;
basepos = base - path;
}
p = base; // 當前目錄
pp = base; // 父目錄
do
{
q = strchr(p + 1, '/');
if (q != NULL)
{
len = q - p;
if (len == 1 || (len == 2 && memcmp(p, "/.", 2) == 0))
memmove(p, q, strlen(q) + 1);
else if (len == 3 && memcmp(p, "/..", 3) == 0)
{
memmove(pp, q, strlen(q) + 1);
p = pp;
pp = ftpd_memrchr(base, '/', pp - base);
if (pp == NULL)
pp = p;
}
else
{
pp = p;
p = q;
}
}
else
{
len = strlen(p);
if (len == 1 || (len == 2 && memcmp(p, "/.", 2) == 0))
{
if (p == path || *(p - 1) == ':')
p++;
*p = '\0';
}
else if (len == 3 && memcmp(p, "/..", 3) == 0)
{
if (pp == path || *(pp - 1) == ':')
pp++;
*pp = '\0';
}
}
} while (q != NULL);
return 0;
}
/* 開闢一塊內存空間, 用於長期保存局部變量中的字符串, 避免函數退出時局部變量失效 */
char *ftpd_strdup(const char *s)
{
char *p;
int len;
len = strlen(s) + 1;
p = mem_malloc(len);
if (p != NULL)
memcpy(p, s, len);
return p;
}