summaryrefslogtreecommitdiff
path: root/decoders/ftp/ftp_decoder_proto.c
diff options
context:
space:
mode:
Diffstat (limited to 'decoders/ftp/ftp_decoder_proto.c')
-rw-r--r--decoders/ftp/ftp_decoder_proto.c653
1 files changed, 653 insertions, 0 deletions
diff --git a/decoders/ftp/ftp_decoder_proto.c b/decoders/ftp/ftp_decoder_proto.c
new file mode 100644
index 0000000..064d176
--- /dev/null
+++ b/decoders/ftp/ftp_decoder_proto.c
@@ -0,0 +1,653 @@
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "ftp_decoder_inner.h"
+#include "ftp_decoder_util.h"
+#include "ftp_decoder_hash.h"
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <time.h>
+ /*
+ https://datatracker.ietf.org/doc/html/rfc959
+ -------------
+ |/---------\|
+ || User || --------
+ ||Interface|<--->| User |
+ |\----^----/| --------
+ ---------- | | |
+ |/------\| FTP Commands |/----V----\|
+ ||Server|<---------------->| User ||
+ || PI || FTP Replies || PI ||
+ |\--^---/| |\----^----/|
+ | | | | | |
+ -------- |/--V---\| Data |/----V----\| --------
+ | File |<--->|Server|<---------------->| User |<--->| File |
+ |System| || DTP || Connection || DTP || |System|
+ -------- |\------/| |\---------/| --------
+ ---------- -------------
+
+ Server-FTP USER-FTP
+ */
+
+ /*
+ ctrl link command data link tcp/ip stack event
+ ---------------------------------------------------------------------------------------------------
+ PORT 192,168,38,2,202,95
+ listen tcp port: 51807
+ 200 PORT command successful.
+ LIST
+ server use src tcp port 20 connecting to client tcp port 51807
+ data connection is established
+ send 'ls -l' result to cliet
+ 226 Directory send OK.
+ close the tcp data connection
+ waiting for next command...
+ */
+
+ static int ftp_create_new_datalink_hash_item(struct ftp_decoder *ftp_env, struct ftp_decoder_exdata *ftp_ext)
+ {
+ int thread_id = module_manager_get_thread_id(ftp_env->mod_mgr_ref);
+ ftp_decoder_stat_incrby(thread_id, ftp_env, FTPD_STAT_DATA_LINK_HTABLE_ITEMS, 1);
+
+ struct ftp_datalink_htable *hitem = (struct ftp_datalink_htable *)calloc(1, sizeof(struct ftp_datalink_htable));
+ hitem->ftp_ext = ftp_exdata_deep_clone(ftp_ext);
+ hitem->ftp_ext->link_type = FTP_LINK_DATA;
+ hitem->insert_htable_time = time(NULL);
+ memcpy(&hitem->hkey, &ftp_ext->ctrl_ext.last_data_link_key, sizeof(ftp_hash_key_t));
+ memset(&ftp_ext->data_ext, 0, sizeof(struct ftp_decoder_data_exdata));
+ int ret = ftp_hash_add(&ftp_ext->ctrl_ext.last_data_link_key, sizeof(ftp_hash_key_t), hitem);
+ if (ret < 0)
+ {
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, "ftp data link hash item add failed, session:%s, hashkey: %s",
+ session_get_readable_addr(ftp_ext->sess_ref), ftp_hash_key_to_str(&ftp_ext->ctrl_ext.last_data_link_key));
+ ftp_decoder_do_exdata_free(hitem->ftp_ext, ftp_env);
+ free(hitem);
+ return -1;
+ }
+ return 0;
+ }
+
+ /* According to the CWD, server address, file name of the current command, concatenated into a complete URL */
+ static void ftp_assemble_uri(struct session *sess, struct ftp_decoder_exdata *ftp_ext)
+ {
+ char dip_str[INET6_ADDRSTRLEN] = {};
+ int url_len = 0;
+ struct ftp_session_addr ses_addr = {};
+ ftp_session_get_addr(sess, &ses_addr);
+ uint16_t server_port_host = ntohs(ses_addr.dport);
+ if (ses_addr.af_inet == AF_INET)
+ {
+ inet_ntop(AF_INET, &ses_addr.daddr4, dip_str, INET_ADDRSTRLEN);
+ }
+ else
+ {
+ inet_ntop(AF_INET6, &ses_addr.daddr6, dip_str, INET6_ADDRSTRLEN);
+ }
+
+ char file_name[PATH_MAX] = {};
+ char absolute_file_path[PATH_MAX] = {};
+ char tmp_url[PATH_MAX] = {};
+ snprintf(file_name, sizeof(file_name), "%.*s", IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer));
+ ftp_join_absolute_path(ftp_ext->ctrl_ext.current_working_dir, (const char *)file_name, absolute_file_path, PATH_MAX);
+ if (server_port_host != 21)
+ {
+ url_len = snprintf(tmp_url, sizeof(tmp_url), "ftp://%s:%u%s", dip_str, server_port_host, absolute_file_path);
+ }
+ else
+ {
+ url_len = snprintf(tmp_url, sizeof(tmp_url), "ftp://%s%s", dip_str, absolute_file_path);
+ }
+
+ struct ftp_dtp *dtp = ftp_ext->dtp;
+ if (dtp->uri != NULL)
+ {
+ free((void *)dtp->uri); // update every time for retr/stor commands
+ }
+ dtp->uri = (char *)calloc(1, url_len + 1);
+ memcpy((char *)dtp->uri, tmp_url, url_len);
+ return;
+ }
+
+ int ftp_cmd_handler_non_implemented(struct session *sess UNUSED, struct ftp_decoder_exdata *ftp_ext UNUSED, struct ftp_decoder *ftp_env UNUSED)
+ {
+ return 0;
+ }
+
+ int ftp_cmd_handler_do_user(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ fstring_safe_dup(&ftp_ext->ftp_login_pri.username, &ftp_ext->ctrl_ext.cmd_line.arg_refer);
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "session: %s, USER: %.*s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer));
+ return 0;
+ }
+
+ int ftp_cmd_handler_do_pass(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ fstring_safe_dup(&ftp_ext->ftp_login_pri.password, &ftp_ext->ctrl_ext.cmd_line.arg_refer);
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "session: %s, PASS: %.*s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer));
+ return 0;
+ }
+
+ int ftp_cmd_handler_do_cwd(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ char tmp_path[PATH_MAX] = {};
+ snprintf(tmp_path, sizeof(tmp_path), "%.*s", IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer));
+ ftp_join_absolute_path(ftp_ext->ctrl_ext.current_working_dir, tmp_path, ftp_ext->ctrl_ext.current_working_dir, sizeof(ftp_ext->ctrl_ext.current_working_dir));
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "session: %s, CWD: %.*s, absolute pwd: %s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer), ftp_ext->ctrl_ext.current_working_dir);
+ return 0;
+ }
+
+ int ftp_parse_ipv4_port_style(const fstring *cmd_str, unsigned int *ipv4_net, unsigned short *port_net)
+ {
+ unsigned int fields[6] = {};
+ char raw_cmd_str_tmp[FTP_CMD_MAX_LENGTH + 1] = {};
+ size_t min_size = MIN(FTP_CMD_MAX_LENGTH, cmd_str->iov_len + 1);
+ snprintf(raw_cmd_str_tmp, min_size, "%.*s", (int)min_size, (char *)cmd_str->iov_base);
+ char only_integer_str_tmp[FTP_CMD_MAX_LENGTH + 1] = {};
+ if (sscanf(raw_cmd_str_tmp, "%*[a-zA-Z (]%255[0-9,]", only_integer_str_tmp) <= 0)
+ {
+ if (sscanf(raw_cmd_str_tmp, "%255[1234567890,]", only_integer_str_tmp) <= 0)
+ {
+ return -1;
+ }
+ }
+ int ret = sscanf(only_integer_str_tmp, "%u,%u,%u,%u,%u,%u", &fields[0], &fields[1], &fields[2], &fields[3], &fields[4], &fields[5]);
+ if (ret != 6)
+ {
+ return -1;
+ }
+ unsigned int dst_ip_host = (fields[0] << 24) | (fields[1] << 16) | (fields[2] << 8) | fields[3];
+ unsigned short dst_port_host = (fields[4] << 8) | fields[5];
+ *ipv4_net = htonl(dst_ip_host);
+ *port_net = htons(dst_port_host);
+ return 0;
+ }
+
+ void ftp_login_info_free(struct ftp_login_internal *login_info)
+ {
+ if (login_info->username.iov_base)
+ {
+ free((void *)login_info->username.iov_base);
+ }
+ if (login_info->password.iov_base)
+ {
+ free((void *)login_info->password.iov_base);
+ }
+ }
+
+ void ftp_exdata_dtp_free(struct ftp_dtp *dtp)
+ {
+ if (NULL == dtp)
+ {
+ return;
+ }
+ if (dtp->uri)
+ {
+ free((void *)dtp->uri);
+ }
+ free(dtp);
+ }
+
+ static void ftp_exdata_dtp_renew(struct ftp_decoder_exdata *ftp_ext)
+ {
+ if (ftp_ext->dtp)
+ {
+ ftp_exdata_dtp_free(ftp_ext->dtp);
+ }
+ ftp_ext->dtp = (struct ftp_dtp *)calloc(1, sizeof(struct ftp_dtp));
+ ftp_ext->dtp->mode = ftp_ext->ctrl_ext.mode;
+ ftp_ext->dtp->dir = ftp_ext->ctrl_ext.dir;
+ ftp_ext->dtp->cmd = ftp_ext->ctrl_ext.cmd_type;
+ ftp_ext->dtp->uri = NULL;
+ return;
+ }
+
+ /* PORT command only support IPv4, arg pattern: h1,h2,h3,h4,p1,p2 */
+ int ftp_cmd_handler_do_port(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ ftp_ext->ctrl_ext.mode = FTP_TRANSFER_PORT;
+ unsigned int dst_ip_net;
+ unsigned short dst_port_net;
+ if (ftp_parse_ipv4_port_style(&ftp_ext->ctrl_ext.cmd_line.arg_refer, &dst_ip_net, &dst_port_net) < 0)
+ {
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "do port cmd, parse error: %.*s", IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.cmd_line));
+ return -1;
+ }
+ struct ftp_session_addr ses_addr = {};
+ ftp_session_get_addr(sess, &ses_addr);
+ // in active mode, new data link src ip address is server ip address
+ ftp_make_hkey_v4(&ftp_ext->ctrl_ext.last_data_link_key, ses_addr.daddr4, dst_ip_net, dst_port_net);
+
+ ftp_create_new_datalink_hash_item(ftp_env, ftp_ext);
+ int thread_idx = module_manager_get_thread_id(ftp_env->mod_mgr_ref);
+ ftp_decoder_stat_incrby(thread_idx, ftp_env, FTPD_STAT_NEGOTIATE_DATA_LINK, 1);
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "do port cmd: %.*s, parsed dip:%x, dport:%u, hashkey:%s",
+ IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer), dst_ip_net, dst_port_net,
+ ftp_hash_key_to_str(&ftp_ext->ctrl_ext.last_data_link_key));
+ return 0;
+ }
+
+ /* PASV command only support IPv4, the response like: '227 Entering Passive Mode (218,13,32,6,78,40).' */
+ int ftp_cmd_handler_do_pasv(struct session *sess, struct ftp_decoder_exdata *ftp_ext UNUSED, struct ftp_decoder *ftp_env)
+ {
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s' PASV command", session_get_readable_addr(sess));
+ return 0;
+ }
+
+ static void ftp_update_datalink_dtp(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ struct ftp_datalink_htable *hitem = ftp_hash_search(&ftp_ext->ctrl_ext.last_data_link_key);
+ if (NULL == hitem)
+ {
+ STELLAR_LOG_WARN(ftp_env->logger_ref, FTP_MODULE_NAME, "data link not found, session:%s, key:%s",
+ session_get_readable_addr(sess), ftp_hash_key_to_str(&ftp_ext->ctrl_ext.last_data_link_key));
+ return;
+ }
+ hitem->ftp_ext->link_type = FTP_LINK_DATA;
+ hitem->ftp_ext->dtp = ftp_dtp_deep_clone(ftp_ext->dtp);
+ }
+
+ int ftp_cmd_handler_do_list(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ ftp_exdata_dtp_renew(ftp_ext);
+ // no uri for list command
+ ftp_update_datalink_dtp(sess, ftp_ext, ftp_env);
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s' LIST command", session_get_readable_addr(sess));
+ return 0;
+ }
+
+ int ftp_cmd_handler_do_stor(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ ftp_ext->ctrl_ext.dir = FTP_TRANSFER_STOR;
+ ftp_exdata_dtp_renew(ftp_ext);
+ ftp_assemble_uri(sess, ftp_ext);
+ ftp_update_datalink_dtp(sess, ftp_ext, ftp_env);
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s', STOR uri:%.*s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer));
+ return 0;
+ }
+
+ int ftp_cmd_handler_do_retr(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ ftp_ext->ctrl_ext.dir = FTP_TRANSFER_RETR;
+ ftp_exdata_dtp_renew(ftp_ext);
+ ftp_assemble_uri(sess, ftp_ext);
+ ftp_update_datalink_dtp(sess, ftp_ext, ftp_env);
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s', RETR uri:%.*s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer));
+ return 0;
+ }
+
+ int ftp_parse_eprt_ipport_style(const fstring *arg_str, struct in6_addr *ipd_addr, unsigned short *port_net, struct ftp_decoder *ftp_env)
+ {
+ unsigned int port_host;
+ int inet_proto;
+ char ip6_addr_str[INET6_ADDRSTRLEN] = {};
+ char raw_cmd_str_tmp[arg_str->iov_len + 1];
+ memset(raw_cmd_str_tmp, 0, sizeof(raw_cmd_str_tmp));
+ memcpy(raw_cmd_str_tmp, arg_str->iov_base, arg_str->iov_len);
+
+ char *save_ptr, *ptr;
+ const char *delim = " |\t";
+ ptr = strtok_r(raw_cmd_str_tmp, delim, &save_ptr);
+ if (NULL == ptr)
+ {
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, "ftp EPRT command parse error: %.*s", IOVEC_PRINT_PTR(arg_str));
+ return -1;
+ }
+ inet_proto = atoi(ptr);
+ if (2 != inet_proto)
+ {
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, "ftp EPRT command parse error: %.*s, not support inet: %d", IOVEC_PRINT_PTR(arg_str), inet_proto);
+ return -1;
+ }
+ ptr = strtok_r(NULL, delim, &save_ptr);
+ if (NULL == ptr)
+ {
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, "ftp EPRT command parse error: %.*s", IOVEC_PRINT_PTR(arg_str));
+ return -1;
+ }
+ strncpy(ip6_addr_str, ptr, INET6_ADDRSTRLEN - 1);
+ ptr = strtok_r(NULL, delim, &save_ptr);
+ if (NULL == ptr)
+ {
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, "ftp EPRT command parse error: %.*s", IOVEC_PRINT_PTR(arg_str));
+ return -1;
+ }
+ port_host = (unsigned int)atoi(ptr);
+
+ while (strtok_r(NULL, "|", &save_ptr))
+ ;
+ inet_pton(AF_INET6, ip6_addr_str, ipd_addr);
+ *port_net = htons((unsigned short)port_host);
+ return 0;
+ }
+
+ /* EPRT support IPv4 and IPv6, pattern: EPRT<space><d><net-prt><d><net-addr><d><tcp-port><d>
+ example:
+ EPRT |1|132.235.1.2|6275|
+ EPRT |2|1080::8:800:200C:417A|5282|
+ refer: https://datatracker.ietf.org/doc/html/rfc2428#section-2
+ */
+ int ftp_cmd_handler_do_eprt(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ ftp_ext->ctrl_ext.mode = FTP_TRANSFER_PORT;
+ struct in6_addr ipd_addr;
+ unsigned short port_net;
+ // fstring_safe_dup(&ftp_ext->cmd_result.cmd_refer, &ftp_ext->parse_result.result_array[FTP_TRANS_MODE]);
+ if (ftp_parse_eprt_ipport_style(&ftp_ext->ctrl_ext.cmd_line.arg_refer, &ipd_addr, &port_net, ftp_env) < 0)
+ {
+ return -1;
+ }
+
+ struct ftp_session_addr ses_addr = {};
+ ftp_session_get_addr(sess, &ses_addr);
+ assert(ses_addr.af_inet == AF_INET6);
+ ftp_make_hkey_v6(&ftp_ext->ctrl_ext.last_data_link_key, (struct in6_addr *)&ses_addr.daddr6, &ipd_addr, port_net);
+
+ ftp_create_new_datalink_hash_item(ftp_env, ftp_ext);
+ int thread_id = module_manager_get_thread_id(ftp_env->mod_mgr_ref);
+ ftp_decoder_stat_incrby(thread_id, ftp_env, FTPD_STAT_NEGOTIATE_DATA_LINK, 1);
+
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s': EPRT command, port:%u", session_get_readable_addr(sess), ntohs(port_net));
+ return 0;
+ }
+
+ /* LPRT support IPv4 and IPv6, pattern: LPRT af,hal,h1,h2,h3,h4...,pal,p1,p2...
+ example:
+ LPRT 6,16,32,2,81,131,67,131,0,0,0,0,0,0,81,131,67,131,2,4,7
+ refer: https://www.rfc-editor.org/rfc/rfc1639.html
+ */
+ int ftp_cmd_handler_do_lprt(struct session *sess, struct ftp_decoder_exdata *ftp_ext UNUSED, struct ftp_decoder *ftp_env)
+ {
+ // todo
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, " '%s': LPRT command not support yet!", session_get_readable_addr(sess));
+ return -1;
+ }
+
+ /* EPSV support IPv4 and IPv6,
+ refer: https://datatracker.ietf.org/doc/html/rfc2428#autoid-3
+ */
+ int ftp_cmd_handler_do_epsv(struct session *sess, struct ftp_decoder_exdata *ftp_ext UNUSED, struct ftp_decoder *ftp_env)
+ {
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s' EPSV command", session_get_readable_addr(sess));
+ return 0;
+ }
+
+ /* LPSV support IPv4 and IPv6, response is 228 xxxx...
+ refer: https://www.rfc-editor.org/rfc/rfc1639.html
+ */
+ int ftp_cmd_handler_do_lpsv(struct session *sess, struct ftp_decoder_exdata *ftp_ext UNUSED, struct ftp_decoder *ftp_env)
+ {
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s' LPSV command", session_get_readable_addr(sess));
+ return 0;
+ }
+
+ static const struct ftp_interact_cmd_parser g_ftp_c2s_cmd_tuple[] =
+ {
+ {FTP_COMMAND_SYST, "SYST", 4, ftp_cmd_handler_non_implemented},
+ {FTP_COMMAND_USER, "USER", 4, ftp_cmd_handler_do_user},
+ {FTP_COMMAND_PASS, "PASS", 4, ftp_cmd_handler_do_pass},
+ {FTP_COMMAND_CWD, "CWD", 3, ftp_cmd_handler_do_cwd},
+ {FTP_COMMAND_PORT, "PORT", 4, ftp_cmd_handler_do_port},
+ {FTP_COMMAND_PASV, "PASV", 4, ftp_cmd_handler_do_pasv},
+ {FTP_COMMAND_EPRT, "EPRT", 4, ftp_cmd_handler_do_eprt},
+ {FTP_COMMAND_EPSV, "EPSV", 4, ftp_cmd_handler_do_epsv},
+ {FTP_COMMAND_LPRT, "LPRT", 4, ftp_cmd_handler_do_lprt},
+ {FTP_COMMAND_LPSV, "LPSV", 4, ftp_cmd_handler_do_lpsv},
+ {FTP_COMMAND_LIST, "LIST", 4, ftp_cmd_handler_do_list},
+ {FTP_COMMAND_STOR, "STOR", 4, ftp_cmd_handler_do_stor},
+ {FTP_COMMAND_RETR, "RETR", 4, ftp_cmd_handler_do_retr},
+ {FTP_COMMAND_OTHERS, NULL, 0, NULL}};
+
+ const char *ftp_command_type_to_string(enum ftp_command cmd_type)
+ {
+ for (size_t i = 0; i < sizeof(g_ftp_c2s_cmd_tuple) / sizeof(struct ftp_interact_cmd_parser); i++)
+ {
+ if (g_ftp_c2s_cmd_tuple[i].cmd_type == cmd_type && cmd_type != FTP_COMMAND_OTHERS)
+ {
+ return g_ftp_c2s_cmd_tuple[i].cmd_name;
+ }
+ }
+ return "OTHERS";
+ }
+
+ int ftp_res_handler_do_220(struct session *sess, struct ftp_decoder_exdata *ftp_ext UNUSED, struct ftp_decoder *ftp_env)
+ {
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, " '%s': reply 220", session_get_readable_addr(sess));
+ return 0;
+ }
+
+ int ftp_res_handler_do_200(struct session *sess, struct ftp_decoder_exdata *ftp_ext UNUSED, struct ftp_decoder *ftp_env)
+ {
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, " '%s': reply 200", session_get_readable_addr(sess));
+ return 0;
+ }
+
+ /*
+ example: 227 Entering Passive Mode (218,13,32,6,78,40).
+ */
+ int ftp_res_handler_do_227(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ ftp_ext->ctrl_ext.mode = FTP_TRANSFER_PASV;
+ unsigned int dst_ip_net;
+ unsigned short dst_port_net;
+ if (ftp_parse_ipv4_port_style(&ftp_ext->ctrl_ext.cmd_line.arg_refer, &dst_ip_net, &dst_port_net) < 0)
+ {
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "ftp_parse_ipv4_port_style parse error: %.*s",
+ IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.cmd_line));
+ return -1;
+ }
+
+ struct ftp_session_addr ses_addr = {};
+ ftp_session_get_addr(sess, &ses_addr);
+ // in passive mode, new data link src ip address is client ip address
+ ftp_make_hkey_v4(&ftp_ext->ctrl_ext.last_data_link_key, ses_addr.saddr4, dst_ip_net, dst_port_net);
+
+ ftp_create_new_datalink_hash_item(ftp_env, ftp_ext);
+ int thread_id = module_manager_get_thread_id(ftp_env->mod_mgr_ref);
+ ftp_decoder_stat_incrby(thread_id, ftp_env, FTPD_STAT_NEGOTIATE_DATA_LINK, 1);
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s': response 227, %.*s parsed dip:0x%x, dport:%u, hashkey:%s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer),
+ ntohl(dst_ip_net), ntohs(dst_port_net),
+ ftp_hash_key_to_str(&ftp_ext->ctrl_ext.last_data_link_key));
+ return 0;
+ }
+
+ /*
+ LPSV reply,
+ example: 228 Entering Long Passive Mode (af, hal, h1, h2, h3,..., pal, p1, p2...)
+ refer: https://www.rfc-editor.org/rfc/rfc1639.html
+ */
+ int ftp_res_handler_do_228(struct session *sess, struct ftp_decoder_exdata *ftp_ext UNUSED, struct ftp_decoder *ftp_env)
+ {
+ ftp_ext->ctrl_ext.mode = FTP_TRANSFER_PASV;
+ // todo
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s': response 228 command not support yet!", session_get_readable_addr(sess));
+ return 0;
+ }
+
+ int ftp_parse_ipv6_port_style(const fstring *cmd_str, unsigned short *port_net)
+ {
+ unsigned int port_host;
+ // char raw_cmd_str_tmp[cmd_str->iov_len + 1];
+ char raw_cmd_str_tmp[FTP_CMD_MAX_LENGTH + 1];
+ memset(raw_cmd_str_tmp, 0, sizeof(raw_cmd_str_tmp));
+ memcpy(raw_cmd_str_tmp, cmd_str->iov_base, cmd_str->iov_len);
+
+ char only_integer_str_tmp[FTP_CMD_MAX_LENGTH + 1];
+ memset(only_integer_str_tmp, 0, sizeof(only_integer_str_tmp));
+ if (sscanf(raw_cmd_str_tmp, "%*[a-zA-Z (|]%255[0-9]", only_integer_str_tmp) <= 0)
+ {
+ if (sscanf(raw_cmd_str_tmp, "|||%255s|", only_integer_str_tmp) <= 0)
+ {
+ return -1;
+ }
+ }
+ int ret = sscanf(only_integer_str_tmp, "%u", &port_host);
+ if (ret != 1)
+ {
+ return -1;
+ }
+ *port_net = htons((unsigned short)port_host);
+ return 0;
+ }
+ /*
+ reply of EPSV command,
+ example: 229 229 Entering Extended Passive Mode (|||20987|)
+ refer: https://datatracker.ietf.org/doc/html/rfc2428#autoid-3
+ */
+ int ftp_res_handler_do_229(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ ftp_ext->ctrl_ext.mode = FTP_TRANSFER_PASV;
+ unsigned short port_net;
+ if (ftp_parse_ipv6_port_style(&ftp_ext->ctrl_ext.cmd_line.arg_refer, &port_net) < 0)
+ {
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s': response 229 parse error, %.*s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer));
+ return -1;
+ }
+
+ struct ftp_session_addr ses_addr = {};
+ ftp_session_get_addr(sess, &ses_addr);
+ if (AF_INET6 != ses_addr.af_inet && AF_INET != ses_addr.af_inet)
+ {
+ STELLAR_LOG_FATAL(ftp_env->logger_ref, FTP_MODULE_NAME, "session: %s, parse response 229 error! %.*s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.arg_refer));
+ return -1;
+ }
+ // in passive mode, new data link src ip address is client ip address
+ if (AF_INET == ses_addr.af_inet)
+ {
+ ftp_make_hkey_v4(&ftp_ext->ctrl_ext.last_data_link_key, ses_addr.saddr4, ses_addr.daddr4, port_net);
+ }
+ else
+ {
+ ftp_make_hkey_v6(&ftp_ext->ctrl_ext.last_data_link_key, (struct in6_addr *)&ses_addr.saddr6, (struct in6_addr *)&ses_addr.daddr6, port_net);
+ }
+ ftp_create_new_datalink_hash_item(ftp_env, ftp_ext);
+ int thread_id = module_manager_get_thread_id(ftp_env->mod_mgr_ref);
+ ftp_decoder_stat_incrby(thread_id, ftp_env, FTPD_STAT_NEGOTIATE_DATA_LINK, 1);
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "'%s': EPSV response 229, port is:%u", session_get_readable_addr(sess), ntohs(port_net));
+ return 0;
+ }
+
+ static const struct ftp_interact_reply_parser g_ftp_s2c_cmd_tuple[] =
+ {
+ {FTP_REPLY_CMD_OK, "220", 3, ftp_res_handler_do_220},
+ {FTP_REPLY_SERVICE_READY, "200", 3, ftp_res_handler_do_200},
+ {FTP_REPLY_ENTER_PASV_MODE, "227", 3, ftp_res_handler_do_227},
+ {FTP_REPLY_ENTER_LONG_PASV_MODE, "228", 3, ftp_res_handler_do_228},
+ {FTP_REPLY_ENTER_EXTEND_PASSIVE_MODE, "229", 3, ftp_res_handler_do_229},
+ {FTP_REPLY_OTHRES, NULL, 0, NULL}};
+
+ const char *ftp_reply_type_to_string(enum ftp_reply_code reply_type)
+ {
+ for (size_t i = 0; i < sizeof(g_ftp_s2c_cmd_tuple) / sizeof(struct ftp_interact_reply_parser); i++)
+ {
+ if (g_ftp_s2c_cmd_tuple[i].reply_code_type == reply_type && reply_type != FTP_REPLY_OTHRES)
+ {
+ return g_ftp_s2c_cmd_tuple[i].reply_code;
+ }
+ }
+ return "OTHERS";
+ }
+
+ int ftp_ctrl_identify(struct session *sess UNUSED, const char *payload, size_t len, enum flow_type curdir)
+ {
+#if 0
+ if(ftp_ctrl_identify_by_addr(sess)){
+ return 1;
+ }
+#endif
+ return ftp_ctrl_identify_by_payload(payload, len, curdir);
+ }
+
+ struct ftp_decoder_exdata *ftp_data_identify(struct session *sess, struct ftp_decoder *ftp_env)
+ {
+ ftp_hash_key_t hash_key = {};
+ ftp_session_get_addr(sess, &hash_key);
+ if (hash_key.af_inet == 0)
+ {
+ STELLAR_LOG_WARN(ftp_env->logger_ref, FTP_MODULE_NAME, "data link identify(): get session addr fail: %s", session_get_readable_addr(sess));
+ return NULL;
+ }
+ /* sport is random value in passive mode, so set sport=0 for all active and passive mode */
+ hash_key.sport = 0;
+ struct ftp_datalink_htable *hitem = ftp_hash_search(&hash_key);
+ if (NULL == hitem)
+ {
+ STELLAR_LOG_DEBUG(ftp_env->logger_ref, FTP_MODULE_NAME, "not ftp session. hashkey: %s, session: %s",
+ ftp_hash_key_to_str(&hash_key), session_get_readable_addr(sess));
+ return NULL;
+ }
+ struct ftp_decoder_exdata *data_ext = hitem->ftp_ext;
+ return data_ext;
+ }
+
+ static const struct ftp_interact_cmd_parser *ftp_fetch_cmd_parser(const struct ftp_interact_cmd_parser *table,
+ size_t table_item_max, const struct ftp_interact_line *cmd_result)
+ {
+ for (size_t i = 0; i < table_item_max && table[i].cmd_name != NULL; i++)
+ {
+ if (0 == strncmp(table[i].cmd_name, (char *)cmd_result->cmd_refer.iov_base, table[i].cmd_len))
+ {
+ return &table[i];
+ }
+ }
+ return &table[table_item_max - 1];
+ }
+
+ static const struct ftp_interact_reply_parser *ftp_fetch_reply_parser(const struct ftp_interact_reply_parser *table,
+ size_t table_item_max, const struct ftp_interact_line *cmd_result)
+ {
+ for (size_t i = 0; i < table_item_max && table[i].reply_code != NULL; i++)
+ {
+ if (0 == strncmp(table[i].reply_code, (char *)cmd_result->cmd_refer.iov_base, table[i].reply_len))
+ {
+ return &table[i];
+ }
+ }
+ return &table[table_item_max - 1];
+ }
+
+ int ftp_command_process(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ const struct ftp_interact_cmd_parser *cmd_parser = NULL;
+ cmd_parser = ftp_fetch_cmd_parser(g_ftp_c2s_cmd_tuple,
+ sizeof(g_ftp_c2s_cmd_tuple) / sizeof(struct ftp_interact_cmd_parser),
+ &ftp_ext->ctrl_ext.cmd_line);
+ ftp_ext->ctrl_ext.cmd_type = cmd_parser->cmd_type;
+ if (NULL == cmd_parser->cmd_handler)
+ {
+ STELLAR_LOG_INFO(ftp_env->logger_ref, FTP_MODULE_NAME, "fetch parser() failed, '%s', not support cmd %.*s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.cmd_line));
+ return 0;
+ }
+ return cmd_parser->cmd_handler(sess, ftp_ext, ftp_env);
+ }
+
+ int ftp_reply_process(struct session *sess, struct ftp_decoder_exdata *ftp_ext, struct ftp_decoder *ftp_env)
+ {
+ const struct ftp_interact_reply_parser *reply_parser = NULL;
+
+ reply_parser = ftp_fetch_reply_parser(g_ftp_s2c_cmd_tuple,
+ sizeof(g_ftp_s2c_cmd_tuple) / sizeof(struct ftp_interact_reply_parser),
+ &ftp_ext->ctrl_ext.cmd_line);
+ ftp_ext->ctrl_ext.reply_code = reply_parser->reply_code_type;
+ if (NULL == reply_parser->reply_handler)
+ {
+ STELLAR_LOG_INFO(ftp_env->logger_ref, FTP_MODULE_NAME, "fetch parser() failed, '%s', not support cmd %.*s",
+ session_get_readable_addr(sess), IOVEC_PRINT(ftp_ext->ctrl_ext.cmd_line.cmd_line));
+ return 0;
+ }
+ return reply_parser->reply_handler(sess, ftp_ext, ftp_env);
+ }
+
+#ifdef __cplusplus
+}
+#endif