此程序是旧版本,请下载最新的20230315版使用。
lwipopts.h里面的MEM_SIZE这个选项非常重要!一定要尽可能大(至少10240以上,最好是102400以上),这样lwip才有足够的内存可以分配。因为FatFs里面DIR结构体和FIL结构体都很大,都是从lwip里面mem_malloc出来的,很耗内存。否则FTP的连接会经常因为内存不足而断开!!!!!!!!!!!!!
本程序在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. FileZilla无法自动检测编码,必须改为强制UTF-8,文件名才不会乱码
【STM32F107VC+DP83848+W25Q128+FTP程序】
程序下载地址:百度网盘 请输入提取码(提取码: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程序】
【LwIP 2.0.3版本兼容性】
若要在2.0.3版本的lwip中运行,需要将2.1.2版本的pbuf_free_header和pbuf_remove_header函数复制过来,然后在ftpd.c顶部包含头文件<ctype.h>。
【BUG勘误】
1. Windows文件管理器中往FTP里面复制某些中文文件名的文件会失败。
这是由于Windows本身的BUG导致的,微软一直没有修复该BUG:UTF8 Encoding Bug Report about using ftp with windows explorer
Win7和Win10均有此BUG。往电脑上的FTP服务器上传文件后会出现问号,往本文的ftpd服务器上上传文件会直接提示失败。
FTP程序本身没有问题,fatfs的配置也没有问题。
要想上传中文文件名的文件,最好选择专业的FTP软件,如FileZilla。
2. tcp_accept函数没有对err参数做判断。当内存不足时收到新的FTP连接,整个程序就会卡死。
可通过FileZilla连续上传多个文件来复现此bug。串口输出如下:
ftpd_data_sent_list: paused! sndbuf=24, slen=62
[Send] len=590
[Recv] len=60
ftpd_sent: 39 bytes of response sent
ftpd_sent: processed 6 bytes
ftpd_data_sent_list: paused! sndbuf=24, slen=62
[Recv] len=66
FTPD accepted [233.2.0.8]:755!
220 LwIP FTP Service
Assertion "tcp_write: invalid pcb" failed at line 414 in lwip-2.1.2\core\tcp_out.c
SIGABRT: Abnormal termination
Exited! returncode=1
在lwip中,tcp_accept回调函数的newpcb参数有可能为NULL,此时err参数为ERR_MEM。所以回调函数中必须要先判断err是否为ERR_OK,才能执行后面的操作。
解决方案是在ftpd_accept和ftpd_data_accept函数的最开头,添加如下判断:
if (err != ERR_OK)
return err;
修改后的文件的下载链接:百度网盘 请输入提取码(提取码:p1du)
【nettime.c移植到其他系列STM32】
以STM32H743ZI为例。
首先,只有STM32F1才需要把日期保存到backup domain中,因此删掉rtc_savedate(),还要把common.c里面跟hrtc.DateToUpdate有关的代码都删掉。
另外,设置日期的时候,还需要填写date.WeekDay(星期几)这个成员。当前日期是星期几不需要自己写代码计算,C库函数localtime_r已经帮我们计算好了,直接用就行了。ptm->tm_wday=0是星期天,=1~6是星期一到星期六。
RTC_TimeTypeDef新增了很多成员。设置时间前,RTC_TimeTypeDef结构体要用={0}语句清零。
int nettime_set(const struct tm *ptm, int check_before_update)
{
......
RTC_TimeTypeDef time = {0}, curr_time, empty_time = {0};
......
if (!check_before_update || diff)
{
HAL_RTC_SetTime(&hrtc, &empty_time, RTC_FORMAT_BIN); // 先清空时间
if (ptm->tm_wday == 0)
date.WeekDay = RTC_WEEKDAY_SUNDAY;
else
date.WeekDay = ptm->tm_wday;
HAL_RTC_SetDate(&hrtc, &date, RTC_FORMAT_BIN); // 设置日期
HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN); // 设置时间
//rtc_savedate();
//sys_restart_timeouts();
printf("RTC is updated from ");
printf("20%02u-%u-%u %02u:%02u:%02u [%d] to ", curr_date.Year, curr_date.Month, curr_date.Date, curr_time.Hours, curr_time.Minutes, curr_time.Seconds, curr_date.WeekDay);
printf("20%02u-%u-%u %02u:%02u:%02u [%d] (UTC+8)!\n", date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds, date.WeekDay);
return 1;
}
else
{
printf("RTC is not updated!\n");
return 0;
}
}
【程序运行截图】
1. 使用Windows文件管理器通过计算机名访问FTP服务器(已登录admin用户,密码为123456)
2. FTP登录界面
3. FileZilla访问FTP服务器(必须要开启强制UTF-8编码才能防止中文文件名乱码)
4. FileZilla上传文件
6. 通过板子的IPv6地址访问FTP服务器
【建议】
建议使用时将lwipopts.h里面的如下选项调大:
#define MEMP_NUM_TCP_PCB 50 // TCP最大连接数
#define MEMP_NUM_TCP_PCB_LISTEN 50 // TCP最大监听数
#define MEMP_NUM_PBUF 50 // struct pbuf结构体最大数量
建议配置下面的选项:
#define TCP_MSS 1500 // TCP报文携带的最大数据量
#define LWIP_TCP_SACK_OUT 1 // 允许TCP选择性确认
【程序运行结果】
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:(2021年7月24日版本)
/*************************** 基于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;
if (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("FTPD failed to accept a client! err=%d\n", err));
return err;
}
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 (err != ERR_OK)
{
LWIP_DEBUGF(FTPD_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("FTPD data failed to accept a client! err=%d\n", err));
return err;
}
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;
}