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   },

       {"377364377362ABOR", 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    },
    {"377364377362ABOR", 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)
", strlen("220 (miniftpd 0.1)
"));
    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]
", sess->cmdline);
        // 去除

        str_trim_crlf(sess->cmdline);
        printf("cmdline=[%s]
", sess->cmdline);
        // 解析FTP命令与参数
        str_split(sess->cmdline, sess->cmd, sess->arg, ' ');
        printf("cmd=[%s] arg=[%s]
", 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]
", 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
", 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    },
    {"377364377362ABOR", 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)
", strlen("220 (miniftpd 0.1)
"));
    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]
", sess->cmdline);
        // 去除

        str_trim_crlf(sess->cmdline);
        printf("cmdline=[%s]
", sess->cmdline);
        // 解析FTP命令与参数
        str_split(sess->cmdline, sess->cmd, sess->arg, ' ');
        printf("cmd=[%s] arg=[%s]
", 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
", 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]
", 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和二进制协议进行传输的区别就在于是否要处理" ",这个在之前FTP协议时有说明过。

编译运行:

接下来就要进行列表的传输了,在传输时需要创建数据连接通道,在创建通道之前是需要协商使用PORT模式还是PASV模式,所以这里先发送PASV模式出来了,这个下次来实现,先学到这~

原文地址:https://www.cnblogs.com/webor2006/p/4605127.html