Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * @file ftpd.c
- * @date 04/09/10
- * @version 1.0
- * @brief A simple ftpd server in C
- *
- * @author Josip Djolonga (jdjolonga), j.djolonga@jacobs-university.de
- *
- * Note that simultaneous connections are supported.
- *
- */
- #define _REETRANT
- #include <dirent.h>
- #include <errno.h>
- #include <grp.h>
- #include <pwd.h>
- #include <signal.h>
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- #include <getopt.h>
- #include <libgen.h>
- #include <sys/stat.h>
- #include <sys/socket.h>
- #include <sys/wait.h>
- #include <sys/types.h>
- #include <netdb.h>
- #include <unistd.h>
- /* maximum pending connections */
- #define MAX_PENDING 50
- #define DEBUG
- struct ftp_user {
- char * name;
- char * pass;
- char * dir;
- };
- struct ftp_user * users;
- unsigned users_num = 0;
- static void parse_conf() {
- static const char * conf_file_path = "myftpd.conf";
- char * line;
- size_t line_len;
- int n;
- char c, last_c = '\0';
- int i;
- char * token;
- FILE * conf = fopen(conf_file_path, "r");
- if(conf == NULL) {
- perror("fopen");
- exit(EXIT_FAILURE);
- }
- n = 0;
- while((c = fgetc(conf)) != EOF) {
- if(c == '\n') {
- n++;
- }
- last_c = c;
- }
- /* some editors might leave a '\n' on the last line, ignore it */
- if(last_c != '\n') {
- n++;
- }
- fseek(conf, 0, SEEK_SET);
- users = malloc(sizeof(*users)*n);
- if(users == NULL) {
- perror("malloc");
- exit(EXIT_FAILURE);
- }
- for(i=0; i<n; i++) {
- line = NULL;
- line_len = 0;
- if(getline(&line, &line_len, conf) == -1) {
- fputs("ill formed configuration file\n", stderr);
- exit(2);
- };
- token = strtok(line, "\n:");
- if(token == NULL) {
- fputs("ill formed configuration file\n", stderr);
- exit(2);
- }
- users[i].name = token;
- token = strtok(NULL, "\n:");
- if(token == NULL) {
- fputs("ill formed configuration file\n", stderr);
- exit(2);
- }
- users[i].pass = token;
- token = strtok(NULL, "\n:");
- if(token == NULL) {
- fputs("ill formed configuration file\n", stderr);
- exit(2);
- }
- users[i].dir = token;
- if(strtok(NULL, "\n:") != NULL) {
- fputs("ill formed configuration file\n", stderr);
- exit(2);
- }
- }
- users_num = n;
- }
- static void print_usage() {
- static const char * usage_str1 =
- "usage: ftpd [options] \n"
- " \n"
- "description: \n"
- " This program runs an FTP server on the given port. Multiple \n"
- "simultaneous connections are supported. If the port is not specified \n";
- static const char * usage_str2 =
- "it defaults to 21. \n"
- " \n"
- "options: \n"
- " -h,--help print this message \n"
- " -p port,--port port run the server on the given port \n";
- /* splitted into two because the string is greater than the
- * max size compilers have to support according to the standard */
- fputs(usage_str1, stderr);
- fputs(usage_str2, stderr);
- }
- static void sock_safe_send(int fd, const char * data, ssize_t size) {
- int s, bytes;
- bytes = 0;
- while(bytes < size) {
- s = write(fd, data+bytes, size-bytes);
- if(s == -1) {
- perror("write");
- exit(EXIT_FAILURE);
- } else {
- bytes += s;
- }
- }
- }
- static void sock_putc(int fd, char c) {
- int s=0;
- while((s = write(fd, &c, 1)) == 0) {
- }
- if(s == -1) {
- perror("write");
- exit(EXIT_FAILURE);
- }
- }
- static void sock_puts(int fd, const char * str) {
- sock_safe_send(fd, str, strlen(str));
- }
- static char * cwd_str() {
- char * path;
- int size = 1024;
- path = malloc(size);
- if(path == NULL) {
- perror("malloc");
- exit(EXIT_FAILURE);
- }
- while(getcwd(path, size) == NULL) {
- if(errno == ERANGE) {
- size += 512;
- if(realloc(path, size) == NULL) {
- perror("realloc");
- exit(EXIT_FAILURE);
- }
- } else {
- perror("getcwd");
- exit(EXIT_FAILURE);
- }
- }
- return path;
- }
- #define sock_printf(fd, buf, fmt, ...) \
- snprintf((buf), (sizeof(buf)), (fmt), __VA_ARGS__); \
- sock_puts((fd), (buf));
- /* the show_* functions are based on the implementation provided in the
- * course homepage */
- static void
- show_mode(int fd, mode_t mode)
- {
- char c;
- if (S_ISDIR(mode)) {
- c = 'd';
- } else if (S_ISREG(mode)) {
- c = '-';
- } else if (S_ISCHR(mode)) {
- c = 'c';
- } else if (S_ISBLK(mode)) {
- c = 'b';
- #ifdef S_ISSOCK
- } else if (S_ISSOCK(mode)) {
- c = 's';
- #endif
- } else if (S_ISFIFO(mode)) {
- c = 'f';
- } else {
- c = 'c';
- }
- sock_putc(fd, (mode & S_IRUSR) ? 'r' : '-');
- sock_putc(fd, (mode & S_IWUSR) ? 'w' : '-');
- sock_putc(fd, (mode & S_IXUSR) ? 'x' : '-');
- sock_putc(fd, (mode & S_IRGRP) ? 'r' : '-');
- sock_putc(fd, (mode & S_IWGRP) ? 'w' : '-');
- sock_putc(fd, (mode & S_IXGRP) ? 'x' : '-');
- sock_putc(fd, (mode & S_IROTH) ? 'r' : '-');
- sock_putc(fd, (mode & S_IWOTH) ? 'w' : '-');
- sock_putc(fd, (mode & S_IXOTH) ? 'x' : '-');
- }
- static void show_user(int fd, uid_t uid)
- {
- struct passwd *pwd;
- static char buf[8];
- pwd = getpwuid(uid);
- if (pwd) {
- sock_printf(fd, buf, " %6s", pwd->pw_name);
- } else {
- sock_printf(fd, buf, " %6u", uid);
- }
- }
- static void
- show_group(int fd, gid_t gid)
- {
- struct group *grp;
- static char buf[8];
- grp = getgrgid(gid);
- if (grp) {
- sock_printf(fd, buf, " %6s", grp->gr_name);
- } else {
- sock_printf(fd, buf, " %6u", gid);
- }
- }
- static void
- show_time(int fd, time_t t, time_t now)
- {
- struct tm *tm;
- char buf[14];
- char buf_out[14];
- const time_t delta = 7905600; /* 366/2 * 12 * 3600 seconds */
- size_t n = 0;
- tm = localtime(&t);
- if (now != (time_t) -1 && t < now - delta) {
- n = strftime(buf, sizeof(buf), "%b %d %Y", tm);
- } else {
- n = strftime(buf, sizeof(buf), "%b %d %H:%M", tm);
- }
- if (n) {
- sock_printf(fd, buf_out, " %12s", buf);
- } else {
- sock_printf(fd, buf_out, " %12lu", (unsigned long) t);
- }
- }
- /* global variables, the state of the FTP transaction */
- int log_in_progress = 0;
- int logged_in = 0;
- static char user[1024];
- struct ftp_user * cur_user = NULL;
- int path_is_safe(const char * path) {
- char * abspath;
- int ret;
- abspath = realpath(path, NULL);
- if(abspath == NULL) {
- perror("realpath");
- ret = -1;
- } else {
- ret = (strstr(abspath, cur_user->dir) == abspath);
- }
- free(abspath);
- return ret;
- }
- static void cmd_user(int fd, char *params) {
- unsigned i;
- if(params == NULL) {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- for(i=0; i<strlen(params); i++) {
- if(params[i] == ' ') {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- }
- logged_in = 0;
- log_in_progress = 1;
- cur_user = NULL;
- memset(user, 0, sizeof(user));
- strncpy(user, params, sizeof(user));
- sock_puts(fd, "331 Please specify the password.\r\n");
- }
- static void cmd_pass(int fd, char *params) {
- unsigned i;
- if(params == NULL) {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- for(i=0; i<strlen(params); i++) {
- if(params[i] == ' ') {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- }
- if(!log_in_progress) {
- sock_puts(fd, "503 Login with USER first.\r\n");
- return;
- }
- for(i=0; i<users_num; i++) {
- if(strcmp(users[i].name, user) == 0 &&
- strcmp(users[i].pass, params) == 0) {
- cur_user = &users[i];
- logged_in = 1;
- log_in_progress = 0;
- if(chdir(users[i].dir) != 0) {
- perror("chdir");
- exit(EXIT_FAILURE);
- }
- sock_puts(fd, "230 Login successful.\r\n");
- return;
- }
- }
- logged_in = 0;
- log_in_progress = 0;
- sock_puts(fd, "530 Invalid user or password, try again.\r\n");
- }
- static void cmd_help(int fd, char *params) {
- static const char * commands = "214-The following commands are recognized,\r\n"
- " USER PASS HELP CWD PWD \r\n"
- " MKD RMD QUIT \r\n"
- "214 End of command list.\r\n";
- if(params == NULL) {
- sock_puts(fd, commands);
- } else {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- }
- }
- static void cmd_cwd(int fd, char *params) {
- unsigned i;
- char * old_path;
- int status;
- if(params == NULL) {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- for(i=0; i<strlen(params); i++) {
- if(params[i] == ' ') {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- }
- if(!logged_in) {
- sock_puts(fd, "530 Please login with USER and PASS.\r\n");
- return;
- }
- old_path = cwd_str();
- status = path_is_safe(params);
- if(status == 1) {
- if(chdir(params) != 0) {
- sock_puts(fd, "550 Failed to change directory.\r\n");
- perror("chdir");
- } else {
- sock_puts(fd, "250 Directory successfully changed.\r\n");
- }
- } else if(status == 0) {
- sock_puts(fd, "550 Permission denied.\r\n");
- } else {
- sock_puts(fd, "550 Failed to change directory.\r\n");
- }
- free(old_path);
- }
- static void cmd_pwd(int fd, char *params) {
- char * path;
- char buf[128];
- if(params != NULL) {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- if(!logged_in) {
- sock_puts(fd, "530 Please login with USER and PASS.\n");
- return;
- }
- path = cwd_str();
- sock_printf(fd, buf, "257 \"%s\"\r\n", path);
- free(path);
- }
- static void cmd_mkd(int fd, char *params) {
- unsigned i;
- int ret;
- char * parent_dir;
- if(params == NULL) {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- for(i=0; i<strlen(params); i++) {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- if(!logged_in) {
- sock_puts(fd, "530 Please login with USER and PASS.\n");
- return;
- }
- /* copy needed of params as dirname might modify it */
- parent_dir = malloc(strlen(params));
- if(parent_dir == NULL) {
- perror("malloc");
- exit(EXIT_FAILURE);
- }
- strncpy(parent_dir, params, strlen(params));
- ret = path_is_safe(dirname(parent_dir));
- free(parent_dir);
- if(ret == 1) {
- if(mkdir(params, 0755) == 0) {
- sock_puts(fd, "220 Created\r\n");
- } else {
- sock_puts(fd, "550 Failed to create directory\r\n");
- perror("mkdir");
- }
- } else if(ret == -1) {
- sock_puts(fd, "550 Failed to create directory\r\n");
- } else {
- sock_puts(fd, "550 Permission denied\r\n");
- }
- }
- static void cmd_rmd(int fd, char *params) {
- unsigned i;
- int ret;
- if(params == NULL) {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- for(i=0; i<strlen(params); i++) {
- if(params[i] == ' ') {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- return;
- }
- }
- if(!logged_in) {
- sock_puts(fd, "530 Please login with USER and PASS.\n");
- return;
- }
- ret = path_is_safe(params);
- if(ret == 1) {
- if(rmdir(params) == 0) {
- sock_puts(fd, "220 Removed\r\n");
- } else {
- sock_puts(fd, "550 Failed to remove directory\r\n");
- perror("rmdir");
- }
- } else if(ret == -1) {
- sock_puts(fd, "550 Failed to remove directory\r\n");
- } else {
- sock_puts(fd, "550 Permission denied\r\n");
- }
- }
- static void cmd_stat(int fd, char *params) {
- DIR * dir;
- struct dirent * dir_ent;
- int status;
- struct stat info;
- char * old_path;
- char buf[128];
- if(!logged_in) {
- sock_puts(fd, "530 Please login with USER and PASS.\n");
- return;
- }
- if(params == NULL) {
- sock_puts (fd, "211-FTP server status: \r\n");
- sock_puts (fd, " Status: Ready\r\n");
- sock_printf(fd, buf, " Logged in as %s\r\n", user);
- sock_puts (fd, " Type: ASCII\r\n");
- sock_puts (fd, "211 End of status\r\n");
- } else {
- status = path_is_safe(params);
- if(status == 1) {
- dir = opendir(params);
- /* no need to error check, if the directory is invalid, *
- * print an empty list (like in proftpd) */
- old_path = cwd_str();
- if(chdir(params) != 0) {
- free(old_path);
- sock_puts(fd, "550 Can not list the directory\r\n");
- return;
- }
- sock_puts (fd, "213-Status follows\r\n");
- while((dir_ent = readdir(dir)) != NULL) {
- sock_puts(fd, " ");
- if(stat(dir_ent->d_name, &info) == 0) {
- show_mode(fd, info.st_mode);
- show_user(fd, info.st_uid);
- show_group(fd, info.st_gid);
- #ifdef FS_SIZE_64_BIT
- sock_printf(fd, buf, " %9llu", info.st_size);
- #else
- sock_printf(fd, buf, " %9lu", info.st_size);
- #endif
- show_time(fd, info.st_mtime, time(NULL));
- }
- sock_printf(fd, buf, " %s\r\n", dir_ent->d_name);
- }
- sock_puts (fd, "213 End of status\r\n");
- if(chdir(old_path) != 0) {
- perror("chdir");
- exit(EXIT_FAILURE);
- }
- } else if(status == 0) {
- sock_puts(fd, "550 Permission denied.\r\n");
- } else {
- sock_puts(fd, "550 No such directory.\r\n");
- }
- }
- }
- static void cmd_quit(int fd, char *params) {
- if(params != NULL) {
- sock_puts(fd, "501 Invalid number of arguments.\r\n");
- } else {
- sock_puts(fd, "221 Goodbye.\r\n");
- close(fd);
- exit(EXIT_SUCCESS);
- }
- }
- struct command {
- const char * name;
- void (*handler)(int fd, char * params);
- };
- static struct command commands[] = {
- {"USER" , cmd_user },
- {"PASS" , cmd_pass },
- {"HELP" , cmd_help },
- {"CWD" , cmd_cwd },
- {"PWD" , cmd_pwd },
- {"MKD" , cmd_mkd },
- {"RMD" , cmd_rmd },
- {"STAT" , cmd_stat },
- {"QUIT" , cmd_quit }
- };
- static int serve_client(int clifd) {
- static char buf[512];
- unsigned i;
- int j;
- int s;
- char * cmd, * params;
- int found = 0;
- sock_puts(clifd, "220 wtftpd 1.0 - the server is ready to serve you!\r\n");
- while(1) {
- s = read(clifd, buf, sizeof(buf)-1);
- if(s == -1) {
- perror("read");
- exit(EXIT_FAILURE);
- }
- if(s > 0 && buf[s-1] == '\n') {
- buf[s-1] = '\0';
- }
- if(s > 1 && buf[s-2] == '\r') {
- buf[s-2] = '\0';
- }
- params = NULL;
- cmd = buf;
- for(j=0; j<s; j++) {
- if(buf[j] == ' ') {
- /* split the command here */
- params = &buf[j+1];
- break;
- }
- }
- buf[j] = '\0'; /* null terminate the command */
- found = 0;
- for(i=0; i<sizeof(commands)/sizeof(*commands); i++) {
- if(strcasecmp(commands[i].name, cmd) == 0) {
- found = 1;
- commands[i].handler(clifd, params);
- }
- }
- if(!found) {
- sock_puts(clifd, "500 Unknown command.\r\n");
- }
- }
- if(close(clifd) < 0) {
- fputs("Can not close the connection", stderr);
- perror("close");
- return EXIT_FAILURE;
- }
- }
- /**
- * SIGCHLD handler, reaps the zombies
- **/
- static void reap_zombies(int sig) {
- /* KILL 'EM ALL */
- while(waitpid(-1, NULL, WNOHANG) > 0) { }
- }
- int main(int argc, char * argv[]) {
- int sockfd, clifd, c;
- struct sigaction sig_chld;
- char * port = "21";
- int help = 0;
- int status;
- int opt_len = -1;
- socklen_t addr_len;
- struct addrinfo ai_hints, *ai_result, *ai;
- struct sockaddr cli_addr;
- pid_t pid;
- static struct option opts[] = {
- {"help", 0, 0, 'h'},
- {"port", 1, 0, 'p'}
- };
- while((c = getopt_long(argc, argv, "hp:", opts, NULL)) != -1) {
- switch(c) {
- case 'h':
- help = 1;
- break;
- case 'p':
- if(atoi(optarg) <= 0) {
- fputs("invalid port number\n", stderr);
- return EXIT_FAILURE;
- }
- opt_len = strlen(optarg);
- port = malloc(opt_len);
- if(port == NULL) {
- perror("malloc");
- return EXIT_FAILURE;
- }
- strncpy(port, optarg, opt_len);
- break;
- case '?':
- fputs("use the --help to see how the program can be used\n",
- stderr);
- return EXIT_FAILURE;
- break;
- }
- }
- if(help == 1) {
- print_usage();
- return EXIT_SUCCESS;
- }
- if(optind != argc) {
- fputs("too many arguments, use --help too see all options\n", stderr);
- return EXIT_FAILURE;
- }
- parse_conf();
- /* set up the zombie reaper */
- memset(&sig_chld, 0, sizeof(sig_chld));
- sig_chld.sa_handler = reap_zombies;
- if(sigaction(SIGCHLD, &sig_chld, NULL) != 0) {
- perror("sigaction");
- return EXIT_FAILURE;
- }
- memset(&ai_hints, 0, sizeof(ai_hints));
- ai_hints.ai_family = AF_UNSPEC;
- ai_hints.ai_socktype = SOCK_STREAM;
- ai_hints.ai_flags = AI_PASSIVE;
- if((status = getaddrinfo(NULL, port, &ai_hints, &ai_result)) != 0) {
- fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
- return EXIT_FAILURE;
- }
- if(opt_len != -1) {
- free(port);
- }
- for(ai = ai_result; ai != NULL; ai = ai->ai_next) {
- sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if(sockfd < 0) {
- perror("socket");
- } else {
- break;
- }
- }
- if(sockfd < 0) {
- fputs("can not create a socket\n", stderr);
- perror("socket");
- return EXIT_FAILURE;
- }
- if(bind(sockfd, ai->ai_addr, ai->ai_addrlen) != 0) {
- fputs("can not bind\n", stderr);
- perror("bind");
- return EXIT_FAILURE;
- }
- if(listen(sockfd, MAX_PENDING) != 0) {
- perror("listen");
- return EXIT_FAILURE;
- }
- memset(user, 0, sizeof(user));
- while(1) {
- addr_len = sizeof(cli_addr);
- clifd = accept(sockfd, &cli_addr, &addr_len);
- if(clifd == -1) {
- /* probably a SIGCHLD */
- if(errno == EINTR) {
- continue;
- } else {
- perror("accept");
- return EXIT_FAILURE;
- }
- }
- pid = fork();
- if(pid == 0) {
- close(sockfd);
- serve_client(clifd);
- /* this is never executed */
- } else {
- close(clifd);
- }
- }
- return EXIT_SUCCESS;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement