#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "magiftpd.h" #include "../../inih/ini.h" static struct ftpclient **clients; static int client_count = 0; struct dllist { char *data; struct dllist *prev; struct dllist *next; }; void sigchld_handler(int s) { // waitpid() might overwrite errno, so we save and restore it: int saved_errno = errno; while(waitpid(-1, NULL, WNOHANG) > 0); errno = saved_errno; } static void parse_path(struct ftpclient *client, char *path, char **result) { struct dllist *proot; struct dllist *pptr; char *rescpy; char *ptr; char *newpath = *result; proot = (struct dllist *)malloc(sizeof(struct dllist)); if (!proot) { fprintf(stderr, "Out of memory\n"); exit(-1); } proot->next = NULL; proot->prev = NULL; proot->data = NULL; pptr = proot; if (path[0] == '/') { rescpy = strdup(path); } else { rescpy = (char *)malloc(strlen(path) + strlen(client->current_path) + 2); if (!rescpy) { fprintf(stderr, "Out of memory\n"); exit(-1); } sprintf(rescpy, "%s/%s", client->current_path, path); } ptr = strtok(rescpy, "/"); while (ptr != NULL) { if (strcmp(ptr, "..") == 0) { if (pptr->prev != NULL) { pptr = pptr->prev; free(pptr->next); pptr->next = NULL; pptr->data = NULL; } } else if (strcmp(ptr, ".") != 0) { pptr->data = ptr; pptr->next = (struct dllist *)malloc(sizeof(struct dllist)); if (!pptr->next) { fprintf(stderr, "Out of memory\n"); exit(-1); } pptr->next->data = NULL; pptr->next->prev = pptr; pptr->next->next = NULL; pptr = pptr->next; } ptr = strtok(NULL, "/"); } newpath[0] = '\0'; pptr = proot; while (pptr != NULL && pptr->data != NULL) { snprintf(newpath, PATH_MAX, "%s/%s", newpath, pptr->data); pptr = pptr->next; } pptr = proot; while (pptr != NULL) { if (pptr->next != NULL) { pptr = pptr->next; free(pptr->prev); } else { free(pptr); pptr = NULL; } } free(rescpy); } static int handler(void* user, const char* section, const char* name, const char* value) { struct ftpserver *cfg = (struct ftpserver *)user; if (strcasecmp(section, "main") == 0) { if (strcasecmp(name, "port") == 0) { cfg->port = atoi(value); } else if (strcasecmp(name, "users db") == 0) { cfg->userdb = strdup(value); } else if (strcasecmp(name, "file root") == 0) { cfg->fileroot = strdup(value); if (cfg->fileroot[strlen(cfg->fileroot) -1] == '/') { cfg->fileroot[strlen(cfg->fileroot) -1] = '\0'; } } else if (strcasecmp(name, "upload folder") == 0) { cfg->upload_folder = strdup(value); } else if (strcasecmp(name, "upload sec level") == 0) { cfg->upload_seclevel = atoi(value); } else if (strcasecmp(name, "min passive port") == 0) { cfg->min_passive_port = atoi(value); } else if (strcasecmp(name, "max passive port") == 0) { cfg->max_passive_port = atoi(value); } } return 1; } char *hash_sha256(char *pass, char *salt) { char *buffer = (char *)malloc(strlen(pass) + strlen(salt) + 1); char *shash = (char *)malloc(66); unsigned char hash[SHA256_DIGEST_LENGTH]; if (!buffer) { fprintf(stderr, "Out of memory!\n"); exit(-1); } sprintf(buffer, "%s%s", pass, salt); SHA256_CTX sha256; SHA256_Init(&sha256); SHA256_Update(&sha256, buffer, strlen(buffer)); SHA256_Final(hash, &sha256); int i = 0; for(i = 0; i < SHA256_DIGEST_LENGTH; i++) { sprintf(shash + (i * 2), "%02x", hash[i]); } shash[64] = 0; free(buffer); return shash; } void send_data(struct ftpclient *client, char *msg, int len) { int n = 0; while (len > 0) { n = send(client->data_socket, msg + n, len, 0); len -= n; } } void send_msg(struct ftpclient *client, char *msg) { int len = strlen(msg); int n = 0; while (len > 0) { n = send(client->fd, msg + n, len, 0); len -= n; } } void close_tcp_connection(struct ftpclient* client) { if (client->data_srv_socket > 0) { close(client->data_srv_socket); client->data_srv_socket = -1; } if (client->data_socket > 0) { close(client->data_socket); client->data_socket = -1; } if (strlen(client->data_ip) > 0) { memset(client->data_ip, 0, 20); client->data_port = 0; } } int open_tcp_connection(struct ftpserver *cfg, struct ftpclient *client) { if (strlen(client->data_ip) != 0) { client->data_socket = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons(client->data_port); if (inet_aton(client->data_ip, &(servaddr.sin_addr)) <= 0) { fprintf(stderr, "Error in port command\n"); return 0; } if (connect(client->data_socket, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) == -1) { fprintf(stderr, "Error connecting to client\n"); return 0; } } else if (client->data_srv_socket != 0) { socklen_t sock = sizeof(struct sockaddr); struct sockaddr_in data_client; client->data_socket = accept(client->data_srv_socket, (struct sockaddr*) &data_client, &sock); if (client->data_socket < 0) { fprintf(stderr, "Accept Error\n"); return 0; } } return 1; } void handle_STOR(struct ftpserver *cfg, struct ftpclient *client, char *path) { char newpath[PATH_MAX]; struct stat s; pid_t pid; char buffer[1024]; int j; int i; FILE *fptr; if (path[0] == '/') { snprintf(newpath, PATH_MAX, "%s", path); } else { snprintf(newpath, PATH_MAX, "%s/%s", client->current_path, path); } for (i=strlen(newpath);i > 0; i--) { if (newpath[i] == '/') { newpath[i] = '\0'; break; } } if (strcmp(&newpath[1], cfg->upload_folder) == 0) { if (path[0] == '/') { snprintf(newpath, PATH_MAX, "%s%s", cfg->fileroot, path); } else { snprintf(newpath, PATH_MAX, "%s%s/%s",cfg->fileroot, client->current_path, path); } if (client->seclevel >= cfg->upload_seclevel) { if (stat(newpath, &s) != 0) { pid = fork(); if (pid == 0) { fptr = fopen(newpath, "wb"); if (fptr) { if (open_tcp_connection(cfg, client)) { send_msg(client, "150 Data connection accepted; transfer starting.\r\n"); while (1) { j = recv(client->data_socket, buffer, 1024, 0); if (j == 0) { break; } if (j < 0) { send_msg(client, "426 TCP connection was established but then broken\r\n"); fclose(fptr); unlink(newpath); close_tcp_connection(client); exit(0); } fwrite(buffer, 1, j, fptr); } fclose(fptr); close_tcp_connection(client); send_msg(client, "226 Transfer OK.\r\n"); exit(0); } else { send_msg(client, "425 TCP connection cannot be established.\r\n"); fclose(fptr); exit(0); } } } else if (pid < 0) { send_msg(client, "451 STOR Failed.\r\n"); } else { client->data_socket = -1; memset(client->data_ip, 0, 20); client->data_srv_socket = -1; } } else { send_msg(client, "553 File Exists.\n"); } } else { send_msg(client, "532 Access Denied.\n"); } } else { send_msg(client, "532 Access Denied.\n"); } } void handle_PASV(struct ftpserver *cfg, struct ftpclient *client) { char buffer[200]; char *ipcpy; char *ipptr; if (client->data_socket > 0) { close(client->data_socket); client->data_socket = -1; } if (client->data_srv_socket > 0) { close(client->data_srv_socket); } client->data_srv_socket = socket(AF_INET, SOCK_STREAM, 0); if (client->data_srv_socket < 0) { send_msg(client, "426 PASV failure.\r\n"); return; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; cfg->last_passive_port++; if (cfg->last_passive_port == cfg->max_passive_port) { cfg->last_passive_port = cfg->min_passive_port; } int port = cfg->last_passive_port; server.sin_port = htons(port); if (bind(client->data_srv_socket, (struct sockaddr*) &server, sizeof(struct sockaddr)) < 0) { send_msg(client, "426 PASV failure\r\n"); return; } if (listen(client->data_srv_socket, 1) < 0) { send_msg(client, "426 PASV failure\r\n"); } struct sockaddr_in file_addr; socklen_t file_sock_len = sizeof(struct sockaddr); getsockname(client->data_srv_socket, (struct sockaddr*) &file_addr, &file_sock_len); ipcpy = strdup(client->hostip); ipptr = strtok(ipcpy, "."); strcpy(buffer, "227 Entering Passive Mode ("); while (ipptr != NULL) { sprintf(buffer, "%s%s,", buffer, ipptr); ipptr = strtok(NULL, "."); } sprintf(buffer, "%s%d,%d)\r\n", buffer, port / 256, port % 256); send_msg(client, buffer); free(ipcpy); } void handle_RETR(struct ftpserver *cfg, struct ftpclient *client, char *file) { char *newpath; char fullpath[PATH_MAX]; FILE *fptr; char buffer[1024]; newpath = (char *)malloc(1024); parse_path(client, file, &newpath); if (newpath[0] == '/') { snprintf(fullpath, PATH_MAX, "%s%s", cfg->fileroot, newpath); } else { snprintf(fullpath, PATH_MAX, "%s/%s/%s", cfg->fileroot, client->current_path, newpath); } free(newpath); struct stat s; pid_t pid = fork(); int n; if (pid > 0) { // nothing client->data_socket = -1; memset(client->data_ip, 0, 20); client->data_srv_socket = -1; } else if (pid == 0) { if (stat(fullpath, &s) == 0) { if (!S_ISDIR(s.st_mode)) { fptr = fopen(fullpath, "rb"); if (fptr) { if (open_tcp_connection(cfg, client)) { send_msg(client, "150 Data connection accepted; transfer starting.\r\n"); do { n = fread(buffer, 1, 1024, fptr); send_data(client, buffer, n); } while (n == 1024); fclose(fptr); close_tcp_connection(client); send_msg(client, "226 Transfer OK.\r\n"); exit(0); } else { send_msg(client, "425 TCP connection cannot be established.\r\n"); fclose(fptr); exit(0); } } } } send_msg(client, "451 RETR Failed.\r\n"); exit(0); } else { send_msg(client, "451 RETR Failed.\r\n"); } } void handle_LIST(struct ftpserver *cfg, struct ftpclient *client) { char newpath[PATH_MAX]; DIR *dirp; struct dirent *dp; char linebuffer[1024]; snprintf(newpath, PATH_MAX, "%s/%s", cfg->fileroot, client->current_path); struct stat s; struct tm file_tm; struct tm now_tm; time_t now; pid_t pid = fork(); char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; if (pid > 0) { // nothing client->data_socket = -1; memset(client->data_ip, 0, 20); client->data_srv_socket = -1; } else if (pid == 0) { dirp = opendir(newpath); if (!dirp) { send_msg(client, "451 Could not read directory.\r\n"); exit(0); } if (!open_tcp_connection(cfg, client)) { send_msg(client, "425 TCP connection cannot be established.\r\n"); closedir(dirp); exit(0); } send_msg(client, "150 Data connection accepted; transfer starting.\r\n"); while ((dp = readdir(dirp)) != NULL) { snprintf(newpath, PATH_MAX, "%s/%s/%s", cfg->fileroot, client->current_path, dp->d_name); if (stat(newpath, &s) == 0) { localtime_r(&s.st_mtime, &file_tm); now = time(NULL); localtime_r(&now, &now_tm); if (now_tm.tm_year != file_tm.tm_year) { snprintf(linebuffer, 1024, "%c%c%c%c%c%c%c%c%c%c %d %d %d %lld %s %d %d %s\r\n", S_ISDIR(s.st_mode) ? 'd' : '-', S_IRUSR & s.st_mode ? 'r' : '-', S_IWUSR & s.st_mode ? 'w' : '-',S_IXUSR & s.st_mode ? 'x' : '-', S_IRGRP & s.st_mode ? 'r' : '-', S_IWGRP & s.st_mode ? 'w' : '-',S_IXGRP & s.st_mode ? 'x' : '-', S_IROTH & s.st_mode ? 'r' : '-', S_IWOTH & s.st_mode ? 'w' : '-',S_IXOTH & s.st_mode ? 'x' : '-', s.st_nlink, s.st_uid, s.st_gid, s.st_size, months[file_tm.tm_mon], file_tm.tm_mday, file_tm.tm_year + 1900, dp->d_name); } else { snprintf(linebuffer, 1024, "%c%c%c%c%c%c%c%c%c%c %d %d %d %lld %s %d %02d:%02d %s\r\n", S_ISDIR(s.st_mode) ? 'd' : '-', S_IRUSR & s.st_mode ? 'r' : '-', S_IWUSR & s.st_mode ? 'w' : '-',S_IXUSR & s.st_mode ? 'x' : '-', S_IRGRP & s.st_mode ? 'r' : '-', S_IWGRP & s.st_mode ? 'w' : '-',S_IXGRP & s.st_mode ? 'x' : '-', S_IROTH & s.st_mode ? 'r' : '-', S_IWOTH & s.st_mode ? 'w' : '-',S_IXOTH & s.st_mode ? 'x' : '-', s.st_nlink, s.st_uid, s.st_gid, s.st_size, months[file_tm.tm_mon], file_tm.tm_mday, file_tm.tm_hour, file_tm.tm_min, dp->d_name); } send_data(client, linebuffer, strlen(linebuffer)); } } closedir(dirp); close_tcp_connection(client); send_msg(client, "226 Transfer ok.\r\n"); exit(0); } else { send_msg(client, "451 Could not read directory.\r\n"); } } void handle_PORT(struct ftpserver *cfg, struct ftpclient *client, char *arg) { if (client->data_socket > 0) { close(client->data_socket); } int a,b,c,d,e,f; sscanf(arg, "%d,%d,%d,%d,%d,%d", &a, &b, &c, &d, &e, &f); sprintf(client->data_ip, "%d.%d.%d.%d", a, b, c, d); client->data_port = e * 256 + f; send_msg(client, "200 PORT command successful.\r\n"); } void handle_CWD(struct ftpserver *cfg, struct ftpclient *client, char *dir) { char *newpath; char fullpath[PATH_MAX]; struct stat s; newpath = (char *)malloc(1024); parse_path(client, dir, &newpath); snprintf(fullpath, PATH_MAX, "%s/%s", cfg->fileroot, newpath); if (stat(fullpath, &s) == 0) { if (S_ISDIR(s.st_mode)) { strcpy(client->current_path, newpath); send_msg(client, "250 Okay.\r\n"); } else { send_msg(client, "550 No such file or directory.\r\n"); } } else { send_msg(client, "550 No such file or directory.\r\n"); } free(newpath); } void handle_TYPE(struct ftpserver *cfg, struct ftpclient *client) { send_msg(client, "200 Type set to I.\r\n"); } void handle_PWD(struct ftpserver *cfg, struct ftpclient *client) { char *buffer = (char *)malloc(strlen(client->current_path) + 9); if (!buffer) { fprintf(stderr, "Out of memory\n"); exit(-1); } sprintf(buffer, "257 \"%s\"\r\n", client->current_path); send_msg(client, buffer); free(buffer); } void handle_SYST(struct ftpserver *cfg, struct ftpclient *client) { send_msg(client, "215 UNIX Type: L8\r\n"); } void handle_PASS(struct ftpserver *cfg, struct ftpclient *client, char *password) { sqlite3 *db; sqlite3_stmt *res; int rc; char *sql = "SELECT password, salt, sec_level FROM users WHERE loginname = ?"; char *pass_hash; if (strlen(client->name) == 0) { send_msg(client, "503 Need username first\r\n"); return; } if (strcmp(client->name, "anonymous") == 0 || strcmp(client->name, "ftp") == 0) { strncpy(client->password, password, 32); client->password[31] = '\0'; send_msg(client, "230 User Logged in, Proceed.\r\n"); } else { rc = sqlite3_open(cfg->userdb, &db); if (rc != SQLITE_OK) { fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); exit(-1); } sqlite3_busy_timeout(db, 5000); rc = sqlite3_prepare_v2(db, sql, -1, &res, 0); if (rc == SQLITE_OK) { sqlite3_bind_text(res, 1, client->name, -1, 0); } else { fprintf(stderr, "Failed to execute statement: %s", sqlite3_errmsg(db)); sqlite3_close(db); exit(-1); } int step = sqlite3_step(res); if (step == SQLITE_ROW) { pass_hash = hash_sha256(password, (char *)sqlite3_column_text(res, 1)); if (strcmp(pass_hash, (char *)sqlite3_column_text(res, 0)) == 0) { client->seclevel = sqlite3_column_int(res, 2); send_msg(client, "230 User Logged in, Proceed.\r\n"); strncpy(client->password, password, 32); } else { send_msg(client, "530 Username or Password unacceptable.\r\n"); } free(pass_hash); sqlite3_finalize(res); sqlite3_close(db); return; } else { send_msg(client, "530 Username or Password unacceptable.\r\n"); sqlite3_finalize(res); sqlite3_close(db); return; } } } void handle_USER(struct ftpserver *cfg, struct ftpclient *client, char *username) { strncpy(client->name, username, 16); client->name[15] = '\0'; if (strcmp(client->name, "anonymous") == 0 || strcmp(client->name, "ftp") == 0) { send_msg(client, "331 Guest login ok, send your complete e-mail address as password.\r\n"); } else { send_msg(client, "331 User name ok, need password.\r\n"); } } int handle_client(struct ftpserver *cfg, struct ftpclient *client, char *buf, int nbytes) { char cmd[1024]; char argument[1024]; int i; int cmd_len = 0; int argument_len = 0; int stage = 0; memset(cmd, 0, 1024); memset(argument, 0, 1024); while (buf[nbytes-1] == '\r' || buf[nbytes-1] == '\n') { buf[nbytes-1] = '\0'; nbytes--; } for (i=0;i 0) { handle_USER(cfg, client, argument); } else { send_msg(client, "530 Missing username.\r\n"); } } else if (strcmp(cmd, "PASS") == 0) { if (argument_len > 0) { handle_PASS(cfg, client, argument); } else { send_msg(client, "530 Username or Password not accepted.\r\n"); } } else if (strcmp(cmd, "SYST") == 0) { handle_SYST(cfg, client); } else if (strcmp(cmd, "PWD") == 0) { handle_PWD(cfg, client); } else if (strcmp(cmd, "TYPE") == 0) { handle_TYPE(cfg, client); } else if (strcmp(cmd, "CWD") == 0) { handle_CWD(cfg, client, argument); } else if (strcmp(cmd, "PORT") == 0) { handle_PORT(cfg, client, argument); } else if (strcmp(cmd, "LIST") == 0) { handle_LIST(cfg, client); } else if (strcmp(cmd, "PASV") == 0) { handle_PASV(cfg, client); } else if (strcmp(cmd, "QUIT") == 0) { send_msg(client, "221 Goodbye!\r\n"); } else if (strcmp(cmd, "RETR") == 0) { handle_RETR(cfg, client, argument); } else if (strcmp(cmd, "STOR") == 0) { handle_STOR(cfg, client, argument); } else { send_msg(client, "500 Command not recognized.\r\n"); } return 0; } void init(struct ftpserver *cfg) { int server_socket; struct sockaddr_in server, client, host_addr; fd_set master, read_fds; int fdmax = 0; socklen_t c; int i,j,k; char buf[1024]; int new_fd; int nbytes; server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { fprintf(stderr, "Couldn't create socket..\n"); exit(-1); } server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(cfg->port); if (bind(server_socket, (struct sockaddr *)&server, sizeof(server)) < 0) { perror("Bind Failed, Error\n"); exit(1); } listen(server_socket, 3); FD_ZERO(&master); FD_SET(server_socket, &master); fdmax = server_socket; c = sizeof(struct sockaddr_in); while (1) { read_fds = master; if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) { if (errno == EINTR) { continue; } perror("select"); exit(-1); } for(i = 0; i <= fdmax; i++) { if (FD_ISSET(i, &read_fds)) { if (i == server_socket) { new_fd = accept(server_socket, (struct sockaddr *)&client, (socklen_t *)&c); if (new_fd == -1) { perror("accept"); } else { if (client_count == 0) { clients = (struct ftpclient **)malloc(sizeof(struct ftpclient *)); } else { clients = (struct ftpclient **)realloc(clients, sizeof(struct ftpclient *) * (client_count + 1)); } if (!clients) { fprintf(stderr, "Out of memory!\n"); exit(-1); } clients[client_count] = (struct ftpclient *)malloc(sizeof(struct ftpclient)); memset(clients[client_count], 0, sizeof(struct ftpclient)); if (!clients[client_count]) { fprintf(stderr, "Out of memory!\n"); exit(-1); } getsockname(new_fd, (struct sockaddr*) &host_addr, &c); inet_ntop(AF_INET, &(host_addr.sin_addr), clients[client_count]->hostip, INET_ADDRSTRLEN); getpeername(new_fd, (struct sockaddr *)&client, &c); inet_ntop(AF_INET, &(client.sin_addr), clients[client_count]->ip, INET_ADDRSTRLEN); clients[client_count]->fd = new_fd; strcpy(clients[client_count]->current_path, "/"); clients[client_count]->data_socket = -1; clients[client_count]->data_srv_socket = -1; clients[client_count]->type = 1; clients[client_count]->status = 0; clients[client_count]->data_port = 0; clients[client_count]->seclevel = 0; memset(clients[client_count]->name, 0, 16); memset(clients[client_count]->password, 0, 32); client_count++; FD_SET(new_fd, &master); if (new_fd > fdmax) { fdmax = new_fd; } send_msg(clients[client_count - 1], "220 magiftpd Ready\r\n"); } } else { if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) { for (k=0;kfd == i) { if (clients[k]->data_socket > 0) { close(clients[k]->data_socket); } if (clients[k]->data_srv_socket > 0) { close(clients[k]->data_srv_socket); } free(clients[k]); for (j=k;jfd == i) { if (handle_client(cfg, clients[j], buf, nbytes) != 0) { close(clients[j]->fd); FD_CLR(i, &master); // remove from master set free(clients[j]); for (k=j;k