suricata
app-layer-ftp.c
Go to the documentation of this file.
1/* Copyright (C) 2007-2025 Open Information Security Foundation
2 *
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
5 * Software Foundation.
6 *
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.
11 *
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
15 * 02110-1301, USA.
16 */
17
18/**
19 * \file
20 *
21 * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
22 * \author Eric Leblond <eric@regit.org>
23 * \author Jeff Lucovsky <jlucovsky@oisf.net>
24 *
25 * App Layer Parser for FTP
26 */
27
28#include "suricata-common.h"
29#include "app-layer-ftp.h"
30#include "app-layer.h"
31#include "app-layer-parser.h"
34#include "app-layer-events.h"
35
36#include "rust.h"
37
38#include "util-misc.h"
39#include "util-mpm.h"
40#include "util-validate.h"
41
46
47#define FTP_MPM mpm_default_matcher
48
49static MpmCtx *ftp_mpm_ctx = NULL;
50
51uint64_t ftp_config_memcap = 0;
52uint32_t ftp_config_maxtx = 1024;
53uint32_t ftp_max_line_len = 4096;
54
55SC_ATOMIC_DECLARE(uint64_t, ftp_memuse);
56SC_ATOMIC_DECLARE(uint64_t, ftp_memcap);
57
58static FTPTransaction *FTPGetOldestTx(const FtpState *, FTPTransaction *);
59
60static void FTPParseMemcap(void)
61{
62 SCFTPGetConfigValues(&ftp_config_memcap, &ftp_config_maxtx, &ftp_max_line_len);
63
64 SC_ATOMIC_INIT(ftp_memuse);
65 SC_ATOMIC_INIT(ftp_memcap);
66}
67
68static void FTPIncrMemuse(uint64_t size)
69{
70 (void)SC_ATOMIC_ADD(ftp_memuse, size);
71}
72
73static void FTPDecrMemuse(uint64_t size)
74{
75 (void)SC_ATOMIC_SUB(ftp_memuse, size);
76}
77
79{
80 uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse);
81 return tmpval;
82}
83
85{
86 uint64_t tmpval = SC_ATOMIC_GET(ftp_memcap);
87 return tmpval;
88}
89
90int FTPSetMemcap(uint64_t size)
91{
92 if ((uint64_t)SC_ATOMIC_GET(ftp_memcap) < size) {
93 SC_ATOMIC_SET(ftp_memcap, size);
94 return 1;
95 }
96
97 return 0;
98}
99
100/**
101 * \brief Check if alloc'ing "size" would mean we're over memcap
102 *
103 * \retval 1 if in bounds
104 * \retval 0 if not in bounds
105 */
106static int FTPCheckMemcap(uint64_t size)
107{
108 if (ftp_config_memcap == 0 || size + SC_ATOMIC_GET(ftp_memuse) <= ftp_config_memcap)
109 return 1;
110 (void) SC_ATOMIC_ADD(ftp_memcap, 1);
111 return 0;
112}
113
114static void *FTPCalloc(size_t n, size_t size)
115{
116 if (FTPCheckMemcap((uint32_t)(n * size)) == 0) {
118 return NULL;
119 }
120
121 void *ptr = SCCalloc(n, size);
122
123 if (unlikely(ptr == NULL)) {
125 return NULL;
126 }
127
128 FTPIncrMemuse((uint64_t)(n * size));
129 return ptr;
130}
131
132static void *FTPRealloc(void *ptr, size_t orig_size, size_t size)
133{
134 if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0) {
136 return NULL;
137 }
138
139 void *rptr = SCRealloc(ptr, size);
140 if (rptr == NULL) {
142 return NULL;
143 }
144
145 if (size > orig_size) {
146 FTPIncrMemuse(size - orig_size);
147 } else {
148 FTPDecrMemuse(orig_size - size);
149 }
150
151 return rptr;
152}
153
154static void FTPFree(void *ptr, size_t size)
155{
156 SCFree(ptr);
157
158 FTPDecrMemuse((uint64_t)size);
159}
160
161static FTPResponseWrapper *FTPResponseWrapperAlloc(FTPResponseLine *response)
162{
163 FTPResponseWrapper *wrapper = FTPCalloc(1, sizeof(FTPResponseWrapper));
164 if (likely(wrapper)) {
165 FTPIncrMemuse(response->total_size);
166 wrapper->response = response;
167 }
168 return wrapper;
169}
170
171static void FTPResponseWrapperFree(FTPResponseWrapper *wrapper)
172{
173 if (wrapper->response) {
174 FTPDecrMemuse(wrapper->response->total_size);
175 SCFTPFreeResponseLine(wrapper->response);
176 }
177
178 FTPFree(wrapper, sizeof(FTPResponseWrapper));
179}
180
181static void *FTPLocalStorageAlloc(void)
182{
183 /* needed by the mpm */
184 FTPThreadCtx *td = SCCalloc(1, sizeof(*td));
185 if (td == NULL) {
186 exit(EXIT_FAILURE);
187 }
188
189 td->pmq = SCCalloc(1, sizeof(*td->pmq));
190 if (td->pmq == NULL) {
191 exit(EXIT_FAILURE);
192 }
193 PmqSetup(td->pmq);
194
195 td->ftp_mpm_thread_ctx = SCCalloc(1, sizeof(MpmThreadCtx));
196 if (unlikely(td->ftp_mpm_thread_ctx == NULL)) {
197 exit(EXIT_FAILURE);
198 }
200 return td;
201}
202
203static void FTPLocalStorageFree(void *ptr)
204{
205 FTPThreadCtx *td = ptr;
206 if (td != NULL) {
207 if (td->pmq != NULL) {
208 PmqFree(td->pmq);
209 SCFree(td->pmq);
210 }
211
212 if (td->ftp_mpm_thread_ctx != NULL) {
215 }
216
217 SCFree(td);
218 }
219}
220static FTPTransaction *FTPTransactionCreate(FtpState *state)
221{
222 SCEnter();
223 FTPTransaction *firsttx = TAILQ_FIRST(&state->tx_list);
224 if (firsttx && state->tx_cnt - firsttx->tx_id > ftp_config_maxtx) {
225 // FTP does not set events yet...
226 return NULL;
227 }
228 FTPTransaction *tx = FTPCalloc(1, sizeof(*tx));
229 if (tx == NULL) {
230 return NULL;
231 }
232
233 TAILQ_INSERT_TAIL(&state->tx_list, tx, next);
234 tx->tx_id = state->tx_cnt++;
235
236 TAILQ_INIT(&tx->response_list);
237
238 SCLogDebug("new transaction %p (state tx cnt %"PRIu64")", tx, state->tx_cnt);
239 return tx;
240}
241
242static void FTPTransactionFree(FTPTransaction *tx)
243{
244 SCEnter();
245
246 SCAppLayerTxDataCleanup(&tx->tx_data);
247
248 if (tx->request) {
249 FTPFree(tx->request, tx->request_length);
250 }
251
252 FTPResponseWrapper *wrapper;
253 while ((wrapper = TAILQ_FIRST(&tx->response_list))) {
254 TAILQ_REMOVE(&tx->response_list, wrapper, next);
255 FTPResponseWrapperFree(wrapper);
256 }
257
258 FTPFree(tx, sizeof(*tx));
259}
260
261typedef struct FtpInput_ {
262 const uint8_t *buf;
263 int32_t consumed;
264 int32_t len;
265 int32_t orig_len;
267
268static AppLayerResult FTPGetLineForDirection(
269 FtpLineState *line, FtpInput *input, bool *current_line_truncated)
270{
271 SCEnter();
272
273 /* we have run out of input */
274 if (input->len <= 0)
275 return APP_LAYER_ERROR;
276
277 uint8_t *lf_idx = memchr(input->buf + input->consumed, 0x0a, input->len);
278
279 if (lf_idx == NULL) {
280 if (!(*current_line_truncated) && (uint32_t)input->len >= ftp_max_line_len) {
281 *current_line_truncated = true;
282 line->buf = input->buf;
283 line->len = ftp_max_line_len;
284 line->delim_len = 0;
285 input->len = 0;
287 }
288 SCReturnStruct(APP_LAYER_INCOMPLETE(input->consumed, input->len + 1));
289 } else if (*current_line_truncated) {
290 // Whatever came in with first LF should also get discarded
291 *current_line_truncated = false;
292 line->len = 0;
293 line->delim_len = 0;
294 input->len = 0;
296 } else {
297 // There could be one chunk of command data that has LF but post the line limit
298 // e.g. input_len = 5077
299 // lf_idx = 5010
300 // max_line_len = 4096
301 uint32_t o_consumed = input->consumed;
302 input->consumed = (uint32_t)(lf_idx - input->buf + 1);
303 line->len = input->consumed - o_consumed;
304 input->len -= line->len;
305 line->lf_found = true;
306 DEBUG_VALIDATE_BUG_ON((input->consumed + input->len) != input->orig_len);
307 line->buf = input->buf + o_consumed;
308 if (line->len >= ftp_max_line_len) {
309 *current_line_truncated = true;
310 line->len = ftp_max_line_len;
312 }
313 if (input->consumed >= 2 && input->buf[input->consumed - 2] == 0x0D) {
314 line->delim_len = 2;
315 line->len -= 2;
316 } else {
317 line->delim_len = 1;
318 line->len -= 1;
319 }
321 }
322}
323
324/**
325 * \brief This function is called to determine and set which command is being
326 * transferred to the ftp server
327 * \param thread context
328 * \param input input line of the command
329 * \param len of the command
330 * \param cmd_descriptor when the command has been parsed
331 *
332 * \retval 1 when the command is parsed, 0 otherwise
333 */
334static int FTPParseRequestCommand(
335 FTPThreadCtx *td, FtpLineState *line, FtpCommandInfo *cmd_descriptor)
336{
337 SCEnter();
338
339 /* I don't like this pmq reset here. We'll devise a method later, that
340 * should make the use of the mpm very efficient */
341 PmqReset(td->pmq);
342 int mpm_cnt = mpm_table[FTP_MPM].Search(
343 ftp_mpm_ctx, td->ftp_mpm_thread_ctx, td->pmq, line->buf, line->len);
344 if (mpm_cnt) {
345 uint8_t command_code;
346 if (SCGetFtpCommandInfo(td->pmq->rule_id_array[0], NULL, &command_code, NULL)) {
347 cmd_descriptor->command_code = command_code;
348 /* FTP command indices are expressed in Rust as a u8 */
349 cmd_descriptor->command_index = (uint8_t)td->pmq->rule_id_array[0];
350 SCReturnInt(1);
351 } else {
352 /* Where is out command? */
354 }
355#ifdef DEBUG
356 if (SCLogDebugEnabled()) {
357 const char *command_name = NULL;
358 (void)SCGetFtpCommandInfo(td->pmq->rule_id_array[0], &command_name, NULL, NULL);
359 SCLogDebug("matching FTP command is %s [code: %d, index %d]", command_name,
360 command_code, td->pmq->rule_id_array[0]);
361 }
362#endif
363 }
364
365 cmd_descriptor->command_code = FTP_COMMAND_UNKNOWN;
366 SCReturnInt(0);
367}
368
369static void FtpTransferCmdFree(void *data)
370{
371 FtpTransferCmd *cmd = (FtpTransferCmd *)data;
372 if (cmd == NULL)
373 return;
374 if (cmd->file_name) {
375 FTPFree((void *)cmd->file_name, cmd->file_len + 1);
376 }
377 SCFTPTransferCmdFree(cmd);
378 FTPDecrMemuse((uint64_t)sizeof(FtpTransferCmd));
379}
380
381static uint32_t CopyCommandLine(uint8_t **dest, FtpLineState *line)
382{
383 if (likely(line->len)) {
384 uint8_t *where = FTPCalloc(line->len + 1, sizeof(char));
385 if (unlikely(where == NULL)) {
386 return 0;
387 }
388 memcpy(where, line->buf, line->len);
389
390 /* Remove trailing newlines/carriage returns */
391 while (line->len && isspace((unsigned char)where[line->len - 1])) {
392 line->len--;
393 }
394
395 where[line->len] = '\0';
396 *dest = where;
397 }
398 /* either 0 or actual */
399 return line->len ? line->len + 1 : 0;
400}
401
402#include "util-print.h"
403
404/**
405 * \brief This function is called to retrieve a ftp request
406 * \param ftp_state the ftp state structure for the parser
407 *
408 * \retval APP_LAYER_OK when input was process successfully
409 * \retval APP_LAYER_ERROR when a unrecoverable error was encountered
410 */
411static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserState *pstate,
412 StreamSlice stream_slice, void *local_data)
413{
414 FTPThreadCtx *thread_data = local_data;
415
416 SCEnter();
417 /* PrintRawDataFp(stdout, input,input_len); */
418
419 FtpState *state = (FtpState *)ftp_state;
420 void *ptmp;
421
422 const uint8_t *input = StreamSliceGetData(&stream_slice);
423 uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
424
425 if (input == NULL && SCAppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) {
427 } else if (input == NULL || input_len == 0) {
429 }
430
431 FtpInput ftpi = { .buf = input, .len = input_len, .orig_len = input_len, .consumed = 0 };
432 FtpLineState line = { .buf = NULL, .len = 0, .delim_len = 0, .lf_found = false };
433
434 uint8_t direction = STREAM_TOSERVER;
435 AppLayerResult res;
436 while (1) {
437 res = FTPGetLineForDirection(&line, &ftpi, &state->current_line_truncated_ts);
438 if (res.status == 1) {
439 return res;
440 } else if (res.status == -1) {
441 break;
442 }
443
444 FtpCommandInfo cmd_descriptor;
445 if (!FTPParseRequestCommand(thread_data, &line, &cmd_descriptor)) {
446 state->command = FTP_COMMAND_UNKNOWN;
447 continue;
448 }
449
450 state->command = cmd_descriptor.command_code;
451 FTPTransaction *tx = FTPTransactionCreate(state);
452 if (unlikely(tx == NULL))
454 tx->tx_data.updated_ts = true;
455 state->curr_tx = tx;
456
457 tx->command_descriptor = cmd_descriptor;
458 tx->request_length = CopyCommandLine(&tx->request, &line);
460
461 if (line.lf_found) {
462 state->current_line_truncated_ts = false;
463 }
464 if (tx->request_truncated) {
465 AppLayerDecoderEventsSetEventRaw(&tx->tx_data.events, FtpEventRequestCommandTooLong);
466 }
467
468 /* change direction (default to server) so expectation will handle
469 * the correct message when expectation will match.
470 * For ftp active mode, data connection direction is opposite to
471 * control direction.
472 */
473 if ((state->active && state->command == FTP_COMMAND_STOR) ||
474 (!state->active && state->command == FTP_COMMAND_RETR)) {
475 direction = STREAM_TOCLIENT;
476 }
477
478 switch (state->command) {
479 case FTP_COMMAND_EPRT:
480 // fallthrough
481 case FTP_COMMAND_PORT:
482 if (line.len + 1 > state->port_line_size) {
483 /* Allocate an extra byte for a NULL terminator */
484 ptmp = FTPRealloc(state->port_line, state->port_line_size, line.len + 1);
485 if (ptmp == NULL) {
486 if (state->port_line) {
487 FTPFree(state->port_line, state->port_line_size);
488 state->port_line = NULL;
489 state->port_line_size = 0;
490 state->port_line_len = 0;
491 }
493 }
494 state->port_line = ptmp;
495 state->port_line_size = line.len + 1;
496 }
497 memcpy(state->port_line, line.buf, line.len);
498 state->port_line_len = line.len;
499 break;
500 case FTP_COMMAND_RETR:
501 // fallthrough
502 case FTP_COMMAND_STOR: {
503 /* Ensure that there is a negotiated dyn port and a file
504 * name -- need more than 5 chars: cmd [4], space, <filename>
505 */
506 if (state->dyn_port == 0 || line.len < 6) {
508 }
509 FtpTransferCmd *data = SCFTPTransferCmdNew();
510 if (data == NULL)
512 FTPIncrMemuse((uint64_t)(sizeof *data));
513 data->data_free = FtpTransferCmdFree;
514
515 /*
516 * Min size has been checked in FTPParseRequestCommand
517 * SC_FILENAME_MAX includes the null
518 */
519 uint32_t file_name_len = MIN(SC_FILENAME_MAX - 1, line.len - 5);
520#if SC_FILENAME_MAX > UINT16_MAX
521#error SC_FILENAME_MAX is greater than UINT16_MAX
522#endif
523 data->file_name = FTPCalloc(file_name_len + 1, sizeof(char));
524 if (data->file_name == NULL) {
525 FtpTransferCmdFree(data);
527 }
528 data->file_name[file_name_len] = 0;
529 data->file_len = (uint16_t)file_name_len;
530 memcpy(data->file_name, line.buf + 5, file_name_len);
531 data->cmd = state->command;
532 data->flow_id = FlowGetId(f);
533 data->direction = direction;
534 int ret = AppLayerExpectationCreate(f, direction,
535 0, state->dyn_port, ALPROTO_FTPDATA, data);
536 if (ret == -1) {
537 FtpTransferCmdFree(data);
538 SCLogDebug("No expectation created.");
540 } else {
541 SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].",
542 state->active ? "to server" : "to client",
543 state->dyn_port);
544 }
545
546 /* reset the dyn port to avoid duplicate */
547 state->dyn_port = 0;
548 /* reset active/passive indicator */
549 state->active = false;
550 } break;
551 default:
552 break;
553 }
554 if (line.len >= ftp_max_line_len) {
555 ftpi.consumed = ftpi.len + 1;
556 break;
557 }
558 }
559
561}
562
563static int FTPParsePassiveResponse(FtpState *state, const uint8_t *input, uint32_t input_len)
564{
565 uint16_t dyn_port = SCFTPParsePortPasv(input, input_len);
566 if (dyn_port == 0) {
567 return -1;
568 }
569 SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port);
570 state->active = false;
571 state->dyn_port = dyn_port;
572 state->curr_tx->dyn_port = dyn_port;
573 state->curr_tx->active = false;
574
575 return 0;
576}
577
578static int FTPParsePassiveResponseV6(FtpState *state, const uint8_t *input, uint32_t input_len)
579{
580 uint16_t dyn_port = SCFTPParsePortEpsv(input, input_len);
581 if (dyn_port == 0) {
582 return -1;
583 }
584 SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port);
585 state->active = false;
586 state->dyn_port = dyn_port;
587 state->curr_tx->dyn_port = dyn_port;
588 state->curr_tx->active = false;
589 return 0;
590}
591
592/**
593 * \brief Handle preliminary replies -- keep tx open
594 * \retval bool True for a positive preliminary reply; false otherwise
595 *
596 * 1yz Positive Preliminary reply
597 *
598 * The requested action is being initiated; expect another
599 * reply before proceeding with a new command
600 */
601static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len)
602{
603 return input_len >= 4 && isdigit(input[0]) && input[0] == '1' &&
604 isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]);
605}
606
607/**
608 * \brief This function is called to retrieve a ftp response
609 * \param ftp_state the ftp state structure for the parser
610 * \param input input line of the command
611 * \param input_len length of the request
612 * \param output the resulting output
613 *
614 * \retval 1 when the command is parsed, 0 otherwise
615 */
616static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
617 StreamSlice stream_slice, void *local_data)
618{
619 FtpState *state = (FtpState *)ftp_state;
620
621 const uint8_t *input = StreamSliceGetData(&stream_slice);
622 uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
623
624 if (unlikely(input_len == 0)) {
626 }
627 FtpInput ftpi = { .buf = input, .len = input_len, .orig_len = input_len, .consumed = 0 };
628 FtpLineState line = { .buf = NULL, .len = 0, .delim_len = 0, .lf_found = false };
629
630 FTPTransaction *lasttx = TAILQ_FIRST(&state->tx_list);
631 AppLayerResult res;
632 while (1) {
633 res = FTPGetLineForDirection(&line, &ftpi, &state->current_line_truncated_tc);
634 if (res.status == 1) {
635 return res;
636 } else if (res.status == -1) {
637 break;
638 }
639 FTPTransaction *tx = FTPGetOldestTx(state, lasttx);
640 if (tx == NULL) {
641 tx = FTPTransactionCreate(state);
642 }
643 if (unlikely(tx == NULL)) {
645 }
646 lasttx = tx;
647 tx->tx_data.updated_tc = true;
648 if (state->command == FTP_COMMAND_UNKNOWN) {
649 /* unknown */
650 tx->command_descriptor.command_code = FTP_COMMAND_UNKNOWN;
651 }
652
653 state->curr_tx = tx;
654 uint16_t dyn_port;
655 switch (state->command) {
656 case FTP_COMMAND_AUTH_TLS:
657 if (line.len >= 4 && SCMemcmp("234 ", line.buf, 4) == 0) {
659 }
660 break;
661
662 case FTP_COMMAND_EPRT:
663 dyn_port = SCFTPParsePortEprt(state->port_line, state->port_line_len);
664 if (dyn_port == 0) {
665 goto tx_complete;
666 }
667 state->dyn_port = dyn_port;
668 state->active = true;
669 tx->dyn_port = dyn_port;
670 tx->active = true;
671 SCLogDebug("FTP active mode (v6): dynamic port %" PRIu16 "", dyn_port);
672 break;
673
674 case FTP_COMMAND_PORT:
675 dyn_port = SCFTPParsePort(state->port_line, state->port_line_len);
676 if (dyn_port == 0) {
677 goto tx_complete;
678 }
679 state->dyn_port = dyn_port;
680 state->active = true;
681 tx->dyn_port = state->dyn_port;
682 tx->active = true;
683 SCLogDebug("FTP active mode (v4): dynamic port %" PRIu16 "", dyn_port);
684 break;
685
686 case FTP_COMMAND_PASV:
687 if (line.len >= 4 && SCMemcmp("227 ", line.buf, 4) == 0) {
688 FTPParsePassiveResponse(ftp_state, line.buf, line.len);
689 }
690 break;
691
692 case FTP_COMMAND_EPSV:
693 if (line.len >= 4 && SCMemcmp("229 ", line.buf, 4) == 0) {
694 FTPParsePassiveResponseV6(ftp_state, line.buf, line.len);
695 }
696 break;
697 default:
698 break;
699 }
700
701 if (likely(line.len)) {
702 FTPResponseLine *response = SCFTPParseResponseLine((const char *)line.buf, line.len);
703 if (likely(response)) {
704 FTPResponseWrapper *wrapper = FTPResponseWrapperAlloc(response);
705 if (likely(wrapper)) {
706 response->truncated = state->current_line_truncated_tc;
707 if (response->truncated) {
709 &tx->tx_data.events, FtpEventResponseCommandTooLong);
710 }
711 if (line.lf_found) {
712 state->current_line_truncated_tc = false;
713 }
714 TAILQ_INSERT_TAIL(&tx->response_list, wrapper, next);
715 } else {
716 SCFTPFreeResponseLine(response);
717 }
718 } else {
719 SCLogDebug("unable to parse FTP response line \"%s\"", line.buf);
720 }
721 }
722
723 /* Handle preliminary replies -- keep tx open */
724 if (FTPIsPPR(line.buf, line.len)) {
725 continue;
726 }
727 tx_complete:
728 tx->done = true;
729
730 if (line.len >= ftp_max_line_len) {
731 ftpi.consumed = ftpi.len + 1;
732 break;
733 }
734 }
735
737}
738
739
740#ifdef DEBUG
741static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER;
742static uint64_t ftp_state_memuse = 0;
743static uint64_t ftp_state_memcnt = 0;
744#endif
745
746static void *FTPStateAlloc(void *orig_state, AppProto proto_orig)
747{
748 void *s = FTPCalloc(1, sizeof(FtpState));
749 if (unlikely(s == NULL))
750 return NULL;
751
752 FtpState *ftp_state = (FtpState *) s;
753 TAILQ_INIT(&ftp_state->tx_list);
754
755#ifdef DEBUG
756 SCMutexLock(&ftp_state_mem_lock);
757 ftp_state_memcnt++;
758 ftp_state_memuse+=sizeof(FtpState);
759 SCMutexUnlock(&ftp_state_mem_lock);
760#endif
761 return s;
762}
763
764static void FTPStateFree(void *s)
765{
766 FtpState *fstate = (FtpState *) s;
767 if (fstate->port_line != NULL)
768 FTPFree(fstate->port_line, fstate->port_line_size);
769
770 FTPTransaction *tx = NULL;
771 while ((tx = TAILQ_FIRST(&fstate->tx_list))) {
772 TAILQ_REMOVE(&fstate->tx_list, tx, next);
773#ifdef DEBUG
774 if (SCLogDebugEnabled()) {
775 const char *command_name = NULL;
776 (void)SCGetFtpCommandInfo(
777 tx->command_descriptor.command_index, &command_name, NULL, NULL);
778 SCLogDebug("[%s] state %p id %" PRIu64 ", Freeing %d bytes at %p",
779 command_name != NULL ? command_name : "n/a", s, tx->tx_id, tx->request_length,
780 tx->request);
781 }
782#endif
783
784 FTPTransactionFree(tx);
785 }
786
787 FTPFree(s, sizeof(FtpState));
788#ifdef DEBUG
789 SCMutexLock(&ftp_state_mem_lock);
790 ftp_state_memcnt--;
791 ftp_state_memuse-=sizeof(FtpState);
792 SCMutexUnlock(&ftp_state_mem_lock);
793#endif
794}
795
796/**
797 * \brief This function returns the oldest open transaction; if none
798 * are open, then the oldest transaction is returned
799 * \param ftp_state the ftp state structure for the parser
800 * \param starttx the ftp transaction where to start looking
801 *
802 * \retval transaction pointer when a transaction was found; NULL otherwise.
803 */
804static FTPTransaction *FTPGetOldestTx(const FtpState *ftp_state, FTPTransaction *starttx)
805{
806 if (unlikely(!ftp_state)) {
807 SCLogDebug("NULL state object; no transactions available");
808 return NULL;
809 }
810 FTPTransaction *tx = starttx;
811 FTPTransaction *lasttx = NULL;
812 while(tx != NULL) {
813 /* Return oldest open tx */
814 if (!tx->done) {
815 SCLogDebug("Returning tx %p id %"PRIu64, tx, tx->tx_id);
816 return tx;
817 }
818 /* save for the end */
819 lasttx = tx;
820 tx = TAILQ_NEXT(tx, next);
821 }
822 /* All tx are closed; return last element */
823 if (lasttx)
824 SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id);
825 return lasttx;
826}
827
828static void *FTPGetTx(void *state, uint64_t tx_id)
829{
830 FtpState *ftp_state = (FtpState *)state;
831 if (ftp_state) {
832 FTPTransaction *tx = NULL;
833
834 if (ftp_state->curr_tx == NULL)
835 return NULL;
836 if (ftp_state->curr_tx->tx_id == tx_id)
837 return ftp_state->curr_tx;
838
839 TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
840 if (tx->tx_id == tx_id)
841 return tx;
842 }
843 }
844 return NULL;
845}
846
847static AppLayerTxData *FTPGetTxData(void *vtx)
848{
849 FTPTransaction *tx = (FTPTransaction *)vtx;
850 return &tx->tx_data;
851}
852
853static AppLayerStateData *FTPGetStateData(void *vstate)
854{
855 FtpState *s = (FtpState *)vstate;
856 return &s->state_data;
857}
858
859static void FTPStateTransactionFree(void *state, uint64_t tx_id)
860{
861 FtpState *ftp_state = state;
862 FTPTransaction *tx = NULL;
863 TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
864 if (tx_id < tx->tx_id)
865 break;
866 else if (tx_id > tx->tx_id)
867 continue;
868
869 if (tx == ftp_state->curr_tx)
870 ftp_state->curr_tx = NULL;
871 TAILQ_REMOVE(&ftp_state->tx_list, tx, next);
872 FTPTransactionFree(tx);
873 break;
874 }
875}
876
877static uint64_t FTPGetTxCnt(void *state)
878{
879 uint64_t cnt = 0;
880 FtpState *ftp_state = state;
881 if (ftp_state) {
882 cnt = ftp_state->tx_cnt;
883 }
884 SCLogDebug("returning state %p %"PRIu64, state, cnt);
885 return cnt;
886}
887
888static int FTPGetAlstateProgress(void *vtx, uint8_t direction)
889{
890 SCLogDebug("tx %p", vtx);
891 FTPTransaction *tx = vtx;
892
893 /* having a tx implies request side is done */
894 if (direction == STREAM_TOSERVER) {
895 return FTP_STATE_FINISHED;
896 }
897 if (!tx->done) {
898 return FTP_STATE_IN_PROGRESS;
899 }
900
901 return FTP_STATE_FINISHED;
902}
903
904static AppProto FTPUserProbingParser(
905 const Flow *f, uint8_t direction, const uint8_t *input, uint32_t len, uint8_t *rdir)
906{
907 if (f->alproto_tc == ALPROTO_POP3) {
908 // POP traffic begins by same "USER" pattern as FTP
909 return ALPROTO_FAILED;
910 }
911 return ALPROTO_FTP;
912}
913
914static AppProto FTPServerProbingParser(
915 const Flow *f, uint8_t direction, const uint8_t *input, uint32_t len, uint8_t *rdir)
916{
917 // another check for minimum length
918 if (len < 5) {
919 return ALPROTO_UNKNOWN;
920 }
921 // begins by 220
922 if (input[0] != '2' || input[1] != '2' || input[2] != '0') {
923 return ALPROTO_FAILED;
924 }
925 // followed by space or hypen
926 if (input[3] != ' ' && input[3] != '-') {
927 return ALPROTO_FAILED;
928 }
929 if (f->alproto_ts == ALPROTO_FTP || (f->todstbytecnt > 4 && f->alproto_ts == ALPROTO_UNKNOWN)) {
930 // only validates FTP if client side was FTP
931 // or if client side is unknown despite having received bytes
932 if (memchr(input + 4, '\n', len - 4) != NULL) {
933 return ALPROTO_FTP;
934 }
935 }
936 return ALPROTO_UNKNOWN;
937}
938
939static int FTPRegisterPatternsForProtocolDetection(void)
940{
942 IPPROTO_TCP, ALPROTO_FTP, "220 (", 5, 0, STREAM_TOCLIENT) < 0) {
943 return -1;
944 }
946 IPPROTO_TCP, ALPROTO_FTP, "FEAT", 4, 0, STREAM_TOSERVER) < 0) {
947 return -1;
948 }
949 if (SCAppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_FTP, "USER ", 5, 0,
950 STREAM_TOSERVER, FTPUserProbingParser, 5, 5) < 0) {
951 return -1;
952 }
954 IPPROTO_TCP, ALPROTO_FTP, "PASS ", 5, 0, STREAM_TOSERVER) < 0) {
955 return -1;
956 }
958 IPPROTO_TCP, ALPROTO_FTP, "PORT ", 5, 0, STREAM_TOSERVER) < 0) {
959 return -1;
960 }
961 // Only check FTP on known ports as the banner has nothing special beyond
962 // the response code shared with SMTP.
964 "tcp", IPPROTO_TCP, "ftp", ALPROTO_FTP, 0, 5, NULL, FTPServerProbingParser)) {
965 // STREAM_TOSERVER here means use 21 as flow destination port
966 // and NULL, FTPServerProbingParser means use probing parser to client
967 SCAppLayerProtoDetectPPRegister(IPPROTO_TCP, "21", ALPROTO_FTP, 0, 5, STREAM_TOSERVER, NULL,
968 FTPServerProbingParser);
969 }
970 return 0;
971}
972
973
975
976/**
977 * \brief This function is called to retrieve a ftp request
978 * \param ftp_state the ftp state structure for the parser
979 * \param output the resulting output
980 *
981 * \retval 1 when the command is parsed, 0 otherwise
982 */
983static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
984 AppLayerParserState *pstate, StreamSlice stream_slice, void *local_data, uint8_t direction)
985{
986 const uint8_t *input = StreamSliceGetData(&stream_slice);
987 uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
988 const bool eof = (direction & STREAM_TOSERVER)
991
992 SCTxDataUpdateFileFlags(&ftpdata_state->tx_data, ftpdata_state->state_data.file_flags);
993 if (ftpdata_state->tx_data.file_tx == 0)
994 ftpdata_state->tx_data.file_tx = direction & (STREAM_TOSERVER | STREAM_TOCLIENT);
995 if (direction & STREAM_TOSERVER) {
996 ftpdata_state->tx_data.updated_ts = true;
997 } else {
998 ftpdata_state->tx_data.updated_tc = true;
999 }
1000 /* we depend on detection engine for file pruning */
1001 const uint16_t flags = FileFlowFlagsToFlags(ftpdata_state->tx_data.file_flags, direction);
1002 int ret = 0;
1003
1004 SCLogDebug("FTP-DATA input_len %u flags %04x dir %d/%s EOF %s", input_len, flags, direction,
1005 (direction & STREAM_TOSERVER) ? "toserver" : "toclient", eof ? "true" : "false");
1006
1007 SCLogDebug("FTP-DATA flags %04x dir %d", flags, direction);
1008 if (input_len && ftpdata_state->files == NULL) {
1009 FtpTransferCmd *data =
1010 (FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId());
1011 if (data == NULL) {
1013 }
1014
1015 /* we shouldn't get data in the wrong dir. Don't set things up for this dir */
1016 if ((direction & data->direction) == 0) {
1017 // TODO set event for data in wrong direction
1018 SCLogDebug("input %u not for our direction (%s): %s/%s", input_len,
1019 (direction & STREAM_TOSERVER) ? "toserver" : "toclient",
1020 data->cmd == FTP_COMMAND_STOR ? "STOR" : "RETR",
1021 (data->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1023 }
1024
1025 ftpdata_state->files = FileContainerAlloc();
1026 if (ftpdata_state->files == NULL) {
1029 }
1030
1031 ftpdata_state->file_name = data->file_name;
1032 ftpdata_state->file_len = data->file_len;
1033 data->file_name = NULL;
1034 data->file_len = 0;
1035 f->parent_id = data->flow_id;
1036 ftpdata_state->command = data->cmd;
1037 switch (data->cmd) {
1038 case FTP_COMMAND_STOR:
1039 ftpdata_state->direction = data->direction;
1040 SCLogDebug("STOR data to %s",
1041 (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1042 break;
1043 case FTP_COMMAND_RETR:
1044 ftpdata_state->direction = data->direction;
1045 SCLogDebug("RETR data to %s",
1046 (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1047 break;
1048 default:
1049 break;
1050 }
1051
1052 /* open with fixed track_id 0 as we can have just one
1053 * file per ftp-data flow. */
1054 if (FileOpenFileWithId(ftpdata_state->files, &sbcfg,
1055 0ULL, (uint8_t *) ftpdata_state->file_name,
1056 ftpdata_state->file_len,
1057 input, input_len, flags) != 0) {
1058 SCLogDebug("Can't open file");
1059 ret = -1;
1060 }
1062 ftpdata_state->tx_data.files_opened = 1;
1063 } else {
1064 if (ftpdata_state->state == FTPDATA_STATE_FINISHED) {
1065 SCLogDebug("state is already finished");
1066 DEBUG_VALIDATE_BUG_ON(input_len); // data after state finished is a bug.
1068 }
1069 if ((direction & ftpdata_state->direction) == 0) {
1070 if (input_len) {
1071 // TODO set event for data in wrong direction
1072 }
1073 SCLogDebug("input %u not for us (%s): %s/%s", input_len,
1074 (direction & STREAM_TOSERVER) ? "toserver" : "toclient",
1075 ftpdata_state->command == FTP_COMMAND_STOR ? "STOR" : "RETR",
1076 (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1078 }
1079 if (input_len != 0) {
1080 ret = FileAppendData(ftpdata_state->files, &sbcfg, input, input_len);
1081 if (ret == -2) {
1082 ret = 0;
1083 SCLogDebug("FileAppendData() - file no longer being extracted");
1084 goto out;
1085 } else if (ret < 0) {
1086 SCLogDebug("FileAppendData() failed: %d", ret);
1087 ret = -2;
1088 goto out;
1089 }
1090 }
1091 }
1092
1093 DEBUG_VALIDATE_BUG_ON((direction & ftpdata_state->direction) == 0); // should be unreachable
1094 if (eof) {
1095 ret = FileCloseFile(ftpdata_state->files, &sbcfg, NULL, 0, flags);
1096 ftpdata_state->state = FTPDATA_STATE_FINISHED;
1097 SCLogDebug("closed because of eof: state now FTPDATA_STATE_FINISHED");
1098 }
1099out:
1100 if (ret < 0) {
1102 }
1104}
1105
1106static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state, AppLayerParserState *pstate,
1107 StreamSlice stream_slice, void *local_data)
1108{
1109 return FTPDataParse(f, ftp_state, pstate, stream_slice, local_data, STREAM_TOSERVER);
1110}
1111
1112static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
1113 StreamSlice stream_slice, void *local_data)
1114{
1115 return FTPDataParse(f, ftp_state, pstate, stream_slice, local_data, STREAM_TOCLIENT);
1116}
1117
1118#ifdef DEBUG
1119static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER;
1120static uint64_t ftpdata_state_memuse = 0;
1121static uint64_t ftpdata_state_memcnt = 0;
1122#endif
1123
1124static void *FTPDataStateAlloc(void *orig_state, AppProto proto_orig)
1125{
1126 void *s = FTPCalloc(1, sizeof(FtpDataState));
1127 if (unlikely(s == NULL))
1128 return NULL;
1129
1130 FtpDataState *state = (FtpDataState *) s;
1131 state->state = FTPDATA_STATE_IN_PROGRESS;
1132
1133#ifdef DEBUG
1134 SCMutexLock(&ftpdata_state_mem_lock);
1135 ftpdata_state_memcnt++;
1136 ftpdata_state_memuse+=sizeof(FtpDataState);
1137 SCMutexUnlock(&ftpdata_state_mem_lock);
1138#endif
1139 return s;
1140}
1141
1142static void FTPDataStateFree(void *s)
1143{
1144 FtpDataState *fstate = (FtpDataState *) s;
1145
1146 SCAppLayerTxDataCleanup(&fstate->tx_data);
1147
1148 if (fstate->file_name != NULL) {
1149 FTPFree(fstate->file_name, fstate->file_len + 1);
1150 }
1151
1152 FileContainerFree(fstate->files, &sbcfg);
1153
1154 FTPFree(s, sizeof(FtpDataState));
1155#ifdef DEBUG
1156 SCMutexLock(&ftpdata_state_mem_lock);
1157 ftpdata_state_memcnt--;
1158 ftpdata_state_memuse-=sizeof(FtpDataState);
1159 SCMutexUnlock(&ftpdata_state_mem_lock);
1160#endif
1161}
1162
1163static AppLayerTxData *FTPDataGetTxData(void *vtx)
1164{
1165 FtpDataState *ftp_state = (FtpDataState *)vtx;
1166 return &ftp_state->tx_data;
1167}
1168
1169static AppLayerStateData *FTPDataGetStateData(void *vstate)
1170{
1171 FtpDataState *ftp_state = (FtpDataState *)vstate;
1172 return &ftp_state->state_data;
1173}
1174
1175static void FTPDataStateTransactionFree(void *state, uint64_t tx_id)
1176{
1177 /* do nothing */
1178}
1179
1180static void *FTPDataGetTx(void *state, uint64_t tx_id)
1181{
1182 FtpDataState *ftp_state = (FtpDataState *)state;
1183 return ftp_state;
1184}
1185
1186static uint64_t FTPDataGetTxCnt(void *state)
1187{
1188 /* ftp-data is single tx */
1189 return 1;
1190}
1191
1192static int FTPDataGetAlstateProgress(void *tx, uint8_t direction)
1193{
1194 FtpDataState *ftpdata_state = (FtpDataState *)tx;
1195 if (direction == ftpdata_state->direction)
1196 return ftpdata_state->state;
1197 else
1198 return FTPDATA_STATE_FINISHED;
1199}
1200
1201static AppLayerGetFileState FTPDataStateGetTxFiles(void *tx, uint8_t direction)
1202{
1203 FtpDataState *ftpdata_state = (FtpDataState *)tx;
1204 AppLayerGetFileState files = { .fc = NULL, .cfg = &sbcfg };
1205
1206 if (direction == ftpdata_state->direction)
1207 files.fc = ftpdata_state->files;
1208
1209 return files;
1210}
1211
1212static void FTPSetMpmState(void)
1213{
1214 ftp_mpm_ctx = SCCalloc(1, sizeof(MpmCtx));
1215 if (unlikely(ftp_mpm_ctx == NULL)) {
1216 exit(EXIT_FAILURE);
1217 }
1218 MpmInitCtx(ftp_mpm_ctx, FTP_MPM);
1219
1220 SCFTPSetMpmState(ftp_mpm_ctx);
1221 mpm_table[FTP_MPM].Prepare(NULL, ftp_mpm_ctx);
1222}
1223
1224static void FTPFreeMpmState(void)
1225{
1226 if (ftp_mpm_ctx != NULL) {
1227 mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx);
1228 SCFree(ftp_mpm_ctx);
1229 ftp_mpm_ctx = NULL;
1230 }
1231}
1232
1233/** \brief FTP tx iterator, specialized for its linked list
1234 *
1235 * \retval txptr or NULL if no more txs in list
1236 */
1237static AppLayerGetTxIterTuple FTPGetTxIterator(const uint8_t ipproto, const AppProto alproto,
1238 void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state)
1239{
1240 FtpState *ftp_state = (FtpState *)alstate;
1241 AppLayerGetTxIterTuple no_tuple = { NULL, 0, false };
1242 if (ftp_state) {
1243 FTPTransaction *tx_ptr;
1244 if (state->un.ptr == NULL) {
1245 tx_ptr = TAILQ_FIRST(&ftp_state->tx_list);
1246 } else {
1247 tx_ptr = (FTPTransaction *)state->un.ptr;
1248 }
1249 if (tx_ptr) {
1250 while (tx_ptr->tx_id < min_tx_id) {
1251 tx_ptr = TAILQ_NEXT(tx_ptr, next);
1252 if (!tx_ptr) {
1253 return no_tuple;
1254 }
1255 }
1256 if (tx_ptr->tx_id >= max_tx_id) {
1257 return no_tuple;
1258 }
1259 state->un.ptr = TAILQ_NEXT(tx_ptr, next);
1260 AppLayerGetTxIterTuple tuple = {
1261 .tx_ptr = tx_ptr,
1262 .tx_id = tx_ptr->tx_id,
1263 .has_next = (state->un.ptr != NULL),
1264 };
1265 return tuple;
1266 }
1267 }
1268 return no_tuple;
1269}
1270
1272{
1273 const char *proto_name = "ftp";
1274 const char *proto_data_name = "ftp-data";
1275
1276 /** FTP */
1277 if (SCAppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
1279 if (FTPRegisterPatternsForProtocolDetection() < 0 )
1280 return;
1282 }
1283
1284 if (SCAppLayerParserConfParserEnabled("tcp", proto_name)) {
1285 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER,
1286 FTPParseRequest);
1287 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT,
1288 FTPParseResponse);
1289 AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
1291 IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT);
1292
1293 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree);
1294
1295 AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx);
1296 AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxData);
1297 AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxIterator);
1298 AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetStateData);
1299
1300 AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc,
1301 FTPLocalStorageFree);
1302 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt);
1303
1304 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress);
1305
1307 ALPROTO_FTP, FTP_STATE_FINISHED, FTP_STATE_FINISHED);
1308
1310 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER,
1311 FTPDataParseRequest);
1312 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT,
1313 FTPDataParseResponse);
1314 AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree);
1316 IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT);
1317 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree);
1318
1319 AppLayerParserRegisterGetTxFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetTxFiles);
1320
1321 AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx);
1322 AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxData);
1323 AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetStateData);
1324
1325 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt);
1326
1327 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress);
1328
1330 ALPROTO_FTPDATA, FTPDATA_STATE_FINISHED, FTPDATA_STATE_FINISHED);
1331
1332 AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_FTP, ftp_get_event_info);
1333 AppLayerParserRegisterGetEventInfoById(IPPROTO_TCP, ALPROTO_FTP, ftp_get_event_info_by_id);
1334
1337
1338 sbcfg.buf_size = 4096;
1339 sbcfg.Calloc = FTPCalloc;
1340 sbcfg.Realloc = FTPRealloc;
1341 sbcfg.Free = FTPFree;
1342
1343 FTPParseMemcap();
1344 } else {
1345 SCLogInfo("Parser disabled for %s protocol. Protocol detection still on.", proto_name);
1346 }
1347
1348 FTPSetMpmState();
1349
1350#ifdef UNITTESTS
1352#endif
1353}
1354
1355/*
1356 * \brief Returns the ending offset of the next line from a multi-line buffer.
1357 *
1358 * "Buffer" refers to a FTP response in a single buffer containing multiple lines.
1359 * Here, "next line" is defined as terminating on
1360 * - Newline character
1361 * - Null character
1362 *
1363 * \param buffer Contains zero or more characters.
1364 * \param len Size, in bytes, of buffer.
1365 *
1366 * \retval Offset from the start of buffer indicating the where the
1367 * next "line ends". The characters between the input buffer and this
1368 * value comprise the line.
1369 *
1370 * NULL is found first or a newline isn't found, then UINT16_MAX is returned.
1371 */
1372uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len)
1373{
1374 if (!buffer || *buffer == '\0') {
1375 return UINT16_MAX;
1376 }
1377
1378 char *c = strchr(buffer, '\n');
1379 return c == NULL ? len : (uint16_t)(c - buffer + 1);
1380}
1381
1382bool EveFTPDataAddMetadata(void *vtx, SCJsonBuilder *jb)
1383{
1384 const FtpDataState *ftp_state = (FtpDataState *)vtx;
1385 SCJbOpenObject(jb, "ftp_data");
1386
1387 if (ftp_state->file_name) {
1388 SCJbSetStringFromBytes(jb, "filename", ftp_state->file_name, ftp_state->file_len);
1389 }
1390 switch (ftp_state->command) {
1391 case FTP_COMMAND_STOR:
1392 JB_SET_STRING(jb, "command", "STOR");
1393 break;
1394 case FTP_COMMAND_RETR:
1395 JB_SET_STRING(jb, "command", "RETR");
1396 break;
1397 default:
1398 break;
1399 }
1400 SCJbClose(jb);
1401 return true;
1402}
1403
1404/**
1405 * \brief Free memory allocated for global FTP parser state.
1406 */
1408{
1409 FTPFreeMpmState();
1410}
1411
1412/* UNITTESTS */
1413#ifdef UNITTESTS
1414#include "stream-tcp.h"
1415
1416/** \test Send a get request in one chunk. */
1417static int FTPParserTest01(void)
1418{
1419 Flow f;
1420 uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n";
1421 uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */
1422 TcpSession ssn;
1424
1425 memset(&f, 0, sizeof(f));
1426 memset(&ssn, 0, sizeof(ssn));
1427
1428 f.protoctx = (void *)&ssn;
1429 f.proto = IPPROTO_TCP;
1430 f.alproto = ALPROTO_FTP;
1431
1432 StreamTcpInitConfig(true);
1433
1434 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1435 STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen);
1436 FAIL_IF(r != 0);
1437
1438 FtpState *ftp_state = f.alstate;
1439 FAIL_IF_NULL(ftp_state);
1440 FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
1441
1443 StreamTcpFreeConfig(true);
1444 PASS;
1445}
1446
1447/** \test Supply RETR without a filename */
1448static int FTPParserTest11(void)
1449{
1450 Flow f;
1451 uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1452 uint8_t ftpbuf2[] = "RETR\r\n";
1453 uint8_t ftpbuf3[] = "227 OK\r\n";
1454 TcpSession ssn;
1455
1457
1458 memset(&f, 0, sizeof(f));
1459 memset(&ssn, 0, sizeof(ssn));
1460
1461 f.protoctx = (void *)&ssn;
1462 f.proto = IPPROTO_TCP;
1463 f.alproto = ALPROTO_FTP;
1464
1465 StreamTcpInitConfig(true);
1466
1467 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1468 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1469 sizeof(ftpbuf1) - 1);
1470 FAIL_IF(r != 0);
1471
1472 /* Response */
1474 STREAM_TOCLIENT,
1475 ftpbuf3,
1476 sizeof(ftpbuf3) - 1);
1477 FAIL_IF(r != 0);
1478
1480 STREAM_TOSERVER, ftpbuf2,
1481 sizeof(ftpbuf2) - 1);
1482 FAIL_IF(r == 0);
1483
1484 FtpState *ftp_state = f.alstate;
1485 FAIL_IF_NULL(ftp_state);
1486
1487 FAIL_IF(ftp_state->command != FTP_COMMAND_RETR);
1488
1490 StreamTcpFreeConfig(true);
1491 PASS;
1492}
1493
1494/** \test Supply STOR without a filename */
1495static int FTPParserTest12(void)
1496{
1497 Flow f;
1498 uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1499 uint8_t ftpbuf2[] = "STOR\r\n";
1500 uint8_t ftpbuf3[] = "227 OK\r\n";
1501 TcpSession ssn;
1502
1504
1505 memset(&f, 0, sizeof(f));
1506 memset(&ssn, 0, sizeof(ssn));
1507
1508 f.protoctx = (void *)&ssn;
1509 f.proto = IPPROTO_TCP;
1510 f.alproto = ALPROTO_FTP;
1511
1512 StreamTcpInitConfig(true);
1513
1514 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1515 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1516 sizeof(ftpbuf1) - 1);
1517 FAIL_IF(r != 0);
1518
1519 /* Response */
1521 STREAM_TOCLIENT,
1522 ftpbuf3,
1523 sizeof(ftpbuf3) - 1);
1524 FAIL_IF(r != 0);
1525
1527 STREAM_TOSERVER, ftpbuf2,
1528 sizeof(ftpbuf2) - 1);
1529 FAIL_IF(r == 0);
1530
1531 FtpState *ftp_state = f.alstate;
1532 FAIL_IF_NULL(ftp_state);
1533
1534 FAIL_IF(ftp_state->command != FTP_COMMAND_STOR);
1535
1537 StreamTcpFreeConfig(true);
1538 PASS;
1539}
1540#endif /* UNITTESTS */
1541
1543{
1544#ifdef UNITTESTS
1545 UtRegisterTest("FTPParserTest01", FTPParserTest01);
1546 UtRegisterTest("FTPParserTest11", FTPParserTest11);
1547 UtRegisterTest("FTPParserTest12", FTPParserTest12);
1548#endif /* UNITTESTS */
1549}
1550
void AppLayerProtoDetectRegisterProtocol(AppProto alproto, const char *alproto_name)
Registers a protocol for protocol detection phase.
int SCAppLayerProtoDetectPPParseConfPorts(const char *ipproto_name, uint8_t ipproto, const char *alproto_name, AppProto alproto, uint16_t min_depth, uint16_t max_depth, ProbingParserFPtr ProbingParserTs, ProbingParserFPtr ProbingParserTc)
void SCAppLayerProtoDetectPPRegister(uint8_t ipproto, const char *portstr, AppProto alproto, uint16_t min_depth, uint16_t max_depth, uint8_t direction, ProbingParserFPtr ProbingParser1, ProbingParserFPtr ProbingParser2)
register parser at a port
int SCAppLayerProtoDetectPMRegisterPatternCSwPP(uint8_t ipproto, AppProto alproto, const char *pattern, uint16_t depth, uint16_t offset, uint8_t direction, ProbingParserFPtr PPFunc, uint16_t pp_min_depth, uint16_t pp_max_depth)
int SCAppLayerProtoDetectPMRegisterPatternCI(uint8_t ipproto, AppProto alproto, const char *pattern, uint16_t depth, uint16_t offset, uint8_t direction)
Registers a case-insensitive pattern for protocol detection.
int SCAppLayerProtoDetectConfProtoDetectionEnabled(const char *ipproto, const char *alproto)
Given a protocol name, checks if proto detection is enabled in the conf file.
void AppLayerRegisterExpectationProto(uint8_t proto, AppProto alproto)
bool SCAppLayerRequestProtocolTLSUpgrade(Flow *f)
request applayer to wrap up this protocol and rerun protocol detection with expectation of TLS....
uint8_t len
void AppLayerDecoderEventsSetEventRaw(AppLayerDecoderEvents **sevents, uint8_t event)
Set an app layer decoder event.
uint64_t FTPMemcapGlobalCounter(void)
bool EveFTPDataAddMetadata(void *vtx, SCJsonBuilder *jb)
#define FTP_MPM
struct FtpInput_ FtpInput
uint32_t ftp_config_maxtx
uint32_t ftp_max_line_len
int FTPSetMemcap(uint64_t size)
uint64_t FTPMemuseGlobalCounter(void)
void FTPParserRegisterTests(void)
uint64_t ftp_config_memcap
void RegisterFTPParsers(void)
uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len)
void FTPParserCleanup(void)
Free memory allocated for global FTP parser state.
struct FTPThreadCtx_ FTPThreadCtx
struct FtpDataState_ FtpDataState
struct FtpState_ FtpState
struct HtpBodyChunk_ * next
void AppLayerParserRegisterGetTxCnt(uint8_t ipproto, AppProto alproto, uint64_t(*StateGetTxCnt)(void *alstate))
void AppLayerParserRegisterTxFreeFunc(uint8_t ipproto, AppProto alproto, void(*StateTransactionFree)(void *, uint64_t))
uint16_t SCAppLayerParserStateIssetFlag(AppLayerParserState *pstate, uint16_t flag)
void AppLayerParserRegisterProtocolUnittests(uint8_t ipproto, AppProto alproto, void(*RegisterUnittests)(void))
void AppLayerParserRegisterTxDataFunc(uint8_t ipproto, AppProto alproto, AppLayerTxData *(*GetTxData)(void *tx))
AppLayerParserThreadCtx * AppLayerParserThreadCtxAlloc(void)
Gets a new app layer protocol's parser thread context.
void AppLayerParserRegisterStateProgressCompletionStatus(AppProto alproto, const int ts, const int tc)
void AppLayerParserThreadCtxFree(AppLayerParserThreadCtx *tctx)
Destroys the app layer parser thread context obtained using AppLayerParserThreadCtxAlloc().
void AppLayerParserRegisterGetTxFilesFunc(uint8_t ipproto, AppProto alproto, AppLayerGetFileState(*GetTxFiles)(void *, uint8_t))
int AppLayerParserParse(ThreadVars *tv, AppLayerParserThreadCtx *alp_tctx, Flow *f, AppProto alproto, uint8_t flags, const uint8_t *input, uint32_t input_len)
void AppLayerParserRegisterStateDataFunc(uint8_t ipproto, AppProto alproto, AppLayerStateData *(*GetStateData)(void *state))
void AppLayerParserRegisterGetTxIterator(uint8_t ipproto, AppProto alproto, AppLayerGetTxIteratorFunc Func)
void AppLayerParserRegisterLocalStorageFunc(uint8_t ipproto, AppProto alproto, void *(*LocalStorageAlloc)(void), void(*LocalStorageFree)(void *))
int AppLayerParserRegisterParser(uint8_t ipproto, AppProto alproto, uint8_t direction, AppLayerParserFPtr Parser)
Register app layer parser for the protocol.
void AppLayerParserRegisterGetTx(uint8_t ipproto, AppProto alproto, void *(StateGetTx)(void *alstate, uint64_t tx_id))
void SCAppLayerParserRegisterLogger(uint8_t ipproto, AppProto alproto)
void AppLayerParserRegisterGetEventInfo(uint8_t ipproto, AppProto alproto, int(*StateGetEventInfo)(const char *event_name, uint8_t *event_id, AppLayerEventType *event_type))
void AppLayerParserRegisterGetStateProgressFunc(uint8_t ipproto, AppProto alproto, int(*StateGetProgress)(void *alstate, uint8_t direction))
void SCAppLayerParserRegisterParserAcceptableDataDirection(uint8_t ipproto, AppProto alproto, uint8_t direction)
int SCAppLayerParserConfParserEnabled(const char *ipproto, const char *alproto_name)
check if a parser is enabled in the config Returns enabled always if: were running unittests
void AppLayerParserRegisterStateFuncs(uint8_t ipproto, AppProto alproto, void *(*StateAlloc)(void *, AppProto), void(*StateFree)(void *))
void AppLayerParserRegisterGetEventInfoById(uint8_t ipproto, AppProto alproto, int(*StateGetEventInfoById)(uint8_t event_id, const char **event_name, AppLayerEventType *event_type))
struct AppLayerGetFileState AppLayerGetFileState
struct AppLayerGetTxIterTuple AppLayerGetTxIterTuple
struct AppLayerTxData AppLayerTxData
#define APP_LAYER_PARSER_EOF_TC
#define APP_LAYER_OK
#define APP_LAYER_INCOMPLETE(c, n)
struct AppLayerResult AppLayerResult
#define APP_LAYER_PARSER_EOF_TS
#define APP_LAYER_ERROR
struct StreamSlice StreamSlice
struct AppLayerStateData AppLayerStateData
uint16_t AppProto
@ ALPROTO_FAILED
@ ALPROTO_FTP
@ ALPROTO_UNKNOWN
@ ALPROTO_FTPDATA
@ ALPROTO_POP3
uint8_t flags
Definition decode-gre.h:0
void * FlowGetStorageById(const Flow *f, FlowStorageId id)
void FlowFreeStorageById(Flow *f, FlowStorageId id)
AppLayerParserThreadCtx * alp_tctx
#define FAIL_IF_NULL(expr)
Fail a test if expression evaluates to NULL.
void UtRegisterTest(const char *name, int(*TestFn)(void))
Register unit test.
#define PASS
Pass the test.
#define FAIL_IF(expr)
Fail a test if expression evaluates to true.
FlowStorageId AppLayerExpectationGetFlowId(void)
int AppLayerExpectationCreate(Flow *f, int direction, Port src, Port dst, AppProto alproto, void *data)
#define TAILQ_FOREACH(var, head, field)
Definition queue.h:252
#define TAILQ_INIT(head)
Definition queue.h:262
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition queue.h:294
#define TAILQ_FIRST(head)
Definition queue.h:250
#define TAILQ_REMOVE(head, elm, field)
Definition queue.h:312
#define TAILQ_NEXT(elm, field)
Definition queue.h:307
#define JB_SET_STRING(jb, key, val)
Definition rust.h:26
void StreamTcpFreeConfig(bool quiet)
Definition stream-tcp.c:859
void StreamTcpInitConfig(bool)
To initialize the stream global configuration data.
Definition stream-tcp.c:488
union AppLayerGetTxIterState::@7 un
FTPResponseLine * response
PrefilterRuleStore * pmq
MpmThreadCtx * ftp_mpm_thread_ctx
FtpCommandInfo command_descriptor
uint8_t * request
AppLayerTxData tx_data
uint32_t request_length
Flow data structure.
Definition flow.h:356
AppProto alproto_ts
Definition flow.h:451
AppProto alproto_tc
Definition flow.h:452
uint8_t proto
Definition flow.h:378
AppProto alproto
application level protocol
Definition flow.h:450
void * alstate
Definition flow.h:479
void * protoctx
Definition flow.h:441
uint64_t todstbytecnt
Definition flow.h:497
int64_t parent_id
Definition flow.h:430
uint8_t command_index
FtpRequestCommand command_code
FtpRequestCommand command
FileContainer * files
AppLayerStateData state_data
uint8_t * file_name
AppLayerTxData tx_data
int32_t len
int32_t consumed
int32_t orig_len
const uint8_t * buf
uint8_t delim_len
const uint8_t * buf
uint32_t port_line_size
uint16_t dyn_port
uint32_t port_line_len
uint64_t tx_cnt
AppLayerStateData state_data
bool current_line_truncated_tc
FTPTransaction * curr_tx
FtpRequestCommand command
bool current_line_truncated_ts
uint8_t * port_line
int(* Prepare)(MpmConfig *, struct MpmCtx_ *)
Definition util-mpm.h:175
uint32_t(* Search)(const struct MpmCtx_ *, struct MpmThreadCtx_ *, PrefilterRuleStore *, const uint8_t *, uint32_t)
Definition util-mpm.h:178
void(* DestroyCtx)(struct MpmCtx_ *)
Definition util-mpm.h:154
structure for storing potential rule matches
void(* Free)(void *ptr, size_t size)
void *(* Realloc)(void *ptr, size_t orig_size, size_t size)
void *(* Calloc)(size_t n, size_t size)
#define MIN(x, y)
#define SCMUTEX_INITIALIZER
#define SCMutex
#define SCMutexUnlock(mut)
#define SCMutexLock(mut)
uint32_t cnt
#define SC_ATOMIC_ADD(name, val)
add a value to our atomic variable
#define SC_ATOMIC_INIT(name)
wrapper for initializing an atomic variable.
#define SC_ATOMIC_DECLARE(type, name)
wrapper for declaring atomic variables.
#define SC_ATOMIC_SUB(name, val)
sub a value from our atomic variable
#define SC_ATOMIC_GET(name)
Get the value from the atomic variable.
#define SC_ATOMIC_SET(name, val)
Set the value for the atomic variable.
int SCLogDebugEnabled(void)
Returns whether debug messages are enabled to be logged or not.
Definition util-debug.c:767
#define SCEnter(...)
Definition util-debug.h:277
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCReturnInt(x)
Definition util-debug.h:281
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition util-debug.h:225
#define SCReturnStruct(x)
Definition util-debug.h:297
thread_local SCError sc_errno
Definition util-error.c:31
@ SC_ELIMIT
Definition util-error.h:31
@ SC_ENOMEM
Definition util-error.h:29
int FileAppendData(FileContainer *ffc, const StreamingBufferConfig *sbcfg, const uint8_t *data, uint32_t data_len)
Store/handle a chunk of file data in the File structure The last file in the FileContainer will be us...
Definition util-file.c:766
FileContainer * FileContainerAlloc(void)
allocate a FileContainer
Definition util-file.c:480
uint16_t FileFlowFlagsToFlags(const uint16_t flow_file_flags, uint8_t direction)
Definition util-file.c:216
int FileCloseFile(FileContainer *ffc, const StreamingBufferConfig *sbcfg, const uint8_t *data, uint32_t data_len, uint16_t flags)
Close a File.
Definition util-file.c:1062
void FileContainerFree(FileContainer *ffc, const StreamingBufferConfig *cfg)
Free a FileContainer.
Definition util-file.c:516
int FileOpenFileWithId(FileContainer *ffc, const StreamingBufferConfig *sbcfg, uint32_t track_id, const uint8_t *name, uint16_t name_len, const uint8_t *data, uint32_t data_len, uint16_t flags)
Open a new File.
Definition util-file.c:967
#define SC_FILENAME_MAX
Definition util-file.h:62
#define SCFree(p)
Definition util-mem.h:61
#define SCRealloc(ptr, sz)
Definition util-mem.h:50
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define SCMemcmp(a, b, c)
MpmTableElmt mpm_table[MPM_TABLE_SIZE]
Definition util-mpm.c:47
void MpmDestroyThreadCtx(MpmThreadCtx *mpm_thread_ctx, const uint16_t matcher)
Definition util-mpm.c:202
void MpmInitThreadCtx(MpmThreadCtx *mpm_thread_ctx, uint16_t matcher)
Definition util-mpm.c:195
void MpmInitCtx(MpmCtx *mpm_ctx, uint8_t matcher)
Definition util-mpm.c:209
#define likely(expr)
#define unlikely(expr)
int PmqSetup(PrefilterRuleStore *pmq)
Setup a pmq.
void PmqReset(PrefilterRuleStore *pmq)
Reset a Pmq for reusage. Meant to be called after a single search.
void PmqFree(PrefilterRuleStore *pmq)
Cleanup and free a Pmq.
#define STREAMING_BUFFER_CONFIG_INITIALIZER
#define DEBUG_VALIDATE_BUG_ON(exp)