diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index bf1ccaf8fe..7aac727370 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -49,6 +49,117 @@ #include "util-debug.h" #include "util-memcmp.h" +static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state) +{ + 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; + SCFree(line_state->db); + 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 = SCMalloc(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 { + line_state->db = SCRealloc(line_state->db, + (line_state->db_len + + state->input_len)); + if (line_state->db == NULL) { + return -1; + } + 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) { + line_state->db = SCRealloc(line_state->db, + (line_state->db_len + + (lf_idx + 1 - state->input))); + if (line_state->db == NULL) { + return -1; + } + 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 * transfered to the ftp server @@ -62,6 +173,7 @@ static int FTPParseRequestCommand(void *ftp_state, uint8_t *input, uint32_t input_len) { SCEnter(); FtpState *fstate = (FtpState *)ftp_state; + fstate->command = FTP_COMMAND_UNKNOWN; if (input_len >= 4) { if (SCMemcmpLowercase("port", input, 4) == 0) { @@ -76,76 +188,6 @@ static int FTPParseRequestCommand(void *ftp_state, uint8_t *input, return 1; } -/** - * \brief This function is called to retrieve the request line and parse it - * \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 int FTPParseRequestCommandLine(Flow *f, void *ftp_state, AppLayerParserState - *pstate, uint8_t *input,uint32_t input_len, - void *local_data, AppLayerParserResult *output) { - SCEnter(); - //PrintRawDataFp(stdout, input,input_len); - - FtpState *fstate = (FtpState *)ftp_state; - uint16_t max_fields = 2; - uint16_t u = 0; - uint32_t offset = 0; - - if (pstate == NULL) - return -1; - - for (u = pstate->parse_field; u < max_fields; u++) { - - switch(u) { - case 0: /* REQUEST COMMAND */ - { - const uint8_t delim[] = { 0x20, }; - int r = AlpParseFieldByDelimiter(output, pstate, - FTP_FIELD_REQUEST_COMMAND, delim, sizeof(delim), - input, input_len, &offset); - - if (r == 0) { - pstate->parse_field = 0; - return 0; - } - fstate->arg_offset = offset; - FTPParseRequestCommand(ftp_state, input, input_len); - break; - } - case 1: /* REQUEST COMMAND ARG */ - { - switch (fstate->command) { - case FTP_COMMAND_PORT: - /* We don't need to parse args, we are going to check - * the ftpbounce condition directly from detect-ftpbounce - */ - if (fstate->port_line != NULL) - SCFree(fstate->port_line); - fstate->port_line = SCMalloc(input_len); - if (fstate->port_line == NULL) - return 0; - fstate->port_line = memcpy(fstate->port_line, input, - input_len); - fstate->port_line_len = input_len; - break; - default: - break; - } /* end switch command specified args */ - - break; - } - } - } - - pstate->parse_field = 0; - return 1; -} - /** * \brief This function is called to retrieve a ftp request * \param ftp_state the ftp state structure for the parser @@ -163,26 +205,32 @@ static int FTPParseRequest(Flow *f, void *ftp_state, SCEnter(); /* PrintRawDataFp(stdout, input,input_len); */ - uint32_t offset = 0; - - if (pstate == NULL) - return -1; - - - //PrintRawDataFp(stdout, pstate->store, pstate->store_len); - - const uint8_t delim[] = { 0x0D, 0x0A }; - int r = AlpParseFieldByDelimiter(output, pstate, FTP_FIELD_REQUEST_LINE, - delim, sizeof(delim), input, input_len, - &offset); - if (r == 0) { - pstate->parse_field = 0; - return 0; + FtpState *state = (FtpState *)ftp_state; + + state->input = input; + state->input_len = input_len; + /* toserver stream */ + state->direction = 0; + + while (FTPGetLine(state) >= 0) { + FTPParseRequestCommand(state, + state->current_line, state->current_line_len); + if (state->command == FTP_COMMAND_PORT) { + if (state->current_line_len > state->port_line_size) { + state->port_line = SCRealloc(state->port_line, + state->current_line_len); + if (state->port_line == NULL) { + state->port_line_size = 0; + return 0; + } + 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; + } } - if (pstate->store_len) - PrintRawDataFp(stdout, pstate->store, pstate->store_len); - pstate->parse_field = 0; return 1; } @@ -253,9 +301,6 @@ void RegisterFTPParsers(void) { FTPParseRequest); AppLayerRegisterProto(proto_name, ALPROTO_FTP, STREAM_TOCLIENT, FTPParseResponse); - AppLayerRegisterParser("ftp.request_command_line", ALPROTO_FTP, - FTP_FIELD_REQUEST_LINE, FTPParseRequestCommandLine, - "ftp"); AppLayerRegisterStateFuncs(ALPROTO_FTP, FTPStateAlloc, FTPStateFree); } else { SCLogInfo("Parsed disabled for %s protocol. Protocol detection" @@ -479,8 +524,9 @@ int FTPParserTest07(void) { goto end; } - if (ftp_state->command != FTP_COMMAND_UNKNOWN) { - SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command); + if (ftp_state->command != FTP_COMMAND_PORT) { + SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", + FTP_COMMAND_PORT, ftp_state->command); result = 0; goto end; } diff --git a/src/app-layer-ftp.h b/src/app-layer-ftp.h index c102addc90..4a001290d5 100644 --- a/src/app-layer-ftp.h +++ b/src/app-layer-ftp.h @@ -91,11 +91,37 @@ enum { FTP_FIELD_MAX, }; +/** used to hold the line state when we have fragmentation. */ +typedef struct FtpLineState_ { + /** used to indicate if the current_line buffer is a malloced buffer. We + * use a malloced buffer, if a line is fragmented */ + uint8_t *db; + uint32_t db_len; + uint8_t current_line_db; + /** we have see LF for the currently parsed line */ + uint8_t current_line_lf_seen; +} FtpLineState; + /** FTP State for app layer parser */ typedef struct FtpState_ { + uint8_t *input; + int32_t input_len; + uint8_t direction; + + /* --parser details-- */ + /** current line extracted by the parser from the call to FTPGetline() */ + uint8_t *current_line; + /** length of the line in current_line. Doesn't include the delimiter */ + uint32_t current_line_len; + uint8_t current_line_delimiter_len; + + /* 0 for toserver, 1 for toclient */ + FtpLineState line_state[2]; + FtpRequestCommand command; FtpRequestCommandArgOfs arg_offset; uint32_t port_line_len; + uint32_t port_line_size; uint8_t *port_line; } FtpState;