#include #include #include #include #include #include #include #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 = -1; int retry; for (retry = 0; retry < 3; retry++) { listener_socket = lftpd_inet_listen(0); if (listener_socket >= 0) break; vTaskDelay(pdMS_TO_TICKS(200)); } 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; /* 等待 CH395F 稳定(Trap 09) */ vTaskDelay(pdMS_TO_TICKS(200)); /* 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; }