mirror of
https://github.com/drakmor/ftpsrv.git
synced 2026-10-22 20:03:01 -05:00
387 lines
7.9 KiB
C
387 lines
7.9 KiB
C
/* Copyright (C) 2025 John Törnblom
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by the
|
|
Free Software Foundation; either version 3, or (at your option) any
|
|
later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; see the file COPYING. If not, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <ifaddrs.h>
|
|
#include <netinet/tcp.h>
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "cmd.h"
|
|
#include "io.h"
|
|
#include "log.h"
|
|
#include "notify.h"
|
|
|
|
|
|
/**
|
|
* Map names of commands to function entry points.
|
|
**/
|
|
typedef struct ftp_command {
|
|
const char *name;
|
|
ftp_command_fn_t *func;
|
|
} ftp_command_t;
|
|
|
|
|
|
/**
|
|
* Lookup table for FTP commands.
|
|
**/
|
|
static ftp_command_t commands[] = {
|
|
{"APPE", ftp_cmd_APPE},
|
|
{"CDUP", ftp_cmd_CDUP},
|
|
{"CWD", ftp_cmd_CWD},
|
|
{"DELE", ftp_cmd_DELE},
|
|
{"LIST", ftp_cmd_LIST},
|
|
{"MKD", ftp_cmd_MKD},
|
|
{"NOOP", ftp_cmd_NOOP},
|
|
{"PASV", ftp_cmd_PASV},
|
|
{"PORT", ftp_cmd_PORT},
|
|
{"PWD", ftp_cmd_PWD},
|
|
{"QUIT", ftp_cmd_QUIT},
|
|
{"REST", ftp_cmd_REST},
|
|
{"RETR", ftp_cmd_RETR},
|
|
{"RMD", ftp_cmd_RMD},
|
|
{"RNFR", ftp_cmd_RNFR},
|
|
{"RNTO", ftp_cmd_RNTO},
|
|
{"SIZE", ftp_cmd_SIZE},
|
|
{"STOR", ftp_cmd_STOR},
|
|
{"SYST", ftp_cmd_SYST},
|
|
{"TYPE", ftp_cmd_TYPE},
|
|
{"USER", ftp_cmd_USER},
|
|
|
|
// custom commands
|
|
{"KILL", ftp_cmd_KILL},
|
|
{"MTRW", ftp_cmd_MTRW},
|
|
{"SELF", ftp_cmd_SELF},
|
|
{"CHMOD", ftp_cmd_CHMOD},
|
|
|
|
// duplicates that ensure commands are 4 bytes long
|
|
{"XCUP", ftp_cmd_CWD},
|
|
{"XMKD", ftp_cmd_MKD},
|
|
{"XPWD", ftp_cmd_PWD},
|
|
{"XRMD", ftp_cmd_RMD},
|
|
|
|
// not yet implemnted
|
|
{"XRCP", ftp_cmd_unavailable},
|
|
{"XRSQ", ftp_cmd_unavailable},
|
|
{"XSEM", ftp_cmd_unavailable},
|
|
{"XSEN", ftp_cmd_unavailable},
|
|
};
|
|
|
|
|
|
/**
|
|
* Number of FTP commands in the lookup table.
|
|
**/
|
|
static int nb_ftp_commands = (sizeof(commands)/sizeof(ftp_command_t));
|
|
|
|
|
|
/**
|
|
* Read a line from a file descriptor.
|
|
**/
|
|
static char*
|
|
ftp_readline(int fd) {
|
|
int bufsize = 1024;
|
|
int position = 0;
|
|
char *buffer_backup;
|
|
char *buffer = calloc(bufsize, sizeof(char));
|
|
char c;
|
|
|
|
if(!buffer) {
|
|
FTP_LOG_PERROR("malloc");
|
|
return NULL;
|
|
}
|
|
|
|
while(1) {
|
|
int len = read(fd, &c, 1);
|
|
if(len == -1 && errno == EINTR) {
|
|
continue;
|
|
}
|
|
|
|
if(len <= 0) {
|
|
free(buffer);
|
|
return NULL;
|
|
}
|
|
|
|
if(c == '\r') {
|
|
buffer[position] = '\0';
|
|
position = 0;
|
|
continue;
|
|
}
|
|
|
|
if(c == '\n') {
|
|
return buffer;
|
|
}
|
|
|
|
buffer[position++] = c;
|
|
|
|
if(position >= bufsize) {
|
|
bufsize += 1024;
|
|
buffer_backup = buffer;
|
|
buffer = realloc(buffer, bufsize);
|
|
if(!buffer) {
|
|
FTP_LOG_PERROR("realloc");
|
|
free(buffer_backup);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Execute an FTP command.
|
|
**/
|
|
static int
|
|
ftp_execute(ftp_env_t *env, char *line) {
|
|
char *sep = strchr(line, ' ');
|
|
char *arg = strchr(line, 0);
|
|
|
|
if(sep) {
|
|
sep[0] = 0;
|
|
arg = sep + 1;
|
|
}
|
|
|
|
for(int i=0; i<nb_ftp_commands; i++) {
|
|
if(strcmp(line, commands[i].name)) {
|
|
continue;
|
|
}
|
|
|
|
return commands[i].func(env, arg);
|
|
}
|
|
|
|
return ftp_cmd_unknown(env, arg);
|
|
}
|
|
|
|
|
|
/**
|
|
* Greet a new FTP connection.
|
|
**/
|
|
static int
|
|
ftp_greet(ftp_env_t *env) {
|
|
char msg[0x100];
|
|
size_t len;
|
|
|
|
snprintf(msg, sizeof(msg),
|
|
"220-Welcome to ftpsrv.elf running on pid %d, compiled at %s %s\r\n",
|
|
getpid(), __DATE__, __TIME__);
|
|
strncat(msg, "220 Service is ready\r\n", sizeof(msg)-1);
|
|
|
|
len = strlen(msg);
|
|
if(io_nwrite(env->active_fd, msg, len)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Entry point for new FTP connections.
|
|
**/
|
|
static void*
|
|
ftp_thread(void *args) {
|
|
ftp_env_t env;
|
|
bool running;
|
|
char *line;
|
|
char* cmd;
|
|
int opt;
|
|
|
|
env.data_fd = -1;
|
|
env.passive_fd = -1;
|
|
env.active_fd = (int)(long)args;
|
|
|
|
env.type = 'I';
|
|
env.data_offset = 0;
|
|
env.self2elf = 1;
|
|
|
|
strcpy(env.cwd, "/");
|
|
memset(env.rename_path, 0, sizeof(env.rename_path));
|
|
memset(&env.data_addr, 0, sizeof(env.data_addr));
|
|
|
|
opt = 1;
|
|
if(setsockopt(env.active_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt))) {
|
|
FTP_LOG_PERROR("setsockopt");
|
|
}
|
|
|
|
env.readbuf_size = IO_COPY_BUFSIZE;
|
|
if(!(env.readbuf=malloc(env.readbuf_size))) {
|
|
FTP_LOG_PERROR("malloc");
|
|
running = 0;
|
|
} else {
|
|
running = !ftp_greet(&env);
|
|
}
|
|
|
|
while(running) {
|
|
if(!(line=ftp_readline(env.active_fd))) {
|
|
break;
|
|
}
|
|
|
|
cmd = line;
|
|
if(!strncmp(line, "SITE ", 5)) {
|
|
cmd += 5;
|
|
}
|
|
|
|
if(ftp_execute(&env, cmd)) {
|
|
running = false;
|
|
}
|
|
|
|
free(line);
|
|
}
|
|
|
|
if(env.active_fd > 0) {
|
|
close(env.active_fd);
|
|
}
|
|
|
|
if(env.passive_fd > 0) {
|
|
close(env.passive_fd);
|
|
}
|
|
|
|
if(env.data_fd > 0) {
|
|
close(env.data_fd);
|
|
}
|
|
|
|
if(env.readbuf) {
|
|
free(env.readbuf);
|
|
}
|
|
|
|
pthread_exit(NULL);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Serve FTP on a given port.
|
|
**/
|
|
int
|
|
ftp_serve(uint16_t port, int notify_user) {
|
|
struct sockaddr_in server_addr;
|
|
struct sockaddr_in client_addr;
|
|
char ip[INET_ADDRSTRLEN];
|
|
struct ifaddrs *ifaddr;
|
|
int ifaddr_wait = 1;
|
|
socklen_t addr_len;
|
|
pthread_t trd;
|
|
int connfd;
|
|
int srvfd;
|
|
|
|
if(notify_user) {
|
|
puts(".-------------------------------------------------------------.");
|
|
puts("| __ _ _ __ |");
|
|
puts("| / _| | |_ _ __ ___ _ __ __ __ ___ | | / _||");
|
|
puts("|| |_ | __| | '_ \\ / __| | '__| \\ \\ / / / _ \\ | | | |_ |");
|
|
puts("|| _| | |_ | |_) | \\__ \\ | | \\ V / _ | __/ | | | _||");
|
|
puts("||_| \\__| | .__/ |___/ |_| \\_/ (_) \\___| |_| |_| |");
|
|
puts("| |_| |");
|
|
printf("| %-26s Copyright (C) 2025 John Törnblom |\n", VERSION_TAG);
|
|
puts("'-------------------------------------------------------------'");
|
|
}
|
|
|
|
if(getifaddrs(&ifaddr) == -1) {
|
|
FTP_LOG_PERROR("getifaddrs");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Enumerate all AF_INET IPs
|
|
for(struct ifaddrs *ifa=ifaddr; ifa!=NULL; ifa=ifa->ifa_next) {
|
|
if(ifa->ifa_addr == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if(ifa->ifa_addr->sa_family != AF_INET) {
|
|
continue;
|
|
}
|
|
|
|
// skip localhost
|
|
if(!strncmp("lo", ifa->ifa_name, 2)) {
|
|
continue;
|
|
}
|
|
|
|
struct sockaddr_in *in = (struct sockaddr_in*)ifa->ifa_addr;
|
|
inet_ntop(AF_INET, &(in->sin_addr), ip, sizeof(ip));
|
|
|
|
// skip interfaces without an ip
|
|
if(!strncmp("0.", ip, 2)) {
|
|
continue;
|
|
}
|
|
|
|
if(notify_user) {
|
|
notify("Serving FTP on %s:%d (%s)", ip, port, ifa->ifa_name);
|
|
}
|
|
|
|
ifaddr_wait = 0;
|
|
}
|
|
|
|
freeifaddrs(ifaddr);
|
|
|
|
if(ifaddr_wait) {
|
|
return 0;
|
|
}
|
|
|
|
if((srvfd=socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
|
FTP_LOG_PERROR("socket");
|
|
return -1;
|
|
}
|
|
|
|
if(setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) {
|
|
FTP_LOG_PERROR("setsockopt");
|
|
return -1;
|
|
}
|
|
|
|
memset(&server_addr, 0, sizeof(server_addr));
|
|
server_addr.sin_family = AF_INET;
|
|
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
server_addr.sin_port = htons(port);
|
|
|
|
if(bind(srvfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) != 0) {
|
|
FTP_LOG_PERROR("bind");
|
|
return -1;
|
|
}
|
|
|
|
if(listen(srvfd, SOMAXCONN) != 0) {
|
|
FTP_LOG_PERROR("listen");
|
|
return -1;
|
|
}
|
|
|
|
addr_len = sizeof(client_addr);
|
|
|
|
while(1) {
|
|
if((connfd=accept(srvfd, (struct sockaddr*)&client_addr, &addr_len)) < 0) {
|
|
FTP_LOG_PERROR("accept");
|
|
break;
|
|
}
|
|
|
|
if(!pthread_create(&trd, NULL, ftp_thread, (void*)(long)connfd)) {
|
|
pthread_detach(trd);
|
|
}
|
|
}
|
|
|
|
return close(srvfd);
|
|
}
|
|
|
|
|
|
/*
|
|
Local Variables:
|
|
c-file-style: "gnu"
|
|
End:
|
|
*/
|