/* Copyright (C) 2007-2020 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * 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 * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ /** * \file * * \author Pablo Rincon Crespo * \author Eric Leblond * \author Jeff Lucovsky * * App Layer Parser for FTP */ #include "suricata-common.h" #include "debug.h" #include "decode.h" #include "threads.h" #include "util-print.h" #include "util-pool.h" #include "flow-util.h" #include "flow-storage.h" #include "detect-engine-state.h" #include "stream-tcp-private.h" #include "stream-tcp-reassemble.h" #include "stream-tcp.h" #include "stream.h" #include "app-layer.h" #include "app-layer-protos.h" #include "app-layer-parser.h" #include "app-layer-ftp.h" #include "app-layer-expectation.h" #include "util-spm.h" #include "util-mpm.h" #include "util-unittest.h" #include "util-debug.h" #include "util-memcmp.h" #include "util-memrchr.h" #include "util-mem.h" #include "util-misc.h" #include "output-json.h" #include "rust.h" typedef struct FTPThreadCtx_ { MpmThreadCtx *ftp_mpm_thread_ctx; PrefilterRuleStore *pmq; } FTPThreadCtx; #define FTP_MPM mpm_default_matcher static MpmCtx *ftp_mpm_ctx = NULL; const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = { /* Parsed and handled */ { FTP_COMMAND_PORT, "PORT", 4}, { FTP_COMMAND_EPRT, "EPRT", 4}, { FTP_COMMAND_AUTH_TLS, "AUTH TLS", 8}, { FTP_COMMAND_PASV, "PASV", 4}, { FTP_COMMAND_RETR, "RETR", 4}, { FTP_COMMAND_EPSV, "EPSV", 4}, { FTP_COMMAND_STOR, "STOR", 4}, /* Parsed, but not handled */ { FTP_COMMAND_ABOR, "ABOR", 4}, { FTP_COMMAND_ACCT, "ACCT", 4}, { FTP_COMMAND_ALLO, "ALLO", 4}, { FTP_COMMAND_APPE, "APPE", 4}, { FTP_COMMAND_CDUP, "CDUP", 4}, { FTP_COMMAND_CHMOD, "CHMOD", 5}, { FTP_COMMAND_CWD, "CWD", 3}, { FTP_COMMAND_DELE, "DELE", 4}, { FTP_COMMAND_HELP, "HELP", 4}, { FTP_COMMAND_IDLE, "IDLE", 4}, { FTP_COMMAND_LIST, "LIST", 4}, { FTP_COMMAND_MAIL, "MAIL", 4}, { FTP_COMMAND_MDTM, "MDTM", 4}, { FTP_COMMAND_MKD, "MKD", 3}, { FTP_COMMAND_MLFL, "MLFL", 4}, { FTP_COMMAND_MODE, "MODE", 4}, { FTP_COMMAND_MRCP, "MRCP", 4}, { FTP_COMMAND_MRSQ, "MRSQ", 4}, { FTP_COMMAND_MSAM, "MSAM", 4}, { FTP_COMMAND_MSND, "MSND", 4}, { FTP_COMMAND_MSOM, "MSOM", 4}, { FTP_COMMAND_NLST, "NLST", 4}, { FTP_COMMAND_NOOP, "NOOP", 4}, { FTP_COMMAND_PASS, "PASS", 4}, { FTP_COMMAND_PWD, "PWD", 3}, { FTP_COMMAND_QUIT, "QUIT", 4}, { FTP_COMMAND_REIN, "REIN", 4}, { FTP_COMMAND_REST, "REST", 4}, { FTP_COMMAND_RMD, "RMD", 3}, { FTP_COMMAND_RNFR, "RNFR", 4}, { FTP_COMMAND_RNTO, "RNTO", 4}, { FTP_COMMAND_SITE, "SITE", 4}, { FTP_COMMAND_SIZE, "SIZE", 4}, { FTP_COMMAND_SMNT, "SMNT", 4}, { FTP_COMMAND_STAT, "STAT", 4}, { FTP_COMMAND_STOU, "STOU", 4}, { FTP_COMMAND_STRU, "STRU", 4}, { FTP_COMMAND_SYST, "SYST", 4}, { FTP_COMMAND_TYPE, "TYPE", 4}, { FTP_COMMAND_UMASK, "UMASK", 5}, { FTP_COMMAND_USER, "USER", 4}, { FTP_COMMAND_UNKNOWN, NULL, 0} }; uint64_t ftp_config_memcap = 0; SC_ATOMIC_DECLARE(uint64_t, ftp_memuse); SC_ATOMIC_DECLARE(uint64_t, ftp_memcap); static FTPTransaction *FTPGetOldestTx(FtpState *); static void FTPParseMemcap(void) { const char *conf_val; /** set config values for memcap, prealloc and hash_size */ if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1) { if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) { SCLogError(SC_ERR_SIZE_PARSE, "Error parsing ftp.memcap " "from conf file - %s. Killing engine", conf_val); exit(EXIT_FAILURE); } SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap); } else { /* default to unlimited */ ftp_config_memcap = 0; } SC_ATOMIC_INIT(ftp_memuse); SC_ATOMIC_INIT(ftp_memcap); } static void FTPIncrMemuse(uint64_t size) { (void) SC_ATOMIC_ADD(ftp_memuse, size); return; } static void FTPDecrMemuse(uint64_t size) { (void) SC_ATOMIC_SUB(ftp_memuse, size); return; } uint64_t FTPMemuseGlobalCounter(void) { uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse); return tmpval; } uint64_t FTPMemcapGlobalCounter(void) { uint64_t tmpval = SC_ATOMIC_GET(ftp_memcap); return tmpval; } /** * \brief Check if alloc'ing "size" would mean we're over memcap * * \retval 1 if in bounds * \retval 0 if not in bounds */ static int FTPCheckMemcap(uint64_t size) { if (ftp_config_memcap == 0 || size + SC_ATOMIC_GET(ftp_memuse) <= ftp_config_memcap) return 1; (void) SC_ATOMIC_ADD(ftp_memcap, 1); return 0; } static void *FTPMalloc(size_t size) { void *ptr = NULL; if (FTPCheckMemcap((uint32_t)size) == 0) return NULL; ptr = SCMalloc(size); if (unlikely(ptr == NULL)) return NULL; FTPIncrMemuse((uint64_t)size); return ptr; } static void *FTPCalloc(size_t n, size_t size) { void *ptr = NULL; if (FTPCheckMemcap((uint32_t)(n * size)) == 0) return NULL; ptr = SCCalloc(n, size); if (unlikely(ptr == NULL)) return NULL; FTPIncrMemuse((uint64_t)(n * size)); return ptr; } static void *FTPRealloc(void *ptr, size_t orig_size, size_t size) { void *rptr = NULL; if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0) return NULL; rptr = SCRealloc(ptr, size); if (rptr == NULL) return NULL; if (size > orig_size) { FTPIncrMemuse(size - orig_size); } else { FTPDecrMemuse(orig_size - size); } return rptr; } static void FTPFree(void *ptr, size_t size) { SCFree(ptr); FTPDecrMemuse((uint64_t)size); } static FTPString *FTPStringAlloc(void) { return FTPCalloc(1, sizeof(FTPString)); } static void FTPStringFree(FTPString *str) { if (str->str) { FTPFree(str->str, str->len); } FTPFree(str, sizeof(FTPString)); } static void *FTPLocalStorageAlloc(void) { /* needed by the mpm */ FTPThreadCtx *td = SCCalloc(1, sizeof(*td)); if (td == NULL) { exit(EXIT_FAILURE); } td->pmq = SCCalloc(1, sizeof(*td->pmq)); if (td->pmq == NULL) { exit(EXIT_FAILURE); } PmqSetup(td->pmq); td->ftp_mpm_thread_ctx = SCCalloc(1, sizeof(MpmThreadCtx)); if (unlikely(td->ftp_mpm_thread_ctx == NULL)) { exit(EXIT_FAILURE); } MpmInitThreadCtx(td->ftp_mpm_thread_ctx, FTP_MPM); return td; } static void FTPLocalStorageFree(void *ptr) { FTPThreadCtx *td = ptr; if (td != NULL) { if (td->pmq != NULL) { PmqFree(td->pmq); SCFree(td->pmq); } if (td->ftp_mpm_thread_ctx != NULL) { mpm_table[FTP_MPM].DestroyThreadCtx(ftp_mpm_ctx, td->ftp_mpm_thread_ctx); SCFree(td->ftp_mpm_thread_ctx); } SCFree(td); } return; } static FTPTransaction *FTPTransactionCreate(FtpState *state) { SCEnter(); FTPTransaction *tx = FTPCalloc(1, sizeof(*tx)); if (tx == NULL) { return NULL; } TAILQ_INSERT_TAIL(&state->tx_list, tx, next); tx->tx_id = state->tx_cnt++; TAILQ_INIT(&tx->response_list); SCLogDebug("new transaction %p (state tx cnt %"PRIu64")", tx, state->tx_cnt); return tx; } static void FTPTransactionFree(FTPTransaction *tx) { SCEnter(); if (tx->de_state != NULL) { DetectEngineStateFree(tx->de_state); } if (tx->request) { FTPFree(tx->request, tx->request_length); } FTPString *str = NULL; while ((str = TAILQ_FIRST(&tx->response_list))) { TAILQ_REMOVE(&tx->response_list, str, next); FTPStringFree(str); } FTPFree(tx, sizeof(*tx)); } static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state) { void *ptmp; if (line_state->current_line_lf_seen == 1) { /* we have seen the lf for the previous line. Clear the parser * details to parse new line */ line_state->current_line_lf_seen = 0; if (line_state->current_line_db == 1) { line_state->current_line_db = 0; FTPFree(line_state->db, line_state->db_len); line_state->db = NULL; line_state->db_len = 0; state->current_line = NULL; state->current_line_len = 0; } } uint8_t *lf_idx = memchr(state->input, 0x0a, state->input_len); if (lf_idx == NULL) { /* fragmented lines. Decoder event for special cases. Not all * fragmented lines should be treated as a possible evasion * attempt. With multi payload ftp chunks we can have valid * cases of fragmentation. But within the same segment chunk * if we see fragmentation then it's definitely something you * should alert about */ if (line_state->current_line_db == 0) { line_state->db = FTPMalloc(state->input_len); if (line_state->db == NULL) { return -1; } line_state->current_line_db = 1; memcpy(line_state->db, state->input, state->input_len); line_state->db_len = state->input_len; } else { ptmp = FTPRealloc(line_state->db, line_state->db_len, (line_state->db_len + state->input_len)); if (ptmp == NULL) { FTPFree(line_state->db, line_state->db_len); line_state->db = NULL; line_state->db_len = 0; return -1; } line_state->db = ptmp; memcpy(line_state->db + line_state->db_len, state->input, state->input_len); line_state->db_len += state->input_len; } state->input += state->input_len; state->input_len = 0; return -1; } else { line_state->current_line_lf_seen = 1; if (line_state->current_line_db == 1) { ptmp = FTPRealloc(line_state->db, line_state->db_len, (line_state->db_len + (lf_idx + 1 - state->input))); if (ptmp == NULL) { FTPFree(line_state->db, line_state->db_len); line_state->db = NULL; line_state->db_len = 0; return -1; } line_state->db = ptmp; memcpy(line_state->db + line_state->db_len, state->input, (lf_idx + 1 - state->input)); line_state->db_len += (lf_idx + 1 - state->input); if (line_state->db_len > 1 && line_state->db[line_state->db_len - 2] == 0x0D) { line_state->db_len -= 2; state->current_line_delimiter_len = 2; } else { line_state->db_len -= 1; state->current_line_delimiter_len = 1; } state->current_line = line_state->db; state->current_line_len = line_state->db_len; } else { state->current_line = state->input; state->current_line_len = lf_idx - state->input; if (state->input != lf_idx && *(lf_idx - 1) == 0x0D) { state->current_line_len--; state->current_line_delimiter_len = 2; } else { state->current_line_delimiter_len = 1; } } state->input_len -= (lf_idx - state->input) + 1; state->input = (lf_idx + 1); return 0; } } static int FTPGetLine(FtpState *state) { SCEnter(); /* we have run out of input */ if (state->input_len <= 0) return -1; /* toserver */ if (state->direction == 0) return FTPGetLineForDirection(state, &state->line_state[0]); else return FTPGetLineForDirection(state, &state->line_state[1]); } /** * \brief This function is called to determine and set which command is being * transferred to the ftp server * \param thread context * \param input input line of the command * \param len of the command * \param cmd_descriptor when the command has been parsed * * \retval 1 when the command is parsed, 0 otherwise */ static int FTPParseRequestCommand(FTPThreadCtx *td, const uint8_t *input, uint32_t input_len, const FtpCommand **cmd_descriptor) { SCEnter(); /* I don't like this pmq reset here. We'll devise a method later, that * should make the use of the mpm very efficient */ PmqReset(td->pmq); int mpm_cnt = mpm_table[FTP_MPM].Search(ftp_mpm_ctx, td->ftp_mpm_thread_ctx, td->pmq, input, input_len); if (mpm_cnt) { *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]]; SCReturnInt(1); } *cmd_descriptor = NULL; SCReturnInt(0); } struct FtpTransferCmd { /** Need to look like a ExpectationData so DFree must * be first field . */ void (*DFree)(void *); uint64_t flow_id; uint8_t *file_name; uint16_t file_len; FtpRequestCommand cmd; }; static void FtpTransferCmdFree(void *data) { struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data; if (cmd == NULL) return; if (cmd->file_name) { FTPFree(cmd->file_name, cmd->file_len + 1); } FTPFree(cmd, sizeof(struct FtpTransferCmd)); } static uint32_t CopyCommandLine(uint8_t **dest, const uint8_t *src, uint32_t length) { if (likely(length)) { uint8_t *where = FTPCalloc(length + 1, sizeof(char)); if (unlikely(where == NULL)) { return 0; } memcpy(where, src, length); /* Remove trailing newlines/carriage returns */ while (length && isspace((unsigned char) where[length - 1])) { length--; } where[length] = '\0'; *dest = where; } /* either 0 or actual */ return length ? length + 1 : 0; } /** * \brief This function is called to retrieve a ftp request * \param ftp_state the ftp state structure for the parser * \param input input line of the command * \param input_len length of the request * \param output the resulting output * * \retval APP_LAYER_OK when input was process successfully * \retval APP_LAYER_ERROR when a unrecoverable error was encountered */ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserState *pstate, const uint8_t *input, uint32_t input_len, void *local_data, const uint8_t flags) { FTPThreadCtx *thread_data = local_data; SCEnter(); /* PrintRawDataFp(stdout, input,input_len); */ FtpState *state = (FtpState *)ftp_state; void *ptmp; if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { SCReturnStruct(APP_LAYER_OK); } else if (input == NULL || input_len == 0) { SCReturnStruct(APP_LAYER_ERROR); } state->input = input; state->input_len = input_len; /* toserver stream */ state->direction = 0; int direction = STREAM_TOSERVER; while (FTPGetLine(state) >= 0) { const FtpCommand *cmd_descriptor; if (!FTPParseRequestCommand(thread_data, state->current_line, state->current_line_len, &cmd_descriptor)) { state->command = FTP_COMMAND_UNKNOWN; continue; } state->command = cmd_descriptor->command; FTPTransaction *tx = FTPTransactionCreate(state); if (unlikely(tx == NULL)) SCReturnStruct(APP_LAYER_ERROR); state->curr_tx = tx; tx->command_descriptor = cmd_descriptor; tx->request_length = CopyCommandLine(&tx->request, state->current_line, state->current_line_len); switch (state->command) { case FTP_COMMAND_EPRT: // fallthrough case FTP_COMMAND_PORT: if (state->current_line_len + 1 > state->port_line_size) { /* Allocate an extra byte for a NULL terminator */ ptmp = FTPRealloc(state->port_line, state->port_line_size, state->current_line_len); if (ptmp == NULL) { if (state->port_line) { FTPFree(state->port_line, state->port_line_size); state->port_line = NULL; state->port_line_size = 0; } SCReturnStruct(APP_LAYER_OK); } state->port_line = ptmp; state->port_line_size = state->current_line_len; } memcpy(state->port_line, state->current_line, state->current_line_len); state->port_line_len = state->current_line_len; break; case FTP_COMMAND_RETR: /* change direction (default to server) so expectation will handle * the correct message when expectation will match. */ direction = STREAM_TOCLIENT; // fallthrough case FTP_COMMAND_STOR: { /* Ensure that there is a negotiated dyn port and a file * name -- need more than 5 chars: cmd [4], space, */ if (state->dyn_port == 0 || state->current_line_len < 6) { SCReturnStruct(APP_LAYER_ERROR); } struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd)); if (data == NULL) SCReturnStruct(APP_LAYER_ERROR); data->DFree = FtpTransferCmdFree; /* Min size has been checked in FTPParseRequestCommand */ data->file_name = FTPCalloc(state->current_line_len - 4, sizeof(char)); if (data->file_name == NULL) { FtpTransferCmdFree(data); SCReturnStruct(APP_LAYER_ERROR); } data->file_name[state->current_line_len - 5] = 0; data->file_len = state->current_line_len - 5; memcpy(data->file_name, state->current_line + 5, state->current_line_len - 5); data->cmd = state->command; data->flow_id = FlowGetId(f); int ret = AppLayerExpectationCreate(f, state->active ? STREAM_TOSERVER : direction, 0, state->dyn_port, ALPROTO_FTPDATA, data); if (ret == -1) { FtpTransferCmdFree(data); SCLogDebug("No expectation created."); SCReturnStruct(APP_LAYER_ERROR); } else { SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].", state->active ? "to server" : "to client", state->dyn_port); } /* reset the dyn port to avoid duplicate */ state->dyn_port = 0; /* reset active/passive indicator */ state->active = false; } break; default: break; } } SCReturnStruct(APP_LAYER_OK); } static int FTPParsePassiveResponse(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len) { uint16_t dyn_port = rs_ftp_pasv_response(input, input_len); if (dyn_port == 0) { return -1; } SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port); state->active = false; state->dyn_port = dyn_port; state->curr_tx->dyn_port = dyn_port; state->curr_tx->active = false; return 0; } static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len) { uint16_t dyn_port = rs_ftp_epsv_response(input, input_len); if (dyn_port == 0) { return -1; } SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port); state->active = false; state->dyn_port = dyn_port; state->curr_tx->dyn_port = dyn_port; state->curr_tx->active = false; return 0; } /** * \brief Handle preliminary replies -- keep tx open * \retval bool True for a positive preliminary reply; false otherwise * * 1yz Positive Preliminary reply * * The requested action is being initiated; expect another * reply before proceeding with a new command */ static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len) { return input_len >= 4 && isdigit(input[0]) && input[0] == '1' && isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]); } /** * \brief This function is called to retrieve a ftp response * \param ftp_state the ftp state structure for the parser * \param input input line of the command * \param input_len length of the request * \param output the resulting output * * \retval 1 when the command is parsed, 0 otherwise */ static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate, const uint8_t *input, uint32_t input_len, void *local_data, const uint8_t flags) { FtpState *state = (FtpState *)ftp_state; if (unlikely(input_len == 0)) { SCReturnStruct(APP_LAYER_OK); } state->input = input; state->input_len = input_len; /* toclient stream */ state->direction = 1; while (FTPGetLine(state) >= 0) { FTPTransaction *tx = FTPGetOldestTx(state); if (tx == NULL) { tx = FTPTransactionCreate(state); } if (unlikely(tx == NULL)) { SCReturnStruct(APP_LAYER_ERROR); } if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) { /* unknown */ tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX -1]; } state->curr_tx = tx; uint16_t dyn_port; switch (state->command) { case FTP_COMMAND_AUTH_TLS: if (state->current_line_len >= 4 && SCMemcmp("234 ", state->current_line, 4) == 0) { AppLayerRequestProtocolTLSUpgrade(f); } break; case FTP_COMMAND_EPRT: dyn_port = rs_ftp_active_eprt(state->port_line, state->port_line_len); if (dyn_port == 0) { goto tx_complete; } state->dyn_port = dyn_port; state->active = true; tx->dyn_port = dyn_port; tx->active = true; SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16"", dyn_port); break; case FTP_COMMAND_PORT: dyn_port = rs_ftp_active_port(state->port_line, state->port_line_len); if (dyn_port == 0) { goto tx_complete; } state->dyn_port = dyn_port; state->active = true; tx->dyn_port = state->dyn_port; tx->active = true; SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16"", dyn_port); break; case FTP_COMMAND_PASV: if (state->current_line_len >= 4 && SCMemcmp("227 ", state->current_line, 4) == 0) { FTPParsePassiveResponse(f, ftp_state, state->current_line, state->current_line_len); } break; case FTP_COMMAND_EPSV: if (state->current_line_len >= 4 && SCMemcmp("229 ", state->current_line, 4) == 0) { FTPParsePassiveResponseV6(f, ftp_state, state->current_line, state->current_line_len); } break; default: break; } if (likely(state->current_line_len)) { FTPString *response = FTPStringAlloc(); if (likely(response)) { response->len = CopyCommandLine(&response->str, state->current_line, state->current_line_len); TAILQ_INSERT_TAIL(&tx->response_list, response, next); } } /* Handle preliminary replies -- keep tx open */ if (FTPIsPPR(state->current_line, state->current_line_len)) { continue; } tx_complete: tx->done = true; } SCReturnStruct(APP_LAYER_OK); } #ifdef DEBUG static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER; static uint64_t ftp_state_memuse = 0; static uint64_t ftp_state_memcnt = 0; #endif static void *FTPStateAlloc(void) { void *s = FTPCalloc(1, sizeof(FtpState)); if (unlikely(s == NULL)) return NULL; FtpState *ftp_state = (FtpState *) s; TAILQ_INIT(&ftp_state->tx_list); #ifdef DEBUG SCMutexLock(&ftp_state_mem_lock); ftp_state_memcnt++; ftp_state_memuse+=sizeof(FtpState); SCMutexUnlock(&ftp_state_mem_lock); #endif return s; } static void FTPStateFree(void *s) { FtpState *fstate = (FtpState *) s; if (fstate->port_line != NULL) FTPFree(fstate->port_line, fstate->port_line_size); if (fstate->line_state[0].db) FTPFree(fstate->line_state[0].db, fstate->line_state[0].db_len); if (fstate->line_state[1].db) FTPFree(fstate->line_state[1].db, fstate->line_state[1].db_len); //AppLayerDecoderEventsFreeEvents(&s->decoder_events); FTPTransaction *tx = NULL; while ((tx = TAILQ_FIRST(&fstate->tx_list))) { TAILQ_REMOVE(&fstate->tx_list, tx, next); SCLogDebug("[%s] state %p id %"PRIu64", Freeing %d bytes at %p", tx->command_descriptor->command_name, s, tx->tx_id, tx->request_length, tx->request); FTPTransactionFree(tx); } FTPFree(s, sizeof(FtpState)); #ifdef DEBUG SCMutexLock(&ftp_state_mem_lock); ftp_state_memcnt--; ftp_state_memuse-=sizeof(FtpState); SCMutexUnlock(&ftp_state_mem_lock); #endif } static int FTPSetTxDetectState(void *vtx, DetectEngineState *de_state) { FTPTransaction *tx = (FTPTransaction *)vtx; tx->de_state = de_state; return 0; } /** * \brief This function returns the oldest open transaction; if none * are open, then the oldest transaction is returned * \param ftp_state the ftp state structure for the parser * * \retval transaction pointer when a transaction was found; NULL otherwise. */ static FTPTransaction *FTPGetOldestTx(FtpState *ftp_state) { if (unlikely(!ftp_state)) { SCLogDebug("NULL state object; no transactions available"); return NULL; } FTPTransaction *tx = NULL; FTPTransaction *lasttx = NULL; TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { /* Return oldest open tx */ if (!tx->done) { SCLogDebug("Returning tx %p id %"PRIu64, tx, tx->tx_id); return tx; } /* save for the end */ lasttx = tx; } /* All tx are closed; return last element */ if (lasttx) SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id); return lasttx; } static void *FTPGetTx(void *state, uint64_t tx_id) { FtpState *ftp_state = (FtpState *)state; if (ftp_state) { FTPTransaction *tx = NULL; if (ftp_state->curr_tx == NULL) return NULL; if (ftp_state->curr_tx->tx_id == tx_id) return ftp_state->curr_tx; TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { if (tx->tx_id == tx_id) return tx; } } return NULL; } static DetectEngineState *FTPGetTxDetectState(void *vtx) { FTPTransaction *tx = (FTPTransaction *)vtx; return tx->de_state; } static uint64_t FTPGetTxDetectFlags(void *vtx, uint8_t dir) { FTPTransaction *tx = (FTPTransaction *)vtx; if (dir & STREAM_TOSERVER) { return tx->detect_flags_ts; } else { return tx->detect_flags_tc; } } static void FTPSetTxDetectFlags(void *vtx, uint8_t dir, uint64_t flags) { FTPTransaction *tx = (FTPTransaction *)vtx; if (dir & STREAM_TOSERVER) { tx->detect_flags_ts = flags; } else { tx->detect_flags_tc = flags; } } static void FTPStateTransactionFree(void *state, uint64_t tx_id) { FtpState *ftp_state = state; FTPTransaction *tx = NULL; TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { if (tx_id < tx->tx_id) break; else if (tx_id > tx->tx_id) continue; if (tx == ftp_state->curr_tx) ftp_state->curr_tx = NULL; TAILQ_REMOVE(&ftp_state->tx_list, tx, next); FTPTransactionFree(tx); break; } } static uint64_t FTPGetTxCnt(void *state) { uint64_t cnt = 0; FtpState *ftp_state = state; if (ftp_state) { cnt = ftp_state->tx_cnt; } SCLogDebug("returning state %p %"PRIu64, state, cnt); return cnt; } static int FTPGetAlstateProgressCompletionStatus(uint8_t direction) { return FTP_STATE_FINISHED; } static int FTPGetAlstateProgress(void *vtx, uint8_t direction) { SCLogDebug("tx %p", vtx); FTPTransaction *tx = vtx; if (!tx->done) { if (direction == STREAM_TOSERVER && tx->command_descriptor->command == FTP_COMMAND_PORT) { return FTP_STATE_PORT_DONE; } return FTP_STATE_IN_PROGRESS; } return FTP_STATE_FINISHED; } static int FTPRegisterPatternsForProtocolDetection(void) { if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, "220 (", 5, 0, STREAM_TOCLIENT) < 0) { return -1; } if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, "FEAT", 4, 0, STREAM_TOSERVER) < 0) { return -1; } if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, "USER ", 5, 0, STREAM_TOSERVER) < 0) { return -1; } if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, "PASS ", 5, 0, STREAM_TOSERVER) < 0) { return -1; } if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, "PORT ", 5, 0, STREAM_TOSERVER) < 0) { return -1; } return 0; } static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER; /** * \brief This function is called to retrieve a ftp request * \param ftp_state the ftp state structure for the parser * \param input input line of the command * \param input_len length of the request * \param output the resulting output * * \retval 1 when the command is parsed, 0 otherwise */ static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state, AppLayerParserState *pstate, const uint8_t *input, uint32_t input_len, void *local_data, int direction) { uint16_t flags = FileFlowToFlags(f, direction); int ret = 0; /* we depend on detection engine for file pruning */ flags |= FILE_USE_DETECT; if (ftpdata_state->files == NULL) { struct FtpTransferCmd *data = (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetDataId()); if (data == NULL) { SCReturnStruct(APP_LAYER_ERROR); } ftpdata_state->files = FileContainerAlloc(); if (ftpdata_state->files == NULL) { FlowFreeStorageById(f, AppLayerExpectationGetDataId()); SCReturnStruct(APP_LAYER_ERROR); } ftpdata_state->file_name = data->file_name; ftpdata_state->file_len = data->file_len; data->file_name = NULL; data->file_len = 0; f->parent_id = data->flow_id; ftpdata_state->command = data->cmd; switch (data->cmd) { case FTP_COMMAND_STOR: ftpdata_state->direction = STREAM_TOSERVER; break; case FTP_COMMAND_RETR: ftpdata_state->direction = STREAM_TOCLIENT; break; default: break; } /* open with fixed track_id 0 as we can have just one * file per ftp-data flow. */ if (FileOpenFileWithId(ftpdata_state->files, &sbcfg, 0ULL, (uint8_t *) ftpdata_state->file_name, ftpdata_state->file_len, input, input_len, flags) != 0) { SCLogDebug("Can't open file"); ret = -1; } FlowFreeStorageById(f, AppLayerExpectationGetDataId()); } else { if (input_len != 0) { ret = FileAppendData(ftpdata_state->files, input, input_len); if (ret == -2) { ret = 0; SCLogDebug("FileAppendData() - file no longer being extracted"); goto out; } else if (ret < 0) { SCLogDebug("FileAppendData() failed: %d", ret); ret = -2; goto out; } } else { ret = FileCloseFile(ftpdata_state->files, NULL, 0, flags); ftpdata_state->state = FTPDATA_STATE_FINISHED; if (ret < 0) goto out; } } if (input_len && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { ret = FileCloseFile(ftpdata_state->files, (uint8_t *) NULL, 0, flags); ftpdata_state->state = FTPDATA_STATE_FINISHED; } out: if (ret < 0) { SCReturnStruct(APP_LAYER_ERROR); } SCReturnStruct(APP_LAYER_OK); } static void FTPStateSetTxLogged(void *state, void *vtx, LoggerId logged) { FTPTransaction *tx = vtx; tx->logged = logged; } static LoggerId FTPStateGetTxLogged(void *state, void *vtx) { FTPTransaction *tx = vtx; return tx->logged; } static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state, AppLayerParserState *pstate, const uint8_t *input, uint32_t input_len, void *local_data, const uint8_t flags) { return FTPDataParse(f, ftp_state, pstate, input, input_len, local_data, STREAM_TOSERVER); } static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate, const uint8_t *input, uint32_t input_len, void *local_data, const uint8_t flags) { return FTPDataParse(f, ftp_state, pstate, input, input_len, local_data, STREAM_TOCLIENT); } #ifdef DEBUG static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER; static uint64_t ftpdata_state_memuse = 0; static uint64_t ftpdata_state_memcnt = 0; #endif static void *FTPDataStateAlloc(void) { void *s = FTPCalloc(1, sizeof(FtpDataState)); if (unlikely(s == NULL)) return NULL; FtpDataState *state = (FtpDataState *) s; state->state = FTPDATA_STATE_IN_PROGRESS; #ifdef DEBUG SCMutexLock(&ftpdata_state_mem_lock); ftpdata_state_memcnt++; ftpdata_state_memuse+=sizeof(FtpDataState); SCMutexUnlock(&ftpdata_state_mem_lock); #endif return s; } static void FTPDataStateFree(void *s) { FtpDataState *fstate = (FtpDataState *) s; if (fstate->de_state != NULL) { DetectEngineStateFree(fstate->de_state); } if (fstate->file_name != NULL) { FTPFree(fstate->file_name, fstate->file_len + 1); } FileContainerFree(fstate->files); FTPFree(s, sizeof(FtpDataState)); #ifdef DEBUG SCMutexLock(&ftpdata_state_mem_lock); ftpdata_state_memcnt--; ftpdata_state_memuse-=sizeof(FtpDataState); SCMutexUnlock(&ftpdata_state_mem_lock); #endif } static int FTPDataSetTxDetectState(void *vtx, DetectEngineState *de_state) { FtpDataState *ftp_state = (FtpDataState *)vtx; ftp_state->de_state = de_state; return 0; } static DetectEngineState *FTPDataGetTxDetectState(void *vtx) { FtpDataState *ftp_state = (FtpDataState *)vtx; return ftp_state->de_state; } static void FTPDataSetTxDetectFlags(void *vtx, uint8_t dir, uint64_t flags) { FtpDataState *ftp_state = (FtpDataState *)vtx; if (dir & STREAM_TOSERVER) { ftp_state->detect_flags_ts = flags; } else { ftp_state->detect_flags_tc = flags; } } static uint64_t FTPDataGetTxDetectFlags(void *vtx, uint8_t dir) { FtpDataState *ftp_state = (FtpDataState *)vtx; if (dir & STREAM_TOSERVER) { return ftp_state->detect_flags_ts; } else { return ftp_state->detect_flags_tc; } } static void FTPDataStateTransactionFree(void *state, uint64_t tx_id) { /* do nothing */ } static void *FTPDataGetTx(void *state, uint64_t tx_id) { FtpDataState *ftp_state = (FtpDataState *)state; return ftp_state; } static uint64_t FTPDataGetTxCnt(void *state) { /* ftp-data is single tx */ return 1; } static int FTPDataGetAlstateProgressCompletionStatus(uint8_t direction) { return FTPDATA_STATE_FINISHED; } static int FTPDataGetAlstateProgress(void *tx, uint8_t direction) { FtpDataState *ftpdata_state = (FtpDataState *)tx; return ftpdata_state->state; } static FileContainer *FTPDataStateGetFiles(void *state, uint8_t direction) { FtpDataState *ftpdata_state = (FtpDataState *)state; if (direction != ftpdata_state->direction) SCReturnPtr(NULL, "FileContainer"); SCReturnPtr(ftpdata_state->files, "FileContainer"); } static void FTPSetMpmState(void) { ftp_mpm_ctx = SCMalloc(sizeof(MpmCtx)); if (unlikely(ftp_mpm_ctx == NULL)) { exit(EXIT_FAILURE); } memset(ftp_mpm_ctx, 0, sizeof(MpmCtx)); MpmInitCtx(ftp_mpm_ctx, FTP_MPM); uint32_t i = 0; for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) { const FtpCommand *cmd = &FtpCommands[i]; if (cmd->command_length == 0) continue; MpmAddPatternCI(ftp_mpm_ctx, (uint8_t *)cmd->command_name, cmd->command_length, 0 /* defunct */, 0 /* defunct */, i /* id */, i /* rule id */ , 0 /* no flags */); } mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx); } static void FTPFreeMpmState(void) { if (ftp_mpm_ctx != NULL) { mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx); SCFree(ftp_mpm_ctx); ftp_mpm_ctx = NULL; } } void RegisterFTPParsers(void) { const char *proto_name = "ftp"; const char *proto_data_name = "ftp-data"; /** FTP */ if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name); if (FTPRegisterPatternsForProtocolDetection() < 0 ) return; AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name); } if (AppLayerParserConfParserEnabled("tcp", proto_name)) { AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER, FTPParseRequest); AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT, FTPParseResponse); AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree); AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT); AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree); AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxDetectState, FTPSetTxDetectState); AppLayerParserRegisterDetectFlagsFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxDetectFlags, FTPSetTxDetectFlags); AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx); AppLayerParserRegisterLoggerFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateGetTxLogged, FTPStateSetTxLogged); AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc, FTPLocalStorageFree); AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt); AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress); AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_FTP, FTPGetAlstateProgressCompletionStatus); AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA); AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER, FTPDataParseRequest); AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT, FTPDataParseResponse); AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree); AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT); AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree); AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxDetectState, FTPDataSetTxDetectState); AppLayerParserRegisterDetectFlagsFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxDetectFlags, FTPDataSetTxDetectFlags); AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetFiles); AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx); AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt); AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress); AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_FTPDATA, FTPDataGetAlstateProgressCompletionStatus); sbcfg.buf_size = 4096; sbcfg.Malloc = FTPMalloc; sbcfg.Calloc = FTPCalloc; sbcfg.Realloc = FTPRealloc; sbcfg.Free = FTPFree; FTPParseMemcap(); } else { SCLogInfo("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name); } FTPSetMpmState(); #ifdef UNITTESTS AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests); #endif } void FTPAtExitPrintStats(void) { #ifdef DEBUG SCMutexLock(&ftp_state_mem_lock); SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"", ftp_state_memcnt, ftp_state_memuse); SCMutexUnlock(&ftp_state_mem_lock); #endif } /* * \brief Returns the ending offset of the next line from a multi-line buffer. * * "Buffer" refers to a FTP response in a single buffer containing multiple lines. * Here, "next line" is defined as terminating on * - Newline character * - Null character * * \param buffer Contains zero or more characters. * \param len Size, in bytes, of buffer. * * \retval Offset from the start of buffer indicating the where the * next "line ends". The characters between the input buffer and this * value comprise the line. * * NULL is found first or a newline isn't found, then UINT16_MAX is returned. */ uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len) { if (!buffer || *buffer == '\0') { return UINT16_MAX; } char *c = strchr(buffer, '\n'); return c == NULL ? len : c - buffer + 1; } void EveFTPDataAddMetadata(const Flow *f, JsonBuilder *jb) { const FtpDataState *ftp_state = NULL; if (f->alstate == NULL) return; ftp_state = (FtpDataState *)f->alstate; if (ftp_state->file_name) { jb_set_string_from_bytes(jb, "filename", ftp_state->file_name, ftp_state->file_len); } switch (ftp_state->command) { case FTP_COMMAND_STOR: JB_SET_STRING(jb, "command", "STOR"); break; case FTP_COMMAND_RETR: JB_SET_STRING(jb, "command", "RETR"); break; default: break; } } /** * \brief Free memory allocated for global FTP parser state. */ void FTPParserCleanup(void) { FTPFreeMpmState(); } /* UNITTESTS */ #ifdef UNITTESTS /** \test Send a get request in one chunk. */ static int FTPParserTest01(void) { int result = 1; Flow f; uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n"; uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */ TcpSession ssn; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); memset(&f, 0, sizeof(f)); memset(&ssn, 0, sizeof(ssn)); FLOW_INITIALIZE(&f); f.protoctx = (void *)&ssn; f.proto = IPPROTO_TCP; f.alproto = ALPROTO_FTP; StreamTcpInitConfig(TRUE); FLOWLOCK_WRLOCK(&f); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen); if (r != 0) { SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FtpState *ftp_state = f.alstate; if (ftp_state == NULL) { SCLogDebug("no ftp state: "); result = 0; goto end; } if (ftp_state->command != FTP_COMMAND_PORT) { SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); result = 0; goto end; } end: if (alp_tctx != NULL) AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(TRUE); FLOW_DESTROY(&f); return result; } /** \test Send a split get request. */ static int FTPParserTest03(void) { int result = 1; Flow f; uint8_t ftpbuf1[] = "POR"; uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ uint8_t ftpbuf2[] = "T 192,168,1"; uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */ uint8_t ftpbuf3[] = "1,1,10,20\r\n"; uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */ TcpSession ssn; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); memset(&f, 0, sizeof(f)); memset(&ssn, 0, sizeof(ssn)); FLOW_INITIALIZE(&f); f.protoctx = (void *)&ssn; f.proto = IPPROTO_TCP; f.alproto = ALPROTO_FTP; StreamTcpInitConfig(TRUE); FLOWLOCK_WRLOCK(&f); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_START, ftpbuf1, ftplen1); if (r != 0) { SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER, ftpbuf2, ftplen2); if (r != 0) { SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_EOF, ftpbuf3, ftplen3); if (r != 0) { SCLogDebug("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FtpState *ftp_state = f.alstate; if (ftp_state == NULL) { SCLogDebug("no ftp state: "); result = 0; goto end; } if (ftp_state->command != FTP_COMMAND_PORT) { SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); result = 0; goto end; } end: if (alp_tctx != NULL) AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(TRUE); return result; } /** \test See how it deals with an incomplete request. */ static int FTPParserTest06(void) { int result = 1; Flow f; uint8_t ftpbuf1[] = "PORT"; uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ TcpSession ssn; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); memset(&f, 0, sizeof(f)); memset(&ssn, 0, sizeof(ssn)); FLOW_INITIALIZE(&f); f.protoctx = (void *)&ssn; f.proto = IPPROTO_TCP; f.alproto = ALPROTO_FTP; StreamTcpInitConfig(TRUE); FLOWLOCK_WRLOCK(&f); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_START | STREAM_EOF, ftpbuf1, ftplen1); if (r != 0) { SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FtpState *ftp_state = f.alstate; if (ftp_state == NULL) { SCLogDebug("no ftp state: "); result = 0; goto end; } if (ftp_state->command != FTP_COMMAND_UNKNOWN) { SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command); result = 0; goto end; } end: if (alp_tctx != NULL) AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(TRUE); FLOW_DESTROY(&f); return result; } /** \test See how it deals with an incomplete request in multiple chunks. */ static int FTPParserTest07(void) { int result = 1; Flow f; uint8_t ftpbuf1[] = "PO"; uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ uint8_t ftpbuf2[] = "RT\r\n"; uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */ TcpSession ssn; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); memset(&f, 0, sizeof(f)); memset(&ssn, 0, sizeof(ssn)); FLOW_INITIALIZE(&f); f.protoctx = (void *)&ssn; f.proto = IPPROTO_TCP; f.alproto = ALPROTO_FTP; StreamTcpInitConfig(TRUE); FLOWLOCK_WRLOCK(&f); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_START, ftpbuf1, ftplen1); if (r != 0) { SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_EOF, ftpbuf2, ftplen2); if (r != 0) { SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FtpState *ftp_state = f.alstate; if (ftp_state == NULL) { SCLogDebug("no ftp state: "); result = 0; goto end; } if (ftp_state->command != FTP_COMMAND_PORT) { SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); result = 0; goto end; } end: if (alp_tctx != NULL) AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(TRUE); FLOW_DESTROY(&f); return result; } /** \test Test case where chunks are smaller than the delim length and the * last chunk is supposed to match the delim. */ static int FTPParserTest10(void) { int result = 1; Flow f; uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n"; uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ TcpSession ssn; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); int r = 0; memset(&f, 0, sizeof(f)); memset(&ssn, 0, sizeof(ssn)); FLOW_INITIALIZE(&f); f.protoctx = (void *)&ssn; f.proto = IPPROTO_TCP; f.alproto = ALPROTO_FTP; StreamTcpInitConfig(TRUE); uint32_t u; for (u = 0; u < ftplen1; u++) { uint8_t flags = 0; if (u == 0) flags = STREAM_TOSERVER|STREAM_START; else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF; else flags = STREAM_TOSERVER; FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, flags, &ftpbuf1[u], 1); if (r != 0) { SCLogDebug("toserver chunk %" PRIu32 " returned %" PRId32 ", expected 0: ", u, r); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); } FtpState *ftp_state = f.alstate; if (ftp_state == NULL) { SCLogDebug("no ftp state: "); result = 0; goto end; } if (ftp_state->command != FTP_COMMAND_PORT) { SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); result = 0; goto end; } end: if (alp_tctx != NULL) AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(TRUE); FLOW_DESTROY(&f); return result; } /** \test Supply RETR without a filename */ static int FTPParserTest11(void) { int result = 1; Flow f; uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n"; uint8_t ftpbuf2[] = "RETR\r\n"; uint8_t ftpbuf3[] = "227 OK\r\n"; TcpSession ssn; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); memset(&f, 0, sizeof(f)); memset(&ssn, 0, sizeof(ssn)); FLOW_INITIALIZE(&f); f.protoctx = (void *)&ssn; f.proto = IPPROTO_TCP; f.alproto = ALPROTO_FTP; StreamTcpInitConfig(TRUE); FLOWLOCK_WRLOCK(&f); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_START, ftpbuf1, sizeof(ftpbuf1) - 1); if (r != 0) { result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); /* Response */ FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOCLIENT, ftpbuf3, sizeof(ftpbuf3) - 1); if (r != 0) { result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER, ftpbuf2, sizeof(ftpbuf2) - 1); if (r == 0) { SCLogDebug("parse should've failed"); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FtpState *ftp_state = f.alstate; if (ftp_state == NULL) { SCLogDebug("no ftp state: "); result = 0; goto end; } if (ftp_state->command != FTP_COMMAND_RETR) { SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_RETR, ftp_state->command); result = 0; goto end; } end: if (alp_tctx != NULL) AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(TRUE); FLOW_DESTROY(&f); return result; } /** \test Supply STOR without a filename */ static int FTPParserTest12(void) { int result = 1; Flow f; uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n"; uint8_t ftpbuf2[] = "STOR\r\n"; uint8_t ftpbuf3[] = "227 OK\r\n"; TcpSession ssn; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); memset(&f, 0, sizeof(f)); memset(&ssn, 0, sizeof(ssn)); FLOW_INITIALIZE(&f); f.protoctx = (void *)&ssn; f.proto = IPPROTO_TCP; f.alproto = ALPROTO_FTP; StreamTcpInitConfig(TRUE); FLOWLOCK_WRLOCK(&f); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER | STREAM_START, ftpbuf1, sizeof(ftpbuf1) - 1); if (r != 0) { result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); /* Response */ FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOCLIENT, ftpbuf3, sizeof(ftpbuf3) - 1); if (r != 0) { result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FLOWLOCK_WRLOCK(&f); r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER, ftpbuf2, sizeof(ftpbuf2) - 1); if (r == 0) { SCLogDebug("parse should've failed"); result = 0; FLOWLOCK_UNLOCK(&f); goto end; } FLOWLOCK_UNLOCK(&f); FtpState *ftp_state = f.alstate; if (ftp_state == NULL) { SCLogDebug("no ftp state: "); result = 0; goto end; } if (ftp_state->command != FTP_COMMAND_STOR) { SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_STOR, ftp_state->command); result = 0; goto end; } end: if (alp_tctx != NULL) AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(TRUE); FLOW_DESTROY(&f); return result; } #endif /* UNITTESTS */ void FTPParserRegisterTests(void) { #ifdef UNITTESTS UtRegisterTest("FTPParserTest01", FTPParserTest01); UtRegisterTest("FTPParserTest03", FTPParserTest03); UtRegisterTest("FTPParserTest06", FTPParserTest06); UtRegisterTest("FTPParserTest07", FTPParserTest07); UtRegisterTest("FTPParserTest10", FTPParserTest10); UtRegisterTest("FTPParserTest11", FTPParserTest11); UtRegisterTest("FTPParserTest12", FTPParserTest12); #endif /* UNITTESTS */ }