]> git.ipfire.org Git - people/ms/suricata.git/blob - src/app-layer-ftp.c
ftp: fix direction of expectation for STOR command
[people/ms/suricata.git] / src / app-layer-ftp.c
1 /* Copyright (C) 2007-2020 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 <jeff@lucovsky.org>
24 *
25 * App Layer Parser for FTP
26 */
27
28 #include "suricata-common.h"
29 #include "debug.h"
30 #include "decode.h"
31 #include "threads.h"
32
33 #include "util-print.h"
34 #include "util-pool.h"
35
36 #include "flow-util.h"
37 #include "flow-storage.h"
38
39 #include "detect-engine-state.h"
40
41 #include "stream-tcp-private.h"
42 #include "stream-tcp-reassemble.h"
43 #include "stream-tcp.h"
44 #include "stream.h"
45
46 #include "app-layer.h"
47 #include "app-layer-protos.h"
48 #include "app-layer-parser.h"
49 #include "app-layer-ftp.h"
50 #include "app-layer-expectation.h"
51
52 #include "util-spm.h"
53 #include "util-mpm.h"
54 #include "util-unittest.h"
55 #include "util-debug.h"
56 #include "util-memcmp.h"
57 #include "util-memrchr.h"
58 #include "util-mem.h"
59 #include "util-misc.h"
60
61 #include "output-json.h"
62 #include "rust.h"
63
64 typedef struct FTPThreadCtx_ {
65 MpmThreadCtx *ftp_mpm_thread_ctx;
66 PrefilterRuleStore *pmq;
67 } FTPThreadCtx;
68
69 #define FTP_MPM mpm_default_matcher
70
71 static MpmCtx *ftp_mpm_ctx = NULL;
72
73 const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = {
74 /* Parsed and handled */
75 { FTP_COMMAND_PORT, "PORT", 4},
76 { FTP_COMMAND_EPRT, "EPRT", 4},
77 { FTP_COMMAND_AUTH_TLS, "AUTH TLS", 8},
78 { FTP_COMMAND_PASV, "PASV", 4},
79 { FTP_COMMAND_RETR, "RETR", 4},
80 { FTP_COMMAND_EPSV, "EPSV", 4},
81 { FTP_COMMAND_STOR, "STOR", 4},
82
83 /* Parsed, but not handled */
84 { FTP_COMMAND_ABOR, "ABOR", 4},
85 { FTP_COMMAND_ACCT, "ACCT", 4},
86 { FTP_COMMAND_ALLO, "ALLO", 4},
87 { FTP_COMMAND_APPE, "APPE", 4},
88 { FTP_COMMAND_CDUP, "CDUP", 4},
89 { FTP_COMMAND_CHMOD, "CHMOD", 5},
90 { FTP_COMMAND_CWD, "CWD", 3},
91 { FTP_COMMAND_DELE, "DELE", 4},
92 { FTP_COMMAND_HELP, "HELP", 4},
93 { FTP_COMMAND_IDLE, "IDLE", 4},
94 { FTP_COMMAND_LIST, "LIST", 4},
95 { FTP_COMMAND_MAIL, "MAIL", 4},
96 { FTP_COMMAND_MDTM, "MDTM", 4},
97 { FTP_COMMAND_MKD, "MKD", 3},
98 { FTP_COMMAND_MLFL, "MLFL", 4},
99 { FTP_COMMAND_MODE, "MODE", 4},
100 { FTP_COMMAND_MRCP, "MRCP", 4},
101 { FTP_COMMAND_MRSQ, "MRSQ", 4},
102 { FTP_COMMAND_MSAM, "MSAM", 4},
103 { FTP_COMMAND_MSND, "MSND", 4},
104 { FTP_COMMAND_MSOM, "MSOM", 4},
105 { FTP_COMMAND_NLST, "NLST", 4},
106 { FTP_COMMAND_NOOP, "NOOP", 4},
107 { FTP_COMMAND_PASS, "PASS", 4},
108 { FTP_COMMAND_PWD, "PWD", 3},
109 { FTP_COMMAND_QUIT, "QUIT", 4},
110 { FTP_COMMAND_REIN, "REIN", 4},
111 { FTP_COMMAND_REST, "REST", 4},
112 { FTP_COMMAND_RMD, "RMD", 3},
113 { FTP_COMMAND_RNFR, "RNFR", 4},
114 { FTP_COMMAND_RNTO, "RNTO", 4},
115 { FTP_COMMAND_SITE, "SITE", 4},
116 { FTP_COMMAND_SIZE, "SIZE", 4},
117 { FTP_COMMAND_SMNT, "SMNT", 4},
118 { FTP_COMMAND_STAT, "STAT", 4},
119 { FTP_COMMAND_STOU, "STOU", 4},
120 { FTP_COMMAND_STRU, "STRU", 4},
121 { FTP_COMMAND_SYST, "SYST", 4},
122 { FTP_COMMAND_TYPE, "TYPE", 4},
123 { FTP_COMMAND_UMASK, "UMASK", 5},
124 { FTP_COMMAND_USER, "USER", 4},
125 { FTP_COMMAND_UNKNOWN, NULL, 0}
126 };
127 uint64_t ftp_config_memcap = 0;
128
129 SC_ATOMIC_DECLARE(uint64_t, ftp_memuse);
130 SC_ATOMIC_DECLARE(uint64_t, ftp_memcap);
131
132 static FTPTransaction *FTPGetOldestTx(FtpState *);
133
134 static void FTPParseMemcap(void)
135 {
136 const char *conf_val;
137
138 /** set config values for memcap, prealloc and hash_size */
139 if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1)
140 {
141 if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) {
142 SCLogError(SC_ERR_SIZE_PARSE, "Error parsing ftp.memcap "
143 "from conf file - %s. Killing engine",
144 conf_val);
145 exit(EXIT_FAILURE);
146 }
147 SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap);
148 } else {
149 /* default to unlimited */
150 ftp_config_memcap = 0;
151 }
152
153 SC_ATOMIC_INIT(ftp_memuse);
154 SC_ATOMIC_INIT(ftp_memcap);
155 }
156
157 static void FTPIncrMemuse(uint64_t size)
158 {
159 (void) SC_ATOMIC_ADD(ftp_memuse, size);
160 return;
161 }
162
163 static void FTPDecrMemuse(uint64_t size)
164 {
165 (void) SC_ATOMIC_SUB(ftp_memuse, size);
166 return;
167 }
168
169 uint64_t FTPMemuseGlobalCounter(void)
170 {
171 uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse);
172 return tmpval;
173 }
174
175 uint64_t FTPMemcapGlobalCounter(void)
176 {
177 uint64_t tmpval = SC_ATOMIC_GET(ftp_memcap);
178 return tmpval;
179 }
180
181 /**
182 * \brief Check if alloc'ing "size" would mean we're over memcap
183 *
184 * \retval 1 if in bounds
185 * \retval 0 if not in bounds
186 */
187 static int FTPCheckMemcap(uint64_t size)
188 {
189 if (ftp_config_memcap == 0 || size + SC_ATOMIC_GET(ftp_memuse) <= ftp_config_memcap)
190 return 1;
191 (void) SC_ATOMIC_ADD(ftp_memcap, 1);
192 return 0;
193 }
194
195 static void *FTPMalloc(size_t size)
196 {
197 void *ptr = NULL;
198
199 if (FTPCheckMemcap((uint32_t)size) == 0)
200 return NULL;
201
202 ptr = SCMalloc(size);
203
204 if (unlikely(ptr == NULL))
205 return NULL;
206
207 FTPIncrMemuse((uint64_t)size);
208
209 return ptr;
210 }
211
212 static void *FTPCalloc(size_t n, size_t size)
213 {
214 if (FTPCheckMemcap((uint32_t)(n * size)) == 0)
215 return NULL;
216
217 void *ptr = SCCalloc(n, size);
218
219 if (unlikely(ptr == NULL))
220 return NULL;
221
222 FTPIncrMemuse((uint64_t)(n * size));
223 return ptr;
224 }
225
226 static void *FTPRealloc(void *ptr, size_t orig_size, size_t size)
227 {
228 void *rptr = NULL;
229
230 if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0)
231 return NULL;
232
233 rptr = SCRealloc(ptr, size);
234 if (rptr == NULL)
235 return NULL;
236
237 if (size > orig_size) {
238 FTPIncrMemuse(size - orig_size);
239 } else {
240 FTPDecrMemuse(orig_size - size);
241 }
242
243 return rptr;
244 }
245
246 static void FTPFree(void *ptr, size_t size)
247 {
248 SCFree(ptr);
249
250 FTPDecrMemuse((uint64_t)size);
251 }
252
253 static FTPString *FTPStringAlloc(void)
254 {
255 return FTPCalloc(1, sizeof(FTPString));
256 }
257
258 static void FTPStringFree(FTPString *str)
259 {
260 if (str->str) {
261 FTPFree(str->str, str->len);
262 }
263
264 FTPFree(str, sizeof(FTPString));
265 }
266
267 static void *FTPLocalStorageAlloc(void)
268 {
269 /* needed by the mpm */
270 FTPThreadCtx *td = SCCalloc(1, sizeof(*td));
271 if (td == NULL) {
272 exit(EXIT_FAILURE);
273 }
274
275 td->pmq = SCCalloc(1, sizeof(*td->pmq));
276 if (td->pmq == NULL) {
277 exit(EXIT_FAILURE);
278 }
279 PmqSetup(td->pmq);
280
281 td->ftp_mpm_thread_ctx = SCCalloc(1, sizeof(MpmThreadCtx));
282 if (unlikely(td->ftp_mpm_thread_ctx == NULL)) {
283 exit(EXIT_FAILURE);
284 }
285 MpmInitThreadCtx(td->ftp_mpm_thread_ctx, FTP_MPM);
286 return td;
287 }
288
289 static void FTPLocalStorageFree(void *ptr)
290 {
291 FTPThreadCtx *td = ptr;
292 if (td != NULL) {
293 if (td->pmq != NULL) {
294 PmqFree(td->pmq);
295 SCFree(td->pmq);
296 }
297
298 if (td->ftp_mpm_thread_ctx != NULL) {
299 mpm_table[FTP_MPM].DestroyThreadCtx(ftp_mpm_ctx, td->ftp_mpm_thread_ctx);
300 SCFree(td->ftp_mpm_thread_ctx);
301 }
302
303 SCFree(td);
304 }
305
306 return;
307 }
308 static FTPTransaction *FTPTransactionCreate(FtpState *state)
309 {
310 SCEnter();
311 FTPTransaction *tx = FTPCalloc(1, sizeof(*tx));
312 if (tx == NULL) {
313 return NULL;
314 }
315
316 TAILQ_INSERT_TAIL(&state->tx_list, tx, next);
317 tx->tx_id = state->tx_cnt++;
318
319 TAILQ_INIT(&tx->response_list);
320
321 SCLogDebug("new transaction %p (state tx cnt %"PRIu64")", tx, state->tx_cnt);
322 return tx;
323 }
324
325 static void FTPTransactionFree(FTPTransaction *tx)
326 {
327 SCEnter();
328
329 if (tx->de_state != NULL) {
330 DetectEngineStateFree(tx->de_state);
331 }
332
333 if (tx->request) {
334 FTPFree(tx->request, tx->request_length);
335 }
336
337 FTPString *str = NULL;
338 while ((str = TAILQ_FIRST(&tx->response_list))) {
339 TAILQ_REMOVE(&tx->response_list, str, next);
340 FTPStringFree(str);
341 }
342
343 FTPFree(tx, sizeof(*tx));
344 }
345
346 static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
347 {
348 void *ptmp;
349 if (line_state->current_line_lf_seen == 1) {
350 /* we have seen the lf for the previous line. Clear the parser
351 * details to parse new line */
352 line_state->current_line_lf_seen = 0;
353 if (line_state->current_line_db == 1) {
354 line_state->current_line_db = 0;
355 FTPFree(line_state->db, line_state->db_len);
356 line_state->db = NULL;
357 line_state->db_len = 0;
358 state->current_line = NULL;
359 state->current_line_len = 0;
360 }
361 }
362
363 uint8_t *lf_idx = memchr(state->input, 0x0a, state->input_len);
364
365 if (lf_idx == NULL) {
366 /* fragmented lines. Decoder event for special cases. Not all
367 * fragmented lines should be treated as a possible evasion
368 * attempt. With multi payload ftp chunks we can have valid
369 * cases of fragmentation. But within the same segment chunk
370 * if we see fragmentation then it's definitely something you
371 * should alert about */
372 if (line_state->current_line_db == 0) {
373 line_state->db = FTPMalloc(state->input_len);
374 if (line_state->db == NULL) {
375 return -1;
376 }
377 line_state->current_line_db = 1;
378 memcpy(line_state->db, state->input, state->input_len);
379 line_state->db_len = state->input_len;
380 } else {
381 ptmp = FTPRealloc(line_state->db, line_state->db_len,
382 (line_state->db_len + state->input_len));
383 if (ptmp == NULL) {
384 FTPFree(line_state->db, line_state->db_len);
385 line_state->db = NULL;
386 line_state->db_len = 0;
387 return -1;
388 }
389 line_state->db = ptmp;
390
391 memcpy(line_state->db + line_state->db_len,
392 state->input, state->input_len);
393 line_state->db_len += state->input_len;
394 }
395 state->input += state->input_len;
396 state->input_len = 0;
397
398 return -1;
399
400 } else {
401 line_state->current_line_lf_seen = 1;
402
403 if (line_state->current_line_db == 1) {
404 ptmp = FTPRealloc(line_state->db, line_state->db_len,
405 (line_state->db_len + (lf_idx + 1 - state->input)));
406 if (ptmp == NULL) {
407 FTPFree(line_state->db, line_state->db_len);
408 line_state->db = NULL;
409 line_state->db_len = 0;
410 return -1;
411 }
412 line_state->db = ptmp;
413
414 memcpy(line_state->db + line_state->db_len,
415 state->input, (lf_idx + 1 - state->input));
416 line_state->db_len += (lf_idx + 1 - state->input);
417
418 if (line_state->db_len > 1 &&
419 line_state->db[line_state->db_len - 2] == 0x0D) {
420 line_state->db_len -= 2;
421 state->current_line_delimiter_len = 2;
422 } else {
423 line_state->db_len -= 1;
424 state->current_line_delimiter_len = 1;
425 }
426
427 state->current_line = line_state->db;
428 state->current_line_len = line_state->db_len;
429
430 } else {
431 state->current_line = state->input;
432 state->current_line_len = lf_idx - state->input;
433
434 if (state->input != lf_idx &&
435 *(lf_idx - 1) == 0x0D) {
436 state->current_line_len--;
437 state->current_line_delimiter_len = 2;
438 } else {
439 state->current_line_delimiter_len = 1;
440 }
441 }
442
443 state->input_len -= (lf_idx - state->input) + 1;
444 state->input = (lf_idx + 1);
445
446 return 0;
447 }
448
449 }
450
451 static int FTPGetLine(FtpState *state)
452 {
453 SCEnter();
454
455 /* we have run out of input */
456 if (state->input_len <= 0)
457 return -1;
458
459 /* toserver */
460 if (state->direction == 0)
461 return FTPGetLineForDirection(state, &state->line_state[0]);
462 else
463 return FTPGetLineForDirection(state, &state->line_state[1]);
464 }
465
466 /**
467 * \brief This function is called to determine and set which command is being
468 * transferred to the ftp server
469 * \param thread context
470 * \param input input line of the command
471 * \param len of the command
472 * \param cmd_descriptor when the command has been parsed
473 *
474 * \retval 1 when the command is parsed, 0 otherwise
475 */
476 static int FTPParseRequestCommand(FTPThreadCtx *td,
477 const uint8_t *input, uint32_t input_len,
478 const FtpCommand **cmd_descriptor)
479 {
480 SCEnter();
481
482 /* I don't like this pmq reset here. We'll devise a method later, that
483 * should make the use of the mpm very efficient */
484 PmqReset(td->pmq);
485 int mpm_cnt = mpm_table[FTP_MPM].Search(ftp_mpm_ctx, td->ftp_mpm_thread_ctx,
486 td->pmq, input, input_len);
487 if (mpm_cnt) {
488 *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]];
489 SCReturnInt(1);
490 }
491
492 *cmd_descriptor = NULL;
493 SCReturnInt(0);
494 }
495
496 struct FtpTransferCmd {
497 /** Need to look like a ExpectationData so DFree must
498 * be first field . */
499 void (*DFree)(void *);
500 uint64_t flow_id;
501 uint8_t *file_name;
502 uint16_t file_len;
503 FtpRequestCommand cmd;
504 };
505
506 static void FtpTransferCmdFree(void *data)
507 {
508 struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data;
509 if (cmd == NULL)
510 return;
511 if (cmd->file_name) {
512 FTPFree(cmd->file_name, cmd->file_len + 1);
513 }
514 FTPFree(cmd, sizeof(struct FtpTransferCmd));
515 }
516
517 static uint32_t CopyCommandLine(uint8_t **dest, const uint8_t *src, uint32_t length)
518 {
519 if (likely(length)) {
520 uint8_t *where = FTPCalloc(length + 1, sizeof(char));
521 if (unlikely(where == NULL)) {
522 return 0;
523 }
524 memcpy(where, src, length);
525
526 /* Remove trailing newlines/carriage returns */
527 while (length && isspace((unsigned char) where[length - 1])) {
528 length--;
529 }
530
531 where[length] = '\0';
532 *dest = where;
533 }
534 /* either 0 or actual */
535 return length ? length + 1 : 0;
536 }
537
538
539 /**
540 * \brief This function is called to retrieve a ftp request
541 * \param ftp_state the ftp state structure for the parser
542 * \param input input line of the command
543 * \param input_len length of the request
544 * \param output the resulting output
545 *
546 * \retval APP_LAYER_OK when input was process successfully
547 * \retval APP_LAYER_ERROR when a unrecoverable error was encountered
548 */
549 static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state,
550 AppLayerParserState *pstate,
551 const uint8_t *input, uint32_t input_len,
552 void *local_data, const uint8_t flags)
553 {
554 FTPThreadCtx *thread_data = local_data;
555
556 SCEnter();
557 /* PrintRawDataFp(stdout, input,input_len); */
558
559 FtpState *state = (FtpState *)ftp_state;
560 void *ptmp;
561
562 if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
563 SCReturnStruct(APP_LAYER_OK);
564 } else if (input == NULL || input_len == 0) {
565 SCReturnStruct(APP_LAYER_ERROR);
566 }
567
568 state->input = input;
569 state->input_len = input_len;
570 /* toserver stream */
571 state->direction = 0;
572
573 int direction = STREAM_TOSERVER;
574 while (FTPGetLine(state) >= 0) {
575 const FtpCommand *cmd_descriptor;
576
577 if (!FTPParseRequestCommand(thread_data,
578 state->current_line, state->current_line_len,
579 &cmd_descriptor)) {
580 state->command = FTP_COMMAND_UNKNOWN;
581 continue;
582 }
583
584 state->command = cmd_descriptor->command;
585
586 FTPTransaction *tx = FTPTransactionCreate(state);
587 if (unlikely(tx == NULL))
588 SCReturnStruct(APP_LAYER_ERROR);
589 state->curr_tx = tx;
590
591 tx->command_descriptor = cmd_descriptor;
592 tx->request_length = CopyCommandLine(&tx->request,
593 state->current_line, state->current_line_len);
594
595 /* change direction (default to server) so expectation will handle
596 * the correct message when expectation will match.
597 * For ftp active mode, data connection direction is opposite to
598 * control direction.
599 */
600 if ((state->active && state->command == FTP_COMMAND_STOR) ||
601 (!state->active && state->command == FTP_COMMAND_RETR)) {
602 direction = STREAM_TOCLIENT;
603 }
604
605 switch (state->command) {
606 case FTP_COMMAND_EPRT:
607 // fallthrough
608 case FTP_COMMAND_PORT:
609 if (state->current_line_len + 1 > state->port_line_size) {
610 /* Allocate an extra byte for a NULL terminator */
611 ptmp = FTPRealloc(state->port_line, state->port_line_size,
612 state->current_line_len);
613 if (ptmp == NULL) {
614 if (state->port_line) {
615 FTPFree(state->port_line, state->port_line_size);
616 state->port_line = NULL;
617 state->port_line_size = 0;
618 }
619 SCReturnStruct(APP_LAYER_OK);
620 }
621 state->port_line = ptmp;
622 state->port_line_size = state->current_line_len;
623 }
624 memcpy(state->port_line, state->current_line,
625 state->current_line_len);
626 state->port_line_len = state->current_line_len;
627 break;
628 case FTP_COMMAND_RETR:
629 // fallthrough
630 case FTP_COMMAND_STOR: {
631 /* Ensure that there is a negotiated dyn port and a file
632 * name -- need more than 5 chars: cmd [4], space, <filename>
633 */
634 if (state->dyn_port == 0 || state->current_line_len < 6) {
635 SCReturnStruct(APP_LAYER_ERROR);
636 }
637 struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd));
638 if (data == NULL)
639 SCReturnStruct(APP_LAYER_ERROR);
640 data->DFree = FtpTransferCmdFree;
641 /*
642 * Min size has been checked in FTPParseRequestCommand
643 * PATH_MAX includes the null
644 */
645 int file_name_len = MIN(PATH_MAX - 1, state->current_line_len - 5);
646 data->file_name = FTPCalloc(file_name_len + 1, sizeof(char));
647 if (data->file_name == NULL) {
648 FtpTransferCmdFree(data);
649 SCReturnStruct(APP_LAYER_ERROR);
650 }
651 data->file_name[file_name_len] = 0;
652 data->file_len = file_name_len;
653 memcpy(data->file_name, state->current_line + 5, file_name_len);
654 data->cmd = state->command;
655 data->flow_id = FlowGetId(f);
656 int ret = AppLayerExpectationCreate(f, direction,
657 0, state->dyn_port, ALPROTO_FTPDATA, data);
658 if (ret == -1) {
659 FtpTransferCmdFree(data);
660 SCLogDebug("No expectation created.");
661 SCReturnStruct(APP_LAYER_ERROR);
662 } else {
663 SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].",
664 state->active ? "to server" : "to client",
665 state->dyn_port);
666 }
667
668 /* reset the dyn port to avoid duplicate */
669 state->dyn_port = 0;
670 /* reset active/passive indicator */
671 state->active = false;
672 }
673 break;
674 default:
675 break;
676 }
677 }
678
679 SCReturnStruct(APP_LAYER_OK);
680 }
681
682 static int FTPParsePassiveResponse(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len)
683 {
684 uint16_t dyn_port = rs_ftp_pasv_response(input, input_len);
685 if (dyn_port == 0) {
686 return -1;
687 }
688 SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port);
689 state->active = false;
690 state->dyn_port = dyn_port;
691 state->curr_tx->dyn_port = dyn_port;
692 state->curr_tx->active = false;
693
694 return 0;
695 }
696
697 static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len)
698 {
699 uint16_t dyn_port = rs_ftp_epsv_response(input, input_len);
700 if (dyn_port == 0) {
701 return -1;
702 }
703 SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port);
704 state->active = false;
705 state->dyn_port = dyn_port;
706 state->curr_tx->dyn_port = dyn_port;
707 state->curr_tx->active = false;
708 return 0;
709 }
710
711 /**
712 * \brief Handle preliminary replies -- keep tx open
713 * \retval bool True for a positive preliminary reply; false otherwise
714 *
715 * 1yz Positive Preliminary reply
716 *
717 * The requested action is being initiated; expect another
718 * reply before proceeding with a new command
719 */
720 static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len)
721 {
722 return input_len >= 4 && isdigit(input[0]) && input[0] == '1' &&
723 isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]);
724 }
725
726 /**
727 * \brief This function is called to retrieve a ftp response
728 * \param ftp_state the ftp state structure for the parser
729 * \param input input line of the command
730 * \param input_len length of the request
731 * \param output the resulting output
732 *
733 * \retval 1 when the command is parsed, 0 otherwise
734 */
735 static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
736 const uint8_t *input, uint32_t input_len,
737 void *local_data, const uint8_t flags)
738 {
739 FtpState *state = (FtpState *)ftp_state;
740
741 if (unlikely(input_len == 0)) {
742 SCReturnStruct(APP_LAYER_OK);
743 }
744 state->input = input;
745 state->input_len = input_len;
746 /* toclient stream */
747 state->direction = 1;
748
749 while (FTPGetLine(state) >= 0) {
750 FTPTransaction *tx = FTPGetOldestTx(state);
751 if (tx == NULL) {
752 tx = FTPTransactionCreate(state);
753 }
754 if (unlikely(tx == NULL)) {
755 SCReturnStruct(APP_LAYER_ERROR);
756 }
757 if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) {
758 /* unknown */
759 tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX -1];
760 }
761
762 state->curr_tx = tx;
763 uint16_t dyn_port;
764 switch (state->command) {
765 case FTP_COMMAND_AUTH_TLS:
766 if (state->current_line_len >= 4 && SCMemcmp("234 ", state->current_line, 4) == 0) {
767 AppLayerRequestProtocolTLSUpgrade(f);
768 }
769 break;
770
771 case FTP_COMMAND_EPRT:
772 dyn_port = rs_ftp_active_eprt(state->port_line, state->port_line_len);
773 if (dyn_port == 0) {
774 goto tx_complete;
775 }
776 state->dyn_port = dyn_port;
777 state->active = true;
778 tx->dyn_port = dyn_port;
779 tx->active = true;
780 SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16"", dyn_port);
781 break;
782
783 case FTP_COMMAND_PORT:
784 dyn_port = rs_ftp_active_port(state->port_line, state->port_line_len);
785 if (dyn_port == 0) {
786 goto tx_complete;
787 }
788 state->dyn_port = dyn_port;
789 state->active = true;
790 tx->dyn_port = state->dyn_port;
791 tx->active = true;
792 SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16"", dyn_port);
793 break;
794
795 case FTP_COMMAND_PASV:
796 if (state->current_line_len >= 4 && SCMemcmp("227 ", state->current_line, 4) == 0) {
797 FTPParsePassiveResponse(f, ftp_state, state->current_line, state->current_line_len);
798 }
799 break;
800
801 case FTP_COMMAND_EPSV:
802 if (state->current_line_len >= 4 && SCMemcmp("229 ", state->current_line, 4) == 0) {
803 FTPParsePassiveResponseV6(f, ftp_state, state->current_line, state->current_line_len);
804 }
805 break;
806 default:
807 break;
808 }
809
810 if (likely(state->current_line_len)) {
811 FTPString *response = FTPStringAlloc();
812 if (likely(response)) {
813 response->len = CopyCommandLine(&response->str, state->current_line, state->current_line_len);
814 TAILQ_INSERT_TAIL(&tx->response_list, response, next);
815 }
816 }
817
818 /* Handle preliminary replies -- keep tx open */
819 if (FTPIsPPR(state->current_line, state->current_line_len)) {
820 continue;
821 }
822 tx_complete:
823 tx->done = true;
824 }
825
826 SCReturnStruct(APP_LAYER_OK);
827 }
828
829
830 #ifdef DEBUG
831 static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER;
832 static uint64_t ftp_state_memuse = 0;
833 static uint64_t ftp_state_memcnt = 0;
834 #endif
835
836 static void *FTPStateAlloc(void)
837 {
838 void *s = FTPCalloc(1, sizeof(FtpState));
839 if (unlikely(s == NULL))
840 return NULL;
841
842 FtpState *ftp_state = (FtpState *) s;
843 TAILQ_INIT(&ftp_state->tx_list);
844
845 #ifdef DEBUG
846 SCMutexLock(&ftp_state_mem_lock);
847 ftp_state_memcnt++;
848 ftp_state_memuse+=sizeof(FtpState);
849 SCMutexUnlock(&ftp_state_mem_lock);
850 #endif
851 return s;
852 }
853
854 static void FTPStateFree(void *s)
855 {
856 FtpState *fstate = (FtpState *) s;
857 if (fstate->port_line != NULL)
858 FTPFree(fstate->port_line, fstate->port_line_size);
859 if (fstate->line_state[0].db)
860 FTPFree(fstate->line_state[0].db, fstate->line_state[0].db_len);
861 if (fstate->line_state[1].db)
862 FTPFree(fstate->line_state[1].db, fstate->line_state[1].db_len);
863
864 //AppLayerDecoderEventsFreeEvents(&s->decoder_events);
865
866 FTPTransaction *tx = NULL;
867 while ((tx = TAILQ_FIRST(&fstate->tx_list))) {
868 TAILQ_REMOVE(&fstate->tx_list, tx, next);
869 SCLogDebug("[%s] state %p id %"PRIu64", Freeing %d bytes at %p",
870 tx->command_descriptor->command_name,
871 s, tx->tx_id,
872 tx->request_length, tx->request);
873 FTPTransactionFree(tx);
874 }
875
876 FTPFree(s, sizeof(FtpState));
877 #ifdef DEBUG
878 SCMutexLock(&ftp_state_mem_lock);
879 ftp_state_memcnt--;
880 ftp_state_memuse-=sizeof(FtpState);
881 SCMutexUnlock(&ftp_state_mem_lock);
882 #endif
883 }
884
885 static int FTPSetTxDetectState(void *vtx, DetectEngineState *de_state)
886 {
887 FTPTransaction *tx = (FTPTransaction *)vtx;
888 tx->de_state = de_state;
889 return 0;
890 }
891
892 /**
893 * \brief This function returns the oldest open transaction; if none
894 * are open, then the oldest transaction is returned
895 * \param ftp_state the ftp state structure for the parser
896 *
897 * \retval transaction pointer when a transaction was found; NULL otherwise.
898 */
899 static FTPTransaction *FTPGetOldestTx(FtpState *ftp_state)
900 {
901 if (unlikely(!ftp_state)) {
902 SCLogDebug("NULL state object; no transactions available");
903 return NULL;
904 }
905 FTPTransaction *tx = NULL;
906 FTPTransaction *lasttx = NULL;
907 TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
908 /* Return oldest open tx */
909 if (!tx->done) {
910 SCLogDebug("Returning tx %p id %"PRIu64, tx, tx->tx_id);
911 return tx;
912 }
913 /* save for the end */
914 lasttx = tx;
915 }
916 /* All tx are closed; return last element */
917 if (lasttx)
918 SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id);
919 return lasttx;
920 }
921
922 static void *FTPGetTx(void *state, uint64_t tx_id)
923 {
924 FtpState *ftp_state = (FtpState *)state;
925 if (ftp_state) {
926 FTPTransaction *tx = NULL;
927
928 if (ftp_state->curr_tx == NULL)
929 return NULL;
930 if (ftp_state->curr_tx->tx_id == tx_id)
931 return ftp_state->curr_tx;
932
933 TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
934 if (tx->tx_id == tx_id)
935 return tx;
936 }
937 }
938 return NULL;
939 }
940
941 static DetectEngineState *FTPGetTxDetectState(void *vtx)
942 {
943 FTPTransaction *tx = (FTPTransaction *)vtx;
944 return tx->de_state;
945 }
946
947
948 static AppLayerTxData *FTPGetTxData(void *vtx)
949 {
950 FTPTransaction *tx = (FTPTransaction *)vtx;
951 return &tx->tx_data;
952 }
953
954 static void FTPStateTransactionFree(void *state, uint64_t tx_id)
955 {
956 FtpState *ftp_state = state;
957 FTPTransaction *tx = NULL;
958 TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
959 if (tx_id < tx->tx_id)
960 break;
961 else if (tx_id > tx->tx_id)
962 continue;
963
964 if (tx == ftp_state->curr_tx)
965 ftp_state->curr_tx = NULL;
966 TAILQ_REMOVE(&ftp_state->tx_list, tx, next);
967 FTPTransactionFree(tx);
968 break;
969 }
970 }
971
972 static uint64_t FTPGetTxCnt(void *state)
973 {
974 uint64_t cnt = 0;
975 FtpState *ftp_state = state;
976 if (ftp_state) {
977 cnt = ftp_state->tx_cnt;
978 }
979 SCLogDebug("returning state %p %"PRIu64, state, cnt);
980 return cnt;
981 }
982
983 static int FTPGetAlstateProgressCompletionStatus(uint8_t direction)
984 {
985 return FTP_STATE_FINISHED;
986 }
987
988 static int FTPGetAlstateProgress(void *vtx, uint8_t direction)
989 {
990 SCLogDebug("tx %p", vtx);
991 FTPTransaction *tx = vtx;
992
993 if (!tx->done) {
994 if (direction == STREAM_TOSERVER &&
995 tx->command_descriptor->command == FTP_COMMAND_PORT) {
996 return FTP_STATE_PORT_DONE;
997 }
998 return FTP_STATE_IN_PROGRESS;
999 }
1000
1001 return FTP_STATE_FINISHED;
1002 }
1003
1004
1005 static int FTPRegisterPatternsForProtocolDetection(void)
1006 {
1007 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
1008 "220 (", 5, 0, STREAM_TOCLIENT) < 0)
1009 {
1010 return -1;
1011 }
1012 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
1013 "FEAT", 4, 0, STREAM_TOSERVER) < 0)
1014 {
1015 return -1;
1016 }
1017 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
1018 "USER ", 5, 0, STREAM_TOSERVER) < 0)
1019 {
1020 return -1;
1021 }
1022 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
1023 "PASS ", 5, 0, STREAM_TOSERVER) < 0)
1024 {
1025 return -1;
1026 }
1027 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
1028 "PORT ", 5, 0, STREAM_TOSERVER) < 0)
1029 {
1030 return -1;
1031 }
1032
1033 return 0;
1034 }
1035
1036
1037 static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
1038
1039 /**
1040 * \brief This function is called to retrieve a ftp request
1041 * \param ftp_state the ftp state structure for the parser
1042 * \param input input line of the command
1043 * \param input_len length of the request
1044 * \param output the resulting output
1045 *
1046 * \retval 1 when the command is parsed, 0 otherwise
1047 */
1048 static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
1049 AppLayerParserState *pstate,
1050 const uint8_t *input, uint32_t input_len,
1051 void *local_data, int direction)
1052 {
1053 uint16_t flags = FileFlowToFlags(f, direction);
1054 int ret = 0;
1055 /* we depend on detection engine for file pruning */
1056 flags |= FILE_USE_DETECT;
1057 if (ftpdata_state->files == NULL) {
1058 struct FtpTransferCmd *data = (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetDataId());
1059 if (data == NULL) {
1060 SCReturnStruct(APP_LAYER_ERROR);
1061 }
1062
1063 ftpdata_state->files = FileContainerAlloc();
1064 if (ftpdata_state->files == NULL) {
1065 FlowFreeStorageById(f, AppLayerExpectationGetDataId());
1066 SCReturnStruct(APP_LAYER_ERROR);
1067 }
1068
1069 ftpdata_state->file_name = data->file_name;
1070 ftpdata_state->file_len = data->file_len;
1071 data->file_name = NULL;
1072 data->file_len = 0;
1073 f->parent_id = data->flow_id;
1074 ftpdata_state->command = data->cmd;
1075 switch (data->cmd) {
1076 case FTP_COMMAND_STOR:
1077 ftpdata_state->direction = STREAM_TOSERVER;
1078 break;
1079 case FTP_COMMAND_RETR:
1080 ftpdata_state->direction = STREAM_TOCLIENT;
1081 break;
1082 default:
1083 break;
1084 }
1085
1086 /* open with fixed track_id 0 as we can have just one
1087 * file per ftp-data flow. */
1088 if (FileOpenFileWithId(ftpdata_state->files, &sbcfg,
1089 0ULL, (uint8_t *) ftpdata_state->file_name,
1090 ftpdata_state->file_len,
1091 input, input_len, flags) != 0) {
1092 SCLogDebug("Can't open file");
1093 ret = -1;
1094 }
1095 FlowFreeStorageById(f, AppLayerExpectationGetDataId());
1096 } else {
1097 if (input_len != 0) {
1098 ret = FileAppendData(ftpdata_state->files, input, input_len);
1099 if (ret == -2) {
1100 ret = 0;
1101 SCLogDebug("FileAppendData() - file no longer being extracted");
1102 goto out;
1103 } else if (ret < 0) {
1104 SCLogDebug("FileAppendData() failed: %d", ret);
1105 ret = -2;
1106 goto out;
1107 }
1108 } else {
1109 ret = FileCloseFile(ftpdata_state->files, NULL, 0, flags);
1110 ftpdata_state->state = FTPDATA_STATE_FINISHED;
1111 if (ret < 0)
1112 goto out;
1113 }
1114 }
1115
1116 if (input_len && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
1117 ret = FileCloseFile(ftpdata_state->files, (uint8_t *) NULL, 0, flags);
1118 ftpdata_state->state = FTPDATA_STATE_FINISHED;
1119 }
1120
1121 out:
1122 if (ret < 0) {
1123 SCReturnStruct(APP_LAYER_ERROR);
1124 }
1125 SCReturnStruct(APP_LAYER_OK);
1126 }
1127
1128 static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state,
1129 AppLayerParserState *pstate,
1130 const uint8_t *input, uint32_t input_len,
1131 void *local_data, const uint8_t flags)
1132 {
1133 return FTPDataParse(f, ftp_state, pstate, input, input_len,
1134 local_data, STREAM_TOSERVER);
1135 }
1136
1137 static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state,
1138 AppLayerParserState *pstate,
1139 const uint8_t *input, uint32_t input_len,
1140 void *local_data, const uint8_t flags)
1141 {
1142 return FTPDataParse(f, ftp_state, pstate, input, input_len,
1143 local_data, STREAM_TOCLIENT);
1144 }
1145
1146 #ifdef DEBUG
1147 static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER;
1148 static uint64_t ftpdata_state_memuse = 0;
1149 static uint64_t ftpdata_state_memcnt = 0;
1150 #endif
1151
1152 static void *FTPDataStateAlloc(void)
1153 {
1154 void *s = FTPCalloc(1, sizeof(FtpDataState));
1155 if (unlikely(s == NULL))
1156 return NULL;
1157
1158 FtpDataState *state = (FtpDataState *) s;
1159 state->state = FTPDATA_STATE_IN_PROGRESS;
1160
1161 #ifdef DEBUG
1162 SCMutexLock(&ftpdata_state_mem_lock);
1163 ftpdata_state_memcnt++;
1164 ftpdata_state_memuse+=sizeof(FtpDataState);
1165 SCMutexUnlock(&ftpdata_state_mem_lock);
1166 #endif
1167 return s;
1168 }
1169
1170 static void FTPDataStateFree(void *s)
1171 {
1172 FtpDataState *fstate = (FtpDataState *) s;
1173
1174 if (fstate->de_state != NULL) {
1175 DetectEngineStateFree(fstate->de_state);
1176 }
1177 if (fstate->file_name != NULL) {
1178 FTPFree(fstate->file_name, fstate->file_len + 1);
1179 }
1180
1181 FileContainerFree(fstate->files);
1182
1183 FTPFree(s, sizeof(FtpDataState));
1184 #ifdef DEBUG
1185 SCMutexLock(&ftpdata_state_mem_lock);
1186 ftpdata_state_memcnt--;
1187 ftpdata_state_memuse-=sizeof(FtpDataState);
1188 SCMutexUnlock(&ftpdata_state_mem_lock);
1189 #endif
1190 }
1191
1192 static int FTPDataSetTxDetectState(void *vtx, DetectEngineState *de_state)
1193 {
1194 FtpDataState *ftp_state = (FtpDataState *)vtx;
1195 ftp_state->de_state = de_state;
1196 return 0;
1197 }
1198
1199 static DetectEngineState *FTPDataGetTxDetectState(void *vtx)
1200 {
1201 FtpDataState *ftp_state = (FtpDataState *)vtx;
1202 return ftp_state->de_state;
1203 }
1204
1205 static AppLayerTxData *FTPDataGetTxData(void *vtx)
1206 {
1207 FtpDataState *ftp_state = (FtpDataState *)vtx;
1208 return &ftp_state->tx_data;
1209 }
1210
1211 static void FTPDataStateTransactionFree(void *state, uint64_t tx_id)
1212 {
1213 /* do nothing */
1214 }
1215
1216 static void *FTPDataGetTx(void *state, uint64_t tx_id)
1217 {
1218 FtpDataState *ftp_state = (FtpDataState *)state;
1219 return ftp_state;
1220 }
1221
1222 static uint64_t FTPDataGetTxCnt(void *state)
1223 {
1224 /* ftp-data is single tx */
1225 return 1;
1226 }
1227
1228 static int FTPDataGetAlstateProgressCompletionStatus(uint8_t direction)
1229 {
1230 return FTPDATA_STATE_FINISHED;
1231 }
1232
1233 static int FTPDataGetAlstateProgress(void *tx, uint8_t direction)
1234 {
1235 FtpDataState *ftpdata_state = (FtpDataState *)tx;
1236 return ftpdata_state->state;
1237 }
1238
1239 static FileContainer *FTPDataStateGetFiles(void *state, uint8_t direction)
1240 {
1241 FtpDataState *ftpdata_state = (FtpDataState *)state;
1242
1243 if (direction != ftpdata_state->direction)
1244 SCReturnPtr(NULL, "FileContainer");
1245
1246 SCReturnPtr(ftpdata_state->files, "FileContainer");
1247 }
1248
1249 static void FTPSetMpmState(void)
1250 {
1251 ftp_mpm_ctx = SCMalloc(sizeof(MpmCtx));
1252 if (unlikely(ftp_mpm_ctx == NULL)) {
1253 exit(EXIT_FAILURE);
1254 }
1255 memset(ftp_mpm_ctx, 0, sizeof(MpmCtx));
1256 MpmInitCtx(ftp_mpm_ctx, FTP_MPM);
1257
1258 uint32_t i = 0;
1259 for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) {
1260 const FtpCommand *cmd = &FtpCommands[i];
1261 if (cmd->command_length == 0)
1262 continue;
1263
1264 MpmAddPatternCI(ftp_mpm_ctx,
1265 (uint8_t *)cmd->command_name,
1266 cmd->command_length,
1267 0 /* defunct */, 0 /* defunct */,
1268 i /* id */, i /* rule id */ , 0 /* no flags */);
1269 }
1270
1271 mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx);
1272
1273 }
1274
1275 static void FTPFreeMpmState(void)
1276 {
1277 if (ftp_mpm_ctx != NULL) {
1278 mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx);
1279 SCFree(ftp_mpm_ctx);
1280 ftp_mpm_ctx = NULL;
1281 }
1282 }
1283
1284 void RegisterFTPParsers(void)
1285 {
1286 const char *proto_name = "ftp";
1287 const char *proto_data_name = "ftp-data";
1288
1289 /** FTP */
1290 if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
1291 AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name);
1292 if (FTPRegisterPatternsForProtocolDetection() < 0 )
1293 return;
1294 AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name);
1295 }
1296
1297 if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
1298 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER,
1299 FTPParseRequest);
1300 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT,
1301 FTPParseResponse);
1302 AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
1303 AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT);
1304
1305 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree);
1306
1307 AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_FTP,
1308 FTPGetTxDetectState, FTPSetTxDetectState);
1309
1310 AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx);
1311 AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxData);
1312
1313 AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc,
1314 FTPLocalStorageFree);
1315 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt);
1316
1317 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress);
1318
1319 AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_FTP,
1320 FTPGetAlstateProgressCompletionStatus);
1321
1322
1323 AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA);
1324 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER,
1325 FTPDataParseRequest);
1326 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT,
1327 FTPDataParseResponse);
1328 AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree);
1329 AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT);
1330 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree);
1331 AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA,
1332 FTPDataGetTxDetectState, FTPDataSetTxDetectState);
1333
1334 AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetFiles);
1335
1336 AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx);
1337 AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxData);
1338
1339 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt);
1340
1341 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress);
1342
1343 AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_FTPDATA,
1344 FTPDataGetAlstateProgressCompletionStatus);
1345
1346 sbcfg.buf_size = 4096;
1347 sbcfg.Malloc = FTPMalloc;
1348 sbcfg.Calloc = FTPCalloc;
1349 sbcfg.Realloc = FTPRealloc;
1350 sbcfg.Free = FTPFree;
1351
1352 FTPParseMemcap();
1353 } else {
1354 SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
1355 "still on.", proto_name);
1356 }
1357
1358 FTPSetMpmState();
1359
1360 #ifdef UNITTESTS
1361 AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests);
1362 #endif
1363 }
1364
1365 void FTPAtExitPrintStats(void)
1366 {
1367 #ifdef DEBUG
1368 SCMutexLock(&ftp_state_mem_lock);
1369 SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"",
1370 ftp_state_memcnt, ftp_state_memuse);
1371 SCMutexUnlock(&ftp_state_mem_lock);
1372 #endif
1373 }
1374
1375
1376 /*
1377 * \brief Returns the ending offset of the next line from a multi-line buffer.
1378 *
1379 * "Buffer" refers to a FTP response in a single buffer containing multiple lines.
1380 * Here, "next line" is defined as terminating on
1381 * - Newline character
1382 * - Null character
1383 *
1384 * \param buffer Contains zero or more characters.
1385 * \param len Size, in bytes, of buffer.
1386 *
1387 * \retval Offset from the start of buffer indicating the where the
1388 * next "line ends". The characters between the input buffer and this
1389 * value comprise the line.
1390 *
1391 * NULL is found first or a newline isn't found, then UINT16_MAX is returned.
1392 */
1393 uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len)
1394 {
1395 if (!buffer || *buffer == '\0') {
1396 return UINT16_MAX;
1397 }
1398
1399 char *c = strchr(buffer, '\n');
1400 return c == NULL ? len : c - buffer + 1;
1401 }
1402
1403 void EveFTPDataAddMetadata(const Flow *f, JsonBuilder *jb)
1404 {
1405 const FtpDataState *ftp_state = NULL;
1406 if (f->alstate == NULL)
1407 return;
1408
1409 ftp_state = (FtpDataState *)f->alstate;
1410
1411 if (ftp_state->file_name) {
1412 jb_set_string_from_bytes(jb, "filename", ftp_state->file_name, ftp_state->file_len);
1413 }
1414 switch (ftp_state->command) {
1415 case FTP_COMMAND_STOR:
1416 JB_SET_STRING(jb, "command", "STOR");
1417 break;
1418 case FTP_COMMAND_RETR:
1419 JB_SET_STRING(jb, "command", "RETR");
1420 break;
1421 default:
1422 break;
1423 }
1424 }
1425
1426 /**
1427 * \brief Free memory allocated for global FTP parser state.
1428 */
1429 void FTPParserCleanup(void)
1430 {
1431 FTPFreeMpmState();
1432 }
1433
1434 /* UNITTESTS */
1435 #ifdef UNITTESTS
1436
1437 /** \test Send a get request in one chunk. */
1438 static int FTPParserTest01(void)
1439 {
1440 int result = 1;
1441 Flow f;
1442 uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n";
1443 uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */
1444 TcpSession ssn;
1445 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1446
1447 memset(&f, 0, sizeof(f));
1448 memset(&ssn, 0, sizeof(ssn));
1449
1450 FLOW_INITIALIZE(&f);
1451 f.protoctx = (void *)&ssn;
1452 f.proto = IPPROTO_TCP;
1453 f.alproto = ALPROTO_FTP;
1454
1455 StreamTcpInitConfig(TRUE);
1456
1457 FLOWLOCK_WRLOCK(&f);
1458 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1459 STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen);
1460 if (r != 0) {
1461 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
1462 result = 0;
1463 FLOWLOCK_UNLOCK(&f);
1464 goto end;
1465 }
1466 FLOWLOCK_UNLOCK(&f);
1467
1468 FtpState *ftp_state = f.alstate;
1469 if (ftp_state == NULL) {
1470 SCLogDebug("no ftp state: ");
1471 result = 0;
1472 goto end;
1473 }
1474
1475 if (ftp_state->command != FTP_COMMAND_PORT) {
1476 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
1477 result = 0;
1478 goto end;
1479 }
1480
1481 end:
1482 if (alp_tctx != NULL)
1483 AppLayerParserThreadCtxFree(alp_tctx);
1484 StreamTcpFreeConfig(TRUE);
1485 FLOW_DESTROY(&f);
1486 return result;
1487 }
1488
1489 /** \test Send a split get request. */
1490 static int FTPParserTest03(void)
1491 {
1492 int result = 1;
1493 Flow f;
1494 uint8_t ftpbuf1[] = "POR";
1495 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1496 uint8_t ftpbuf2[] = "T 192,168,1";
1497 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
1498 uint8_t ftpbuf3[] = "1,1,10,20\r\n";
1499 uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */
1500 TcpSession ssn;
1501 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1502
1503 memset(&f, 0, sizeof(f));
1504 memset(&ssn, 0, sizeof(ssn));
1505
1506 FLOW_INITIALIZE(&f);
1507 f.protoctx = (void *)&ssn;
1508 f.proto = IPPROTO_TCP;
1509 f.alproto = ALPROTO_FTP;
1510
1511 StreamTcpInitConfig(TRUE);
1512
1513 FLOWLOCK_WRLOCK(&f);
1514 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1515 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1516 ftplen1);
1517 if (r != 0) {
1518 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
1519 result = 0;
1520 FLOWLOCK_UNLOCK(&f);
1521 goto end;
1522 }
1523 FLOWLOCK_UNLOCK(&f);
1524
1525 FLOWLOCK_WRLOCK(&f);
1526 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER,
1527 ftpbuf2, ftplen2);
1528 if (r != 0) {
1529 SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
1530 result = 0;
1531 FLOWLOCK_UNLOCK(&f);
1532 goto end;
1533 }
1534 FLOWLOCK_UNLOCK(&f);
1535
1536 FLOWLOCK_WRLOCK(&f);
1537 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1538 STREAM_TOSERVER | STREAM_EOF, ftpbuf3, ftplen3);
1539 if (r != 0) {
1540 SCLogDebug("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
1541 result = 0;
1542 FLOWLOCK_UNLOCK(&f);
1543 goto end;
1544 }
1545 FLOWLOCK_UNLOCK(&f);
1546
1547 FtpState *ftp_state = f.alstate;
1548 if (ftp_state == NULL) {
1549 SCLogDebug("no ftp state: ");
1550 result = 0;
1551 goto end;
1552 }
1553
1554 if (ftp_state->command != FTP_COMMAND_PORT) {
1555 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
1556 result = 0;
1557 goto end;
1558 }
1559
1560 end:
1561 if (alp_tctx != NULL)
1562 AppLayerParserThreadCtxFree(alp_tctx);
1563 StreamTcpFreeConfig(TRUE);
1564 return result;
1565 }
1566
1567 /** \test See how it deals with an incomplete request. */
1568 static int FTPParserTest06(void)
1569 {
1570 int result = 1;
1571 Flow f;
1572 uint8_t ftpbuf1[] = "PORT";
1573 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1574 TcpSession ssn;
1575 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1576
1577 memset(&f, 0, sizeof(f));
1578 memset(&ssn, 0, sizeof(ssn));
1579
1580 FLOW_INITIALIZE(&f);
1581 f.protoctx = (void *)&ssn;
1582 f.proto = IPPROTO_TCP;
1583 f.alproto = ALPROTO_FTP;
1584
1585 StreamTcpInitConfig(TRUE);
1586
1587 FLOWLOCK_WRLOCK(&f);
1588 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1589 STREAM_TOSERVER | STREAM_START | STREAM_EOF,
1590 ftpbuf1,
1591 ftplen1);
1592 if (r != 0) {
1593 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
1594 result = 0;
1595 FLOWLOCK_UNLOCK(&f);
1596 goto end;
1597 }
1598 FLOWLOCK_UNLOCK(&f);
1599
1600 FtpState *ftp_state = f.alstate;
1601 if (ftp_state == NULL) {
1602 SCLogDebug("no ftp state: ");
1603 result = 0;
1604 goto end;
1605 }
1606
1607 if (ftp_state->command != FTP_COMMAND_UNKNOWN) {
1608 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command);
1609 result = 0;
1610 goto end;
1611 }
1612
1613 end:
1614 if (alp_tctx != NULL)
1615 AppLayerParserThreadCtxFree(alp_tctx);
1616 StreamTcpFreeConfig(TRUE);
1617 FLOW_DESTROY(&f);
1618 return result;
1619 }
1620
1621 /** \test See how it deals with an incomplete request in multiple chunks. */
1622 static int FTPParserTest07(void)
1623 {
1624 int result = 1;
1625 Flow f;
1626 uint8_t ftpbuf1[] = "PO";
1627 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1628 uint8_t ftpbuf2[] = "RT\r\n";
1629 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
1630 TcpSession ssn;
1631 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1632
1633 memset(&f, 0, sizeof(f));
1634 memset(&ssn, 0, sizeof(ssn));
1635
1636 FLOW_INITIALIZE(&f);
1637 f.protoctx = (void *)&ssn;
1638 f.proto = IPPROTO_TCP;
1639 f.alproto = ALPROTO_FTP;
1640
1641 StreamTcpInitConfig(TRUE);
1642
1643 FLOWLOCK_WRLOCK(&f);
1644 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1645 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1646 ftplen1);
1647 if (r != 0) {
1648 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
1649 result = 0;
1650 FLOWLOCK_UNLOCK(&f);
1651 goto end;
1652 }
1653 FLOWLOCK_UNLOCK(&f);
1654
1655 FLOWLOCK_WRLOCK(&f);
1656 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1657 STREAM_TOSERVER | STREAM_EOF, ftpbuf2, ftplen2);
1658 if (r != 0) {
1659 SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
1660 result = 0;
1661 FLOWLOCK_UNLOCK(&f);
1662 goto end;
1663 }
1664 FLOWLOCK_UNLOCK(&f);
1665
1666 FtpState *ftp_state = f.alstate;
1667 if (ftp_state == NULL) {
1668 SCLogDebug("no ftp state: ");
1669 result = 0;
1670 goto end;
1671 }
1672
1673 if (ftp_state->command != FTP_COMMAND_PORT) {
1674 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ",
1675 FTP_COMMAND_PORT, ftp_state->command);
1676 result = 0;
1677 goto end;
1678 }
1679
1680 end:
1681 if (alp_tctx != NULL)
1682 AppLayerParserThreadCtxFree(alp_tctx);
1683 StreamTcpFreeConfig(TRUE);
1684 FLOW_DESTROY(&f);
1685 return result;
1686 }
1687
1688 /** \test Test case where chunks are smaller than the delim length and the
1689 * last chunk is supposed to match the delim. */
1690 static int FTPParserTest10(void)
1691 {
1692 int result = 1;
1693 Flow f;
1694 uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n";
1695 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1696 TcpSession ssn;
1697 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1698 int r = 0;
1699 memset(&f, 0, sizeof(f));
1700 memset(&ssn, 0, sizeof(ssn));
1701
1702 FLOW_INITIALIZE(&f);
1703 f.protoctx = (void *)&ssn;
1704 f.proto = IPPROTO_TCP;
1705 f.alproto = ALPROTO_FTP;
1706
1707 StreamTcpInitConfig(TRUE);
1708
1709 uint32_t u;
1710 for (u = 0; u < ftplen1; u++) {
1711 uint8_t flags = 0;
1712
1713 if (u == 0) flags = STREAM_TOSERVER|STREAM_START;
1714 else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF;
1715 else flags = STREAM_TOSERVER;
1716
1717 FLOWLOCK_WRLOCK(&f);
1718 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, flags,
1719 &ftpbuf1[u], 1);
1720 if (r != 0) {
1721 SCLogDebug("toserver chunk %" PRIu32 " returned %" PRId32 ", expected 0: ", u, r);
1722 result = 0;
1723 FLOWLOCK_UNLOCK(&f);
1724 goto end;
1725 }
1726 FLOWLOCK_UNLOCK(&f);
1727 }
1728
1729 FtpState *ftp_state = f.alstate;
1730 if (ftp_state == NULL) {
1731 SCLogDebug("no ftp state: ");
1732 result = 0;
1733 goto end;
1734 }
1735
1736 if (ftp_state->command != FTP_COMMAND_PORT) {
1737 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
1738 result = 0;
1739 goto end;
1740 }
1741
1742 end:
1743 if (alp_tctx != NULL)
1744 AppLayerParserThreadCtxFree(alp_tctx);
1745 StreamTcpFreeConfig(TRUE);
1746 FLOW_DESTROY(&f);
1747 return result;
1748 }
1749
1750 /** \test Supply RETR without a filename */
1751 static int FTPParserTest11(void)
1752 {
1753 int result = 1;
1754 Flow f;
1755 uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1756 uint8_t ftpbuf2[] = "RETR\r\n";
1757 uint8_t ftpbuf3[] = "227 OK\r\n";
1758 TcpSession ssn;
1759
1760 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1761
1762 memset(&f, 0, sizeof(f));
1763 memset(&ssn, 0, sizeof(ssn));
1764
1765 FLOW_INITIALIZE(&f);
1766 f.protoctx = (void *)&ssn;
1767 f.proto = IPPROTO_TCP;
1768 f.alproto = ALPROTO_FTP;
1769
1770 StreamTcpInitConfig(TRUE);
1771
1772 FLOWLOCK_WRLOCK(&f);
1773 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1774 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1775 sizeof(ftpbuf1) - 1);
1776 if (r != 0) {
1777 result = 0;
1778 FLOWLOCK_UNLOCK(&f);
1779 goto end;
1780 }
1781 FLOWLOCK_UNLOCK(&f);
1782
1783 /* Response */
1784 FLOWLOCK_WRLOCK(&f);
1785 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1786 STREAM_TOCLIENT,
1787 ftpbuf3,
1788 sizeof(ftpbuf3) - 1);
1789 if (r != 0) {
1790 result = 0;
1791 FLOWLOCK_UNLOCK(&f);
1792 goto end;
1793 }
1794 FLOWLOCK_UNLOCK(&f);
1795
1796 FLOWLOCK_WRLOCK(&f);
1797 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1798 STREAM_TOSERVER, ftpbuf2,
1799 sizeof(ftpbuf2) - 1);
1800 if (r == 0) {
1801 SCLogDebug("parse should've failed");
1802 result = 0;
1803 FLOWLOCK_UNLOCK(&f);
1804 goto end;
1805 }
1806 FLOWLOCK_UNLOCK(&f);
1807
1808 FtpState *ftp_state = f.alstate;
1809 if (ftp_state == NULL) {
1810 SCLogDebug("no ftp state: ");
1811 result = 0;
1812 goto end;
1813 }
1814
1815 if (ftp_state->command != FTP_COMMAND_RETR) {
1816 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ",
1817 FTP_COMMAND_RETR, ftp_state->command);
1818 result = 0;
1819 goto end;
1820 }
1821
1822 end:
1823 if (alp_tctx != NULL)
1824 AppLayerParserThreadCtxFree(alp_tctx);
1825 StreamTcpFreeConfig(TRUE);
1826 FLOW_DESTROY(&f);
1827 return result;
1828 }
1829
1830 /** \test Supply STOR without a filename */
1831 static int FTPParserTest12(void)
1832 {
1833 int result = 1;
1834 Flow f;
1835 uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1836 uint8_t ftpbuf2[] = "STOR\r\n";
1837 uint8_t ftpbuf3[] = "227 OK\r\n";
1838 TcpSession ssn;
1839
1840 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1841
1842 memset(&f, 0, sizeof(f));
1843 memset(&ssn, 0, sizeof(ssn));
1844
1845 FLOW_INITIALIZE(&f);
1846 f.protoctx = (void *)&ssn;
1847 f.proto = IPPROTO_TCP;
1848 f.alproto = ALPROTO_FTP;
1849
1850 StreamTcpInitConfig(TRUE);
1851
1852 FLOWLOCK_WRLOCK(&f);
1853 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1854 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1855 sizeof(ftpbuf1) - 1);
1856 if (r != 0) {
1857 result = 0;
1858 FLOWLOCK_UNLOCK(&f);
1859 goto end;
1860 }
1861 FLOWLOCK_UNLOCK(&f);
1862
1863 /* Response */
1864 FLOWLOCK_WRLOCK(&f);
1865 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1866 STREAM_TOCLIENT,
1867 ftpbuf3,
1868 sizeof(ftpbuf3) - 1);
1869 if (r != 0) {
1870 result = 0;
1871 FLOWLOCK_UNLOCK(&f);
1872 goto end;
1873 }
1874 FLOWLOCK_UNLOCK(&f);
1875
1876 FLOWLOCK_WRLOCK(&f);
1877 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1878 STREAM_TOSERVER, ftpbuf2,
1879 sizeof(ftpbuf2) - 1);
1880 if (r == 0) {
1881 SCLogDebug("parse should've failed");
1882 result = 0;
1883 FLOWLOCK_UNLOCK(&f);
1884 goto end;
1885 }
1886 FLOWLOCK_UNLOCK(&f);
1887
1888 FtpState *ftp_state = f.alstate;
1889 if (ftp_state == NULL) {
1890 SCLogDebug("no ftp state: ");
1891 result = 0;
1892 goto end;
1893 }
1894
1895 if (ftp_state->command != FTP_COMMAND_STOR) {
1896 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ",
1897 FTP_COMMAND_STOR, ftp_state->command);
1898 result = 0;
1899 goto end;
1900 }
1901
1902 end:
1903 if (alp_tctx != NULL)
1904 AppLayerParserThreadCtxFree(alp_tctx);
1905 StreamTcpFreeConfig(TRUE);
1906 FLOW_DESTROY(&f);
1907 return result;
1908 }
1909 #endif /* UNITTESTS */
1910
1911 void FTPParserRegisterTests(void)
1912 {
1913 #ifdef UNITTESTS
1914 UtRegisterTest("FTPParserTest01", FTPParserTest01);
1915 UtRegisterTest("FTPParserTest03", FTPParserTest03);
1916 UtRegisterTest("FTPParserTest06", FTPParserTest06);
1917 UtRegisterTest("FTPParserTest07", FTPParserTest07);
1918 UtRegisterTest("FTPParserTest10", FTPParserTest10);
1919 UtRegisterTest("FTPParserTest11", FTPParserTest11);
1920 UtRegisterTest("FTPParserTest12", FTPParserTest12);
1921 #endif /* UNITTESTS */
1922 }
1923