ISC tftp server 修改支持多目录

在linux开发发现平时用tftp传输文件比较频繁,正好赶上需要对tftp 服务端进行修改支持多目录操作,所以就下载了ISC tftp源码(tftp-hpa-5.2)最新的版本了,linux系统下可以通过wget ftp://ftp.kernel.org/pub/software/network/tftp/tftp- hpa/tftp-hpa-5.2.tar.gz 来获取源码。关于tftp的协议原理方面我就不废话了,网上有很多解释,都比较详细生动,不知道的可 以先去了解下,不然看源码就很吃力!废话少说。把源码解压,里面有tftp client 和 tftp server 目录,编译后得到可执行文件 tftp 和 tftpd ,放到搭建的虚拟机里面运行

root@ubuntu:~/tftpserver# ls
test  test1  tftp  tftpd
root@ubuntu:~/tftpserver# ps -ef|grep tftpd
root      2281     1  0 17:18 ?        00:00:00 ./tftpd -l -c -s /root/tftpserver/test/
root      2400  2176  0 17:36 pts/1    00:00:00 grep --color=auto tftpd
root@ubuntu:~/tftpserver# 

root@ubuntu:~/tftpserver# cat test/mytest 

this is a simple test!
root@ubuntu:~/tftpserver#

dgq@t468 tftpclient $ tftp 192.168.0.67
tftp> get mytest
tftp> quit
dgq@t468 tftpclient $ cat mytest 

this is a simple test!
dgq@t468 tftpclient $ 

可以看到一个目录是没有问题的,接下来再看看多目录

root@ubuntu:~/tftpserver# ./tftpd -l -c /root/tftpserver/test /root/tftpserver/test1
root@ubuntu:~/tftpserver# ps -ef|grep tftpd
root      2418     1  0 17:41 ?        00:00:00 ./tftpd -l -c /root/tftpserver/test /root/tftpserver/test1
root      2420  2176  0 17:41 pts/1    00:00:00 grep --color=auto tftpd


dgq@t468 tftpclient $ tftp 192.168.0.67
tftp> get mytest
tftp: error received from server <Only absolute filenames allowed>
tftp: aborting
tftp> get /root/tftpserver/test/mytest
tftp> quit
root@t468 tftpclient # ls
mytest
root@t468 tftpclient # cat mytest 

this is a simple test!

这个时候就不能通过文件名就可以获取了,必须得加上要文件的绝对路径,但是这样是不可行的,因为客户是不知道你的服务端的信息的(除非是你自己创建了server),所以我们需要修改代码

修改之前有个很重要的结构体必须得知道,如下

 struct tftphdr {
USHORT tu_opcode; // 操作码
union
{
USHORT tu_block; // 块号
USHORT tu_code; // 错误码
char tu_stuff[1]; // 请求包填充物
}th_u;
char th_data[1]; // 数据或错误字符串  //mode (binary和ASCII和text)and data 
};

这个就是根据协议定义的tftp结构体了,ok进入tftpd 这个目录下,vim tftpd.c打开代码 直接定位到main函数,首先有个while循环来解析输入的选项信息,用了getopt_long()这个函数来解析命令行选项参数。就不用自己写东东来出来argv了,首先看看-s这个选项

    case 's':
            secure = 1;
            break;

如果有-s 选项,则secure赋值为1.有什么作用呢,看下面就知道了

 dirs = xmalloc((argc - optind + 1) * sizeof(char *));
    for (ndirs = 0; optind != argc; optind++)
        dirs[ndirs++] = argv[optind];

    dirs[ndirs] = NULL;

    if (secure) {
        if (ndirs == 0) {
            syslog(LOG_ERR, "no -s directory");
            exit(EX_USAGE);
        }
        if (ndirs > 1) {
            syslog(LOG_ERR, "too many -s directories");
            exit(EX_USAGE);
        }
        if (chdir(dirs[0])) {
            syslog(LOG_ERR, "%s: %m", dirs[0]);
            exit(EX_NOINPUT);
        }
    }

secure=1 则会执行if判断语句,代码一看就知道如果是多目录就不能加-s这个选项,如果不加,那么如何来修改才能让客户端跟之前的一样只输入文件就可以得到文件呢,关键点就是下面的函数里面

