FTP上传下载的断点续传实现

 

    FTP上传下载的断点续传实现
                                                             
一:开发背景 
    由于需要对多个服务器发布大的数据包,所以自己在LINUX用C语言,开发了一个传送器工具。因为上传时需要支持断点续传,所以自己参考ftp-rfc959和一些文章开发了这个支持断点续传的上传工具。整个工具分两部分开发的:一是支持断点续传的Ftp工具,包含下载和上传功能。二是同时对多个服务器发布不同数据包的传送器工具。本文只是对断点续传的商船和下载作探讨。
 
二:实现
     其实Ftp上传下载的实现很简单,首先Ftp服务器端要支持文件的定位,
然后就是通过建立的Socket用Ftp服务器命令和服务器交互.
    有些也可以通过Socket做自己的客户端和服务器端,但是有些服务器是自己能管理控制的,而且原来自己也写过一些这样C/S模式的工具,简单消息的传送接收还不错。
但用来传输文件效率很低,比文件传送协议FTP(File Transfer Protocol)差的太多。  利用Ftp的服务端可以省去服务器端的开发,而且可以向任何开放Ftp服务的服务器上传送文件,可以不考虑对方安装的
是什么的操作系统。
 
三:FTP
 文件传送协议 FTP 只提供文件传送的一些基本的服务,它使用 TCP 可靠的运输服务。FTP 的主要功能是减少或消除在不同操作系统下处理文件的不兼容性。
FTP 使用客户服务器方式。
一个 FTP 服务器进程可同时为多个客户进程提供服务。FTP的服务器进程由两大部分组成:一个主进程,负责接受新的请求;另外有若干个从属进程,负责处理单个请求。
A、通常的方式:
控制连接在整个会话期间一直保持打开,FTP 客户所发出的传送请求通过控制连接发送给控制进程,但控制连接并不用来传送文件,实际用于传输文件的是“数据连接”。
控制进程在接收到 FTP 客户发送来的文件传输请求后就创建一个“数据传送进程”和一个“数据连接”,并将数据连接连接到“数据传送进程”,数据传送进程实际完成文件的传送,在传送完毕后关闭“数据传送连接”并结束运行
当客户进程向服务器进程发出建立连接请求时,要寻找连接服务器进程的熟知端口(21),同时还要告诉服务器进程自己的另一个端口号码,用于建立数据传送连接。接着,服务器进程用自己传送数据的熟知端口(20)与客户进程所提供的端口号码建立数据传送连接。

B、 被动模式:
FTP客户端发出的连接请求,一般通过服务器的21号端口建立控制连接,专门用来传输一些字符串命令和响应信息。控制命令通道一定是由客户端向服务器的连接(默认的端口是21,也可以指定端口,这要看服务器的设置)。
PASV:通过控制通道通过发送PASV 服务器命令到 FTP服务器。请求建立被动模式数据连接通道。 (客户端的命令 passive)
服务器返回连接的信息(227 Entering Passive Mode (70,0,10,62,120,18) )地址和端口。端口=最后第二位乘256再加上最后一位(120*256+18)。(注意端口设为0的情况) 如:   *f_port = atoi(port_1) * 256 + atoi(port_2);
服务器端和客户端身份转换,原客户端在本地建立监听,监听来自原服务器远端的连接请求建立数据连接通道。
四、 实现方法:
  A、下载:
1、向服务器发送“REST + 本地文件长度”,告诉服务器,客户端要断点下载了。这时服务器还不知道客户端要下载的文件名;
2、向服务器发送"RETR + 文件名",通知服务器要下载的文件名,这时服务器开始定位文件指针读文件并发送数据。
3、客户端定位本地文件指针偏移到文件末尾;
4、两端的准备工作都做完了以后,客户端创建socket,以被动或非被动方式建立数据链接,循环调用recv接收文件数据并追加到本地文件末尾;

B、上传:
1、获取服务器上和本地要上传文件的同名文件大小;
2、向服务器发送“APPE + 文件名”,通知服务器,从数据通道发送给你的数据要附加到这个文件末尾。
3、定位本地文件指针,文件指针偏移到指定位置,这个位置与FTP服务器上文件大小相同的位置。
4、从文件指针处读数据并发送。

C、Ftp服务器命令
我们平时使用的命令,大多是客户端的。服务器端的命令可以参考下面:
命令   描述          
ABOR 中断数据连接程序      ACCT <account> 系统特权帐号
ALLO <bytes>  为服务器上的文件存储器分配字节   APPE <filename> 添加文件到服务器同名文件
CDUP <dir path> 改变服务器上的父目录   CWD <dir path> 改变服务器上的工作目录
DELE <filename> 删除服务器上的指定文件   HELP <command> 返回指定命令信息 LIST <name> 如果是文件名列出文件信息,如果是目录则列出文件列表  MODE <mode> 传输模式(S=流模式,B=块模式,C=压缩模式)
MKD <directory> 在服务器上建立指定目录   NLST <directory> 列出指定目录内容
NOOP 无动作,除了来自服务器上的承认   PASS <password> 系统登录密码
PASV 请求服务器等待数据连接    PORT <address> IP 地址和两字节的端口 ID PWD 显示当前工作目录     QUIT 从 FTP 服务器上退出登录
REIN 重新初始化登录状态连接    REST <offset> 由特定偏移量重启文件传递
RETR <filename> 从服务器上找回(复制)文件  RMD <directory> 在服务器上删除指定目录
RNFR <old path> 对旧路径重命名    RNTO <new path> 对新路径重命名
SITE <params> 由服务器提供的站点特殊参数  SMNT <pathname> 挂载指定文件结构
STAT <directory> 在当前程序或目录上返回信息  STOR <filename> 储存(复制)文件到服务器上
STOU <filename> 储存文件到服务器名称上   STRU <type> 数据结构(F=文件,R=记录,P=页面)
SYST 返回服务器使用的操作系统    TYPE <data type> 数据类型(A=ASCII,E=EBCDIC,I=binary)
USER <username>> 系统登录的用户名
D、服务器返回的部分数字代码含义
      125 Data connection already open; Transfer starting.
      226 Transfer complete.
      227 Entering Passive Mode (127,0,0,1,4,18).
      230 User xxxxx logged in.
      331 Password required for xxxxx.
      425 Can’t open data connection.
      226 Closing data connection.
      200 return a state of TYPE or MODE commond
      220 connection state
