这两天有点空闲,于是写了一个非常简单的FTP服务器练练手。包括浏览、删除、上传、下载、转换目录等最基本的功能,用传统的多进程的方式实现。可供初学Linux网络编程的朋友参考。后续有时间我会考虑进一步完善,例如加入配置文件、权限控制、多线程、对FTP协议更完善的支持、断点续传、ASCII传输模式、限速、日志、……太多了。有兴趣的朋友,在充分理解FTP协议的基础上,也可以自己对它做完善。关于FTP协议,请参见RFC 0959。 什么都不用说了,一切尽在代码中,注释还是比较丰富的。 :)
编译: gcc cuftpd.c -o cuftpd
运行: ./cuftpd -d -p 2121 其中-d打开调试开关,会输出大量调试信息。 -p 2121指定监听端口,默认是21。 还可以./cuftpd -h看帮助信息。
cuftpd.h
#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 cuftpd.c
/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include
#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); }
| | |