static int validate_access(char *filename, int mode,
                           const struct formats *pf, const char **errmsg)
{
    struct stat stbuf;
    int i, len;
    int fd, wmode, rmode;
    char *cp;
    const char **dirp;
    char stdio_mode[3];

    tsize_ok = 0;
    *errmsg = NULL;

    if (!secure) {
        if (*filename != '/') {
            *errmsg = "Only absolute filenames allowed";
            return (EACCESS);
        }

        /*
         * prevent tricksters from getting around the directory
         * restrictions
         */
        len = strlen(filename);
        for (i = 1; i < len - 3; i++) {
            cp = filename + i;
            if (*cp == '.' && memcmp(cp - 1, "/../", 4) == 0) {
                *errmsg = "Reverse path not allowed";
                return (EACCESS);
            }
        }

        for (dirp = dirs; *dirp; dirp++)
            if (strncmp(filename, *dirp, strlen(*dirp)) == 0)
                break;
        if (*dirp == 0 && dirp != dirs) {
            *errmsg = "Forbidden directory";
            return (EACCESS);
        }
    }

所以不加-s结果就是要是文件的绝对路径,要不然等下利用open这个文件的时候就默认是当前目录下文件,如果这个文件不在当前目录下,则会出错了,跟我们之前的验证一样,所以我的修改如下

 * Note also, full path name must be
 * given as we have no login directory.
 */

static int quit_func_flag;
static int search_file(const char *path, const char *filename)
{
        char *tmp_filename;
        DIR *dir;
        struct dirent *tmp_file;
        struct stat tmp_file_stat;
        char fullpath[256] = {0};

        char tmp_fullpath[256] = {0};
        if ((dir = opendir(path)) == NULL) {
                syslog(LOG_ERR, "Cannot open coresppending directory: %s: %m", path);
                return;
        }
        sprintf(fullpath, "%s/", path);
        strcpy(tmp_fullpath, fullpath);

        while ( (tmp_file = readdir(dir)) != NULL) {
                strcat(fullpath, tmp_file->d_name);
                if (strncmp(tmp_file->d_name, ".", 1) == 0) {
                        strcpy(fullpath, tmp_fullpath);
                        continue;
                }
                if (strcmp(tmp_file->d_name, filename) == 0) {
                        chdir(path);
                        quit_func_flag = 1;
                        break;
                }
                if (stat(fullpath, &tmp_file_stat) == 0 && S_ISDIR(tmp_file_stat.st_mode)) {
                        search_file(fullpath, filename);

                }
                if (quit_func_flag) {
                        break;
                }
                strcpy(fullpath, tmp_fullpath);
        }

        closedir(dir);
         return ;

}



static int validate_access(char *filename, int mode,
                           const struct formats *pf, const char **errmsg)
{
    struct stat stbuf;
    int i, len;
    int fd, wmode, rmode;
    char *cp;
    const char **dirp;
    char stdio_mode[3];

    tsize_ok = 0;
    *errmsg = NULL;

    if (!secure) {
    #if 0
        if (*filename != '/') {
            *errmsg = "Only absolute filenames allowed";
            return (EACCESS);
        }
    #endif
        /*
         * prevent tricksters from getting around the directory
         * restrictions
         */
        len = strlen(filename);
        for (i = 1; i < len - 3; i++) {
            cp = filename + i;
            if (*cp == '.' && memcmp(cp - 1, "/../", 4) == 0) {
                *errmsg = "Reverse path not allowed";
                return (EACCESS);
            }
        }

        for (dirp = dirs; *dirp; dirp++) {
                search_file(*dirp, filename);
                if (quit_func_flag) {
                        break;
                }
        }
        // if (strncmp(filename, *dirp, strlen(*dirp)) == 0)
             //   break;
        if (*dirp == 0 && dirp != dirs) {
            *errmsg = "Forbidden directory";
            return (EACCESS);
        }
    }

增加了一个search_file函数来查找指定的目录下有没有对应的文件存在,找到就退出,采用递归所以支持多级目录查找,把相关的头文件加进来,编译测试结果如下

root@ubuntu:~/tftpserver# cat test/mytest 

this is a simple test!
root@ubuntu:~/tftpserver# cat test1/yourtest 

his is your sipmle test!
root@ubuntu:~/tftpserver# ps -ef|grep tftpd
root      2457     1  0 19:10 ?        00:00:00 ./tftpd -l -c /root/tftpserver/test /root/tftpserver/test1
root      2493  2176  0 19:40 pts/1    00:00:00 grep --color=auto tftpd
root@ubuntu:~/tftpserver# 



root@t468 tftpd # tftp 192.168.0.67
tftp> get mytest
tftp> get yourtest
tftp> quit
root@t468 tftpd # cat mytest 

this is a simple test!
root@t468 tftpd # cat yourtest 

his is your sipmle test!
root@t468 tftpd # 

运行正常了,貌似没什么问题,但是是临时改的,也没什么测试,暂时想到如下问题需要注意下

1)里面用到的strcpy ,sprintf, strcat,等函数都没有检查溢出状况的,为了安全,最好都改了strncpy, strncat, snprintf等等

2)在多个目录下面如果有相同的文件会出现什么情况呢,结果就是最先找到后的就直接退出了,所以应该再修改下,把所有的目录搜一遍,定义一个指针数组,找到一个就malloc来存储完整的路径,最后如果有两个或者两个以上相同就打印log,并且异常退出之前,先free

3)有个全局的标示符quit_func_flag。它的目的就是找到对应的文件就设置为1,后面会根据它的值做对应的处理,其实这样定义是很麻烦的,因为当第一次客户端来请求文件时,如果有,那么quit_func_flag就赋值为1,当后面的客户端再来请求的时候,不管文件存在不存在,它的值都是为1了。好在tftp server 是这样的,先在while循环里面利用select来监听请求,如果有,则会fork一个子进程来处理,那么父进程的quit_func_flag的值永远都是0,所以这里不会出现什么问题。不过为了养成好习惯,还是利用参数传递好点

4)get 操作没有问题,假如put 操作呢?这里就不知道往哪里放了,其实也是可以进行处理的,看以后有时间再说了。

 

 

 

 

 

 

 

 

 

 

posted on 2013-02-05 17:34  流川仙道  阅读(753)  评论(0编辑  收藏  举报

导航