五、关于Socket
     关于Socket编程有很多参考资料,这里不作详细说明
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口
Socket 是一个基本的通信机制Socket把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议.
Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
常用的Socket类型有两种:
A、流式Socket(SOCK_STREAM):流式是一种面向连接的Socket,针对于面向连接的TCP服务应用。
B、数据报式Socket(SOCK_DGRAM):数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
 Socket的程序是一种C/S结构,分客户端和服务器端。
A、客户端
– 初始化Socket
– 连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了
– 客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端
– 客户端读取数据,最后关闭连接,一次交互结束。
B、服务器端
- 初始化Socket
- 端口绑定(bind)
- 监听(listen)端口
- 调用accept阻塞,等待客户端连接
在这里我们使用的就是面向连接的流式Socket,只编写客户端的程序。


六、代码:
  下面仅提供一个编译过并在使用中的函数简单的说明其实现原理,
需要有一点C语言和Linux/UNIX的socket编程基础即可理解。
 只是建立连接和传输部分的一个函数。当文件传出结束或断开时,
可以调用检查函数看是否成功,字节是否正常等,如果不正常结束,
可以再次循环调用它并定为服务器文件的字节,继续传输。
其他的控制和功能都在其他函数中实现。为移植方便使用标准C语法符合C89标准。
code:
 /*****************************************************************
 * FileName:     uftt_ver5.c                                      *
 * Company:      algorithmics china lib Co.,Ltd.                  *
 * Author:       G.L.Zhang --- zglcl008                           *
 * Time:         [2006-12-20]                                     *
 * Description:   uninterrupted file transfers tools              *
 *****************************************************************/

