627 lines
18 KiB
C
627 lines
18 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <ctype.h>
|
|
|
|
#include "lftpd.h"
|
|
#include "private/lftpd_status.h"
|
|
#include "private/lftpd_inet.h"
|
|
#include "private/lftpd_log.h"
|
|
#include "private/lftpd_string.h"
|
|
#include "private/lftpd_io.h"
|
|
#include "net_socket.h"
|
|
#include "net_config.h"
|
|
#include "ff.h"
|
|
|
|
typedef struct {
|
|
const char *command;
|
|
int (*handler)(lftpd_client_t *client, const char *arg);
|
|
} command_t;
|
|
|
|
static int cmd_cwd(lftpd_client_t *client, const char *arg);
|
|
static int cmd_dele(lftpd_client_t *client, const char *arg);
|
|
static int cmd_epsv(lftpd_client_t *client, const char *arg);
|
|
static int cmd_feat(lftpd_client_t *client, const char *arg);
|
|
static int cmd_list(lftpd_client_t *client, const char *arg);
|
|
static int cmd_nlst(lftpd_client_t *client, const char *arg);
|
|
static int cmd_noop(lftpd_client_t *client, const char *arg);
|
|
static int cmd_pass(lftpd_client_t *client, const char *arg);
|
|
static int cmd_pasv(lftpd_client_t *client, const char *arg);
|
|
static int cmd_pwd(lftpd_client_t *client, const char *arg);
|
|
static int cmd_quit(lftpd_client_t *client, const char *arg);
|
|
static int cmd_retr(lftpd_client_t *client, const char *arg);
|
|
static int cmd_size(lftpd_client_t *client, const char *arg);
|
|
static int cmd_stor(lftpd_client_t *client, const char *arg);
|
|
static int cmd_syst(lftpd_client_t *client, const char *arg);
|
|
static int cmd_type(lftpd_client_t *client, const char *arg);
|
|
static int cmd_user(lftpd_client_t *client, const char *arg);
|
|
|
|
static command_t commands[] = {
|
|
{ "CWD", cmd_cwd },
|
|
{ "DELE", cmd_dele },
|
|
{ "EPSV", cmd_epsv },
|
|
{ "FEAT", cmd_feat },
|
|
{ "LIST", cmd_list },
|
|
{ "NLST", cmd_nlst },
|
|
{ "NOOP", cmd_noop },
|
|
{ "PASS", cmd_pass },
|
|
{ "PASV", cmd_pasv },
|
|
{ "PWD", cmd_pwd },
|
|
{ "QUIT", cmd_quit },
|
|
{ "RETR", cmd_retr },
|
|
{ "SIZE", cmd_size },
|
|
{ "STOR", cmd_stor },
|
|
{ "SYST", cmd_syst },
|
|
{ "TYPE", cmd_type },
|
|
{ "USER", cmd_user },
|
|
{ NULL, NULL },
|
|
};
|
|
|
|
static int send_response(int socket, int code, bool include_code,
|
|
bool multiline_start, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
char message[256];
|
|
char response[512];
|
|
|
|
va_start(args, format);
|
|
vsnprintf(message, sizeof(message), format, args);
|
|
va_end(args);
|
|
|
|
if (include_code) {
|
|
if (multiline_start) {
|
|
snprintf(response, sizeof(response), "%d-%s%s", code, message, CRLF);
|
|
} else {
|
|
snprintf(response, sizeof(response), "%d %s%s", code, message, CRLF);
|
|
}
|
|
} else {
|
|
snprintf(response, sizeof(response), "%s%s", message, CRLF);
|
|
}
|
|
|
|
return lftpd_inet_write_string(socket, response);
|
|
}
|
|
|
|
#define send_simple_response(socket, code, format, ...) \
|
|
send_response(socket, code, true, false, format, ##__VA_ARGS__)
|
|
|
|
#define send_multiline_response_begin(socket, code, format, ...) \
|
|
send_response(socket, code, true, true, format, ##__VA_ARGS__)
|
|
|
|
#define send_multiline_response_line(socket, format, ...) \
|
|
send_response(socket, 0, false, false, format, ##__VA_ARGS__)
|
|
|
|
#define send_multiline_response_end(socket, code, format, ...) \
|
|
send_response(socket, code, true, false, format, ##__VA_ARGS__)
|
|
|
|
static int send_list(int socket, const char *path)
|
|
{
|
|
static const char *directory_format = "drw-rw-rw- 1 owner group %13lu Jan 01 1970 %s";
|
|
static const char *file_format = "-rw-rw-rw- 1 owner group %13lu Jan 01 1970 %s";
|
|
|
|
void *dp = lftpd_io_opendir(path);
|
|
if (dp == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
char name[256];
|
|
uint32_t size;
|
|
int is_dir;
|
|
|
|
while (lftpd_io_readdir(dp, name, sizeof(name), &size, &is_dir) == 0) {
|
|
if (is_dir) {
|
|
send_multiline_response_line(socket, directory_format,
|
|
(unsigned long)size, name);
|
|
} else {
|
|
send_multiline_response_line(socket, file_format,
|
|
(unsigned long)size, name);
|
|
}
|
|
}
|
|
|
|
lftpd_io_closedir(dp);
|
|
return 0;
|
|
}
|
|
|
|
static int send_nlst(int socket, const char *path)
|
|
{
|
|
void *dp = lftpd_io_opendir(path);
|
|
if (dp == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
char name[256];
|
|
uint32_t size;
|
|
int is_dir;
|
|
|
|
while (lftpd_io_readdir(dp, name, sizeof(name), &size, &is_dir) == 0) {
|
|
if (!is_dir) {
|
|
send_multiline_response_line(socket, name);
|
|
}
|
|
}
|
|
|
|
lftpd_io_closedir(dp);
|
|
return 0;
|
|
}
|
|
|
|
static int send_file(int socket, const char *path)
|
|
{
|
|
int fh = lftpd_io_open_read(path);
|
|
if (fh < 0) {
|
|
lftpd_log_error("failed to open file for read");
|
|
return -1;
|
|
}
|
|
|
|
unsigned char buffer[1024];
|
|
int read_len;
|
|
|
|
while ((read_len = lftpd_io_read(fh, buffer, sizeof(buffer))) > 0) {
|
|
unsigned char *p = buffer;
|
|
while (read_len > 0) {
|
|
int write_len = lftpd_inet_write(socket, p, read_len);
|
|
if (write_len < 0) {
|
|
lftpd_log_error("write error");
|
|
lftpd_io_close(fh);
|
|
return -1;
|
|
}
|
|
p += write_len;
|
|
read_len -= write_len;
|
|
}
|
|
}
|
|
|
|
lftpd_io_close(fh);
|
|
return 0;
|
|
}
|
|
|
|
static int receive_file(int socket, const char *path)
|
|
{
|
|
int fh = lftpd_io_open_write(path);
|
|
if (fh < 0) {
|
|
lftpd_log_error("failed to open file for write");
|
|
return -1;
|
|
}
|
|
|
|
unsigned char buffer[1024];
|
|
int err;
|
|
|
|
while ((err = lftpd_inet_read(socket, buffer, sizeof(buffer))) > 0) {
|
|
if (lftpd_io_write(fh, buffer, err) != err) {
|
|
err = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
lftpd_io_close(fh);
|
|
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_cwd(lftpd_client_t *client, const char *arg)
|
|
{
|
|
if (arg == NULL || strlen(arg) == 0) {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
return -1;
|
|
}
|
|
|
|
char *path = lftpd_io_canonicalize_path(client->directory, arg);
|
|
|
|
uint32_t size;
|
|
int is_dir;
|
|
if (lftpd_io_stat(path, &size, &is_dir) != 0) {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
if (!is_dir) {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
strncpy(client->directory, path, sizeof(client->directory) - 1);
|
|
client->directory[sizeof(client->directory) - 1] = '\0';
|
|
free(path);
|
|
|
|
send_simple_response(client->socket, 250, STATUS_250);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_dele(lftpd_client_t *client, const char *arg)
|
|
{
|
|
if (arg == NULL || strlen(arg) == 0) {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
return -1;
|
|
}
|
|
|
|
char *path = lftpd_io_canonicalize_path(client->directory, arg);
|
|
|
|
uint32_t size;
|
|
int is_dir;
|
|
if (lftpd_io_stat(path, &size, &is_dir) != 0) {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
if (is_dir) {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
lftpd_io_unlink(path);
|
|
free(path);
|
|
send_simple_response(client->socket, 250, STATUS_250);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_epsv(lftpd_client_t *client, const char *arg)
|
|
{
|
|
int listener_socket = lftpd_inet_listen(0);
|
|
if (listener_socket < 0) {
|
|
send_simple_response(client->socket, 425, STATUS_425);
|
|
return -1;
|
|
}
|
|
|
|
int port = lftpd_inet_get_socket_port(listener_socket);
|
|
|
|
send_simple_response(client->socket, 229, STATUS_229, port);
|
|
|
|
lftpd_log_debug("waiting for data port connection on port %d...", port);
|
|
int data_sock = lftpd_inet_accept(listener_socket);
|
|
if (data_sock < 0) {
|
|
lftpd_log_error("error accepting client socket");
|
|
lftpd_inet_close(listener_socket);
|
|
return -1;
|
|
}
|
|
lftpd_log_debug("data port connection received...");
|
|
|
|
client->data_socket = data_sock;
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_feat(lftpd_client_t *client, const char *arg)
|
|
{
|
|
send_multiline_response_begin(client->socket, 211, STATUS_211);
|
|
send_multiline_response_line(client->socket, "EPSV");
|
|
send_multiline_response_line(client->socket, "PASV");
|
|
send_multiline_response_line(client->socket, "SIZE");
|
|
send_multiline_response_line(client->socket, "NLST");
|
|
send_multiline_response_end(client->socket, 211, STATUS_211);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_list(lftpd_client_t *client, const char *arg)
|
|
{
|
|
if (client->data_socket == -1) {
|
|
send_simple_response(client->socket, 425, STATUS_425);
|
|
return -1;
|
|
}
|
|
|
|
send_simple_response(client->socket, 150, STATUS_150);
|
|
int err = send_list(client->data_socket, client->directory);
|
|
lftpd_inet_close(client->data_socket);
|
|
client->data_socket = -1;
|
|
|
|
if (err == 0) {
|
|
send_simple_response(client->socket, 226, STATUS_226);
|
|
} else {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_nlst(lftpd_client_t *client, const char *arg)
|
|
{
|
|
if (client->data_socket == -1) {
|
|
send_simple_response(client->socket, 425, STATUS_425);
|
|
return -1;
|
|
}
|
|
|
|
send_simple_response(client->socket, 150, STATUS_150);
|
|
int err = send_nlst(client->data_socket, client->directory);
|
|
lftpd_inet_close(client->data_socket);
|
|
client->data_socket = -1;
|
|
|
|
if (err == 0) {
|
|
send_simple_response(client->socket, 226, STATUS_226);
|
|
} else {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_noop(lftpd_client_t *client, const char *arg)
|
|
{
|
|
send_simple_response(client->socket, 200, STATUS_200);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_pass(lftpd_client_t *client, const char *arg)
|
|
{
|
|
send_simple_response(client->socket, 230, STATUS_230);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_pasv(lftpd_client_t *client, const char *arg)
|
|
{
|
|
int listener_socket = lftpd_inet_listen(0);
|
|
if (listener_socket < 0) {
|
|
send_simple_response(client->socket, 425, STATUS_425);
|
|
return -1;
|
|
}
|
|
|
|
int port = lftpd_inet_get_socket_port(listener_socket);
|
|
|
|
{
|
|
struct net_sockaddr_in local_addr;
|
|
int addrlen = sizeof(local_addr);
|
|
int err = net_getsockname(client->socket,
|
|
(struct net_sockaddr *)&local_addr, &addrlen);
|
|
if (err != 0) {
|
|
lftpd_log_error("error getting client IP info");
|
|
send_simple_response(client->socket, 425, STATUS_425);
|
|
lftpd_inet_close(listener_socket);
|
|
return -1;
|
|
}
|
|
|
|
uint32_t ip = net_htonl(local_addr.sin_addr.s_addr);
|
|
send_simple_response(client->socket, 227, STATUS_227,
|
|
(ip >> 24) & 0xff,
|
|
(ip >> 16) & 0xff,
|
|
(ip >> 8) & 0xff,
|
|
(ip >> 0) & 0xff,
|
|
(port >> 8) & 0xff, (port >> 0) & 0xff);
|
|
}
|
|
|
|
lftpd_log_debug("waiting for data port connection on port %d...", port);
|
|
int data_sock = lftpd_inet_accept(listener_socket);
|
|
if (data_sock < 0) {
|
|
lftpd_log_error("error accepting client socket");
|
|
lftpd_inet_close(listener_socket);
|
|
return -1;
|
|
}
|
|
lftpd_log_debug("data port connection received...");
|
|
|
|
client->data_socket = data_sock;
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_pwd(lftpd_client_t *client, const char *arg)
|
|
{
|
|
send_simple_response(client->socket, 257, "\"%s\"", client->directory);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_quit(lftpd_client_t *client, const char *arg)
|
|
{
|
|
send_simple_response(client->socket, 221, STATUS_221);
|
|
return -1;
|
|
}
|
|
|
|
static int cmd_retr(lftpd_client_t *client, const char *arg)
|
|
{
|
|
if (client->data_socket == -1) {
|
|
send_simple_response(client->socket, 425, STATUS_425);
|
|
return -1;
|
|
}
|
|
|
|
send_simple_response(client->socket, 150, STATUS_150);
|
|
char *path = lftpd_io_canonicalize_path(client->directory, arg);
|
|
lftpd_log_debug("send '%s'", path);
|
|
int err = send_file(client->data_socket, path);
|
|
free(path);
|
|
lftpd_inet_close(client->data_socket);
|
|
client->data_socket = -1;
|
|
|
|
if (err == 0) {
|
|
send_simple_response(client->socket, 226, STATUS_226);
|
|
} else {
|
|
send_simple_response(client->socket, 450, STATUS_450);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_size(lftpd_client_t *client, const char *arg)
|
|
{
|
|
if (arg == NULL) {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
return 0;
|
|
}
|
|
|
|
char *path = lftpd_io_canonicalize_path(client->directory, arg);
|
|
lftpd_log_debug("size %s", path);
|
|
|
|
uint32_t size;
|
|
int is_dir;
|
|
if (lftpd_io_stat(path, &size, &is_dir) == 0 && !is_dir) {
|
|
send_simple_response(client->socket, 213, "%lu", (unsigned long)size);
|
|
} else {
|
|
send_simple_response(client->socket, 550, STATUS_550);
|
|
}
|
|
|
|
free(path);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_stor(lftpd_client_t *client, const char *arg)
|
|
{
|
|
if (client->data_socket == -1) {
|
|
send_simple_response(client->socket, 425, STATUS_425);
|
|
return -1;
|
|
}
|
|
|
|
send_simple_response(client->socket, 150, STATUS_150);
|
|
char *path = lftpd_io_canonicalize_path(client->directory, arg);
|
|
lftpd_log_debug("receive '%s'", path);
|
|
int err = receive_file(client->data_socket, path);
|
|
free(path);
|
|
lftpd_inet_close(client->data_socket);
|
|
client->data_socket = -1;
|
|
|
|
if (err == 0) {
|
|
send_simple_response(client->socket, 226, STATUS_226);
|
|
} else {
|
|
send_simple_response(client->socket, 450, STATUS_450);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_syst(lftpd_client_t *client, const char *arg)
|
|
{
|
|
send_simple_response(client->socket, 215, "UNIX Type: L8");
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_type(lftpd_client_t *client, const char *arg)
|
|
{
|
|
send_simple_response(client->socket, 200, STATUS_200);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_user(lftpd_client_t *client, const char *arg)
|
|
{
|
|
send_simple_response(client->socket, 230, STATUS_230);
|
|
return 0;
|
|
}
|
|
|
|
static int handle_control_channel(lftpd_client_t *client)
|
|
{
|
|
int err = send_simple_response(client->socket, 220, STATUS_220);
|
|
if (err != 0) {
|
|
lftpd_log_error("error sending welcome message");
|
|
goto cleanup;
|
|
}
|
|
|
|
char read_buffer[512];
|
|
|
|
while (err == 0) {
|
|
int line_len = lftpd_inet_read_line(client->socket, read_buffer,
|
|
sizeof(read_buffer));
|
|
if (line_len != 0) {
|
|
lftpd_log_error("error reading next command");
|
|
goto cleanup;
|
|
}
|
|
|
|
int index;
|
|
char *p = strchr(read_buffer, ' ');
|
|
if (p != NULL) {
|
|
index = (int)(p - read_buffer);
|
|
} else {
|
|
index = (int)strlen(read_buffer);
|
|
}
|
|
|
|
if (index >= 5) {
|
|
err = send_simple_response(client->socket, 500, STATUS_500);
|
|
continue;
|
|
}
|
|
|
|
char command_tmp[4 + 1];
|
|
memset(command_tmp, 0, sizeof(command_tmp));
|
|
memcpy(command_tmp, read_buffer, (size_t)index);
|
|
|
|
for (int i = 0; command_tmp[i]; i++) {
|
|
command_tmp[i] = (char)toupper((int)command_tmp[i]);
|
|
}
|
|
|
|
bool matched = false;
|
|
for (int i = 0; commands[i].command; i++) {
|
|
if (strcmp(commands[i].command, command_tmp) == 0) {
|
|
char arg_buf[512];
|
|
const char *arg = NULL;
|
|
|
|
if (index < (int)strlen(read_buffer)) {
|
|
strncpy(arg_buf, read_buffer + index + 1, sizeof(arg_buf) - 1);
|
|
arg_buf[sizeof(arg_buf) - 1] = '\0';
|
|
arg = lftpd_string_trim(arg_buf);
|
|
}
|
|
|
|
err = commands[i].handler(client, arg);
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!matched) {
|
|
send_simple_response(client->socket, 502, STATUS_502);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
lftpd_inet_close(client->socket);
|
|
return 0;
|
|
}
|
|
|
|
int lftpd_start(const char *directory, int port, lftpd_t *lftpd)
|
|
{
|
|
memset(lftpd, 0, sizeof(lftpd_t));
|
|
|
|
lftpd->directory = directory;
|
|
lftpd->port = port;
|
|
|
|
while (true) {
|
|
lftpd->server_socket = lftpd_inet_listen(port);
|
|
if (lftpd->server_socket < 0) {
|
|
lftpd_log_error("error creating listener");
|
|
vTaskDelay(pdMS_TO_TICKS(3000));
|
|
continue;
|
|
}
|
|
|
|
lftpd_log_info("waiting for connection...");
|
|
|
|
int client_socket = lftpd_inet_accept(lftpd->server_socket);
|
|
if (client_socket < 0) {
|
|
lftpd_log_error("error accepting client socket");
|
|
lftpd_inet_close(lftpd->server_socket);
|
|
break;
|
|
}
|
|
|
|
{
|
|
char ip_str[16];
|
|
struct net_sockaddr_in addr;
|
|
int addrlen = sizeof(addr);
|
|
if (net_getsockname(client_socket,
|
|
(struct net_sockaddr *)&addr, &addrlen) == 0) {
|
|
net_inet_ntoa(addr.sin_addr.s_addr, ip_str);
|
|
int remote_port = lftpd_inet_get_socket_port(client_socket);
|
|
lftpd_log_info("connection received from %s:%d...",
|
|
ip_str, remote_port);
|
|
} else {
|
|
lftpd_log_info("connection received...");
|
|
}
|
|
}
|
|
|
|
lftpd_client_t client;
|
|
memset(&client, 0, sizeof(client));
|
|
strncpy(client.directory, directory, sizeof(client.directory) - 1);
|
|
client.directory[sizeof(client.directory) - 1] = '\0';
|
|
client.socket = client_socket;
|
|
client.data_socket = -1;
|
|
|
|
lftpd->client = &client;
|
|
handle_control_channel(&client);
|
|
lftpd->client = NULL;
|
|
|
|
/* standalone mode: socket was closed in handle_control_channel,
|
|
* loop back to create a new listener */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lftpd_stop(lftpd_t *lftpd)
|
|
{
|
|
if (lftpd->server_socket >= 0) {
|
|
lftpd_inet_close(lftpd->server_socket);
|
|
}
|
|
if (lftpd->client) {
|
|
lftpd_inet_close(lftpd->client->socket);
|
|
}
|
|
return 0;
|
|
}
|