]> git.ipfire.org Git - people/ms/suricata.git/blob - src/app-layer-ftp.c
app-layer: pass STREAM_* flags to parser
[people/ms/suricata.git] / src / app-layer-ftp.c
1 /* Copyright (C) 2007-2017 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 *
24 * App Layer Parser for FTP
25 */
26
27 #include "suricata-common.h"
28 #include "debug.h"
29 #include "decode.h"
30 #include "threads.h"
31
32 #include "util-print.h"
33 #include "util-pool.h"
34
35 #include "flow-util.h"
36 #include "flow-storage.h"
37
38 #include "detect-engine-state.h"
39
40 #include "stream-tcp-private.h"
41 #include "stream-tcp-reassemble.h"
42 #include "stream-tcp.h"
43 #include "stream.h"
44
45 #include "app-layer.h"
46 #include "app-layer-protos.h"
47 #include "app-layer-parser.h"
48 #include "app-layer-ftp.h"
49 #include "app-layer-expectation.h"
50
51 #include "util-spm.h"
52 #include "util-unittest.h"
53 #include "util-debug.h"
54 #include "util-memcmp.h"
55 #include "util-memrchr.h"
56 #include "util-byte.h"
57 #include "util-mem.h"
58 #include "util-misc.h"
59
60 #ifdef HAVE_RUST
61 #include "rust-ftp-mod-gen.h"
62 #endif
63
64 uint64_t ftp_config_memcap = 0;
65
66 SC_ATOMIC_DECLARE(uint64_t, ftp_memuse);
67 SC_ATOMIC_DECLARE(uint64_t, ftp_memcap);
68
69 static void FTPParseMemcap(void)
70 {
71 const char *conf_val;
72
73 /** set config values for memcap, prealloc and hash_size */
74 if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1)
75 {
76 if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) {
77 SCLogError(SC_ERR_SIZE_PARSE, "Error parsing ftp.memcap "
78 "from conf file - %s. Killing engine",
79 conf_val);
80 exit(EXIT_FAILURE);
81 }
82 SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap);
83 } else {
84 /* default to unlimited */
85 ftp_config_memcap = 0;
86 }
87
88 SC_ATOMIC_INIT(ftp_memuse);
89 SC_ATOMIC_INIT(ftp_memcap);
90 }
91
92 static void FTPIncrMemuse(uint64_t size)
93 {
94 (void) SC_ATOMIC_ADD(ftp_memuse, size);
95 return;
96 }
97
98 static void FTPDecrMemuse(uint64_t size)
99 {
100 (void) SC_ATOMIC_SUB(ftp_memuse, size);
101 return;
102 }
103
104 uint64_t FTPMemuseGlobalCounter(void)
105 {
106 uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse);
107 return tmpval;
108 }
109
110 uint64_t FTPMemcapGlobalCounter(void)
111 {
112 uint64_t tmpval = SC_ATOMIC_GET(ftp_memcap);
113 return tmpval;
114 }
115
116 /**
117 * \brief Check if alloc'ing "size" would mean we're over memcap
118 *
119 * \retval 1 if in bounds
120 * \retval 0 if not in bounds
121 */
122 static int FTPCheckMemcap(uint64_t size)
123 {
124 if (ftp_config_memcap == 0 || size + SC_ATOMIC_GET(ftp_memuse) <= ftp_config_memcap)
125 return 1;
126 (void) SC_ATOMIC_ADD(ftp_memcap, 1);
127 return 0;
128 }
129
130 static void *FTPMalloc(size_t size)
131 {
132 void *ptr = NULL;
133
134 if (FTPCheckMemcap((uint32_t)size) == 0)
135 return NULL;
136
137 ptr = SCMalloc(size);
138
139 if (unlikely(ptr == NULL))
140 return NULL;
141
142 FTPIncrMemuse((uint64_t)size);
143
144 return ptr;
145 }
146
147 static void *FTPCalloc(size_t n, size_t size)
148 {
149 void *ptr = NULL;
150
151 if (FTPCheckMemcap((uint32_t)(n * size)) == 0)
152 return NULL;
153
154 ptr = SCCalloc(n, size);
155
156 if (unlikely(ptr == NULL))
157 return NULL;
158
159 FTPIncrMemuse((uint64_t)(n * size));
160
161 return ptr;
162 }
163
164 static void *FTPRealloc(void *ptr, size_t orig_size, size_t size)
165 {
166 void *rptr = NULL;
167
168 if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0)
169 return NULL;
170
171 rptr = SCRealloc(ptr, size);
172 if (rptr == NULL)
173 return NULL;
174
175 if (size > orig_size) {
176 FTPIncrMemuse(size - orig_size);
177 } else {
178 FTPDecrMemuse(orig_size - size);
179 }
180
181 return rptr;
182 }
183
184 static void FTPFree(void *ptr, size_t size)
185 {
186 SCFree(ptr);
187
188 FTPDecrMemuse((uint64_t)size);
189 }
190
191 static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
192 {
193 void *ptmp;
194 if (line_state->current_line_lf_seen == 1) {
195 /* we have seen the lf for the previous line. Clear the parser
196 * details to parse new line */
197 line_state->current_line_lf_seen = 0;
198 if (line_state->current_line_db == 1) {
199 line_state->current_line_db = 0;
200 FTPFree(line_state->db, line_state->db_len);
201 line_state->db = NULL;
202 line_state->db_len = 0;
203 state->current_line = NULL;
204 state->current_line_len = 0;
205 }
206 }
207
208 uint8_t *lf_idx = memchr(state->input, 0x0a, state->input_len);
209
210 if (lf_idx == NULL) {
211 /* fragmented lines. Decoder event for special cases. Not all
212 * fragmented lines should be treated as a possible evasion
213 * attempt. With multi payload ftp chunks we can have valid
214 * cases of fragmentation. But within the same segment chunk
215 * if we see fragmentation then it's definitely something you
216 * should alert about */
217 if (line_state->current_line_db == 0) {
218 line_state->db = FTPMalloc(state->input_len);
219 if (line_state->db == NULL) {
220 return -1;
221 }
222 line_state->current_line_db = 1;
223 memcpy(line_state->db, state->input, state->input_len);
224 line_state->db_len = state->input_len;
225 } else {
226 ptmp = FTPRealloc(line_state->db, line_state->db_len,
227 (line_state->db_len + state->input_len));
228 if (ptmp == NULL) {
229 FTPFree(line_state->db, line_state->db_len);
230 line_state->db = NULL;
231 line_state->db_len = 0;
232 return -1;
233 }
234 line_state->db = ptmp;
235
236 memcpy(line_state->db + line_state->db_len,
237 state->input, state->input_len);
238 line_state->db_len += state->input_len;
239 }
240 state->input += state->input_len;
241 state->input_len = 0;
242
243 return -1;
244
245 } else {
246 line_state->current_line_lf_seen = 1;
247
248 if (line_state->current_line_db == 1) {
249 ptmp = FTPRealloc(line_state->db, line_state->db_len,
250 (line_state->db_len + (lf_idx + 1 - state->input)));
251 if (ptmp == NULL) {
252 FTPFree(line_state->db, line_state->db_len);
253 line_state->db = NULL;
254 line_state->db_len = 0;
255 return -1;
256 }
257 line_state->db = ptmp;
258
259 memcpy(line_state->db + line_state->db_len,
260 state->input, (lf_idx + 1 - state->input));
261 line_state->db_len += (lf_idx + 1 - state->input);
262
263 if (line_state->db_len > 1 &&
264 line_state->db[line_state->db_len - 2] == 0x0D) {
265 line_state->db_len -= 2;
266 state->current_line_delimiter_len = 2;
267 } else {
268 line_state->db_len -= 1;
269 state->current_line_delimiter_len = 1;
270 }
271
272 state->current_line = line_state->db;
273 state->current_line_len = line_state->db_len;
274
275 } else {
276 state->current_line = state->input;
277 state->current_line_len = lf_idx - state->input;
278
279 if (state->input != lf_idx &&
280 *(lf_idx - 1) == 0x0D) {
281 state->current_line_len--;
282 state->current_line_delimiter_len = 2;
283 } else {
284 state->current_line_delimiter_len = 1;
285 }
286 }
287
288 state->input_len -= (lf_idx - state->input) + 1;
289 state->input = (lf_idx + 1);
290
291 return 0;
292 }
293
294 }
295
296 static int FTPGetLine(FtpState *state)
297 {
298 SCEnter();
299
300 /* we have run out of input */
301 if (state->input_len <= 0)
302 return -1;
303
304 /* toserver */
305 if (state->direction == 0)
306 return FTPGetLineForDirection(state, &state->line_state[0]);
307 else
308 return FTPGetLineForDirection(state, &state->line_state[1]);
309 }
310
311 /**
312 * \brief This function is called to determine and set which command is being
313 * transfered to the ftp server
314 * \param ftp_state the ftp state structure for the parser
315 * \param input input line of the command
316 * \param len of the command
317 *
318 * \retval 1 when the command is parsed, 0 otherwise
319 */
320 static int FTPParseRequestCommand(void *ftp_state, uint8_t *input,
321 uint32_t input_len)
322 {
323 SCEnter();
324 FtpState *fstate = (FtpState *)ftp_state;
325 fstate->command = FTP_COMMAND_UNKNOWN;
326
327 if (input_len >= 4 && SCMemcmpLowercase("port", input, 4) == 0) {
328 fstate->command = FTP_COMMAND_PORT;
329 }
330
331 if (input_len >= 8 && SCMemcmpLowercase("auth tls", input, 8) == 0) {
332 fstate->command = FTP_COMMAND_AUTH_TLS;
333 }
334
335 if (input_len >= 4 && SCMemcmpLowercase("pasv", input, 4) == 0) {
336 fstate->command = FTP_COMMAND_PASV;
337 }
338
339 if (input_len > 5 && SCMemcmpLowercase("retr", input, 4) == 0) {
340 fstate->command = FTP_COMMAND_RETR;
341 }
342
343 if (input_len >= 4 && SCMemcmpLowercase("epsv", input, 4) == 0) {
344 fstate->command = FTP_COMMAND_EPSV;
345 }
346
347 if (input_len > 5 && SCMemcmpLowercase("stor", input, 4) == 0) {
348 fstate->command = FTP_COMMAND_STOR;
349 }
350
351 return 1;
352 }
353
354 struct FtpTransferCmd {
355 /** Need to look like a ExpectationData so DFree must
356 * be first field . */
357 void (*DFree)(void *);
358 uint64_t flow_id;
359 uint8_t *file_name;
360 uint16_t file_len;
361 FtpRequestCommand cmd;
362 };
363
364 static void FtpTransferCmdFree(void *data)
365 {
366 struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data;
367 if (cmd == NULL)
368 return;
369 if (cmd->file_name) {
370 SCFree(cmd->file_name);
371 }
372 FTPFree(cmd, sizeof(struct FtpTransferCmd));
373 }
374
375 /**
376 * \brief This function is called to retrieve a ftp request
377 * \param ftp_state the ftp state structure for the parser
378 * \param input input line of the command
379 * \param input_len length of the request
380 * \param output the resulting output
381 *
382 * \retval 1 when the command is parsed, 0 otherwise
383 */
384 static int FTPParseRequest(Flow *f, void *ftp_state,
385 AppLayerParserState *pstate,
386 uint8_t *input, uint32_t input_len,
387 void *local_data, const uint8_t flags)
388 {
389 SCEnter();
390 /* PrintRawDataFp(stdout, input,input_len); */
391
392 FtpState *state = (FtpState *)ftp_state;
393 void *ptmp;
394
395 if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
396 SCReturnInt(1);
397 } else if (input == NULL || input_len == 0) {
398 SCReturnInt(-1);
399 }
400
401 state->input = input;
402 state->input_len = input_len;
403 /* toserver stream */
404 state->direction = 0;
405
406 int direction = STREAM_TOSERVER;
407 while (FTPGetLine(state) >= 0) {
408 FTPParseRequestCommand(state,
409 state->current_line, state->current_line_len);
410 switch (state->command) {
411 case FTP_COMMAND_PORT:
412 if (state->current_line_len > state->port_line_size) {
413 ptmp = FTPRealloc(state->port_line, state->port_line_size,
414 state->current_line_len);
415 if (ptmp == NULL) {
416 FTPFree(state->port_line, state->port_line_size);
417 state->port_line = NULL;
418 state->port_line_size = 0;
419 return 0;
420 }
421 state->port_line = ptmp;
422
423 state->port_line_size = state->current_line_len;
424 }
425 memcpy(state->port_line, state->current_line,
426 state->current_line_len);
427 state->port_line_len = state->current_line_len;
428 break;
429 case FTP_COMMAND_RETR:
430 /* change direction (default to server) so expectation will handle
431 * the correct message when expectation will match.
432 */
433 direction = STREAM_TOCLIENT;
434 // fallthrough
435 case FTP_COMMAND_STOR:
436 {
437 /* No dyn port negotiated so get out */
438 if (state->dyn_port == 0) {
439 SCReturnInt(-1);
440 }
441 struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd));
442 if (data == NULL)
443 SCReturnInt(-1);
444 data->DFree = FtpTransferCmdFree;
445 /* Min size has been checked in FTPParseRequestCommand */
446 data->file_name = FTPCalloc(state->current_line_len - 4, sizeof(char));
447 if (data->file_name == NULL) {
448 FTPFree(data, sizeof(struct FtpTransferCmd));
449 SCReturnInt(-1);
450 }
451 data->file_name[state->current_line_len - 5] = 0;
452 data->file_len = state->current_line_len - 5;
453 memcpy(data->file_name, state->current_line + 5, state->current_line_len - 5);
454 data->cmd = state->command;
455 data->flow_id = FlowGetId(f);
456 int ret = AppLayerExpectationCreate(f, direction, 0,
457 state->dyn_port,
458 ALPROTO_FTPDATA, data);
459 if (ret == -1) {
460 FTPFree(data, sizeof(struct FtpTransferCmd));
461 SCLogDebug("No expectation created.");
462 SCReturnInt(-1);
463 } else {
464 SCLogDebug("Expectation created.");
465 }
466 /* reset the dyn port to avoid duplicate */
467 state->dyn_port = 0;
468 }
469 break;
470 default:
471 break;
472 }
473 }
474
475 return 1;
476 }
477
478 static int FTPParsePassiveResponse(Flow *f, FtpState *state, uint8_t *input, uint32_t input_len)
479 {
480 uint16_t dyn_port;
481
482 #ifdef HAVE_RUST
483 dyn_port = rs_ftp_pasv_response(input, input_len);
484 if (dyn_port == 0) {
485 return -1;
486 }
487 #else
488 uint16_t part1, part2;
489 uint8_t *ptr = memrchr(input, ',', input_len);
490 if (ptr == NULL)
491 return -1;
492
493 part2 = atoi((char *)ptr + 1);
494 ptr = memrchr(input, ',', (ptr - input) - 1);
495 if (ptr == NULL)
496 return -1;
497 part1 = atoi((char *)ptr + 1);
498
499 dyn_port = 256 * part1 + part2;
500 #endif
501 state->dyn_port = dyn_port;
502
503 return 0;
504 }
505
506 static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, uint8_t *input, uint32_t input_len)
507 {
508 #ifdef HAVE_RUST
509 uint16_t dyn_port = rs_ftp_epsv_response(input, input_len);
510 if (dyn_port == 0) {
511 return -1;
512 }
513
514 state->dyn_port = dyn_port;
515 #else
516 uint8_t *ptr = memrchr(input, '|', input_len);
517 if (ptr == NULL) {
518 return -1;
519 } else {
520 int n_length = ptr - input - 1;
521 if (n_length < 4)
522 return -1;
523 ptr = memrchr(input, '|', n_length);
524 if (ptr == NULL)
525 return -1;
526 }
527 state->dyn_port = atoi((char *)ptr + 1);
528 #endif
529 return 0;
530 }
531
532 /**
533 * \brief This function is called to retrieve a ftp response
534 * \param ftp_state the ftp state structure for the parser
535 * \param input input line of the command
536 * \param input_len length of the request
537 * \param output the resulting output
538 *
539 * \retval 1 when the command is parsed, 0 otherwise
540 */
541 static int FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
542 uint8_t *input, uint32_t input_len,
543 void *local_data, const uint8_t flags)
544 {
545 FtpState *state = (FtpState *)ftp_state;
546
547 if (state->command == FTP_COMMAND_AUTH_TLS) {
548 if (input_len >= 4 && SCMemcmp("234 ", input, 4) == 0) {
549 AppLayerRequestProtocolTLSUpgrade(f);
550 }
551 }
552
553 if (state->command == FTP_COMMAND_PASV) {
554 if (input_len >= 4 && SCMemcmp("227 ", input, 4) == 0) {
555 FTPParsePassiveResponse(f, ftp_state, input, input_len);
556 }
557 }
558
559 if (state->command == FTP_COMMAND_EPSV) {
560 if (input_len >= 4 && SCMemcmp("229 ", input, 4) == 0) {
561 FTPParsePassiveResponseV6(f, ftp_state, input, input_len);
562 }
563 }
564
565 return 1;
566 }
567
568 #ifdef DEBUG
569 static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER;
570 static uint64_t ftp_state_memuse = 0;
571 static uint64_t ftp_state_memcnt = 0;
572 #endif
573
574 static void *FTPStateAlloc(void)
575 {
576 void *s = FTPMalloc(sizeof(FtpState));
577 if (unlikely(s == NULL))
578 return NULL;
579
580 memset(s, 0, sizeof(FtpState));
581
582 #ifdef DEBUG
583 SCMutexLock(&ftp_state_mem_lock);
584 ftp_state_memcnt++;
585 ftp_state_memuse+=sizeof(FtpState);
586 SCMutexUnlock(&ftp_state_mem_lock);
587 #endif
588 return s;
589 }
590
591 static void FTPStateFree(void *s)
592 {
593 FtpState *fstate = (FtpState *) s;
594 if (fstate->port_line != NULL)
595 FTPFree(fstate->port_line, fstate->port_line_size);
596 if (fstate->line_state[0].db)
597 FTPFree(fstate->line_state[0].db, fstate->line_state[0].db_len);
598 if (fstate->line_state[1].db)
599 FTPFree(fstate->line_state[1].db, fstate->line_state[1].db_len);
600
601 //AppLayerDecoderEventsFreeEvents(&s->decoder_events);
602
603 if (fstate->de_state != NULL) {
604 DetectEngineStateFree(fstate->de_state);
605 }
606
607 FTPFree(s, sizeof(FtpState));
608 #ifdef DEBUG
609 SCMutexLock(&ftp_state_mem_lock);
610 ftp_state_memcnt--;
611 ftp_state_memuse-=sizeof(FtpState);
612 SCMutexUnlock(&ftp_state_mem_lock);
613 #endif
614 }
615
616 static int FTPSetTxDetectState(void *vtx, DetectEngineState *de_state)
617 {
618 FtpState *ftp_state = (FtpState *)vtx;
619 ftp_state->de_state = de_state;
620 return 0;
621 }
622
623 static DetectEngineState *FTPGetTxDetectState(void *vtx)
624 {
625 FtpState *ftp_state = (FtpState *)vtx;
626 return ftp_state->de_state;
627 }
628
629 static void FTPStateTransactionFree(void *state, uint64_t tx_id)
630 {
631 /* do nothing */
632 }
633
634 static void *FTPGetTx(void *state, uint64_t tx_id)
635 {
636 FtpState *ftp_state = (FtpState *)state;
637 return ftp_state;
638 }
639
640 static uint64_t FTPGetTxCnt(void *state)
641 {
642 /* single tx */
643 return 1;
644 }
645
646 static int FTPGetAlstateProgressCompletionStatus(uint8_t direction)
647 {
648 return FTP_STATE_FINISHED;
649 }
650
651 static int FTPGetAlstateProgress(void *tx, uint8_t direction)
652 {
653 FtpState *ftp_state = (FtpState *)tx;
654
655 if (direction == STREAM_TOSERVER &&
656 ftp_state->command == FTP_COMMAND_PORT) {
657 return FTP_STATE_PORT_DONE;
658 }
659
660 /* TODO: figure out further progress handling */
661
662 return FTP_STATE_IN_PROGRESS;
663 }
664
665
666 static int FTPRegisterPatternsForProtocolDetection(void)
667 {
668 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
669 "220 (", 5, 0, STREAM_TOCLIENT) < 0)
670 {
671 return -1;
672 }
673 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
674 "FEAT", 4, 0, STREAM_TOSERVER) < 0)
675 {
676 return -1;
677 }
678 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
679 "USER ", 5, 0, STREAM_TOSERVER) < 0)
680 {
681 return -1;
682 }
683 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
684 "PASS ", 5, 0, STREAM_TOSERVER) < 0)
685 {
686 return -1;
687 }
688 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
689 "PORT ", 5, 0, STREAM_TOSERVER) < 0)
690 {
691 return -1;
692 }
693
694 return 0;
695 }
696
697
698 static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
699
700 /**
701 * \brief This function is called to retrieve a ftp request
702 * \param ftp_state the ftp state structure for the parser
703 * \param input input line of the command
704 * \param input_len length of the request
705 * \param output the resulting output
706 *
707 * \retval 1 when the command is parsed, 0 otherwise
708 */
709 static int FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
710 AppLayerParserState *pstate,
711 uint8_t *input, uint32_t input_len,
712 void *local_data, int direction)
713 {
714 uint16_t flags = FileFlowToFlags(f, direction);
715 int ret = 0;
716 /* we depend on detection engine for file pruning */
717 flags |= FILE_USE_DETECT;
718 if (ftpdata_state->files == NULL) {
719 struct FtpTransferCmd *data = (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetDataId());
720 if (data == NULL) {
721 SCReturnInt(-1);
722 }
723
724 ftpdata_state->files = FileContainerAlloc();
725 if (ftpdata_state->files == NULL) {
726 FlowFreeStorageById(f, AppLayerExpectationGetDataId());
727 SCReturnInt(-1);
728 }
729
730 ftpdata_state->file_name = data->file_name;
731 ftpdata_state->file_len = data->file_len;
732 data->file_name = NULL;
733 data->file_len = 0;
734 f->parent_id = data->flow_id;
735 ftpdata_state->command = data->cmd;
736 switch (data->cmd) {
737 case FTP_COMMAND_STOR:
738 ftpdata_state->direction = STREAM_TOSERVER;
739 break;
740 case FTP_COMMAND_RETR:
741 ftpdata_state->direction = STREAM_TOCLIENT;
742 break;
743 default:
744 break;
745 }
746
747 if (FileOpenFile(ftpdata_state->files, &sbcfg,
748 (uint8_t *) ftpdata_state->file_name,
749 ftpdata_state->file_len,
750 input, input_len, flags) == NULL) {
751 SCLogDebug("Can't open file");
752 ret = -1;
753 }
754 FlowFreeStorageById(f, AppLayerExpectationGetDataId());
755 } else {
756 if (input_len != 0) {
757 ret = FileAppendData(ftpdata_state->files, input, input_len);
758 if (ret == -2) {
759 ret = 0;
760 SCLogDebug("FileAppendData() - file no longer being extracted");
761 goto out;
762 } else if (ret < 0) {
763 SCLogDebug("FileAppendData() failed: %d", ret);
764 ret = -2;
765 goto out;
766 }
767 } else {
768 ret = FileCloseFile(ftpdata_state->files, NULL, 0, flags);
769 ftpdata_state->state = FTPDATA_STATE_FINISHED;
770 if (ret < 0)
771 goto out;
772 }
773 }
774
775 if (input_len && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
776 ret = FileCloseFile(ftpdata_state->files, (uint8_t *) NULL, 0, flags);
777 ftpdata_state->state = FTPDATA_STATE_FINISHED;
778 }
779
780 out:
781 if (ftpdata_state->files) {
782 FilePrune(ftpdata_state->files);
783 }
784 return ret;
785 }
786
787 static int FTPDataParseRequest(Flow *f, void *ftp_state,
788 AppLayerParserState *pstate,
789 uint8_t *input, uint32_t input_len,
790 void *local_data, const uint8_t flags)
791 {
792 return FTPDataParse(f, ftp_state, pstate, input, input_len,
793 local_data, STREAM_TOSERVER);
794 }
795
796 static int FTPDataParseResponse(Flow *f, void *ftp_state,
797 AppLayerParserState *pstate,
798 uint8_t *input, uint32_t input_len,
799 void *local_data, const uint8_t flags)
800 {
801 return FTPDataParse(f, ftp_state, pstate, input, input_len,
802 local_data, STREAM_TOCLIENT);
803 }
804
805 #ifdef DEBUG
806 static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER;
807 static uint64_t ftpdata_state_memuse = 0;
808 static uint64_t ftpdata_state_memcnt = 0;
809 #endif
810
811 static void *FTPDataStateAlloc(void)
812 {
813 void *s = FTPMalloc(sizeof(FtpDataState));
814 if (unlikely(s == NULL))
815 return NULL;
816
817 memset(s, 0, sizeof(FtpDataState));
818 ((FtpDataState *)s)->state = FTPDATA_STATE_IN_PROGRESS;
819
820 #ifdef DEBUG
821 SCMutexLock(&ftpdata_state_mem_lock);
822 ftpdata_state_memcnt++;
823 ftpdata_state_memuse+=sizeof(FtpDataState);
824 SCMutexUnlock(&ftpdata_state_mem_lock);
825 #endif
826 return s;
827 }
828
829 static void FTPDataStateFree(void *s)
830 {
831 FtpDataState *fstate = (FtpDataState *) s;
832
833 if (fstate->de_state != NULL) {
834 DetectEngineStateFree(fstate->de_state);
835 }
836 if (fstate->file_name != NULL) {
837 FTPFree(fstate->file_name, fstate->file_len);
838 }
839
840 FileContainerFree(fstate->files);
841
842 SCFree(s);
843 #ifdef DEBUG
844 SCMutexLock(&ftpdata_state_mem_lock);
845 ftpdata_state_memcnt--;
846 ftpdata_state_memuse-=sizeof(FtpDataState);
847 SCMutexUnlock(&ftpdata_state_mem_lock);
848 #endif
849 }
850
851 static int FTPDataSetTxDetectState(void *vtx, DetectEngineState *de_state)
852 {
853 FtpDataState *ftp_state = (FtpDataState *)vtx;
854 ftp_state->de_state = de_state;
855 return 0;
856 }
857
858 static DetectEngineState *FTPDataGetTxDetectState(void *vtx)
859 {
860 FtpDataState *ftp_state = (FtpDataState *)vtx;
861 return ftp_state->de_state;
862 }
863
864 static void FTPDataStateTransactionFree(void *state, uint64_t tx_id)
865 {
866 /* do nothing */
867 }
868
869 static void *FTPDataGetTx(void *state, uint64_t tx_id)
870 {
871 FtpDataState *ftp_state = (FtpDataState *)state;
872 return ftp_state;
873 }
874
875 static uint64_t FTPDataGetTxCnt(void *state)
876 {
877 /* ftp-data is single tx */
878 return 1;
879 }
880
881 static int FTPDataGetAlstateProgressCompletionStatus(uint8_t direction)
882 {
883 return FTPDATA_STATE_FINISHED;
884 }
885
886 static int FTPDataGetAlstateProgress(void *tx, uint8_t direction)
887 {
888 FtpDataState *ftpdata_state = (FtpDataState *)tx;
889 return ftpdata_state->state;
890 }
891
892 static FileContainer *FTPDataStateGetFiles(void *state, uint8_t direction)
893 {
894 FtpDataState *ftpdata_state = (FtpDataState *)state;
895
896 if (direction != ftpdata_state->direction)
897 SCReturnPtr(NULL, "FileContainer");
898
899 SCReturnPtr(ftpdata_state->files, "FileContainer");
900 }
901
902 void RegisterFTPParsers(void)
903 {
904 const char *proto_name = "ftp";
905 const char *proto_data_name = "ftp-data";
906
907 /** FTP */
908 if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
909 AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name);
910 if (FTPRegisterPatternsForProtocolDetection() < 0 )
911 return;
912 AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name);
913 }
914
915 if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
916 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER,
917 FTPParseRequest);
918 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT,
919 FTPParseResponse);
920 AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
921 AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT);
922
923 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree);
924
925 AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_FTP,
926 FTPGetTxDetectState, FTPSetTxDetectState);
927
928 AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx);
929
930 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt);
931
932 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress);
933
934 AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_FTP,
935 FTPGetAlstateProgressCompletionStatus);
936
937
938 AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA);
939 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER,
940 FTPDataParseRequest);
941 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT,
942 FTPDataParseResponse);
943 AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree);
944 AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT);
945 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree);
946 AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA,
947 FTPDataGetTxDetectState, FTPDataSetTxDetectState);
948
949 AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetFiles);
950
951 AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx);
952
953 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt);
954
955 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress);
956
957 AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_FTPDATA,
958 FTPDataGetAlstateProgressCompletionStatus);
959
960 sbcfg.buf_size = 4096;
961 sbcfg.Malloc = FTPMalloc;
962 sbcfg.Calloc = FTPCalloc;
963 sbcfg.Realloc = FTPRealloc;
964 sbcfg.Free = FTPFree;
965
966 FTPParseMemcap();
967 } else {
968 SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
969 "still on.", proto_name);
970 }
971 #ifdef UNITTESTS
972 AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests);
973 #endif
974 }
975
976 void FTPAtExitPrintStats(void)
977 {
978 #ifdef DEBUG
979 SCMutexLock(&ftp_state_mem_lock);
980 SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"",
981 ftp_state_memcnt, ftp_state_memuse);
982 SCMutexUnlock(&ftp_state_mem_lock);
983 #endif
984 }
985
986
987 #ifdef HAVE_LIBJANSSON
988 json_t *JsonFTPDataAddMetadata(const Flow *f)
989 {
990 const FtpDataState *ftp_state = NULL;
991 if (f->alstate == NULL)
992 return NULL;
993 ftp_state = (FtpDataState *)f->alstate;
994 json_t *ftpd = json_object();
995 if (ftpd == NULL)
996 return NULL;
997 if (ftp_state->file_name) {
998 char *s = BytesToString(ftp_state->file_name, ftp_state->file_len);
999 json_object_set_new(ftpd, "filename", json_string(s));
1000 if (s != NULL)
1001 SCFree(s);
1002 }
1003 switch (ftp_state->command) {
1004 case FTP_COMMAND_STOR:
1005 json_object_set_new(ftpd, "command", json_string("STOR"));
1006 break;
1007 case FTP_COMMAND_RETR:
1008 json_object_set_new(ftpd, "command", json_string("RETR"));
1009 break;
1010 default:
1011 break;
1012 }
1013 return ftpd;
1014 }
1015 #endif /* HAVE_LIBJANSSON */
1016
1017 /* UNITTESTS */
1018 #ifdef UNITTESTS
1019
1020 /** \test Send a get request in one chunk. */
1021 static int FTPParserTest01(void)
1022 {
1023 int result = 1;
1024 Flow f;
1025 uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n";
1026 uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */
1027 TcpSession ssn;
1028 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1029
1030 memset(&f, 0, sizeof(f));
1031 memset(&ssn, 0, sizeof(ssn));
1032
1033 FLOW_INITIALIZE(&f);
1034 f.protoctx = (void *)&ssn;
1035 f.proto = IPPROTO_TCP;
1036 f.alproto = ALPROTO_FTP;
1037
1038 StreamTcpInitConfig(TRUE);
1039
1040 FLOWLOCK_WRLOCK(&f);
1041 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1042 STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen);
1043 if (r != 0) {
1044 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
1045 result = 0;
1046 FLOWLOCK_UNLOCK(&f);
1047 goto end;
1048 }
1049 FLOWLOCK_UNLOCK(&f);
1050
1051 FtpState *ftp_state = f.alstate;
1052 if (ftp_state == NULL) {
1053 SCLogDebug("no ftp state: ");
1054 result = 0;
1055 goto end;
1056 }
1057
1058 if (ftp_state->command != FTP_COMMAND_PORT) {
1059 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
1060 result = 0;
1061 goto end;
1062 }
1063
1064 end:
1065 if (alp_tctx != NULL)
1066 AppLayerParserThreadCtxFree(alp_tctx);
1067 StreamTcpFreeConfig(TRUE);
1068 FLOW_DESTROY(&f);
1069 return result;
1070 }
1071
1072 /** \test Send a splitted get request. */
1073 static int FTPParserTest03(void)
1074 {
1075 int result = 1;
1076 Flow f;
1077 uint8_t ftpbuf1[] = "POR";
1078 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1079 uint8_t ftpbuf2[] = "T 192,168,1";
1080 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
1081 uint8_t ftpbuf3[] = "1,1,10,20\r\n";
1082 uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */
1083 TcpSession ssn;
1084 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1085
1086 memset(&f, 0, sizeof(f));
1087 memset(&ssn, 0, sizeof(ssn));
1088
1089 FLOW_INITIALIZE(&f);
1090 f.protoctx = (void *)&ssn;
1091 f.proto = IPPROTO_TCP;
1092 f.alproto = ALPROTO_FTP;
1093
1094 StreamTcpInitConfig(TRUE);
1095
1096 FLOWLOCK_WRLOCK(&f);
1097 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1098 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1099 ftplen1);
1100 if (r != 0) {
1101 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
1102 result = 0;
1103 FLOWLOCK_UNLOCK(&f);
1104 goto end;
1105 }
1106 FLOWLOCK_UNLOCK(&f);
1107
1108 FLOWLOCK_WRLOCK(&f);
1109 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER,
1110 ftpbuf2, ftplen2);
1111 if (r != 0) {
1112 SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
1113 result = 0;
1114 FLOWLOCK_UNLOCK(&f);
1115 goto end;
1116 }
1117 FLOWLOCK_UNLOCK(&f);
1118
1119 FLOWLOCK_WRLOCK(&f);
1120 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1121 STREAM_TOSERVER | STREAM_EOF, ftpbuf3, ftplen3);
1122 if (r != 0) {
1123 SCLogDebug("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
1124 result = 0;
1125 FLOWLOCK_UNLOCK(&f);
1126 goto end;
1127 }
1128 FLOWLOCK_UNLOCK(&f);
1129
1130 FtpState *ftp_state = f.alstate;
1131 if (ftp_state == NULL) {
1132 SCLogDebug("no ftp state: ");
1133 result = 0;
1134 goto end;
1135 }
1136
1137 if (ftp_state->command != FTP_COMMAND_PORT) {
1138 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
1139 result = 0;
1140 goto end;
1141 }
1142
1143 end:
1144 if (alp_tctx != NULL)
1145 AppLayerParserThreadCtxFree(alp_tctx);
1146 StreamTcpFreeConfig(TRUE);
1147 return result;
1148 }
1149
1150 /** \test See how it deals with an incomplete request. */
1151 static int FTPParserTest06(void)
1152 {
1153 int result = 1;
1154 Flow f;
1155 uint8_t ftpbuf1[] = "PORT";
1156 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1157 TcpSession ssn;
1158 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1159
1160 memset(&f, 0, sizeof(f));
1161 memset(&ssn, 0, sizeof(ssn));
1162
1163 FLOW_INITIALIZE(&f);
1164 f.protoctx = (void *)&ssn;
1165 f.proto = IPPROTO_TCP;
1166 f.alproto = ALPROTO_FTP;
1167
1168 StreamTcpInitConfig(TRUE);
1169
1170 FLOWLOCK_WRLOCK(&f);
1171 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1172 STREAM_TOSERVER | STREAM_START | STREAM_EOF,
1173 ftpbuf1,
1174 ftplen1);
1175 if (r != 0) {
1176 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
1177 result = 0;
1178 FLOWLOCK_UNLOCK(&f);
1179 goto end;
1180 }
1181 FLOWLOCK_UNLOCK(&f);
1182
1183 FtpState *ftp_state = f.alstate;
1184 if (ftp_state == NULL) {
1185 SCLogDebug("no ftp state: ");
1186 result = 0;
1187 goto end;
1188 }
1189
1190 if (ftp_state->command != FTP_COMMAND_UNKNOWN) {
1191 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command);
1192 result = 0;
1193 goto end;
1194 }
1195
1196 end:
1197 if (alp_tctx != NULL)
1198 AppLayerParserThreadCtxFree(alp_tctx);
1199 StreamTcpFreeConfig(TRUE);
1200 FLOW_DESTROY(&f);
1201 return result;
1202 }
1203
1204 /** \test See how it deals with an incomplete request in multiple chunks. */
1205 static int FTPParserTest07(void)
1206 {
1207 int result = 1;
1208 Flow f;
1209 uint8_t ftpbuf1[] = "PO";
1210 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1211 uint8_t ftpbuf2[] = "RT\r\n";
1212 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
1213 TcpSession ssn;
1214 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1215
1216 memset(&f, 0, sizeof(f));
1217 memset(&ssn, 0, sizeof(ssn));
1218
1219 FLOW_INITIALIZE(&f);
1220 f.protoctx = (void *)&ssn;
1221 f.proto = IPPROTO_TCP;
1222 f.alproto = ALPROTO_FTP;
1223
1224 StreamTcpInitConfig(TRUE);
1225
1226 FLOWLOCK_WRLOCK(&f);
1227 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1228 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1229 ftplen1);
1230 if (r != 0) {
1231 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
1232 result = 0;
1233 FLOWLOCK_UNLOCK(&f);
1234 goto end;
1235 }
1236 FLOWLOCK_UNLOCK(&f);
1237
1238 FLOWLOCK_WRLOCK(&f);
1239 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1240 STREAM_TOSERVER | STREAM_EOF, ftpbuf2, ftplen2);
1241 if (r != 0) {
1242 SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
1243 result = 0;
1244 FLOWLOCK_UNLOCK(&f);
1245 goto end;
1246 }
1247 FLOWLOCK_UNLOCK(&f);
1248
1249 FtpState *ftp_state = f.alstate;
1250 if (ftp_state == NULL) {
1251 SCLogDebug("no ftp state: ");
1252 result = 0;
1253 goto end;
1254 }
1255
1256 if (ftp_state->command != FTP_COMMAND_PORT) {
1257 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ",
1258 FTP_COMMAND_PORT, ftp_state->command);
1259 result = 0;
1260 goto end;
1261 }
1262
1263 end:
1264 if (alp_tctx != NULL)
1265 AppLayerParserThreadCtxFree(alp_tctx);
1266 StreamTcpFreeConfig(TRUE);
1267 FLOW_DESTROY(&f);
1268 return result;
1269 }
1270
1271 /** \test Test case where chunks are smaller than the delim length and the
1272 * last chunk is supposed to match the delim. */
1273 static int FTPParserTest10(void)
1274 {
1275 int result = 1;
1276 Flow f;
1277 uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n";
1278 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1279 TcpSession ssn;
1280 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1281 int r = 0;
1282 memset(&f, 0, sizeof(f));
1283 memset(&ssn, 0, sizeof(ssn));
1284
1285 FLOW_INITIALIZE(&f);
1286 f.protoctx = (void *)&ssn;
1287 f.proto = IPPROTO_TCP;
1288 f.alproto = ALPROTO_FTP;
1289
1290 StreamTcpInitConfig(TRUE);
1291
1292 uint32_t u;
1293 for (u = 0; u < ftplen1; u++) {
1294 uint8_t flags = 0;
1295
1296 if (u == 0) flags = STREAM_TOSERVER|STREAM_START;
1297 else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF;
1298 else flags = STREAM_TOSERVER;
1299
1300 FLOWLOCK_WRLOCK(&f);
1301 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, flags,
1302 &ftpbuf1[u], 1);
1303 if (r != 0) {
1304 SCLogDebug("toserver chunk %" PRIu32 " returned %" PRId32 ", expected 0: ", u, r);
1305 result = 0;
1306 FLOWLOCK_UNLOCK(&f);
1307 goto end;
1308 }
1309 FLOWLOCK_UNLOCK(&f);
1310 }
1311
1312 FtpState *ftp_state = f.alstate;
1313 if (ftp_state == NULL) {
1314 SCLogDebug("no ftp state: ");
1315 result = 0;
1316 goto end;
1317 }
1318
1319 if (ftp_state->command != FTP_COMMAND_PORT) {
1320 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
1321 result = 0;
1322 goto end;
1323 }
1324
1325 end:
1326 if (alp_tctx != NULL)
1327 AppLayerParserThreadCtxFree(alp_tctx);
1328 StreamTcpFreeConfig(TRUE);
1329 FLOW_DESTROY(&f);
1330 return result;
1331 }
1332 #endif /* UNITTESTS */
1333
1334 void FTPParserRegisterTests(void)
1335 {
1336 #ifdef UNITTESTS
1337 UtRegisterTest("FTPParserTest01", FTPParserTest01);
1338 UtRegisterTest("FTPParserTest03", FTPParserTest03);
1339 UtRegisterTest("FTPParserTest06", FTPParserTest06);
1340 UtRegisterTest("FTPParserTest07", FTPParserTest07);
1341 UtRegisterTest("FTPParserTest10", FTPParserTest10);
1342 #endif /* UNITTESTS */
1343 }
1344