1 /* Copyright (C) 2007-2020 Open Information Security Foundation
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
22 * \author Eric Leblond <eric@regit.org>
23 * \author Jeff Lucovsky <jeff@lucovsky.org>
25 * App Layer Parser for FTP
28 #include "suricata-common.h"
33 #include "util-print.h"
34 #include "util-pool.h"
36 #include "flow-util.h"
37 #include "flow-storage.h"
39 #include "detect-engine-state.h"
41 #include "stream-tcp-private.h"
42 #include "stream-tcp-reassemble.h"
43 #include "stream-tcp.h"
46 #include "app-layer.h"
47 #include "app-layer-protos.h"
48 #include "app-layer-parser.h"
49 #include "app-layer-ftp.h"
50 #include "app-layer-expectation.h"
54 #include "util-unittest.h"
55 #include "util-debug.h"
56 #include "util-memcmp.h"
57 #include "util-memrchr.h"
59 #include "util-misc.h"
61 #include "output-json.h"
64 typedef struct FTPThreadCtx_
{
65 MpmThreadCtx
*ftp_mpm_thread_ctx
;
66 PrefilterRuleStore
*pmq
;
69 #define FTP_MPM mpm_default_matcher
71 static MpmCtx
*ftp_mpm_ctx
= NULL
;
73 const FtpCommand FtpCommands
[FTP_COMMAND_MAX
+ 1] = {
74 /* Parsed and handled */
75 { FTP_COMMAND_PORT
, "PORT", 4},
76 { FTP_COMMAND_EPRT
, "EPRT", 4},
77 { FTP_COMMAND_AUTH_TLS
, "AUTH TLS", 8},
78 { FTP_COMMAND_PASV
, "PASV", 4},
79 { FTP_COMMAND_RETR
, "RETR", 4},
80 { FTP_COMMAND_EPSV
, "EPSV", 4},
81 { FTP_COMMAND_STOR
, "STOR", 4},
83 /* Parsed, but not handled */
84 { FTP_COMMAND_ABOR
, "ABOR", 4},
85 { FTP_COMMAND_ACCT
, "ACCT", 4},
86 { FTP_COMMAND_ALLO
, "ALLO", 4},
87 { FTP_COMMAND_APPE
, "APPE", 4},
88 { FTP_COMMAND_CDUP
, "CDUP", 4},
89 { FTP_COMMAND_CHMOD
, "CHMOD", 5},
90 { FTP_COMMAND_CWD
, "CWD", 3},
91 { FTP_COMMAND_DELE
, "DELE", 4},
92 { FTP_COMMAND_HELP
, "HELP", 4},
93 { FTP_COMMAND_IDLE
, "IDLE", 4},
94 { FTP_COMMAND_LIST
, "LIST", 4},
95 { FTP_COMMAND_MAIL
, "MAIL", 4},
96 { FTP_COMMAND_MDTM
, "MDTM", 4},
97 { FTP_COMMAND_MKD
, "MKD", 3},
98 { FTP_COMMAND_MLFL
, "MLFL", 4},
99 { FTP_COMMAND_MODE
, "MODE", 4},
100 { FTP_COMMAND_MRCP
, "MRCP", 4},
101 { FTP_COMMAND_MRSQ
, "MRSQ", 4},
102 { FTP_COMMAND_MSAM
, "MSAM", 4},
103 { FTP_COMMAND_MSND
, "MSND", 4},
104 { FTP_COMMAND_MSOM
, "MSOM", 4},
105 { FTP_COMMAND_NLST
, "NLST", 4},
106 { FTP_COMMAND_NOOP
, "NOOP", 4},
107 { FTP_COMMAND_PASS
, "PASS", 4},
108 { FTP_COMMAND_PWD
, "PWD", 3},
109 { FTP_COMMAND_QUIT
, "QUIT", 4},
110 { FTP_COMMAND_REIN
, "REIN", 4},
111 { FTP_COMMAND_REST
, "REST", 4},
112 { FTP_COMMAND_RMD
, "RMD", 3},
113 { FTP_COMMAND_RNFR
, "RNFR", 4},
114 { FTP_COMMAND_RNTO
, "RNTO", 4},
115 { FTP_COMMAND_SITE
, "SITE", 4},
116 { FTP_COMMAND_SIZE
, "SIZE", 4},
117 { FTP_COMMAND_SMNT
, "SMNT", 4},
118 { FTP_COMMAND_STAT
, "STAT", 4},
119 { FTP_COMMAND_STOU
, "STOU", 4},
120 { FTP_COMMAND_STRU
, "STRU", 4},
121 { FTP_COMMAND_SYST
, "SYST", 4},
122 { FTP_COMMAND_TYPE
, "TYPE", 4},
123 { FTP_COMMAND_UMASK
, "UMASK", 5},
124 { FTP_COMMAND_USER
, "USER", 4},
125 { FTP_COMMAND_UNKNOWN
, NULL
, 0}
127 uint64_t ftp_config_memcap
= 0;
129 SC_ATOMIC_DECLARE(uint64_t, ftp_memuse
);
130 SC_ATOMIC_DECLARE(uint64_t, ftp_memcap
);
132 static FTPTransaction
*FTPGetOldestTx(FtpState
*, FTPTransaction
*);
134 static void FTPParseMemcap(void)
136 const char *conf_val
;
138 /** set config values for memcap, prealloc and hash_size */
139 if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val
)) == 1)
141 if (ParseSizeStringU64(conf_val
, &ftp_config_memcap
) < 0) {
142 SCLogError(SC_ERR_SIZE_PARSE
, "Error parsing ftp.memcap "
143 "from conf file - %s. Killing engine",
147 SCLogInfo("FTP memcap: %"PRIu64
, ftp_config_memcap
);
149 /* default to unlimited */
150 ftp_config_memcap
= 0;
153 SC_ATOMIC_INIT(ftp_memuse
);
154 SC_ATOMIC_INIT(ftp_memcap
);
157 static void FTPIncrMemuse(uint64_t size
)
159 (void) SC_ATOMIC_ADD(ftp_memuse
, size
);
163 static void FTPDecrMemuse(uint64_t size
)
165 (void) SC_ATOMIC_SUB(ftp_memuse
, size
);
169 uint64_t FTPMemuseGlobalCounter(void)
171 uint64_t tmpval
= SC_ATOMIC_GET(ftp_memuse
);
175 uint64_t FTPMemcapGlobalCounter(void)
177 uint64_t tmpval
= SC_ATOMIC_GET(ftp_memcap
);
182 * \brief Check if alloc'ing "size" would mean we're over memcap
184 * \retval 1 if in bounds
185 * \retval 0 if not in bounds
187 static int FTPCheckMemcap(uint64_t size
)
189 if (ftp_config_memcap
== 0 || size
+ SC_ATOMIC_GET(ftp_memuse
) <= ftp_config_memcap
)
191 (void) SC_ATOMIC_ADD(ftp_memcap
, 1);
195 static void *FTPMalloc(size_t size
)
199 if (FTPCheckMemcap((uint32_t)size
) == 0)
202 ptr
= SCMalloc(size
);
204 if (unlikely(ptr
== NULL
))
207 FTPIncrMemuse((uint64_t)size
);
212 static void *FTPCalloc(size_t n
, size_t size
)
214 if (FTPCheckMemcap((uint32_t)(n
* size
)) == 0)
217 void *ptr
= SCCalloc(n
, size
);
219 if (unlikely(ptr
== NULL
))
222 FTPIncrMemuse((uint64_t)(n
* size
));
226 static void *FTPRealloc(void *ptr
, size_t orig_size
, size_t size
)
230 if (FTPCheckMemcap((uint32_t)(size
- orig_size
)) == 0)
233 rptr
= SCRealloc(ptr
, size
);
237 if (size
> orig_size
) {
238 FTPIncrMemuse(size
- orig_size
);
240 FTPDecrMemuse(orig_size
- size
);
246 static void FTPFree(void *ptr
, size_t size
)
250 FTPDecrMemuse((uint64_t)size
);
253 static FTPString
*FTPStringAlloc(void)
255 return FTPCalloc(1, sizeof(FTPString
));
258 static void FTPStringFree(FTPString
*str
)
261 FTPFree(str
->str
, str
->len
);
264 FTPFree(str
, sizeof(FTPString
));
267 static void *FTPLocalStorageAlloc(void)
269 /* needed by the mpm */
270 FTPThreadCtx
*td
= SCCalloc(1, sizeof(*td
));
275 td
->pmq
= SCCalloc(1, sizeof(*td
->pmq
));
276 if (td
->pmq
== NULL
) {
281 td
->ftp_mpm_thread_ctx
= SCCalloc(1, sizeof(MpmThreadCtx
));
282 if (unlikely(td
->ftp_mpm_thread_ctx
== NULL
)) {
285 MpmInitThreadCtx(td
->ftp_mpm_thread_ctx
, FTP_MPM
);
289 static void FTPLocalStorageFree(void *ptr
)
291 FTPThreadCtx
*td
= ptr
;
293 if (td
->pmq
!= NULL
) {
298 if (td
->ftp_mpm_thread_ctx
!= NULL
) {
299 mpm_table
[FTP_MPM
].DestroyThreadCtx(ftp_mpm_ctx
, td
->ftp_mpm_thread_ctx
);
300 SCFree(td
->ftp_mpm_thread_ctx
);
308 static FTPTransaction
*FTPTransactionCreate(FtpState
*state
)
311 FTPTransaction
*tx
= FTPCalloc(1, sizeof(*tx
));
316 TAILQ_INSERT_TAIL(&state
->tx_list
, tx
, next
);
317 tx
->tx_id
= state
->tx_cnt
++;
319 TAILQ_INIT(&tx
->response_list
);
321 SCLogDebug("new transaction %p (state tx cnt %"PRIu64
")", tx
, state
->tx_cnt
);
325 static void FTPTransactionFree(FTPTransaction
*tx
)
329 if (tx
->tx_data
.de_state
!= NULL
) {
330 DetectEngineStateFree(tx
->tx_data
.de_state
);
334 FTPFree(tx
->request
, tx
->request_length
);
337 FTPString
*str
= NULL
;
338 while ((str
= TAILQ_FIRST(&tx
->response_list
))) {
339 TAILQ_REMOVE(&tx
->response_list
, str
, next
);
343 FTPFree(tx
, sizeof(*tx
));
346 static int FTPGetLineForDirection(FtpState
*state
, FtpLineState
*line_state
)
349 if (line_state
->current_line_lf_seen
== 1) {
350 /* we have seen the lf for the previous line. Clear the parser
351 * details to parse new line */
352 line_state
->current_line_lf_seen
= 0;
353 if (line_state
->current_line_db
== 1) {
354 line_state
->current_line_db
= 0;
355 FTPFree(line_state
->db
, line_state
->db_len
);
356 line_state
->db
= NULL
;
357 line_state
->db_len
= 0;
358 state
->current_line
= NULL
;
359 state
->current_line_len
= 0;
363 uint8_t *lf_idx
= memchr(state
->input
, 0x0a, state
->input_len
);
365 if (lf_idx
== NULL
) {
366 /* fragmented lines. Decoder event for special cases. Not all
367 * fragmented lines should be treated as a possible evasion
368 * attempt. With multi payload ftp chunks we can have valid
369 * cases of fragmentation. But within the same segment chunk
370 * if we see fragmentation then it's definitely something you
371 * should alert about */
372 if (line_state
->current_line_db
== 0) {
373 line_state
->db
= FTPMalloc(state
->input_len
);
374 if (line_state
->db
== NULL
) {
377 line_state
->current_line_db
= 1;
378 memcpy(line_state
->db
, state
->input
, state
->input_len
);
379 line_state
->db_len
= state
->input_len
;
381 ptmp
= FTPRealloc(line_state
->db
, line_state
->db_len
,
382 (line_state
->db_len
+ state
->input_len
));
384 FTPFree(line_state
->db
, line_state
->db_len
);
385 line_state
->db
= NULL
;
386 line_state
->db_len
= 0;
389 line_state
->db
= ptmp
;
391 memcpy(line_state
->db
+ line_state
->db_len
,
392 state
->input
, state
->input_len
);
393 line_state
->db_len
+= state
->input_len
;
395 state
->input
+= state
->input_len
;
396 state
->input_len
= 0;
401 line_state
->current_line_lf_seen
= 1;
403 if (line_state
->current_line_db
== 1) {
404 ptmp
= FTPRealloc(line_state
->db
, line_state
->db_len
,
405 (line_state
->db_len
+ (lf_idx
+ 1 - state
->input
)));
407 FTPFree(line_state
->db
, line_state
->db_len
);
408 line_state
->db
= NULL
;
409 line_state
->db_len
= 0;
412 line_state
->db
= ptmp
;
414 memcpy(line_state
->db
+ line_state
->db_len
,
415 state
->input
, (lf_idx
+ 1 - state
->input
));
416 line_state
->db_len
+= (lf_idx
+ 1 - state
->input
);
418 if (line_state
->db_len
> 1 &&
419 line_state
->db
[line_state
->db_len
- 2] == 0x0D) {
420 line_state
->db_len
-= 2;
421 state
->current_line_delimiter_len
= 2;
423 line_state
->db_len
-= 1;
424 state
->current_line_delimiter_len
= 1;
427 state
->current_line
= line_state
->db
;
428 state
->current_line_len
= line_state
->db_len
;
431 state
->current_line
= state
->input
;
432 state
->current_line_len
= lf_idx
- state
->input
;
434 if (state
->input
!= lf_idx
&&
435 *(lf_idx
- 1) == 0x0D) {
436 state
->current_line_len
--;
437 state
->current_line_delimiter_len
= 2;
439 state
->current_line_delimiter_len
= 1;
443 state
->input_len
-= (lf_idx
- state
->input
) + 1;
444 state
->input
= (lf_idx
+ 1);
451 static int FTPGetLine(FtpState
*state
)
455 /* we have run out of input */
456 if (state
->input_len
<= 0)
460 if (state
->direction
== 0)
461 return FTPGetLineForDirection(state
, &state
->line_state
[0]);
463 return FTPGetLineForDirection(state
, &state
->line_state
[1]);
467 * \brief This function is called to determine and set which command is being
468 * transferred to the ftp server
469 * \param thread context
470 * \param input input line of the command
471 * \param len of the command
472 * \param cmd_descriptor when the command has been parsed
474 * \retval 1 when the command is parsed, 0 otherwise
476 static int FTPParseRequestCommand(FTPThreadCtx
*td
,
477 const uint8_t *input
, uint32_t input_len
,
478 const FtpCommand
**cmd_descriptor
)
482 /* I don't like this pmq reset here. We'll devise a method later, that
483 * should make the use of the mpm very efficient */
485 int mpm_cnt
= mpm_table
[FTP_MPM
].Search(ftp_mpm_ctx
, td
->ftp_mpm_thread_ctx
,
486 td
->pmq
, input
, input_len
);
488 *cmd_descriptor
= &FtpCommands
[td
->pmq
->rule_id_array
[0]];
492 *cmd_descriptor
= NULL
;
496 struct FtpTransferCmd
{
497 /** Need to look like a ExpectationData so DFree must
498 * be first field . */
499 void (*DFree
)(void *);
503 FtpRequestCommand cmd
;
506 static void FtpTransferCmdFree(void *data
)
508 struct FtpTransferCmd
*cmd
= (struct FtpTransferCmd
*) data
;
511 if (cmd
->file_name
) {
512 FTPFree(cmd
->file_name
, cmd
->file_len
+ 1);
514 FTPFree(cmd
, sizeof(struct FtpTransferCmd
));
517 static uint32_t CopyCommandLine(uint8_t **dest
, const uint8_t *src
, uint32_t length
)
519 if (likely(length
)) {
520 uint8_t *where
= FTPCalloc(length
+ 1, sizeof(char));
521 if (unlikely(where
== NULL
)) {
524 memcpy(where
, src
, length
);
526 /* Remove trailing newlines/carriage returns */
527 while (length
&& isspace((unsigned char) where
[length
- 1])) {
531 where
[length
] = '\0';
534 /* either 0 or actual */
535 return length
? length
+ 1 : 0;
540 * \brief This function is called to retrieve a ftp request
541 * \param ftp_state the ftp state structure for the parser
542 * \param input input line of the command
543 * \param input_len length of the request
544 * \param output the resulting output
546 * \retval APP_LAYER_OK when input was process successfully
547 * \retval APP_LAYER_ERROR when a unrecoverable error was encountered
549 static AppLayerResult
FTPParseRequest(Flow
*f
, void *ftp_state
,
550 AppLayerParserState
*pstate
,
551 const uint8_t *input
, uint32_t input_len
,
552 void *local_data
, const uint8_t flags
)
554 FTPThreadCtx
*thread_data
= local_data
;
557 /* PrintRawDataFp(stdout, input,input_len); */
559 FtpState
*state
= (FtpState
*)ftp_state
;
562 if (input
== NULL
&& AppLayerParserStateIssetFlag(pstate
, APP_LAYER_PARSER_EOF_TS
)) {
563 SCReturnStruct(APP_LAYER_OK
);
564 } else if (input
== NULL
|| input_len
== 0) {
565 SCReturnStruct(APP_LAYER_ERROR
);
568 state
->input
= input
;
569 state
->input_len
= input_len
;
570 /* toserver stream */
571 state
->direction
= 0;
573 int direction
= STREAM_TOSERVER
;
574 while (FTPGetLine(state
) >= 0) {
575 const FtpCommand
*cmd_descriptor
;
577 if (!FTPParseRequestCommand(thread_data
,
578 state
->current_line
, state
->current_line_len
,
580 state
->command
= FTP_COMMAND_UNKNOWN
;
584 state
->command
= cmd_descriptor
->command
;
586 FTPTransaction
*tx
= FTPTransactionCreate(state
);
587 if (unlikely(tx
== NULL
))
588 SCReturnStruct(APP_LAYER_ERROR
);
591 tx
->command_descriptor
= cmd_descriptor
;
592 tx
->request_length
= CopyCommandLine(&tx
->request
,
593 state
->current_line
, state
->current_line_len
);
595 /* change direction (default to server) so expectation will handle
596 * the correct message when expectation will match.
597 * For ftp active mode, data connection direction is opposite to
600 if ((state
->active
&& state
->command
== FTP_COMMAND_STOR
) ||
601 (!state
->active
&& state
->command
== FTP_COMMAND_RETR
)) {
602 direction
= STREAM_TOCLIENT
;
605 switch (state
->command
) {
606 case FTP_COMMAND_EPRT
:
608 case FTP_COMMAND_PORT
:
609 if (state
->current_line_len
+ 1 > state
->port_line_size
) {
610 /* Allocate an extra byte for a NULL terminator */
611 ptmp
= FTPRealloc(state
->port_line
, state
->port_line_size
,
612 state
->current_line_len
);
614 if (state
->port_line
) {
615 FTPFree(state
->port_line
, state
->port_line_size
);
616 state
->port_line
= NULL
;
617 state
->port_line_size
= 0;
619 SCReturnStruct(APP_LAYER_OK
);
621 state
->port_line
= ptmp
;
622 state
->port_line_size
= state
->current_line_len
;
624 memcpy(state
->port_line
, state
->current_line
,
625 state
->current_line_len
);
626 state
->port_line_len
= state
->current_line_len
;
628 case FTP_COMMAND_RETR
:
630 case FTP_COMMAND_STOR
: {
631 /* Ensure that there is a negotiated dyn port and a file
632 * name -- need more than 5 chars: cmd [4], space, <filename>
634 if (state
->dyn_port
== 0 || state
->current_line_len
< 6) {
635 SCReturnStruct(APP_LAYER_ERROR
);
637 struct FtpTransferCmd
*data
= FTPCalloc(1, sizeof(struct FtpTransferCmd
));
639 SCReturnStruct(APP_LAYER_ERROR
);
640 data
->DFree
= FtpTransferCmdFree
;
642 * Min size has been checked in FTPParseRequestCommand
643 * PATH_MAX includes the null
645 int file_name_len
= MIN(PATH_MAX
- 1, state
->current_line_len
- 5);
646 data
->file_name
= FTPCalloc(file_name_len
+ 1, sizeof(char));
647 if (data
->file_name
== NULL
) {
648 FtpTransferCmdFree(data
);
649 SCReturnStruct(APP_LAYER_ERROR
);
651 data
->file_name
[file_name_len
] = 0;
652 data
->file_len
= file_name_len
;
653 memcpy(data
->file_name
, state
->current_line
+ 5, file_name_len
);
654 data
->cmd
= state
->command
;
655 data
->flow_id
= FlowGetId(f
);
656 int ret
= AppLayerExpectationCreate(f
, direction
,
657 0, state
->dyn_port
, ALPROTO_FTPDATA
, data
);
659 FtpTransferCmdFree(data
);
660 SCLogDebug("No expectation created.");
661 SCReturnStruct(APP_LAYER_ERROR
);
663 SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16
"].",
664 state
->active
? "to server" : "to client",
668 /* reset the dyn port to avoid duplicate */
670 /* reset active/passive indicator */
671 state
->active
= false;
679 SCReturnStruct(APP_LAYER_OK
);
682 static int FTPParsePassiveResponse(Flow
*f
, FtpState
*state
, const uint8_t *input
, uint32_t input_len
)
684 uint16_t dyn_port
= rs_ftp_pasv_response(input
, input_len
);
688 SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16
"", dyn_port
);
689 state
->active
= false;
690 state
->dyn_port
= dyn_port
;
691 state
->curr_tx
->dyn_port
= dyn_port
;
692 state
->curr_tx
->active
= false;
697 static int FTPParsePassiveResponseV6(Flow
*f
, FtpState
*state
, const uint8_t *input
, uint32_t input_len
)
699 uint16_t dyn_port
= rs_ftp_epsv_response(input
, input_len
);
703 SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16
"", dyn_port
);
704 state
->active
= false;
705 state
->dyn_port
= dyn_port
;
706 state
->curr_tx
->dyn_port
= dyn_port
;
707 state
->curr_tx
->active
= false;
712 * \brief Handle preliminary replies -- keep tx open
713 * \retval bool True for a positive preliminary reply; false otherwise
715 * 1yz Positive Preliminary reply
717 * The requested action is being initiated; expect another
718 * reply before proceeding with a new command
720 static inline bool FTPIsPPR(const uint8_t *input
, uint32_t input_len
)
722 return input_len
>= 4 && isdigit(input
[0]) && input
[0] == '1' &&
723 isdigit(input
[1]) && isdigit(input
[2]) && isspace(input
[3]);
727 * \brief This function is called to retrieve a ftp response
728 * \param ftp_state the ftp state structure for the parser
729 * \param input input line of the command
730 * \param input_len length of the request
731 * \param output the resulting output
733 * \retval 1 when the command is parsed, 0 otherwise
735 static AppLayerResult
FTPParseResponse(Flow
*f
, void *ftp_state
, AppLayerParserState
*pstate
,
736 const uint8_t *input
, uint32_t input_len
,
737 void *local_data
, const uint8_t flags
)
739 FtpState
*state
= (FtpState
*)ftp_state
;
741 if (unlikely(input_len
== 0)) {
742 SCReturnStruct(APP_LAYER_OK
);
744 state
->input
= input
;
745 state
->input_len
= input_len
;
746 /* toclient stream */
747 state
->direction
= 1;
749 FTPTransaction
*lasttx
= TAILQ_FIRST(&state
->tx_list
);
750 while (FTPGetLine(state
) >= 0) {
751 FTPTransaction
*tx
= FTPGetOldestTx(state
, lasttx
);
753 tx
= FTPTransactionCreate(state
);
755 if (unlikely(tx
== NULL
)) {
756 SCReturnStruct(APP_LAYER_ERROR
);
759 if (state
->command
== FTP_COMMAND_UNKNOWN
|| tx
->command_descriptor
== NULL
) {
761 tx
->command_descriptor
= &FtpCommands
[FTP_COMMAND_MAX
-1];
766 switch (state
->command
) {
767 case FTP_COMMAND_AUTH_TLS
:
768 if (state
->current_line_len
>= 4 && SCMemcmp("234 ", state
->current_line
, 4) == 0) {
769 AppLayerRequestProtocolTLSUpgrade(f
);
773 case FTP_COMMAND_EPRT
:
774 dyn_port
= rs_ftp_active_eprt(state
->port_line
, state
->port_line_len
);
778 state
->dyn_port
= dyn_port
;
779 state
->active
= true;
780 tx
->dyn_port
= dyn_port
;
782 SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16
"", dyn_port
);
785 case FTP_COMMAND_PORT
:
786 dyn_port
= rs_ftp_active_port(state
->port_line
, state
->port_line_len
);
790 state
->dyn_port
= dyn_port
;
791 state
->active
= true;
792 tx
->dyn_port
= state
->dyn_port
;
794 SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16
"", dyn_port
);
797 case FTP_COMMAND_PASV
:
798 if (state
->current_line_len
>= 4 && SCMemcmp("227 ", state
->current_line
, 4) == 0) {
799 FTPParsePassiveResponse(f
, ftp_state
, state
->current_line
, state
->current_line_len
);
803 case FTP_COMMAND_EPSV
:
804 if (state
->current_line_len
>= 4 && SCMemcmp("229 ", state
->current_line
, 4) == 0) {
805 FTPParsePassiveResponseV6(f
, ftp_state
, state
->current_line
, state
->current_line_len
);
812 if (likely(state
->current_line_len
)) {
813 FTPString
*response
= FTPStringAlloc();
814 if (likely(response
)) {
815 response
->len
= CopyCommandLine(&response
->str
, state
->current_line
, state
->current_line_len
);
816 TAILQ_INSERT_TAIL(&tx
->response_list
, response
, next
);
820 /* Handle preliminary replies -- keep tx open */
821 if (FTPIsPPR(state
->current_line
, state
->current_line_len
)) {
828 SCReturnStruct(APP_LAYER_OK
);
833 static SCMutex ftp_state_mem_lock
= SCMUTEX_INITIALIZER
;
834 static uint64_t ftp_state_memuse
= 0;
835 static uint64_t ftp_state_memcnt
= 0;
838 static void *FTPStateAlloc(void *orig_state
, AppProto proto_orig
)
840 void *s
= FTPCalloc(1, sizeof(FtpState
));
841 if (unlikely(s
== NULL
))
844 FtpState
*ftp_state
= (FtpState
*) s
;
845 TAILQ_INIT(&ftp_state
->tx_list
);
848 SCMutexLock(&ftp_state_mem_lock
);
850 ftp_state_memuse
+=sizeof(FtpState
);
851 SCMutexUnlock(&ftp_state_mem_lock
);
856 static void FTPStateFree(void *s
)
858 FtpState
*fstate
= (FtpState
*) s
;
859 if (fstate
->port_line
!= NULL
)
860 FTPFree(fstate
->port_line
, fstate
->port_line_size
);
861 if (fstate
->line_state
[0].db
)
862 FTPFree(fstate
->line_state
[0].db
, fstate
->line_state
[0].db_len
);
863 if (fstate
->line_state
[1].db
)
864 FTPFree(fstate
->line_state
[1].db
, fstate
->line_state
[1].db_len
);
866 FTPTransaction
*tx
= NULL
;
867 while ((tx
= TAILQ_FIRST(&fstate
->tx_list
))) {
868 TAILQ_REMOVE(&fstate
->tx_list
, tx
, next
);
869 SCLogDebug("[%s] state %p id %"PRIu64
", Freeing %d bytes at %p",
870 tx
->command_descriptor
->command_name
,
872 tx
->request_length
, tx
->request
);
873 FTPTransactionFree(tx
);
876 FTPFree(s
, sizeof(FtpState
));
878 SCMutexLock(&ftp_state_mem_lock
);
880 ftp_state_memuse
-=sizeof(FtpState
);
881 SCMutexUnlock(&ftp_state_mem_lock
);
886 * \brief This function returns the oldest open transaction; if none
887 * are open, then the oldest transaction is returned
888 * \param ftp_state the ftp state structure for the parser
889 * \param starttx the ftp transaction where to start looking
891 * \retval transaction pointer when a transaction was found; NULL otherwise.
893 static FTPTransaction
*FTPGetOldestTx(FtpState
*ftp_state
, FTPTransaction
*starttx
)
895 if (unlikely(!ftp_state
)) {
896 SCLogDebug("NULL state object; no transactions available");
899 FTPTransaction
*tx
= starttx
;
900 FTPTransaction
*lasttx
= NULL
;
902 /* Return oldest open tx */
904 SCLogDebug("Returning tx %p id %"PRIu64
, tx
, tx
->tx_id
);
907 /* save for the end */
909 tx
= TAILQ_NEXT(tx
, next
);
911 /* All tx are closed; return last element */
913 SCLogDebug("Returning OLDEST tx %p id %"PRIu64
, lasttx
, lasttx
->tx_id
);
917 static void *FTPGetTx(void *state
, uint64_t tx_id
)
919 FtpState
*ftp_state
= (FtpState
*)state
;
921 FTPTransaction
*tx
= NULL
;
923 if (ftp_state
->curr_tx
== NULL
)
925 if (ftp_state
->curr_tx
->tx_id
== tx_id
)
926 return ftp_state
->curr_tx
;
928 TAILQ_FOREACH(tx
, &ftp_state
->tx_list
, next
) {
929 if (tx
->tx_id
== tx_id
)
936 static AppLayerTxData
*FTPGetTxData(void *vtx
)
938 FTPTransaction
*tx
= (FTPTransaction
*)vtx
;
942 static void FTPStateTransactionFree(void *state
, uint64_t tx_id
)
944 FtpState
*ftp_state
= state
;
945 FTPTransaction
*tx
= NULL
;
946 TAILQ_FOREACH(tx
, &ftp_state
->tx_list
, next
) {
947 if (tx_id
< tx
->tx_id
)
949 else if (tx_id
> tx
->tx_id
)
952 if (tx
== ftp_state
->curr_tx
)
953 ftp_state
->curr_tx
= NULL
;
954 TAILQ_REMOVE(&ftp_state
->tx_list
, tx
, next
);
955 FTPTransactionFree(tx
);
960 static uint64_t FTPGetTxCnt(void *state
)
963 FtpState
*ftp_state
= state
;
965 cnt
= ftp_state
->tx_cnt
;
967 SCLogDebug("returning state %p %"PRIu64
, state
, cnt
);
971 static int FTPGetAlstateProgress(void *vtx
, uint8_t direction
)
973 SCLogDebug("tx %p", vtx
);
974 FTPTransaction
*tx
= vtx
;
977 if (direction
== STREAM_TOSERVER
&&
978 tx
->command_descriptor
->command
== FTP_COMMAND_PORT
) {
979 return FTP_STATE_PORT_DONE
;
981 return FTP_STATE_IN_PROGRESS
;
984 return FTP_STATE_FINISHED
;
988 static int FTPRegisterPatternsForProtocolDetection(void)
990 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP
, ALPROTO_FTP
,
991 "220 (", 5, 0, STREAM_TOCLIENT
) < 0)
995 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP
, ALPROTO_FTP
,
996 "FEAT", 4, 0, STREAM_TOSERVER
) < 0)
1000 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP
, ALPROTO_FTP
,
1001 "USER ", 5, 0, STREAM_TOSERVER
) < 0)
1005 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP
, ALPROTO_FTP
,
1006 "PASS ", 5, 0, STREAM_TOSERVER
) < 0)
1010 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP
, ALPROTO_FTP
,
1011 "PORT ", 5, 0, STREAM_TOSERVER
) < 0)
1020 static StreamingBufferConfig sbcfg
= STREAMING_BUFFER_CONFIG_INITIALIZER
;
1023 * \brief This function is called to retrieve a ftp request
1024 * \param ftp_state the ftp state structure for the parser
1025 * \param input input line of the command
1026 * \param input_len length of the request
1027 * \param output the resulting output
1029 * \retval 1 when the command is parsed, 0 otherwise
1031 static AppLayerResult
FTPDataParse(Flow
*f
, FtpDataState
*ftpdata_state
,
1032 AppLayerParserState
*pstate
,
1033 const uint8_t *input
, uint32_t input_len
,
1034 void *local_data
, int direction
)
1036 uint16_t flags
= FileFlowToFlags(f
, direction
);
1038 /* we depend on detection engine for file pruning */
1039 flags
|= FILE_USE_DETECT
;
1040 if (ftpdata_state
->files
== NULL
) {
1041 struct FtpTransferCmd
*data
=
1042 (struct FtpTransferCmd
*)FlowGetStorageById(f
, AppLayerExpectationGetFlowId());
1044 SCReturnStruct(APP_LAYER_ERROR
);
1047 ftpdata_state
->files
= FileContainerAlloc();
1048 if (ftpdata_state
->files
== NULL
) {
1049 FlowFreeStorageById(f
, AppLayerExpectationGetFlowId());
1050 SCReturnStruct(APP_LAYER_ERROR
);
1053 ftpdata_state
->file_name
= data
->file_name
;
1054 ftpdata_state
->file_len
= data
->file_len
;
1055 data
->file_name
= NULL
;
1057 f
->parent_id
= data
->flow_id
;
1058 ftpdata_state
->command
= data
->cmd
;
1059 switch (data
->cmd
) {
1060 case FTP_COMMAND_STOR
:
1061 ftpdata_state
->direction
= STREAM_TOSERVER
;
1063 case FTP_COMMAND_RETR
:
1064 ftpdata_state
->direction
= STREAM_TOCLIENT
;
1070 /* open with fixed track_id 0 as we can have just one
1071 * file per ftp-data flow. */
1072 if (FileOpenFileWithId(ftpdata_state
->files
, &sbcfg
,
1073 0ULL, (uint8_t *) ftpdata_state
->file_name
,
1074 ftpdata_state
->file_len
,
1075 input
, input_len
, flags
) != 0) {
1076 SCLogDebug("Can't open file");
1079 FlowFreeStorageById(f
, AppLayerExpectationGetFlowId());
1080 ftpdata_state
->tx_data
.files_opened
= 1;
1082 if (input_len
!= 0) {
1083 ret
= FileAppendData(ftpdata_state
->files
, input
, input_len
);
1086 SCLogDebug("FileAppendData() - file no longer being extracted");
1088 } else if (ret
< 0) {
1089 SCLogDebug("FileAppendData() failed: %d", ret
);
1094 ret
= FileCloseFile(ftpdata_state
->files
, NULL
, 0, flags
);
1095 ftpdata_state
->state
= FTPDATA_STATE_FINISHED
;
1101 const bool eof_flag
= flags
& STREAM_TOSERVER
?
1102 AppLayerParserStateIssetFlag(pstate
, APP_LAYER_PARSER_EOF_TS
) != 0 :
1103 AppLayerParserStateIssetFlag(pstate
, APP_LAYER_PARSER_EOF_TC
) != 0;
1104 if (input_len
&& eof_flag
) {
1105 ret
= FileCloseFile(ftpdata_state
->files
, (uint8_t *) NULL
, 0, flags
);
1106 ftpdata_state
->state
= FTPDATA_STATE_FINISHED
;
1111 SCReturnStruct(APP_LAYER_ERROR
);
1113 SCReturnStruct(APP_LAYER_OK
);
1116 static AppLayerResult
FTPDataParseRequest(Flow
*f
, void *ftp_state
,
1117 AppLayerParserState
*pstate
,
1118 const uint8_t *input
, uint32_t input_len
,
1119 void *local_data
, const uint8_t flags
)
1121 return FTPDataParse(f
, ftp_state
, pstate
, input
, input_len
,
1122 local_data
, STREAM_TOSERVER
);
1125 static AppLayerResult
FTPDataParseResponse(Flow
*f
, void *ftp_state
,
1126 AppLayerParserState
*pstate
,
1127 const uint8_t *input
, uint32_t input_len
,
1128 void *local_data
, const uint8_t flags
)
1130 return FTPDataParse(f
, ftp_state
, pstate
, input
, input_len
,
1131 local_data
, STREAM_TOCLIENT
);
1135 static SCMutex ftpdata_state_mem_lock
= SCMUTEX_INITIALIZER
;
1136 static uint64_t ftpdata_state_memuse
= 0;
1137 static uint64_t ftpdata_state_memcnt
= 0;
1140 static void *FTPDataStateAlloc(void *orig_state
, AppProto proto_orig
)
1142 void *s
= FTPCalloc(1, sizeof(FtpDataState
));
1143 if (unlikely(s
== NULL
))
1146 FtpDataState
*state
= (FtpDataState
*) s
;
1147 state
->state
= FTPDATA_STATE_IN_PROGRESS
;
1150 SCMutexLock(&ftpdata_state_mem_lock
);
1151 ftpdata_state_memcnt
++;
1152 ftpdata_state_memuse
+=sizeof(FtpDataState
);
1153 SCMutexUnlock(&ftpdata_state_mem_lock
);
1158 static void FTPDataStateFree(void *s
)
1160 FtpDataState
*fstate
= (FtpDataState
*) s
;
1162 if (fstate
->tx_data
.de_state
!= NULL
) {
1163 DetectEngineStateFree(fstate
->tx_data
.de_state
);
1165 if (fstate
->file_name
!= NULL
) {
1166 FTPFree(fstate
->file_name
, fstate
->file_len
+ 1);
1169 FileContainerFree(fstate
->files
);
1171 FTPFree(s
, sizeof(FtpDataState
));
1173 SCMutexLock(&ftpdata_state_mem_lock
);
1174 ftpdata_state_memcnt
--;
1175 ftpdata_state_memuse
-=sizeof(FtpDataState
);
1176 SCMutexUnlock(&ftpdata_state_mem_lock
);
1180 static AppLayerTxData
*FTPDataGetTxData(void *vtx
)
1182 FtpDataState
*ftp_state
= (FtpDataState
*)vtx
;
1183 return &ftp_state
->tx_data
;
1186 static void FTPDataStateTransactionFree(void *state
, uint64_t tx_id
)
1191 static void *FTPDataGetTx(void *state
, uint64_t tx_id
)
1193 FtpDataState
*ftp_state
= (FtpDataState
*)state
;
1197 static uint64_t FTPDataGetTxCnt(void *state
)
1199 /* ftp-data is single tx */
1203 static int FTPDataGetAlstateProgress(void *tx
, uint8_t direction
)
1205 FtpDataState
*ftpdata_state
= (FtpDataState
*)tx
;
1206 return ftpdata_state
->state
;
1209 static FileContainer
*FTPDataStateGetFiles(void *state
, uint8_t direction
)
1211 FtpDataState
*ftpdata_state
= (FtpDataState
*)state
;
1213 if (direction
!= ftpdata_state
->direction
)
1214 SCReturnPtr(NULL
, "FileContainer");
1216 SCReturnPtr(ftpdata_state
->files
, "FileContainer");
1219 static void FTPSetMpmState(void)
1221 ftp_mpm_ctx
= SCMalloc(sizeof(MpmCtx
));
1222 if (unlikely(ftp_mpm_ctx
== NULL
)) {
1225 memset(ftp_mpm_ctx
, 0, sizeof(MpmCtx
));
1226 MpmInitCtx(ftp_mpm_ctx
, FTP_MPM
);
1229 for (i
= 0; i
< sizeof(FtpCommands
)/sizeof(FtpCommand
) - 1; i
++) {
1230 const FtpCommand
*cmd
= &FtpCommands
[i
];
1231 if (cmd
->command_length
== 0)
1234 MpmAddPatternCI(ftp_mpm_ctx
,
1235 (uint8_t *)cmd
->command_name
,
1236 cmd
->command_length
,
1237 0 /* defunct */, 0 /* defunct */,
1238 i
/* id */, i
/* rule id */ , 0 /* no flags */);
1241 mpm_table
[FTP_MPM
].Prepare(ftp_mpm_ctx
);
1245 static void FTPFreeMpmState(void)
1247 if (ftp_mpm_ctx
!= NULL
) {
1248 mpm_table
[FTP_MPM
].DestroyCtx(ftp_mpm_ctx
);
1249 SCFree(ftp_mpm_ctx
);
1254 void RegisterFTPParsers(void)
1256 const char *proto_name
= "ftp";
1257 const char *proto_data_name
= "ftp-data";
1260 if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name
)) {
1261 AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP
, proto_name
);
1262 if (FTPRegisterPatternsForProtocolDetection() < 0 )
1264 AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA
, proto_data_name
);
1267 if (AppLayerParserConfParserEnabled("tcp", proto_name
)) {
1268 AppLayerParserRegisterParser(IPPROTO_TCP
, ALPROTO_FTP
, STREAM_TOSERVER
,
1270 AppLayerParserRegisterParser(IPPROTO_TCP
, ALPROTO_FTP
, STREAM_TOCLIENT
,
1272 AppLayerParserRegisterStateFuncs(IPPROTO_TCP
, ALPROTO_FTP
, FTPStateAlloc
, FTPStateFree
);
1273 AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP
, ALPROTO_FTP
, STREAM_TOSERVER
| STREAM_TOCLIENT
);
1275 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP
, ALPROTO_FTP
, FTPStateTransactionFree
);
1277 AppLayerParserRegisterGetTx(IPPROTO_TCP
, ALPROTO_FTP
, FTPGetTx
);
1278 AppLayerParserRegisterTxDataFunc(IPPROTO_TCP
, ALPROTO_FTP
, FTPGetTxData
);
1280 AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP
, ALPROTO_FTP
, FTPLocalStorageAlloc
,
1281 FTPLocalStorageFree
);
1282 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP
, ALPROTO_FTP
, FTPGetTxCnt
);
1284 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP
, ALPROTO_FTP
, FTPGetAlstateProgress
);
1286 AppLayerParserRegisterStateProgressCompletionStatus(
1287 ALPROTO_FTP
, FTP_STATE_FINISHED
, FTP_STATE_FINISHED
);
1289 AppLayerRegisterExpectationProto(IPPROTO_TCP
, ALPROTO_FTPDATA
);
1290 AppLayerParserRegisterParser(IPPROTO_TCP
, ALPROTO_FTPDATA
, STREAM_TOSERVER
,
1291 FTPDataParseRequest
);
1292 AppLayerParserRegisterParser(IPPROTO_TCP
, ALPROTO_FTPDATA
, STREAM_TOCLIENT
,
1293 FTPDataParseResponse
);
1294 AppLayerParserRegisterStateFuncs(IPPROTO_TCP
, ALPROTO_FTPDATA
, FTPDataStateAlloc
, FTPDataStateFree
);
1295 AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP
, ALPROTO_FTPDATA
, STREAM_TOSERVER
| STREAM_TOCLIENT
);
1296 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP
, ALPROTO_FTPDATA
, FTPDataStateTransactionFree
);
1298 AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP
, ALPROTO_FTPDATA
, FTPDataStateGetFiles
);
1300 AppLayerParserRegisterGetTx(IPPROTO_TCP
, ALPROTO_FTPDATA
, FTPDataGetTx
);
1301 AppLayerParserRegisterTxDataFunc(IPPROTO_TCP
, ALPROTO_FTPDATA
, FTPDataGetTxData
);
1303 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP
, ALPROTO_FTPDATA
, FTPDataGetTxCnt
);
1305 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP
, ALPROTO_FTPDATA
, FTPDataGetAlstateProgress
);
1307 AppLayerParserRegisterStateProgressCompletionStatus(
1308 ALPROTO_FTPDATA
, FTPDATA_STATE_FINISHED
, FTPDATA_STATE_FINISHED
);
1310 sbcfg
.buf_size
= 4096;
1311 sbcfg
.Malloc
= FTPMalloc
;
1312 sbcfg
.Calloc
= FTPCalloc
;
1313 sbcfg
.Realloc
= FTPRealloc
;
1314 sbcfg
.Free
= FTPFree
;
1318 SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
1319 "still on.", proto_name
);
1325 AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP
, ALPROTO_FTP
, FTPParserRegisterTests
);
1329 void FTPAtExitPrintStats(void)
1332 SCMutexLock(&ftp_state_mem_lock
);
1333 SCLogDebug("ftp_state_memcnt %"PRIu64
", ftp_state_memuse %"PRIu64
"",
1334 ftp_state_memcnt
, ftp_state_memuse
);
1335 SCMutexUnlock(&ftp_state_mem_lock
);
1341 * \brief Returns the ending offset of the next line from a multi-line buffer.
1343 * "Buffer" refers to a FTP response in a single buffer containing multiple lines.
1344 * Here, "next line" is defined as terminating on
1345 * - Newline character
1348 * \param buffer Contains zero or more characters.
1349 * \param len Size, in bytes, of buffer.
1351 * \retval Offset from the start of buffer indicating the where the
1352 * next "line ends". The characters between the input buffer and this
1353 * value comprise the line.
1355 * NULL is found first or a newline isn't found, then UINT16_MAX is returned.
1357 uint16_t JsonGetNextLineFromBuffer(const char *buffer
, const uint16_t len
)
1359 if (!buffer
|| *buffer
== '\0') {
1363 char *c
= strchr(buffer
, '\n');
1364 return c
== NULL
? len
: c
- buffer
+ 1;
1367 void EveFTPDataAddMetadata(const Flow
*f
, JsonBuilder
*jb
)
1369 const FtpDataState
*ftp_state
= NULL
;
1370 if (f
->alstate
== NULL
)
1373 ftp_state
= (FtpDataState
*)f
->alstate
;
1375 if (ftp_state
->file_name
) {
1376 jb_set_string_from_bytes(jb
, "filename", ftp_state
->file_name
, ftp_state
->file_len
);
1378 switch (ftp_state
->command
) {
1379 case FTP_COMMAND_STOR
:
1380 JB_SET_STRING(jb
, "command", "STOR");
1382 case FTP_COMMAND_RETR
:
1383 JB_SET_STRING(jb
, "command", "RETR");
1391 * \brief Free memory allocated for global FTP parser state.
1393 void FTPParserCleanup(void)
1401 /** \test Send a get request in one chunk. */
1402 static int FTPParserTest01(void)
1405 uint8_t ftpbuf
[] = "PORT 192,168,1,1,0,80\r\n";
1406 uint32_t ftplen
= sizeof(ftpbuf
) - 1; /* minus the \0 */
1408 AppLayerParserThreadCtx
*alp_tctx
= AppLayerParserThreadCtxAlloc();
1410 memset(&f
, 0, sizeof(f
));
1411 memset(&ssn
, 0, sizeof(ssn
));
1413 f
.protoctx
= (void *)&ssn
;
1414 f
.proto
= IPPROTO_TCP
;
1415 f
.alproto
= ALPROTO_FTP
;
1417 StreamTcpInitConfig(true);
1419 int r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1420 STREAM_TOSERVER
| STREAM_EOF
, ftpbuf
, ftplen
);
1423 FtpState
*ftp_state
= f
.alstate
;
1424 FAIL_IF_NULL(ftp_state
);
1425 FAIL_IF(ftp_state
->command
!= FTP_COMMAND_PORT
);
1427 AppLayerParserThreadCtxFree(alp_tctx
);
1428 StreamTcpFreeConfig(true);
1432 /** \test Send a split get request. */
1433 static int FTPParserTest03(void)
1436 uint8_t ftpbuf1
[] = "POR";
1437 uint32_t ftplen1
= sizeof(ftpbuf1
) - 1; /* minus the \0 */
1438 uint8_t ftpbuf2
[] = "T 192,168,1";
1439 uint32_t ftplen2
= sizeof(ftpbuf2
) - 1; /* minus the \0 */
1440 uint8_t ftpbuf3
[] = "1,1,10,20\r\n";
1441 uint32_t ftplen3
= sizeof(ftpbuf3
) - 1; /* minus the \0 */
1443 AppLayerParserThreadCtx
*alp_tctx
= AppLayerParserThreadCtxAlloc();
1445 memset(&f
, 0, sizeof(f
));
1446 memset(&ssn
, 0, sizeof(ssn
));
1448 f
.protoctx
= (void *)&ssn
;
1449 f
.proto
= IPPROTO_TCP
;
1450 f
.alproto
= ALPROTO_FTP
;
1452 StreamTcpInitConfig(true);
1454 int r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1455 STREAM_TOSERVER
| STREAM_START
, ftpbuf1
,
1459 r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
, STREAM_TOSERVER
,
1463 r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1464 STREAM_TOSERVER
| STREAM_EOF
, ftpbuf3
, ftplen3
);
1467 FtpState
*ftp_state
= f
.alstate
;
1468 FAIL_IF_NULL(ftp_state
);
1470 FAIL_IF(ftp_state
->command
!= FTP_COMMAND_PORT
);
1472 AppLayerParserThreadCtxFree(alp_tctx
);
1473 StreamTcpFreeConfig(true);
1477 /** \test See how it deals with an incomplete request. */
1478 static int FTPParserTest06(void)
1481 uint8_t ftpbuf1
[] = "PORT";
1482 uint32_t ftplen1
= sizeof(ftpbuf1
) - 1; /* minus the \0 */
1484 AppLayerParserThreadCtx
*alp_tctx
= AppLayerParserThreadCtxAlloc();
1486 memset(&f
, 0, sizeof(f
));
1487 memset(&ssn
, 0, sizeof(ssn
));
1489 f
.protoctx
= (void *)&ssn
;
1490 f
.proto
= IPPROTO_TCP
;
1491 f
.alproto
= ALPROTO_FTP
;
1493 StreamTcpInitConfig(true);
1495 int r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1496 STREAM_TOSERVER
| STREAM_START
| STREAM_EOF
,
1501 FtpState
*ftp_state
= f
.alstate
;
1502 FAIL_IF_NULL(ftp_state
);
1504 FAIL_IF(ftp_state
->command
!= FTP_COMMAND_UNKNOWN
);
1506 AppLayerParserThreadCtxFree(alp_tctx
);
1507 StreamTcpFreeConfig(true);
1511 /** \test See how it deals with an incomplete request in multiple chunks. */
1512 static int FTPParserTest07(void)
1515 uint8_t ftpbuf1
[] = "PO";
1516 uint32_t ftplen1
= sizeof(ftpbuf1
) - 1; /* minus the \0 */
1517 uint8_t ftpbuf2
[] = "RT\r\n";
1518 uint32_t ftplen2
= sizeof(ftpbuf2
) - 1; /* minus the \0 */
1520 AppLayerParserThreadCtx
*alp_tctx
= AppLayerParserThreadCtxAlloc();
1522 memset(&f
, 0, sizeof(f
));
1523 memset(&ssn
, 0, sizeof(ssn
));
1525 f
.protoctx
= (void *)&ssn
;
1526 f
.proto
= IPPROTO_TCP
;
1527 f
.alproto
= ALPROTO_FTP
;
1529 StreamTcpInitConfig(true);
1531 int r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1532 STREAM_TOSERVER
| STREAM_START
, ftpbuf1
,
1536 r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1537 STREAM_TOSERVER
| STREAM_EOF
, ftpbuf2
, ftplen2
);
1540 FtpState
*ftp_state
= f
.alstate
;
1541 FAIL_IF_NULL(ftp_state
);
1543 FAIL_IF(ftp_state
->command
!= FTP_COMMAND_PORT
);
1545 AppLayerParserThreadCtxFree(alp_tctx
);
1546 StreamTcpFreeConfig(true);
1550 /** \test Test case where chunks are smaller than the delim length and the
1551 * last chunk is supposed to match the delim. */
1552 static int FTPParserTest10(void)
1555 uint8_t ftpbuf1
[] = "PORT 1,2,3,4,5,6\r\n";
1556 uint32_t ftplen1
= sizeof(ftpbuf1
) - 1; /* minus the \0 */
1558 AppLayerParserThreadCtx
*alp_tctx
= AppLayerParserThreadCtxAlloc();
1560 memset(&f
, 0, sizeof(f
));
1561 memset(&ssn
, 0, sizeof(ssn
));
1563 f
.protoctx
= (void *)&ssn
;
1564 f
.proto
= IPPROTO_TCP
;
1565 f
.alproto
= ALPROTO_FTP
;
1567 StreamTcpInitConfig(true);
1570 for (u
= 0; u
< ftplen1
; u
++) {
1573 if (u
== 0) flags
= STREAM_TOSERVER
|STREAM_START
;
1574 else if (u
== (ftplen1
- 1)) flags
= STREAM_TOSERVER
|STREAM_EOF
;
1575 else flags
= STREAM_TOSERVER
;
1577 r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
, flags
,
1582 FtpState
*ftp_state
= f
.alstate
;
1583 FAIL_IF_NULL(ftp_state
);
1585 FAIL_IF(ftp_state
->command
!= FTP_COMMAND_PORT
);
1587 AppLayerParserThreadCtxFree(alp_tctx
);
1588 StreamTcpFreeConfig(true);
1592 /** \test Supply RETR without a filename */
1593 static int FTPParserTest11(void)
1596 uint8_t ftpbuf1
[] = "PORT 192,168,1,1,0,80\r\n";
1597 uint8_t ftpbuf2
[] = "RETR\r\n";
1598 uint8_t ftpbuf3
[] = "227 OK\r\n";
1601 AppLayerParserThreadCtx
*alp_tctx
= AppLayerParserThreadCtxAlloc();
1603 memset(&f
, 0, sizeof(f
));
1604 memset(&ssn
, 0, sizeof(ssn
));
1606 f
.protoctx
= (void *)&ssn
;
1607 f
.proto
= IPPROTO_TCP
;
1608 f
.alproto
= ALPROTO_FTP
;
1610 StreamTcpInitConfig(true);
1612 int r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1613 STREAM_TOSERVER
| STREAM_START
, ftpbuf1
,
1614 sizeof(ftpbuf1
) - 1);
1618 r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1621 sizeof(ftpbuf3
) - 1);
1624 r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1625 STREAM_TOSERVER
, ftpbuf2
,
1626 sizeof(ftpbuf2
) - 1);
1629 FtpState
*ftp_state
= f
.alstate
;
1630 FAIL_IF_NULL(ftp_state
);
1632 FAIL_IF(ftp_state
->command
!= FTP_COMMAND_RETR
);
1634 AppLayerParserThreadCtxFree(alp_tctx
);
1635 StreamTcpFreeConfig(true);
1639 /** \test Supply STOR without a filename */
1640 static int FTPParserTest12(void)
1643 uint8_t ftpbuf1
[] = "PORT 192,168,1,1,0,80\r\n";
1644 uint8_t ftpbuf2
[] = "STOR\r\n";
1645 uint8_t ftpbuf3
[] = "227 OK\r\n";
1648 AppLayerParserThreadCtx
*alp_tctx
= AppLayerParserThreadCtxAlloc();
1650 memset(&f
, 0, sizeof(f
));
1651 memset(&ssn
, 0, sizeof(ssn
));
1653 f
.protoctx
= (void *)&ssn
;
1654 f
.proto
= IPPROTO_TCP
;
1655 f
.alproto
= ALPROTO_FTP
;
1657 StreamTcpInitConfig(true);
1659 int r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1660 STREAM_TOSERVER
| STREAM_START
, ftpbuf1
,
1661 sizeof(ftpbuf1
) - 1);
1665 r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1668 sizeof(ftpbuf3
) - 1);
1671 r
= AppLayerParserParse(NULL
, alp_tctx
, &f
, ALPROTO_FTP
,
1672 STREAM_TOSERVER
, ftpbuf2
,
1673 sizeof(ftpbuf2
) - 1);
1676 FtpState
*ftp_state
= f
.alstate
;
1677 FAIL_IF_NULL(ftp_state
);
1679 FAIL_IF(ftp_state
->command
!= FTP_COMMAND_STOR
);
1681 AppLayerParserThreadCtxFree(alp_tctx
);
1682 StreamTcpFreeConfig(true);
1685 #endif /* UNITTESTS */
1687 void FTPParserRegisterTests(void)
1690 UtRegisterTest("FTPParserTest01", FTPParserTest01
);
1691 UtRegisterTest("FTPParserTest03", FTPParserTest03
);
1692 UtRegisterTest("FTPParserTest06", FTPParserTest06
);
1693 UtRegisterTest("FTPParserTest07", FTPParserTest07
);
1694 UtRegisterTest("FTPParserTest10", FTPParserTest10
);
1695 UtRegisterTest("FTPParserTest11", FTPParserTest11
);
1696 UtRegisterTest("FTPParserTest12", FTPParserTest12
);
1697 #endif /* UNITTESTS */