int  f_file_trans(const char *ft_addr, int ft_port, const char *ft_usr,const char *ft_pwd, const char *ft_opt, const char *ft_src, char *ft_obj, int ft_flg)
{
    int cmd_sock = -1;
    int dat_sock = -1;
    int stream_sock= -1;
    int dat_port = 0;
    char dat_buffer[1024*5];
    struct sockaddr_in f_server;
    struct sockaddr_in f_datasvr;
    unsigned char *pasv_ip = NULL;   
    unsigned char *pasv_port = NULL;   
    unsigned int len_addr = 0;
    long rc_size = 0;
    /* long svr_file_size = 0; */
    long file_size = 0;
    int rc = 0;
    if (ft_addr == NULL || ft_usr == NULL || ft_pwd == NULL ||
        ft_opt == NULL || ft_src == NULL || ft_obj == NULL ||
        ft_port == 0 || ft_flg < 0) {
        err_quit("--- file transfers parameter error");
    }
    /* get ftp commomd socket */
    if ((cmd_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("cmmond socket");
        err_quit("--- command sock error");
    }
  
    bzero(&f_server, sizeof(f_server));
    f_server.sin_family = AF_INET;
    f_server.sin_port = htons(ft_port);
    f_server.sin_addr.s_addr  = inet_addr(ft_addr);
 
    if (connect(cmd_sock, (struct sockaddr *)&f_server, sizeof(f_server)) < 0) {
        perror("connect");
        err_quit("--- command sock error");
    }
    rc = uftt_cmd(dat_buffer, cmd_sock, NULL);
    if (rc == 220) 
        printf("-1- %d OK\n", rc);
    else
        printf("-1- %d ERR\n", rc);
 
    rc  = uftt_cmd(dat_buffer, cmd_sock,"USER %s",ft_usr);
    if (rc == 331)
        printf("-2- %d OK\n", rc);
    else
        printf("-3- %d ERR\n", rc);
    rc  = uftt_cmd(dat_buffer, cmd_sock,"PASS %s",ft_pwd);
    if (rc == 230)
        printf("-3- %d OK\n", rc);
    else
        printf("-3- %d ERR\n", rc);
    rc = uftt_cmd(dat_buffer, cmd_sock, "TYPE I");
    if (rc == 200)
        printf("-41- %d OK\n", rc);
    else
        printf("-41- %d ERR\n", rc);
    rc = uftt_cmd(dat_buffer, cmd_sock, "MODE S");
    if (rc == 200)
        printf("-42- %d OK\n", rc);
    else
        printf("-42- %d ERR\n", rc);
   
    memset(dat_buffer, '\0', sizeof(dat_buffer));
    rc = uftt_cmd(dat_buffer, cmd_sock, "PASV");
    if (rc == 227)
        printf("-5- %d OK\n", rc);
    else
        printf("-5- %d ERR\n", rc);
#ifdef ZGL_DEBUG
printf("-5- %d %s\n", rc, dat_buffer);
#endif
    /* get passive port */
    dat_port = 0;
    if ((rc = get_svr_port(dat_buffer, (int *)&dat_port)) < 0)
         err_quit("data stream port error");
#ifdef ZGL_DEBUG
printf("--- pasive port=[%d]\n", dat_port);
#endif
    if ((dat_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("data socket");
        err_quit("--- data sock error");
    }
    len_addr = sizeof(f_datasvr);
    bzero(&f_datasvr, sizeof(f_datasvr));
    rc = getsockname(cmd_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
    f_datasvr.sin_port = htons(dat_port);
    /* 0=all port or appoint port TEST OK ***
    f_datasvr.sin_port = htons(dat_port);
    f_datasvr.sin_port = 0;
    */
    if (bind(dat_sock,(struct sockaddr *)&f_datasvr,len_addr) == -1)
        err_sys("--- data sock bind error");
    if (listen(dat_sock,1) == -1)
        err_sys("--- data sock bind error");
    rc = getsockname(dat_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
    pasv_ip = (unsigned char *)&f_datasvr.sin_addr;
    pasv_port = (unsigned char *)&f_datasvr.sin_port;
    rc = uftt_cmd(dat_buffer, cmd_sock,"PORT %d,%d,%d,%d,%d,%d",pasv_ip[0],
             pasv_ip[1], pasv_ip[2], pasv_ip[3], pasv_port[0], pasv_port[1]);
    if (rc == 200)
        printf("-6- %d OK\n", rc);
    else
        printf("-6- %d ERR\n", rc);

    /* get RETR put STOR append APPE */
    if (ft_flg == 0) {
        rc = uftt_cmd(dat_buffer, cmd_sock, "STOR %s", ft_obj);
    }
    else if (ft_flg > 0){
        rc = uftt_cmd(dat_buffer, cmd_sock, "APPE %s", ft_obj);
    }
    if (rc == 150)
        printf("-7- %d OK\n", rc);
    else
        printf("-7- %d ERR\n", rc);
    stream_sock = accept(dat_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
    if (stream_sock < 0)
        err_sys("--- stream socket error");
    rc_size = f_put_file(stream_sock, ft_src, ft_obj, ft_flg, file_size);
    /* do without ***
    rc = uftt_cmd(dat_buffer, dat_sock,"QUIT");
    printf("-8- %d \n", rc);
    */
    close(stream_sock);
    close(dat_sock);
    rc = uftt_cmd(dat_buffer, cmd_sock,"QUIT");
    if (rc == 226)
        printf("-9- %d OK\n", rc);
    else
        printf("-9- %d ERR\n", rc);
    close(cmd_sock);
    return rc_size ;
}
 
七、参考资料: FTP-RFC959

 

 

 

代码:

#ifndef _CUFTPD_H
#define _CUFTPD_H

#define CUFTPD_DEBUG(fmt, ...)        cuftpd_debug(__FILE__, __LINE__, fmt, __VA_ARGS__)
#define CUFTPD_ARR_LEN(arr)                (sizeof(arr)/sizeof(arr[0]))

#define CUFTPD_VER                                "1.0"
#define CUFTPD_DEF_SRV_PORT                21
#define CUFTPD_LISTEN_QU_LEN        8
#define CUFTPD_LINE_END                        "\r\n"

#define CUFTPD_OK        0
#define CUFTPD_ERR        (-1)

#define CUFTPD_CHECK_LOGIN()  \
        do { \
                if (!cuftpd_cur_user) { \
                        cuftpd_send_resp(ctrlfd, 530, "first please"); \
                        return CUFTPD_ERR; \
                } \
        } while(0)

struct cuftpd_cmd_struct {
        char *cmd_name;
        int (*cmd_handler)(int ctrlfd, char *cmd_line);
};

struct cuftpd_user_struct {
        char user[128];
        char pass[128];
};


#endif

/*
* A very simple ftp server's source code for demonstration.
* It supports PASV/PORT modes and following operations:
*   ls,pwd,cwd,get,put,dele.
* I have tested it using following ftp clients:
* 1. Windows XP's command line ftp client,
* 2. IE 6.0,
* 3. Redhat 9.0's ftp client,
* 4. CuteFTP 8,
* I'll introduce more functions and improve its performance
* if i have enough idle time afterwards.
* Any issues pls paste to CU.
*
* Change History:
* 11/24/2006                1.0                        Initial version.
*/
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdarg.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>

#include "cuftpd.h"

/* local functions declarations */
static int cuftpd_do_user(int,char*);
static int cuftpd_do_pass(int,char*);
static int cuftpd_do_pwd(int, char*);
static int cuftpd_do_cwd(int, char*);
static int cuftpd_do_syst(int,char*);
static int cuftpd_do_list(int,char*);
static int cuftpd_do_size(int,char*);
static int cuftpd_do_dele(int,char*);
static int cuftpd_do_type(int,char*);
static int cuftpd_do_port(int,char*);
static int cuftpd_do_pasv(int,char*);
static int cuftpd_do_retr(int,char*);
static int cuftpd_do_stor(int,char*);
static int cuftpd_do_quit(int,char*);

/* global variables */
int cuftpd_debug_on;
int cuftpd_srv_port = CUFTPD_DEF_SRV_PORT;
int cuftpd_cur_child_num;
int cuftpd_quit_flag;
struct cuftpd_user_struct *cuftpd_cur_user;
int cuftpd_pasv_fd = -1;
int cuftpd_pasv_connfd = -1;
int cuftpd_port_connfd = -1;
char cuftpd_home_dir[PATH_MAX];

/*
* Currently supported ftp commands.
*/
struct cuftpd_cmd_struct cuftpd_cmds[] = {
        {"USER", cuftpd_do_user},
        {"PASS", cuftpd_do_pass},
        {"PWD",  cuftpd_do_pwd},
        {"XPWD", cuftpd_do_pwd},
        {"CWD",  cuftpd_do_cwd},
        {"LIST", cuftpd_do_list},
        {"NLST", cuftpd_do_list},
        {"SYST", cuftpd_do_syst},
        {"TYPE", cuftpd_do_type},
        {"SIZE", cuftpd_do_size},
        {"DELE", cuftpd_do_dele},
        {"RMD",  cuftpd_do_dele},
        {"PORT", cuftpd_do_port},
        {"PASV", cuftpd_do_pasv},
        {"RETR", cuftpd_do_retr},
        {"STOR", cuftpd_do_stor},
        {"QUIT", cuftpd_do_quit},
        { NULL,         NULL}
};

/*
* Anonymous users, you can add more user/pass pairs here.
*/
struct cuftpd_user_struct cuftpd_users[] = {
        {"anonymous", ""},
        {"ftp", ""}
};

/*
* Ftp server's responses, and are not complete.
*/
char cuftpd_srv_resps[][256] = {
        "150 Begin transfer"                                                        CUFTPD_LINE_END,
        "200 OK"                                                                                CUFTPD_LINE_END,
        "213 %d"                                                                                CUFTPD_LINE_END,
        "215 UNIX Type: L8"                                                                CUFTPD_LINE_END,
        "220 CuFTPD " CUFTPD_VER " Server"                                CUFTPD_LINE_END,
        "221 Goodbye"                                                                        CUFTPD_LINE_END,
        "226 Transfer complete"                                                        CUFTPD_LINE_END,
        "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)"        CUFTPD_LINE_END,
        "230 User %s logged in"                                                        CUFTPD_LINE_END,
        "250 CWD command successful"                                        CUFTPD_LINE_END,
        "257 \"%s\" is current directory"                                CUFTPD_LINE_END,
        "331 Password required for %s"                                        CUFTPD_LINE_END,
        "500 Unsupport command %s"                                                CUFTPD_LINE_END,
        "530 Login %s"                                                                        CUFTPD_LINE_END,
        "550 Error"                                                                                CUFTPD_LINE_END
};

 

static void
cuftpd_debug(const char *file, int line, const char *fmt, ...)
{
        va_list ap;

        if (!cuftpd_debug_on)
                return;

        fprintf(stderr, "(%s:%d:%d)", file, line, getpid());
        va_start(ap, fmt);
        vfprintf(stderr, fmt, ap);
        va_end(ap);
}

static void
cuftpd_usage(void)
{
        printf("cuftpd " CUFTPD_VER " usage:\n");
        printf("cuftpd -p port -d\n");
}

static void
cuftpd_parse_args(int argc, char *argv[])
{
        int opt;
        int err = 0;

        while ((opt = getopt(argc, argv, "p:dh")) != -1) {
                switch (opt) {
                        case 'p':
                            cuftpd_srv_port = atoi(optarg);
                                err = (cuftpd_srv_port < 0 || cuftpd_srv_port > 65535);          
                            break;
                        case 'd':
                            cuftpd_debug_on = 1;
                            break;
                        default:
                            err = 1;
                            break;
                }
               
                if (err) {
                        cuftpd_usage();
                        exit(-1);
                }
        }

        CUFTPD_DEBUG("srv port:%d\n", cuftpd_srv_port);
}

static void
cuftpd_sigchild(int signo)
{
        int status;

        if (signo != SIGCHLD)
                return;

        while (waitpid(-1, &status, WNOHANG) > 0)
                cuftpd_cur_child_num--;

        CUFTPD_DEBUG("caught a SIGCHLD, cuftpd_cur_child_num=%d\n", cuftpd_cur_child_num);
}

static int
cuftpd_init(void)
{
        /* add all init statements here */

        signal(SIGPIPE, SIG_IGN);
        signal(SIGCHLD, cuftpd_sigchild);
        getcwd(cuftpd_home_dir, sizeof(cuftpd_home_dir));

        return CUFTPD_OK;
}

/*
* Create ftp server's listening socket.
*/
static int
cuftpd_create_srv(void)
{
        int fd;
        int on = 1;
        int err;
        struct sockaddr_in srvaddr;

        if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                CUFTPD_DEBUG("socket() failed: %s\n", strerror(errno));
                return fd;
        }

        err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
        if (err < 0) {
                CUFTPD_DEBUG("setsockopt() failed: %s\n", strerror(errno));
                close(fd);
                return CUFTPD_ERR;
        }
       
        memset(&srvaddr, 0, sizeof(srvaddr));
        srvaddr.sin_family = AF_INET;
        srvaddr.sin_port = htons(cuftpd_srv_port);
        srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        err = bind(fd, (struct sockaddr*)&srvaddr, sizeof(srvaddr));
        if (err < 0) {
                CUFTPD_DEBUG("bind() failed: %s\n", strerror(errno));
                close(fd);
                return CUFTPD_ERR;
        }

        err = listen(fd, CUFTPD_LISTEN_QU_LEN);
        if (err < 0) {
                CUFTPD_DEBUG("listen() failed: %s\n", strerror(errno));
                close(fd);
                return CUFTPD_ERR;
        }

        if (cuftpd_debug_on) {
                int len = sizeof(srvaddr);
                getsockname(fd, (struct sockaddr*)&srvaddr, &len);
                CUFTPD_DEBUG("create srv listen socket OK: %s:%d\n",
                           inet_ntoa(srvaddr.sin_addr), ntohs(srvaddr.sin_port));
        }

        return fd;       

}

/*
* Map server response's number to the msg pointer.
*/
static char *
cuftpd_srv_resp_num2msg(int num)
{
        int i;
        char buf[8];

        snprintf(buf, sizeof(buf), "%d", num);
        if (strlen(buf) != 3)
                return NULL;

        for (i = 0; i < CUFTPD_ARR_LEN(cuftpd_srv_resps); i++)
                if (strncmp(buf, cuftpd_srv_resps[i], strlen(buf)) == 0)
                        return cuftpd_srv_resps[i];

        return NULL;
}

static int
cuftpd_send_msg(int fd, char *msg, int len)
{
        int n, off = 0, left = len;

        while (1) {
                n = write(fd, msg + off, left);
                if (n < 0) {
                        if (errno == EINTR)
                                continue;
                        return n;
                }
                if (n < left) {
                        left -= n;
                        off += n;
                        continue;
                }
                return len;
        }
}

static int
cuftpd_recv_msg(int fd, char buf[], int len)
{
        int n;

        while (1) {
                n = read(fd, buf, len);
                if (n < 0) {
                        if (errno == EINTR)
                                continue;
                        return n;
                }
                return n;
        }
}

static int
cuftpd_send_resp(int fd, int num, ...)
{
        char *cp = cuftpd_srv_resp_num2msg(num);
        va_list ap;
        char buf[BUFSIZ];

        if (!cp) {
                CUFTPD_DEBUG("cuftpd_srv_resp_num2msg(%d) failed\n", num);
                return CUFTPD_ERR;
        }

        va_start(ap, num);
        vsnprintf(buf, sizeof(buf), cp, ap);
        va_end(ap);

        CUFTPD_DEBUG("send resp:%s\n", buf);
        if (cuftpd_send_msg(fd, buf, strlen(buf)) != strlen(buf)) {
                CUFTPD_DEBUG("cuftpd_send_msg() failed: %s\n", strerror(errno));
                return CUFTPD_ERR;
        }

        return CUFTPD_OK;
}

static void
cuftpd_trim_lineend(char *cp)
{
        if (cp && strlen(cp) > 0) {
                char *cp2 = &cp[strlen(cp) - 1];
               
                while (*cp2 == '\r' || *cp2 == '\n')
                        if (--cp2 < cp)
                                break;
                cp2[1] = '\0';
        }
}

static int
cuftpd_get_connfd(void)
{
        int fd;

        if (cuftpd_pasv_fd >= 0) {
                fd = accept(cuftpd_pasv_fd, NULL, NULL);
                if (fd >= 0) {
                        close(cuftpd_pasv_fd);
                        cuftpd_pasv_fd = -1;
                        cuftpd_pasv_connfd = fd;
                        return fd;
                }
                else
                        CUFTPD_DEBUG("accept() failed:%s\n", strerror(errno));
        }
        else if (cuftpd_port_connfd >= 0)
                return cuftpd_port_connfd;
               
        return (-1);
}

static int
cuftpd_close_all_fd(void)
{
        if (cuftpd_pasv_fd >= 0) {
                close(cuftpd_pasv_fd);
                cuftpd_pasv_fd = -1;
        }
        if (cuftpd_pasv_connfd >= 0) {
                close(cuftpd_pasv_connfd);
                cuftpd_pasv_connfd = -1;
        }
        if (cuftpd_port_connfd >= 0) {
                close(cuftpd_port_connfd);
                cuftpd_port_connfd = -1;
        }
        return CUFTPD_OK;
}


static int
cuftpd_do_user(int ctrlfd, char *cmdline)
{
        char *cp = strchr(cmdline, ' ');

        if (cp) {
                int i;

                for (i = 0; i < CUFTPD_ARR_LEN(cuftpd_users); i++)
                        if (strcmp(cp + 1, cuftpd_users[i].user) == 0) {
                                CUFTPD_DEBUG("user(%s) is found\n", cp + 1);
                                cuftpd_cur_user = &cuftpd_users[i];
                                break;
                        }

                if (!cuftpd_cur_user)
                        CUFTPD_DEBUG("user(%s) not found\n", cp + 1);
               
                /*
                 * If user name is bad, we still don't close the connection
                 * and send back the 331 response to ask for password.
                 */
                return cuftpd_send_resp(ctrlfd, 331, cp + 1);
        }
       
        return cuftpd_send_resp(ctrlfd, 550);
}

static int
cuftpd_do_pass(int ctrlfd, char *cmdline)
{
        char *space = strchr(cmdline, ' ');

        if (cuftpd_cur_user && space) {
                if (strlen(cuftpd_cur_user->pass) == 0 ||
                        strcmp(space + 1, cuftpd_cur_user->pass) == 0) {
                        CUFTPD_DEBUG("password for %s OK\n", cuftpd_cur_user->user);
                        return cuftpd_send_resp(ctrlfd, 230, cuftpd_cur_user->user);
                }
                CUFTPD_DEBUG("password for %s ERR\n", cuftpd_cur_user->user);
        }
               
        /*
         * User and pass don't match or
         * cmd line does not contain a space character.
         */
        cuftpd_cur_user = NULL;
        return cuftpd_send_resp(ctrlfd, 530, "incorrect");
}

static int
cuftpd_do_pwd(int ctrlfd, char *cmdline)
{
        char curdir[PATH_MAX];
        char *cp;
       
        CUFTPD_CHECK_LOGIN();

        getcwd(curdir, sizeof(curdir));
        cp = &curdir[strlen(cuftpd_home_dir)];
        return cuftpd_send_resp(ctrlfd, 257, (*cp == '\0') ? "/" : cp);
}

static int
cuftpd_do_cwd(int ctrlfd, char *cmdline)
{
        char *space = strchr(cmdline, ' ');
        char curdir[PATH_MAX];

        CUFTPD_CHECK_LOGIN();

        if (!space)
                return cuftpd_send_resp(ctrlfd, 550);

        getcwd(curdir, sizeof(curdir));
        if (strcmp(curdir, cuftpd_home_dir) == 0 &&
                space[1] == '.' &&
                space[2] == '.')
                return cuftpd_send_resp(ctrlfd, 550);

        /* Absolute path */       
        if (space[1] == '/') {
                if (chdir(cuftpd_home_dir) == 0) {
                        if (space[2] == '\0' || chdir(space+2) == 0)
                                return cuftpd_send_resp(ctrlfd, 250);       
                }
                chdir(curdir);
                return cuftpd_send_resp(ctrlfd, 550);
        }

        /* Relative path */
        if (chdir(space+1) == 0)
                return cuftpd_send_resp(ctrlfd, 250);

        chdir(curdir);
        return cuftpd_send_resp(ctrlfd, 550);
}

/*
* This function acts as a implementation like 'ls -l' shell command.
*/
static int
cuftpd_get_list(char buf[], int len)
{
        DIR *dir;
        struct dirent *ent;
        int off = 0;

        if ((dir = opendir(".")) < 0) {
                CUFTPD_DEBUG("opendir() failed:%s\n", strerror(errno));
                return CUFTPD_ERR;
        }

        buf[0] = '\0';

        while ((ent = readdir(dir)) != NULL) {
                char *filename = ent->d_name;
                struct stat st;
                char mode[] = "----------";
                struct passwd *pwd;
                struct group *grp;
                struct tm *ptm;
                char timebuf[BUFSIZ];
                int timelen;

                if (strcmp(filename, ".") == 0 ||
                        strcmp(filename, "..") == 0)
                        continue;

                if (stat(filename, &st) < 0) {
                        closedir(dir);
                        CUFTPD_DEBUG("stat() failed:%s\n", strerror(errno));
                        return CUFTPD_ERR;
                }

                if (S_ISDIR(st.st_mode))
                        mode[0] = 'd';
                if (st.st_mode & S_IRUSR)
                        mode[1] = 'r';
                if (st.st_mode & S_IWUSR)
                        mode[2] = 'w';
                if (st.st_mode & S_IXUSR)
                        mode[3] = 'x';
                if (st.st_mode & S_IRGRP)
                        mode[4] = 'r';
                if (st.st_mode & S_IWGRP)
                        mode[5] = 'w';
                if (st.st_mode & S_IXGRP)
                        mode[6] = 'x';
                if (st.st_mode & S_IROTH)
                        mode[7] = 'r';
                if (st.st_mode & S_IWOTH)
                        mode[8] = 'w';
                if (st.st_mode & S_IXOTH)
                        mode[9] = 'x';
                mode[10] = '\0';
                off += snprintf(buf + off, len - off, "%s ", mode);

                /* hard link number, this field is nonsense for ftp */
                off += snprintf(buf + off, len - off, "%d ", 1);

                /* user */
                if ((pwd = getpwuid(st.st_uid)) == NULL) {
                        closedir(dir);
                        return CUFTPD_ERR;
                }
                off += snprintf(buf + off, len - off, "%s ", pwd->pw_name);

                /* group */
                if ((grp = getgrgid(st.st_gid)) == NULL) {
                        closedir(dir);
                        return CUFTPD_ERR;
                }
                off += snprintf(buf + off, len - off, "%s ", grp->gr_name);

                /* size */
                off += snprintf(buf + off, len - off, "%*d ", 10, st.st_size);

                /* mtime */
                ptm = localtime(&st.st_mtime);
                if (ptm && (timelen = strftime(timebuf, sizeof(timebuf), "%b %d %H:%S", ptm)) > 0) {
                        timebuf[timelen] = '\0';
                        off += snprintf(buf + off, len - off, "%s ", timebuf);
                }
                else {
                        closedir(dir);
                        return CUFTPD_ERR;
                }
               
                off += snprintf(buf + off, len - off, "%s\r\n", filename);
               
        }

        return off;
}

static int
cuftpd_do_list(int ctrlfd, char *cmdline)
{
        char buf[BUFSIZ];
        int n;
        int fd;

        CUFTPD_CHECK_LOGIN();

        if ((fd = cuftpd_get_connfd()) < 0) {
                CUFTPD_DEBUG("LIST cmd:no available fd%s", "\n");
                goto err_label;
        }

        cuftpd_send_resp(ctrlfd, 150);

        /*
         * Get the 'ls -l'-like result and send it to client.
         */
        n = cuftpd_get_list(buf, sizeof(buf));
        if (n >= 0) {
                if (cuftpd_send_msg(fd, buf, n) != n) {
                        CUFTPD_DEBUG("cuftpd_send_msg() failed: %s\n", strerror(errno));
                        goto err_label;
                }
        }
        else {
                CUFTPD_DEBUG("cuftpd_get_list() failed %s", "\n");
                goto err_label;
        }
       
        cuftpd_close_all_fd();
        return cuftpd_send_resp(ctrlfd, 226);

err_label:
        cuftpd_close_all_fd();
        return cuftpd_send_resp(ctrlfd, 550);
}

static int
cuftpd_do_syst(int ctrlfd, char *cmdline)
{
        CUFTPD_CHECK_LOGIN();
        return cuftpd_send_resp(ctrlfd, 215);
}

static int
cuftpd_do_size(int ctrlfd, char *cmdline)
{
        char *space = strchr(cmdline, ' ');
        struct stat st;

        CUFTPD_CHECK_LOGIN();

        if (!space || lstat(space + 1, &st) < 0) {
                CUFTPD_DEBUG("SIZE cmd err: %s\n", cmdline);
                return cuftpd_send_resp(ctrlfd, 550);
        }

        return cuftpd_send_resp(ctrlfd, 213, st.st_size);
}

static int
cuftpd_do_dele(int ctrlfd, char *cmdline)
{
        char *space = strchr(cmdline, ' ');
        struct stat st;

        CUFTPD_CHECK_LOGIN();

        if (!space || lstat(space+1, &st) < 0 ||
                remove(space+1) < 0) {
                CUFTPD_DEBUG("DELE cmd err: %s\n", cmdline);
                return cuftpd_send_resp(ctrlfd, 550);
        }

        return cuftpd_send_resp(ctrlfd, 200);
}

static int
cuftpd_do_type(int ctrlfd, char *cmdline)
{
        CUFTPD_CHECK_LOGIN();

        /*
         * Just send back 200 response and do nothing
         */
        return cuftpd_send_resp(ctrlfd, 200);
}

/*
* Parse PORT cmd and fetch the ip and port,
* and both in network byte order.
*/
static int
cuftpd_get_port_mode_ipport(char *cmdline, unsigned int *ip, unsigned short *port)
{
        char *cp = strchr(cmdline, ' ');
        int i;
        unsigned char buf[6];

        if (!cp)
                return CUFTPD_ERR;

        for (cp++, i = 0; i < CUFTPD_ARR_LEN(buf); i++) {
                buf[i] = atoi(cp);
                cp = strchr(cp, ',');
                if (!cp && i < CUFTPD_ARR_LEN(buf) - 1)
                        return CUFTPD_ERR;
                cp++;
        }

        if (ip)
                *ip = *(unsigned int*)&buf[0];

        if (port)
                *port = *(unsigned short*)&buf[4];

        return CUFTPD_OK;
}

/*
* Ftp client shipped with Windows XP uses PORT
* mode as default to connect ftp server.
*/
static int
cuftpd_do_port(int ctrlfd, char *cmdline)
{
        unsigned int ip;
        unsigned short port;
        struct sockaddr_in sin;

        CUFTPD_CHECK_LOGIN();

        if (cuftpd_get_port_mode_ipport(cmdline, &ip, &port) != CUFTPD_OK) {
                CUFTPD_DEBUG("cuftpd_get_port_mode_ipport() failed%s", "\n");
                goto err_label;
        }

        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = ip;
        sin.sin_port = port;
       
        CUFTPD_DEBUG("PORT cmd:%s:%d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));

        if (cuftpd_port_connfd >= 0) {
                close(cuftpd_port_connfd);
                cuftpd_port_connfd = -1;
        }

        cuftpd_port_connfd = socket(AF_INET, SOCK_STREAM, 0);
        if (cuftpd_port_connfd < 0) {
                CUFTPD_DEBUG("socket() failed:%s\n", strerror(errno));
                goto err_label;
        }

        if (connect(cuftpd_port_connfd, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
                CUFTPD_DEBUG("bind() failed:%s\n", strerror(errno));
                goto err_label;
        }

        CUFTPD_DEBUG("PORT mode connect OK%s", "\n");
        return cuftpd_send_resp(ctrlfd, 200);

err_label:
        if (cuftpd_port_connfd >= 0) {
                close(cuftpd_port_connfd);
                cuftpd_port_connfd = -1;
        }
        return cuftpd_send_resp(ctrlfd, 550);

}

static int
cuftpd_do_pasv(int ctrlfd, char *cmdline)
{
        struct sockaddr_in pasvaddr;
        int len;
        unsigned int ip;
        unsigned short port;

        CUFTPD_CHECK_LOGIN();

        if (cuftpd_pasv_fd >= 0) {
                close(cuftpd_pasv_fd);
                cuftpd_pasv_fd = -1;
        }

        cuftpd_pasv_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (cuftpd_pasv_fd < 0) {
                CUFTPD_DEBUG("socket() failed: %s\n", strerror(errno));
                return cuftpd_send_resp(ctrlfd, 550);
        }

        /*
         * must bind to the same interface as ctrl connectin came from.
         */
        len = sizeof(pasvaddr);
        getsockname(ctrlfd, (struct sockaddr*)&pasvaddr, &len);
        pasvaddr.sin_port = 0;

        if (bind(cuftpd_pasv_fd, (struct sockaddr*)&pasvaddr, sizeof(pasvaddr)) < 0) {
                CUFTPD_DEBUG("bind() failed: %s\n", strerror(errno));
                close(cuftpd_pasv_fd);
                cuftpd_pasv_fd = -1;
                return cuftpd_send_resp(ctrlfd, 550);
        }

        if (listen(cuftpd_pasv_fd, CUFTPD_LISTEN_QU_LEN) < 0) {
                CUFTPD_DEBUG("listen() failed: %s\n", strerror(errno));
                close(cuftpd_pasv_fd);
                cuftpd_pasv_fd = -1;
                return cuftpd_send_resp(ctrlfd, 550);
        }
               
        len = sizeof(pasvaddr);
        getsockname(cuftpd_pasv_fd, (struct sockaddr*)&pasvaddr, &len);
        ip = ntohl(pasvaddr.sin_addr.s_addr);
        port = ntohs(pasvaddr.sin_port);
        CUFTPD_DEBUG("local bind: %s:%d\n", inet_ntoa(pasvaddr.sin_addr), port);

        /*
         * write local ip/port into response msg
         * and send to client.
         */
        return cuftpd_send_resp(ctrlfd, 227, (ip>>24)&0xff, (ip>>16)&0xff,
                        (ip>>8)&0xff, ip&0xff, (port>>8)&0xff, port&0xff);

}

static int
cuftpd_do_retr(int ctrlfd, char *cmdline)
{
        char buf[BUFSIZ];
        char *space = strchr(cmdline, ' ');
        struct stat st;
        int fd = -1, n;
        int connfd;

        CUFTPD_CHECK_LOGIN();

        if (!space || lstat(space + 1, &st) < 0) {
                CUFTPD_DEBUG("RETR cmd err: %s\n", cmdline);
                goto err_label;
        }

        if ((connfd = cuftpd_get_connfd()) < 0) {
                CUFTPD_DEBUG("cuftpd_get_connfd() failed%s", "\n");
                goto err_label;
        }

        cuftpd_send_resp(ctrlfd, 150);

        /* begin to read file and write it to conn socket */
        if ((fd = open(space + 1, O_RDONLY)) < 0) {
                CUFTPD_DEBUG("open() failed: %s\n", strerror(errno));
                goto err_label;
        }

        while (1) {
                if ((n = read(fd, buf, sizeof(buf))) < 0) {
                        if (errno == EINTR)
                                continue;
                        CUFTPD_DEBUG("read() failed: %s\n", strerror(errno));
                        goto err_label;
                }

                if (n == 0)
                        break;
                       
                if (cuftpd_send_msg(connfd, buf, n) != n) {
                        CUFTPD_DEBUG("cuftpd_send_msg() failed: %s\n", strerror(errno));
                        goto err_label;
                }
        }

        CUFTPD_DEBUG("RETR(%s) OK\n", space + 1);
        if (fd >= 0)
                close(fd);
        cuftpd_close_all_fd();
        return cuftpd_send_resp(ctrlfd, 226);

err_label:
        if (fd >= 0)
                close(fd);
        cuftpd_close_all_fd();
        return cuftpd_send_resp(ctrlfd, 550);
}

static int
cuftpd_do_stor(int ctrlfd, char *cmdline)
{
        char buf[BUFSIZ];
        char *space = strchr(cmdline, ' ');
        struct stat st;
        int fd = -1, n;
        int left, off;
        int connfd;

        CUFTPD_CHECK_LOGIN();

        /*
         * Should add some permission control mechanism here.
         */
        if (!space || lstat(space + 1, &st) == 0) {
                CUFTPD_DEBUG("STOR cmd err: %s\n", cmdline);
                goto err_label;
        }
       
        if ((connfd = cuftpd_get_connfd()) < 0) {
                CUFTPD_DEBUG("cuftpd_get_connfd() failed%s", "\n");
                goto err_label;
        }

        cuftpd_send_resp(ctrlfd, 150);
               
        if ((fd = open(space + 1, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
                CUFTPD_DEBUG("open() failed: %s\n", strerror(errno));
                goto err_label;
        }

        /* begin to read data from socket and wirte to disk file */
        while (1) {
                if ((n = cuftpd_recv_msg(connfd, buf, sizeof(buf))) < 0) {
                        CUFTPD_DEBUG("cuftpd_recv_msg() failed: %s\n", strerror(errno));
                        goto err_label;
                }       

                if (n == 0)
                        break;

                left = n;
                off = 0;
                while (left > 0) {
                        int nwrite;

                        if ((nwrite = write(fd, buf + off, left)) < 0) {
                                if (errno == EINTR)
                                        continue;
                                CUFTPD_DEBUG("write() failed:%s\n", strerror(errno));
                                goto err_label;
                        }
                        off += nwrite;
                        left -= nwrite;
                }
        }

        CUFTPD_DEBUG("STOR(%s) OK\n", space+1);
        if (fd >= 0)
                close(fd);
        cuftpd_close_all_fd();
        sync();
        return cuftpd_send_resp(ctrlfd, 226);

err_label:
        if (fd >= 0) {
                close(fd);
                unlink(space+1);
        }
        cuftpd_close_all_fd();
        return cuftpd_send_resp(ctrlfd, 550);
}

static int
cuftpd_do_quit(int ctrlfd, char *cmdline)
{
        cuftpd_send_resp(ctrlfd, 221);
        cuftpd_quit_flag = 1;
        return CUFTPD_OK;
}

static int
cuftpd_do_request(int ctrlfd, char buf[])
{
        char *end = &buf[strlen(buf) - 1];
        char *space = strchr(buf, ' ');
        int i;
        char save;
        int err;

        if (*end == '\n' || *end == '\r') {
                /*
                 * this is a valid ftp request.
                 */
                cuftpd_trim_lineend(buf);

                if (!space)
                        space = &buf[strlen(buf)];

                save = *space;
                *space = '\0';
                for (i = 0; cuftpd_cmds[i].cmd_name; i++) {
                        if (strcmp(buf, cuftpd_cmds[i].cmd_name) == 0) {
                                *space = save;
                                CUFTPD_DEBUG("recved a valid ftp cmd:%s\n", buf);
                                return cuftpd_cmds[i].cmd_handler(ctrlfd, buf);
                        }
                }

                /*
                 * unrecognized cmd
                 */
                *space = save;
                CUFTPD_DEBUG("recved a unsupported ftp cmd:%s\n", buf);
       
                *space = '\0';
                err = cuftpd_send_resp(ctrlfd, 500, buf);
                *space = save;

                return err;       
        }

        CUFTPD_DEBUG("recved a invalid ftp cmd:%s\n", buf);

        /*
         * Even if it's a invalid cmd, we should also send 
         * back a response to prevent client from blocking.
         */
        return cuftpd_send_resp(ctrlfd, 550);
}

static int
cuftpd_ctrl_conn_handler(int connfd)
{
        char buf[BUFSIZ];
        int buflen;
        int err = CUFTPD_OK;

        /*
         * Control connection has set up,
         * we can send out the first ftp msg.
         */
        if (cuftpd_send_resp(connfd, 220) != CUFTPD_OK) {
                close(connfd);
                CUFTPD_DEBUG("Close the ctrl connection OK%s", "\n");
                return CUFTPD_ERR;
        }

        /*
         * Begin to interact with client and one should implement
         * a state machine here for full compatibility. But we only
         * show a demonstration ftp server and i do my best to
         * simplify it. Base on this skeleton, you can write a
         * full-funtional ftp server if you like. ;-)
         */
        while (1) {
                buflen = cuftpd_recv_msg(connfd, buf, sizeof(buf));
                if (buflen < 0) {
                        CUFTPD_DEBUG("cuftpd_recv_msg() failed: %s\n", strerror(errno));
                        err = CUFTPD_ERR;
                        break;
                }
       
                if (buflen == 0)
                        /* this means client launch a active close */
                        break;

                buf[buflen] = '\0';
                cuftpd_do_request(connfd, buf);
               
                /*
                 * The negative return value from cuftpd_do_request
                 * should not cause the breaking of ctrl connection.
                 * Only when the client send QUIT cmd, we exit and
                 * launch a active close.
                 */
               
                if (cuftpd_quit_flag)
                        break;
        }

        close(connfd);
        CUFTPD_DEBUG("Close the ctrl connection OK%s", "\n");
        return err;
}

static int
cuftpd_do_loop(int listenfd)
{
        int connfd;
        int pid;

        while (1) {
                CUFTPD_DEBUG("Server ready, wait client's connection...%s", "\n");

                connfd = accept(listenfd, NULL, NULL);
                if (connfd < 0) {
                        CUFTPD_DEBUG("accept() failed: %s\n", strerror(errno));
                        continue;
                }
               
                if (cuftpd_debug_on) {
                        struct sockaddr_in cliaddr;
                        int len = sizeof(cliaddr);
                        getpeername(connfd, (struct sockaddr*)&cliaddr, &len);
                        CUFTPD_DEBUG("accept a conn from %s:%d\n",
                                        inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
                }

                if ((pid = fork()) < 0) {
                        CUFTPD_DEBUG("fork() failed: %s\n", strerror(errno));
                        close(connfd);
                        continue;
                }
                if (pid > 0) {
                        /* parent */
                        close(connfd);
                        cuftpd_cur_child_num++;
                        continue;
                }
               
                /* child */
                close(listenfd);
                signal(SIGCHLD, SIG_IGN);
                if (cuftpd_ctrl_conn_handler(connfd) != CUFTPD_OK)
                        exit(-1);
                exit(0);
        }

        return CUFTPD_OK;
}

int
main(int argc, char *argv[])
{
        int listenfd;

        cuftpd_parse_args(argc, argv);
        cuftpd_init();
       
        listenfd = cuftpd_create_srv();
        if (listenfd >= 0)
                cuftpd_do_loop(listenfd);

        exit(0);
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值