Linux网络编程综合运用之MiniFtp实现(八)
上节中实现了"USER"和"PASS"命令,如下:
事实上FTP是有很多命令组成的,如果就采用上面的这种方法来实现的话,就会有很多if...else if语句,代码显得很臃肿,所以有必要想办法来避免这种写法,所以一个新的方式既将诞生------命令映射,实际上在之前读取配置文件变量时就已经接触到了,下面则看具体的做法:
下面这个表是FTP命令的声明:
函数 |
说明 |
static void do_user(session_t *sess); |
|
static void do_pass(session_t *sess); |
|
static void do_cwd(session_t *sess); |
|
static void do_cdup(session_t *sess); |
|
static void do_quit(session_t *sess); |
|
static void do_port(session_t *sess); |
|
static void do_pasv(session_t *sess); |
|
static void do_type(session_t *sess); |
|
static void do_stru(session_t *sess); |
|
static void do_mode(session_t *sess); |
|
static void do_retr(session_t *sess); |
|
static void do_stor(session_t *sess); |
|
static void do_appe(session_t *sess); |
|
static void do_list(session_t *sess); |
|
static void do_nlst(session_t *sess); |
|
static void do_rest(session_t *sess); |
|
static void do_abor(session_t *sess); |
|
static void do_pwd(session_t *sess); |
|
static void do_mkd(session_t *sess); |
|
static void do_rmd(session_t *sess); |
|
static void do_dele(session_t *sess); |
|
static void do_rnfr(session_t *sess); |
|
static void do_rnto(session_t *sess); |
|
static void do_site(session_t *sess); |
|
static void do_syst(session_t *sess); |
|
static void do_feat(session_t *sess); |
|
static void do_size(session_t *sess); |
|
static void do_stat(session_t *sess); |
|
static void do_noop(session_t *sess); |
|
static void do_help(session_t *sess); |
|
所以在程序中先声明:
记得当时实现配置模块时,一个配置项与配置项变量相对应,而这边则应该是一个命令字符串与一个命令处理函数相对应,因而也能定义一个结构体来配置这种对应关系,如下:
FTP命令与命令处理函数对应表 |
typedef struct ftpcmd { const char *cmd;//命令字符串 void (*cmd_handler)(session_t *sess);//函数指针 } ftpcmd_t;
static ftpcmd_t ctrl_cmds[] = { /* 访问控制命令 */ {"USER", do_user },//如果是USER命令,则执行do_user方法 {"PASS", do_pass }, {"CWD", do_cwd }, {"XCWD", do_cwd }, {"CDUP", do_cdup }, {"XCUP", do_cdup }, {"QUIT", do_quit }, {"ACCT", NULL }, {"SMNT", NULL }, {"REIN", NULL },//这种命令是没有执行函数的 /* 传输参数命令 */ {"PORT", do_port }, {"PASV", do_pasv }, {"TYPE", do_type }, {"STRU", do_stru }, {"MODE", do_mode },
/* 服务命令 */ {"RETR", do_retr }, {"STOR", do_stor }, {"APPE", do_appe }, {"LIST", do_list }, {"NLST", do_nlst }, {"REST", do_rest }, {"ABOR", do_abor }, {"\377\364\377\362ABOR", do_abor}, {"PWD", do_pwd }, {"XPWD", do_pwd }, {"MKD", do_mkd }, {"XMKD", do_mkd }, {"RMD", do_rmd }, {"XRMD", do_rmd }, {"DELE", do_dele }, {"RNFR", do_rnfr }, {"RNTO", do_rnto }, {"SITE", do_site }, {"SYST", do_syst }, {"FEAT", do_feat }, {"SIZE", do_size }, {"STAT", do_stat }, {"NOOP", do_noop }, {"HELP", do_help }, {"STOU", NULL }, {"ALLO", NULL } }; |
另外发现,之前配置模块的最后是NULL结尾的,而这里并没有,所以这次是用另外一种遍历方法来判断是否遍历完,下面先将上面的映射关系代码拷至代码中:
ftpproto.c:
#include "ftpproto.h" #include "sysutil.h" #include "str.h" #include "ftpcodes.h" static void ftp_reply(session_t *sess, int status, const char *text); static void do_user(session_t *sess); static void do_pass(session_t *sess); static void do_cwd(session_t *sess); static void do_cdup(session_t *sess); static void do_quit(session_t *sess); static void do_port(session_t *sess); static void do_pasv(session_t *sess); static void do_type(session_t *sess); static void do_stru(session_t *sess); static void do_mode(session_t *sess); static void do_retr(session_t *sess); static void do_stor(session_t *sess); static void do_appe(session_t *sess); static void do_list(session_t *sess); static void do_nlst(session_t *sess); static void do_rest(session_t *sess); static void do_abor(session_t *sess); static void do_pwd(session_t *sess); static void do_mkd(session_t *sess); static void do_rmd(session_t *sess); static void do_dele(session_t *sess); static void do_rnfr(session_t *sess); static void do_rnto(session_t *sess); static void do_site(session_t *sess); static void do_syst(session_t *sess); static void do_feat(session_t *sess); static void do_size(session_t *sess); static void do_stat(session_t *sess); static void do_noop(session_t *sess); static void do_help(session_t *sess); typedef struct ftpcmd { const char *cmd; void (*cmd_handler)(session_t *sess); } ftpcmd_t; static ftpcmd_t ctrl_cmds[] = { /* 访问控制命令 */ {"USER", do_user }, {"PASS", do_pass }, {"CWD", do_cwd }, {"XCWD", do_cwd }, {"CDUP", do_cdup }, {"XCUP", do_cdup }, {"QUIT", do_quit }, {"ACCT", NULL }, {"SMNT", NULL }, {"REIN", NULL }, /* 传输参数命令 */ {"PORT", do_port }, {"PASV", do_pasv }, {"TYPE", do_type }, {"STRU", do_stru }, {"MODE", do_mode }, /* 服务命令 */ {"RETR", do_retr }, {"STOR", do_stor }, {"APPE", do_appe }, {"LIST", do_list }, {"NLST", do_nlst }, {"REST", do_rest }, {"ABOR", do_abor }, {"\377\364\377\362ABOR", do_abor}, {"PWD", do_pwd }, {"XPWD", do_pwd }, {"MKD", do_mkd }, {"XMKD", do_mkd }, {"RMD", do_rmd }, {"XRMD", do_rmd }, {"DELE", do_dele }, {"RNFR", do_rnfr }, {"RNTO", do_rnto }, {"SITE", do_site }, {"SYST", do_syst }, {"FEAT", do_feat }, {"SIZE", do_size }, {"STAT", do_stat }, {"NOOP", do_noop }, {"HELP", do_help }, {"STOU", NULL }, {"ALLO", NULL } }; void handle_child(session_t *sess) { writen(sess->ctrl_fd, "220 (miniftpd 0.1)\r\n", strlen("220 (miniftpd 0.1)\r\n")); int ret; while (1) { memset(sess->cmdline, 0, sizeof(sess->cmdline)); memset(sess->cmd, 0, sizeof(sess->cmd)); memset(sess->arg, 0, sizeof(sess->arg)); ret = readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE); if (ret == -1) ERR_EXIT("readline"); else if (ret == 0) exit(EXIT_SUCCESS); printf("cmdline=[%s]\n", sess->cmdline); // 去除\r\n str_trim_crlf(sess->cmdline); printf("cmdline=[%s]\n", sess->cmdline); // 解析FTP命令与参数 str_split(sess->cmdline, sess->cmd, sess->arg, ' '); printf("cmd=[%s] arg=[%s]\n", sess->cmd, sess->arg); // 将命令转换为大写 str_upper(sess->cmd); // 处理FTP命令 if (strcmp("USER", sess->cmd) == 0) { do_user(sess); } else if (strcmp("PASS", sess->cmd) == 0) { do_pass(sess); } } } static void do_user(session_t *sess) { //USER jjl struct passwd *pw = getpwnam(sess->arg); if (pw == NULL) { // 用户不存在 ftp_reply(sess, FTP_LOGINERR, "Login incorrect."); return; } sess->uid = pw->pw_uid; ftp_reply(sess, FTP_GIVEPWORD, "Please specify the password."); } static void do_pass(session_t *sess) { // PASS 123456 struct passwd *pw = getpwuid(sess->uid); if (pw == NULL) { // 用户不存在 ftp_reply(sess, FTP_LOGINERR, "Login incorrect."); return; } printf("name=[%s]\n", pw->pw_name); struct spwd *sp = getspnam(pw->pw_name); if (sp == NULL) { // 没有找到影子文件,则代表登录也是失败的 ftp_reply(sess, FTP_LOGINERR, "Login incorrect."); return; } // 将明文进行加密 char *encrypted_pass = crypt(sess->arg, sp->sp_pwdp); // 验证密码 if (strcmp(encrypted_pass, sp->sp_pwdp) != 0) { ftp_reply(sess, FTP_LOGINERR, "Login incorrect."); return; } setegid(pw->pw_gid); seteuid(pw->pw_uid); chdir(pw->pw_dir); ftp_reply(sess, FTP_LOGINOK, "Login successful."); } static void ftp_reply(session_t *sess, int status, const char *text) { char buf[1024] = {0}; sprintf(buf, "%d %s\r\n", status, text); writen(sess->ctrl_fd, buf, strlen(buf)); }
而像之前的配置映射的方式代码写法如下:
但是由于这次并没有以NULL结尾,所以说得换一种新的方式,如下:
编译一下:
所以需要实现这些函数:
#include "ftpproto.h" #include "sysutil.h" #include "str.h" #include "ftpcodes.h" static void ftp_reply(session_t *sess, int status, const char *text); static void do_user(session_t *sess); static void do_pass(session_t *sess); static void do_cwd(session_t *sess); static void do_cdup(session_t *sess); static void do_quit(session_t *sess); static void do_port(session_t *sess); static void do_pasv(session_t *sess); static void do_type(session_t *sess); static void do_stru(session_t *sess); static void do_mode(session_t *sess); static void do_retr(session_t *sess); static void do_stor(session_t *sess); static void do_appe(session_t *sess); static void do_list(session_t *sess); static void do_nlst(session_t *sess); static void do_rest(session_t *sess); static void do_abor(session_t *sess); static void do_pwd(session_t *sess); static void do_mkd(session_t *sess); static void do_rmd(session_t *sess); static void do_dele(session_t *sess); static void do_rnfr(session_t *sess); static void do_rnto(session_t *sess); static void do_site(session_t *sess); static void do_syst(session_t *sess); static void do_feat(session_t *sess); static void do_size(session_t *sess); static void do_stat(session_t *sess); static void do_noop(session_t *sess); static void do_help(session_t *sess); typedef struct ftpcmd { const char *cmd; void (*cmd_handler)(session_t *sess); } ftpcmd_t; static ftpcmd_t ctrl_cmds[] = { /* 访问控制命令 */ {"USER", do_user }, {"PASS", do_pass }, {"CWD", do_cwd }, {"XCWD", do_cwd }, {"CDUP", do_cdup }, {"XCUP", do_cdup }, {"QUIT", do_quit }, {"ACCT", NULL }, {"SMNT", NULL }, {"REIN", NULL }, /* 传输参数命令 */ {"PORT", do_port }, {"PASV", do_pasv }, {"TYPE", do_type }, {"STRU", do_stru }, {"MODE", do_mode }, /* 服务命令 */ {"RETR", do_retr }, {"STOR", do_stor }, {"APPE", do_appe }, {"LIST", do_list }, {"NLST", do_nlst }, {"REST", do_rest }, {"ABOR", do_abor }, {"\377\364\377\362ABOR", do_abor}, {"PWD", do_pwd }, {"XPWD", do_pwd }, {"MKD", do_mkd }, {"XMKD", do_mkd }, {"RMD", do_rmd }, {"XRMD", do_rmd }, {"DELE", do_dele }, {"RNFR", do_rnfr }, {"RNTO", do_rnto }, {"SITE", do_site }, {"SYST", do_syst }, {"FEAT", do_feat }, {"SIZE", do_size }, {"STAT", do_stat }, {"NOOP", do_noop }, {"HELP", do_help }, {"STOU", NULL }, {"ALLO", NULL } }; void handle_child(session_t *sess) { writen(sess->ctrl_fd, "220 (miniftpd 0.1)\r\n", strlen("220 (miniftpd 0.1)\r\n")); int ret; while (1) { memset(sess->cmdline, 0, sizeof(sess->cmdline)); memset(sess->cmd, 0, sizeof(sess->cmd)); memset(sess->arg, 0, sizeof(sess->arg)); ret = readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE); if (ret == -1) ERR_EXIT("readline"); else if (ret == 0) exit(EXIT_SUCCESS); printf("cmdline=[%s]\n", sess->cmdline); // 去除\r\n str_trim_crlf(sess->cmdline); printf("cmdline=[%s]\n", sess->cmdline); // 解析FTP命令与参数 str_split(sess->cmdline, sess->cmd, sess->arg, ' '); printf("cmd=[%s] arg=[%s]\n", sess->cmd, sess->arg); // 将命令转换为大写 str_upper(sess->cmd); // 处理FTP命令 /* if (strcmp("USER", sess->cmd) == 0) { do_user(sess); } else if (strcmp("PASS", sess->cmd) == 0) { do_pass(sess); }*/ int i; int size = sizeof(ctrl_cmds) / sizeof(ctrl_cmds[0]); for (i=0; i<size; i++) { if (strcmp(ctrl_cmds[i].cmd, sess->cmd) == 0) { if (ctrl_cmds[i].cmd_handler != NULL) { ctrl_cmds[i].cmd_handler(sess); } else { ftp_reply(sess, FTP_COMMANDNOTIMPL, "Unimplement command."); } break; } } if (i == size) { ftp_reply(sess, FTP_BADCMD, "Unknown command."); } } } static void ftp_reply(session_t *sess, int status, const char *text) { char buf[1024] = {0}; sprintf(buf, "%d %s\r\n", status, text); writen(sess->ctrl_fd, buf, strlen(buf)); } static void do_user(session_t *sess) { //USER jjl struct passwd *pw = getpwnam(sess->arg); if (pw == NULL) { // 用户不存在 ftp_reply(sess, FTP_LOGINERR, "Login incorrect."); return; } sess->uid = pw->pw_uid; ftp_reply(sess, FTP_GIVEPWORD, "Please specify the password."); } static void do_pass(session_t *sess) { // PASS 123456 struct passwd *pw = getpwuid(sess->uid); if (pw == NULL) { // 用户不存在 ftp_reply(sess, FTP_LOGINERR, "Login incorrect."); return; } printf("name=[%s]\n", pw->pw_name); struct spwd *sp = getspnam(pw->pw_name); if (sp == NULL) { // 没有找到影子文件,则代表登录也是失败的 ftp_reply(sess, FTP_LOGINERR, "Login incorrect."); return; } // 将明文进行加密 char *encrypted_pass = crypt(sess->arg, sp->sp_pwdp); // 验证密码 if (strcmp(encrypted_pass, sp->sp_pwdp) != 0) { ftp_reply(sess, FTP_LOGINERR, "Login incorrect."); return; } setegid(pw->pw_gid); seteuid(pw->pw_uid); chdir(pw->pw_dir); ftp_reply(sess, FTP_LOGINOK, "Login successful."); } static void do_cwd(session_t *sess) { } static void do_cdup(session_t *sess) { } static void do_quit(session_t *sess) { } static void do_port(session_t *sess) { } static void do_pasv(session_t *sess) { } static void do_type(session_t *sess) { } static void do_stru(session_t *sess) { } static void do_mode(session_t *sess) { } static void do_retr(session_t *sess) { } static void do_stor(session_t *sess) { } static void do_appe(session_t *sess) { } static void do_list(session_t *sess) { } static void do_nlst(session_t *sess) { } static void do_rest(session_t *sess) { } static void do_abor(session_t *sess) { } static void do_pwd(session_t *sess) { } static void do_mkd(session_t *sess) { } static void do_rmd(session_t *sess) { } static void do_dele(session_t *sess) { } static void do_rnfr(session_t *sess) { } static void do_rnto(session_t *sess) { } static void do_site(session_t *sess) { } static void do_syst(session_t *sess) { } static void do_feat(session_t *sess) { } static void do_size(session_t *sess) { } static void do_stat(session_t *sess) { } static void do_noop(session_t *sess) { } static void do_help(session_t *sess) { }
再次编译运行:
可见,命令解析一切OK,接下来就一个个命令来实现,首先是"SYST"命令,对照着vsftpd来做:
所以对应的函数中给出如下输出:
编译运行:
其中"FEAT"表示Feature,表示服务端的特性,实际上这条命令不实现也没有关系,如果暂且不想实现它,则可以给它对应的处理函数配置成NULL,如下:
编译运行:
紧着着发送了CLNT命令,这个命令没有,所以就提示无效的命令:
【注意】:当无效的命令时,我们也必须给它响应,否则客户端就会阻塞。
当响应了CLNT命令之后,最后发送了REST 100命令,这表示断点续传,对比着vsftpd输出结果来看:
所以,我们也来先实现FEAT命令,实现完之后,看是否最后还会发送RESET 100命令,将FEAT的命令注释还原:
然后处理它对应的函数:
然后接vsftpd的响应格式来输出:
再次编译运行:
接着来实现PWD命令,先来看下vsftpd这个命令是如何响应的:
下面来实现PWD命令:
【说明】:
编译运行:
先看一下vsftpd中的"TYPE A"的响应:
如果是输入其它的TYPE命令呢?可以按如下步骤输出:
而如果输入"TYPE I"呢?
所以则照着这几种情况实现TYPE命令:
另外这个状态需要记录在session当中,之后会用到,所以需要增加一个变量至session结构体中:
修改它的初始化:
在do_type函数中来进行记录:
【说明】:Ascii和二进制协议进行传输的区别就在于是否要处理"\r\n",这个在之前FTP协议时有说明过。
编译运行:
接下来就要进行列表的传输了,在传输时需要创建数据连接通道,在创建通道之前是需要协商使用PORT模式还是PASV模式,所以这里先发送PASV模式出来了,这个下次来实现,先学到这~