上節中實現了"USER"和"PASS"命令,以下:session
事實上FTP是有不少命令組成的,若是就採用上面的這種方法來實現的話,就會有不少if...else if語句,代碼顯得很臃腫,因此有必要想辦法來避免這種寫法,因此一個新的方式既將誕生------命令映射,實際上在以前讀取配置文件變量時就已經接觸到了,下面則看具體的作法:app
下面這個表是FTP命令的聲明:函數
函數oop |
說明ui |
static void do_user(session_t *sess);加密 |
|
static void do_pass(session_t *sess);spa |
|
static void do_cwd(session_t *sess);3d |
|
static void do_cdup(session_t *sess);指針 |
|
static void do_quit(session_t *sess);rest |
|
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模式出來了,這個下次來實現,先學到這~