]> git.ipfire.org Git - people/ms/suricata.git/blame - src/app-layer-ftp.c
app-layer: include decoder events in app-layer tx data
[people/ms/suricata.git] / src / app-layer-ftp.c
CommitLineData
03de315b 1/* Copyright (C) 2007-2020 Open Information Security Foundation
ce019275
WM
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.
f2f9b832 6 *
ce019275
WM
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/**
f2f9b832 19 * \file
ce019275 20 *
f2f9b832 21 * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
b0a69344 22 * \author Eric Leblond <eric@regit.org>
1930b1f5 23 * \author Jeff Lucovsky <jeff@lucovsky.org>
f2f9b832
PR
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
cc76aa4b 36#include "flow-util.h"
b0a69344 37#include "flow-storage.h"
cc76aa4b 38
0e4235cc
WM
39#include "detect-engine-state.h"
40
f2f9b832
PR
41#include "stream-tcp-private.h"
42#include "stream-tcp-reassemble.h"
6a53ab9c 43#include "stream-tcp.h"
f2f9b832
PR
44#include "stream.h"
45
429c6388 46#include "app-layer.h"
f2f9b832
PR
47#include "app-layer-protos.h"
48#include "app-layer-parser.h"
49#include "app-layer-ftp.h"
b0a69344 50#include "app-layer-expectation.h"
f2f9b832 51
705471e4 52#include "util-spm.h"
09ab032a 53#include "util-mpm.h"
f2f9b832
PR
54#include "util-unittest.h"
55#include "util-debug.h"
1859ed54 56#include "util-memcmp.h"
b0a69344 57#include "util-memrchr.h"
711b6fb3
EL
58#include "util-mem.h"
59#include "util-misc.h"
b0a69344 60
7e1235c9 61#include "output-json.h"
b573c16d 62#include "rust.h"
7e1235c9 63
09ab032a
JL
64typedef struct FTPThreadCtx_ {
65 MpmThreadCtx *ftp_mpm_thread_ctx;
66 PrefilterRuleStore *pmq;
67} FTPThreadCtx;
68
69#define FTP_MPM mpm_default_matcher
70
71static MpmCtx *ftp_mpm_ctx = NULL;
72
1930b1f5
JL
73const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = {
74 /* Parsed and handled */
09ab032a
JL
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},
1930b1f5
JL
82
83 /* Parsed, but not handled */
09ab032a
JL
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}
1930b1f5 126};
711b6fb3
EL
127uint64_t ftp_config_memcap = 0;
128
129SC_ATOMIC_DECLARE(uint64_t, ftp_memuse);
130SC_ATOMIC_DECLARE(uint64_t, ftp_memcap);
131
057c4b34 132static FTPTransaction *FTPGetOldestTx(FtpState *, FTPTransaction *);
2149807b 133
711b6fb3
EL
134static 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
157static void FTPIncrMemuse(uint64_t size)
158{
159 (void) SC_ATOMIC_ADD(ftp_memuse, size);
160 return;
161}
162
163static void FTPDecrMemuse(uint64_t size)
164{
165 (void) SC_ATOMIC_SUB(ftp_memuse, size);
166 return;
167}
168
169uint64_t FTPMemuseGlobalCounter(void)
170{
171 uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse);
172 return tmpval;
173}
174
175uint64_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 */
187static 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
195static 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
212static void *FTPCalloc(size_t n, size_t size)
213{
711b6fb3
EL
214 if (FTPCheckMemcap((uint32_t)(n * size)) == 0)
215 return NULL;
216
7c364017 217 void *ptr = SCCalloc(n, size);
711b6fb3
EL
218
219 if (unlikely(ptr == NULL))
220 return NULL;
221
222 FTPIncrMemuse((uint64_t)(n * size));
711b6fb3
EL
223 return ptr;
224}
225
226static 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
246static void FTPFree(void *ptr, size_t size)
247{
248 SCFree(ptr);
249
250 FTPDecrMemuse((uint64_t)size);
251}
252
1930b1f5
JL
253static FTPString *FTPStringAlloc(void)
254{
255 return FTPCalloc(1, sizeof(FTPString));
256}
257
258static void FTPStringFree(FTPString *str)
259{
260 if (str->str) {
261 FTPFree(str->str, str->len);
262 }
263
264 FTPFree(str, sizeof(FTPString));
265}
266
09ab032a
JL
267static 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
289static 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}
1930b1f5
JL
308static 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
325static void FTPTransactionFree(FTPTransaction *tx)
326{
327 SCEnter();
328
9c67c634
JI
329 if (tx->tx_data.de_state != NULL) {
330 DetectEngineStateFree(tx->tx_data.de_state);
1930b1f5
JL
331 }
332
333 if (tx->request) {
334 FTPFree(tx->request, tx->request_length);
335 }
1930b1f5
JL
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
aa3f784d 343 FTPFree(tx, sizeof(*tx));
1930b1f5
JL
344}
345
6ea8ac44
AS
346static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
347{
1f07d152 348 void *ptmp;
6ea8ac44
AS
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;
711b6fb3 355 FTPFree(line_state->db, line_state->db_len);
6ea8ac44
AS
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) {
711b6fb3 373 line_state->db = FTPMalloc(state->input_len);
6ea8ac44
AS
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 {
711b6fb3 381 ptmp = FTPRealloc(line_state->db, line_state->db_len,
1f07d152
EL
382 (line_state->db_len + state->input_len));
383 if (ptmp == NULL) {
711b6fb3 384 FTPFree(line_state->db, line_state->db_len);
1f07d152
EL
385 line_state->db = NULL;
386 line_state->db_len = 0;
6ea8ac44
AS
387 return -1;
388 }
1f07d152
EL
389 line_state->db = ptmp;
390
6ea8ac44
AS
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) {
711b6fb3 404 ptmp = FTPRealloc(line_state->db, line_state->db_len,
1f07d152
EL
405 (line_state->db_len + (lf_idx + 1 - state->input)));
406 if (ptmp == NULL) {
711b6fb3 407 FTPFree(line_state->db, line_state->db_len);
1f07d152
EL
408 line_state->db = NULL;
409 line_state->db_len = 0;
6ea8ac44
AS
410 return -1;
411 }
1f07d152
EL
412 line_state->db = ptmp;
413
6ea8ac44
AS
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
451static 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
f2f9b832
PR
466/**
467 * \brief This function is called to determine and set which command is being
1930b1f5 468 * transferred to the ftp server
86fabef0 469 * \param thread context
f2f9b832
PR
470 * \param input input line of the command
471 * \param len of the command
1930b1f5 472 * \param cmd_descriptor when the command has been parsed
f2f9b832
PR
473 *
474 * \retval 1 when the command is parsed, 0 otherwise
475 */
86fabef0 476static int FTPParseRequestCommand(FTPThreadCtx *td,
579cc9f0 477 const uint8_t *input, uint32_t input_len,
09ab032a 478 const FtpCommand **cmd_descriptor)
8f1d7503 479{
f2f9b832 480 SCEnter();
11b9e6fd 481
09ab032a
JL
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]];
86fabef0 489 SCReturnInt(1);
b0a69344 490 }
09ab032a
JL
491
492 *cmd_descriptor = NULL;
86fabef0 493 SCReturnInt(0);
f2f9b832
PR
494}
495
b0a69344
EL
496struct 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
506static void FtpTransferCmdFree(void *data)
507{
508 struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data;
509 if (cmd == NULL)
510 return;
511 if (cmd->file_name) {
aa3f784d 512 FTPFree(cmd->file_name, cmd->file_len + 1);
b0a69344 513 }
711b6fb3 514 FTPFree(cmd, sizeof(struct FtpTransferCmd));
b0a69344
EL
515}
516
579cc9f0 517static uint32_t CopyCommandLine(uint8_t **dest, const uint8_t *src, uint32_t length)
1930b1f5
JL
518{
519 if (likely(length)) {
1588cd87 520 uint8_t *where = FTPCalloc(length + 1, sizeof(char));
1930b1f5
JL
521 if (unlikely(where == NULL)) {
522 return 0;
523 }
524 memcpy(where, src, length);
525
526 /* Remove trailing newlines/carriage returns */
1588cd87
ZK
527 while (length && isspace((unsigned char) where[length - 1])) {
528 length--;
1930b1f5 529 }
1588cd87
ZK
530
531 where[length] = '\0';
1930b1f5
JL
532 *dest = where;
533 }
534 /* either 0 or actual */
aa3f784d 535 return length ? length + 1 : 0;
1930b1f5
JL
536}
537
4f33b8c1 538
f2f9b832
PR
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 *
3bcf948a
VJ
546 * \retval APP_LAYER_OK when input was process successfully
547 * \retval APP_LAYER_ERROR when a unrecoverable error was encountered
f2f9b832 548 */
44d3f264 549static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state,
9634e60e 550 AppLayerParserState *pstate,
579cc9f0 551 const uint8_t *input, uint32_t input_len,
7bc3c3ac 552 void *local_data, const uint8_t flags)
9a6aef45 553{
09ab032a
JL
554 FTPThreadCtx *thread_data = local_data;
555
f2f9b832 556 SCEnter();
f2f9b832
PR
557 /* PrintRawDataFp(stdout, input,input_len); */
558
6ea8ac44 559 FtpState *state = (FtpState *)ftp_state;
1f07d152 560 void *ptmp;
6ea8ac44 561
4f73943d 562 if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) {
44d3f264 563 SCReturnStruct(APP_LAYER_OK);
f4f53924 564 } else if (input == NULL || input_len == 0) {
44d3f264 565 SCReturnStruct(APP_LAYER_ERROR);
4e7cb7b8
VJ
566 }
567
6ea8ac44
AS
568 state->input = input;
569 state->input_len = input_len;
570 /* toserver stream */
571 state->direction = 0;
572
b0a69344 573 int direction = STREAM_TOSERVER;
6ea8ac44 574 while (FTPGetLine(state) >= 0) {
1930b1f5
JL
575 const FtpCommand *cmd_descriptor;
576
157d01e8
VJ
577 if (!FTPParseRequestCommand(thread_data,
578 state->current_line, state->current_line_len,
579 &cmd_descriptor)) {
1930b1f5
JL
580 state->command = FTP_COMMAND_UNKNOWN;
581 continue;
582 }
583
584 state->command = cmd_descriptor->command;
585
2149807b
JL
586 FTPTransaction *tx = FTPTransactionCreate(state);
587 if (unlikely(tx == NULL))
44d3f264 588 SCReturnStruct(APP_LAYER_ERROR);
2149807b 589 state->curr_tx = tx;
1930b1f5
JL
590
591 tx->command_descriptor = cmd_descriptor;
157d01e8
VJ
592 tx->request_length = CopyCommandLine(&tx->request,
593 state->current_line, state->current_line_len);
1930b1f5 594
071f55dc
XW
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
b0a69344 605 switch (state->command) {
4f33b8c1
JL
606 case FTP_COMMAND_EPRT:
607 // fallthrough
b0a69344 608 case FTP_COMMAND_PORT:
3ae2edb2 609 if (state->current_line_len + 1 > state->port_line_size) {
4f33b8c1 610 /* Allocate an extra byte for a NULL terminator */
711b6fb3 611 ptmp = FTPRealloc(state->port_line, state->port_line_size,
b4070b6d 612 state->current_line_len);
b0a69344 613 if (ptmp == NULL) {
4f33b8c1
JL
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 }
44d3f264 619 SCReturnStruct(APP_LAYER_OK);
b0a69344
EL
620 }
621 state->port_line = ptmp;
b4070b6d 622 state->port_line_size = state->current_line_len;
6ea8ac44 623 }
b0a69344
EL
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:
b0a69344 629 // fallthrough
3bcf948a 630 case FTP_COMMAND_STOR: {
354074ba
JL
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) {
44d3f264 635 SCReturnStruct(APP_LAYER_ERROR);
b0a69344 636 }
711b6fb3 637 struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd));
b0a69344 638 if (data == NULL)
44d3f264 639 SCReturnStruct(APP_LAYER_ERROR);
b0a69344 640 data->DFree = FtpTransferCmdFree;
72e2f36f
JL
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));
b0a69344 647 if (data->file_name == NULL) {
de983fb7 648 FtpTransferCmdFree(data);
44d3f264 649 SCReturnStruct(APP_LAYER_ERROR);
b0a69344 650 }
72e2f36f
JL
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);
b0a69344
EL
654 data->cmd = state->command;
655 data->flow_id = FlowGetId(f);
071f55dc 656 int ret = AppLayerExpectationCreate(f, direction,
4f33b8c1 657 0, state->dyn_port, ALPROTO_FTPDATA, data);
b0a69344 658 if (ret == -1) {
de983fb7 659 FtpTransferCmdFree(data);
b0a69344 660 SCLogDebug("No expectation created.");
44d3f264 661 SCReturnStruct(APP_LAYER_ERROR);
b0a69344 662 } else {
4f33b8c1
JL
663 SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].",
664 state->active ? "to server" : "to client",
665 state->dyn_port);
b0a69344 666 }
4f33b8c1 667
b0a69344
EL
668 /* reset the dyn port to avoid duplicate */
669 state->dyn_port = 0;
4f33b8c1
JL
670 /* reset active/passive indicator */
671 state->active = false;
b0a69344
EL
672 }
673 break;
674 default:
675 break;
6ea8ac44 676 }
f2f9b832 677 }
f2f9b832 678
44d3f264 679 SCReturnStruct(APP_LAYER_OK);
f2f9b832
PR
680}
681
579cc9f0 682static int FTPParsePassiveResponse(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len)
b0a69344 683{
f79316d7 684 uint16_t dyn_port = rs_ftp_pasv_response(input, input_len);
b0a69344
EL
685 if (dyn_port == 0) {
686 return -1;
687 }
4f33b8c1
JL
688 SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port);
689 state->active = false;
b0a69344 690 state->dyn_port = dyn_port;
1930b1f5
JL
691 state->curr_tx->dyn_port = dyn_port;
692 state->curr_tx->active = false;
b0a69344
EL
693
694 return 0;
695}
696
579cc9f0 697static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len)
b0a69344 698{
f79316d7 699 uint16_t dyn_port = rs_ftp_epsv_response(input, input_len);
b0a69344
EL
700 if (dyn_port == 0) {
701 return -1;
702 }
4f33b8c1
JL
703 SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port);
704 state->active = false;
b0a69344 705 state->dyn_port = dyn_port;
1930b1f5
JL
706 state->curr_tx->dyn_port = dyn_port;
707 state->curr_tx->active = false;
b0a69344
EL
708 return 0;
709}
710
1930b1f5
JL
711/**
712 * \brief Handle preliminary replies -- keep tx open
3bcf948a 713 * \retval bool True for a positive preliminary reply; false otherwise
1930b1f5
JL
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 */
579cc9f0 720static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len)
1930b1f5
JL
721{
722 return input_len >= 4 && isdigit(input[0]) && input[0] == '1' &&
723 isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]);
724}
725
f2f9b832
PR
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 */
44d3f264 735static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
579cc9f0 736 const uint8_t *input, uint32_t input_len,
7bc3c3ac 737 void *local_data, const uint8_t flags)
9a6aef45 738{
11b9e6fd 739 FtpState *state = (FtpState *)ftp_state;
1930b1f5 740
b595da6c 741 if (unlikely(input_len == 0)) {
44d3f264 742 SCReturnStruct(APP_LAYER_OK);
b595da6c 743 }
a6294d6e
PA
744 state->input = input;
745 state->input_len = input_len;
746 /* toclient stream */
747 state->direction = 1;
a04b1c16 748
057c4b34 749 FTPTransaction *lasttx = TAILQ_FIRST(&state->tx_list);
a6294d6e 750 while (FTPGetLine(state) >= 0) {
057c4b34 751 FTPTransaction *tx = FTPGetOldestTx(state, lasttx);
699d6682
PA
752 if (tx == NULL) {
753 tx = FTPTransactionCreate(state);
754 }
755 if (unlikely(tx == NULL)) {
756 SCReturnStruct(APP_LAYER_ERROR);
757 }
057c4b34 758 lasttx = tx;
699d6682
PA
759 if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) {
760 /* unknown */
761 tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX -1];
11b9e6fd 762 }
11b9e6fd 763
699d6682 764 state->curr_tx = tx;
fef124b9
PA
765 uint16_t dyn_port;
766 switch (state->command) {
767 case FTP_COMMAND_AUTH_TLS:
768 if (state->current_line_len >= 4 && SCMemcmp("234 ", state->current_line, 4) == 0) {
769 AppLayerRequestProtocolTLSUpgrade(f);
770 }
771 break;
4f33b8c1 772
fef124b9
PA
773 case FTP_COMMAND_EPRT:
774 dyn_port = rs_ftp_active_eprt(state->port_line, state->port_line_len);
775 if (dyn_port == 0) {
776 goto tx_complete;
777 }
778 state->dyn_port = dyn_port;
779 state->active = true;
780 tx->dyn_port = dyn_port;
781 tx->active = true;
782 SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16"", dyn_port);
783 break;
1930b1f5 784
fef124b9
PA
785 case FTP_COMMAND_PORT:
786 dyn_port = rs_ftp_active_port(state->port_line, state->port_line_len);
699d6682
PA
787 if (dyn_port == 0) {
788 goto tx_complete;
789 }
790 state->dyn_port = dyn_port;
791 state->active = true;
792 tx->dyn_port = state->dyn_port;
793 tx->active = true;
794 SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16"", dyn_port);
fef124b9 795 break;
b0a69344 796
fef124b9
PA
797 case FTP_COMMAND_PASV:
798 if (state->current_line_len >= 4 && SCMemcmp("227 ", state->current_line, 4) == 0) {
799 FTPParsePassiveResponse(f, ftp_state, state->current_line, state->current_line_len);
800 }
801 break;
b0a69344 802
fef124b9
PA
803 case FTP_COMMAND_EPSV:
804 if (state->current_line_len >= 4 && SCMemcmp("229 ", state->current_line, 4) == 0) {
805 FTPParsePassiveResponseV6(f, ftp_state, state->current_line, state->current_line_len);
806 }
807 break;
808 default:
809 break;
1930b1f5 810 }
1930b1f5 811
699d6682
PA
812 if (likely(state->current_line_len)) {
813 FTPString *response = FTPStringAlloc();
814 if (likely(response)) {
815 response->len = CopyCommandLine(&response->str, state->current_line, state->current_line_len);
816 TAILQ_INSERT_TAIL(&tx->response_list, response, next);
817 }
818 }
819
820 /* Handle preliminary replies -- keep tx open */
821 if (FTPIsPPR(state->current_line, state->current_line_len)) {
822 continue;
823 }
824 tx_complete:
825 tx->done = true;
a6294d6e
PA
826 }
827
44d3f264 828 SCReturnStruct(APP_LAYER_OK);
f2f9b832
PR
829}
830
911d423a 831
f2f9b832 832#ifdef DEBUG
5532af46 833static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER;
f2f9b832
PR
834static uint64_t ftp_state_memuse = 0;
835static uint64_t ftp_state_memcnt = 0;
836#endif
837
547d6c2d 838static void *FTPStateAlloc(void *orig_state, AppProto proto_orig)
8f1d7503 839{
1930b1f5 840 void *s = FTPCalloc(1, sizeof(FtpState));
e176be6f 841 if (unlikely(s == NULL))
f2f9b832
PR
842 return NULL;
843
1930b1f5
JL
844 FtpState *ftp_state = (FtpState *) s;
845 TAILQ_INIT(&ftp_state->tx_list);
f2f9b832
PR
846
847#ifdef DEBUG
848 SCMutexLock(&ftp_state_mem_lock);
849 ftp_state_memcnt++;
850 ftp_state_memuse+=sizeof(FtpState);
851 SCMutexUnlock(&ftp_state_mem_lock);
852#endif
853 return s;
854}
855
8f1d7503
KS
856static void FTPStateFree(void *s)
857{
18954a2c
PR
858 FtpState *fstate = (FtpState *) s;
859 if (fstate->port_line != NULL)
711b6fb3 860 FTPFree(fstate->port_line, fstate->port_line_size);
b4ab9a0a 861 if (fstate->line_state[0].db)
711b6fb3 862 FTPFree(fstate->line_state[0].db, fstate->line_state[0].db_len);
b4ab9a0a 863 if (fstate->line_state[1].db)
711b6fb3 864 FTPFree(fstate->line_state[1].db, fstate->line_state[1].db_len);
402eb645 865
1930b1f5
JL
866 FTPTransaction *tx = NULL;
867 while ((tx = TAILQ_FIRST(&fstate->tx_list))) {
868 TAILQ_REMOVE(&fstate->tx_list, tx, next);
2149807b 869 SCLogDebug("[%s] state %p id %"PRIu64", Freeing %d bytes at %p",
09ab032a 870 tx->command_descriptor->command_name,
2149807b
JL
871 s, tx->tx_id,
872 tx->request_length, tx->request);
1930b1f5 873 FTPTransactionFree(tx);
402eb645
VJ
874 }
875
711b6fb3 876 FTPFree(s, sizeof(FtpState));
f2f9b832
PR
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
2149807b
JL
885/**
886 * \brief This function returns the oldest open transaction; if none
887 * are open, then the oldest transaction is returned
888 * \param ftp_state the ftp state structure for the parser
057c4b34 889 * \param starttx the ftp transaction where to start looking
2149807b
JL
890 *
891 * \retval transaction pointer when a transaction was found; NULL otherwise.
892 */
057c4b34 893static FTPTransaction *FTPGetOldestTx(FtpState *ftp_state, FTPTransaction *starttx)
2149807b
JL
894{
895 if (unlikely(!ftp_state)) {
896 SCLogDebug("NULL state object; no transactions available");
897 return NULL;
898 }
057c4b34 899 FTPTransaction *tx = starttx;
2149807b 900 FTPTransaction *lasttx = NULL;
057c4b34 901 while(tx != NULL) {
2149807b
JL
902 /* Return oldest open tx */
903 if (!tx->done) {
904 SCLogDebug("Returning tx %p id %"PRIu64, tx, tx->tx_id);
905 return tx;
906 }
907 /* save for the end */
908 lasttx = tx;
057c4b34 909 tx = TAILQ_NEXT(tx, next);
2149807b
JL
910 }
911 /* All tx are closed; return last element */
912 if (lasttx)
913 SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id);
914 return lasttx;
915}
916
1930b1f5
JL
917static void *FTPGetTx(void *state, uint64_t tx_id)
918{
919 FtpState *ftp_state = (FtpState *)state;
920 if (ftp_state) {
921 FTPTransaction *tx = NULL;
922
923 if (ftp_state->curr_tx == NULL)
924 return NULL;
925 if (ftp_state->curr_tx->tx_id == tx_id)
926 return ftp_state->curr_tx;
927
928 TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
929 if (tx->tx_id == tx_id)
930 return tx;
931 }
932 }
933 return NULL;
1930b1f5
JL
934}
935
c98f5978 936static AppLayerTxData *FTPGetTxData(void *vtx)
402eb645 937{
1930b1f5 938 FTPTransaction *tx = (FTPTransaction *)vtx;
c98f5978 939 return &tx->tx_data;
1930b1f5
JL
940}
941
942static void FTPStateTransactionFree(void *state, uint64_t tx_id)
943{
944 FtpState *ftp_state = state;
945 FTPTransaction *tx = NULL;
946 TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
947 if (tx_id < tx->tx_id)
948 break;
949 else if (tx_id > tx->tx_id)
950 continue;
951
952 if (tx == ftp_state->curr_tx)
953 ftp_state->curr_tx = NULL;
954 TAILQ_REMOVE(&ftp_state->tx_list, tx, next);
955 FTPTransactionFree(tx);
956 break;
957 }
402eb645
VJ
958}
959
960static uint64_t FTPGetTxCnt(void *state)
961{
1930b1f5
JL
962 uint64_t cnt = 0;
963 FtpState *ftp_state = state;
964 if (ftp_state) {
965 cnt = ftp_state->tx_cnt;
966 }
967 SCLogDebug("returning state %p %"PRIu64, state, cnt);
968 return cnt;
402eb645
VJ
969}
970
1930b1f5 971static int FTPGetAlstateProgress(void *vtx, uint8_t direction)
402eb645 972{
1930b1f5
JL
973 SCLogDebug("tx %p", vtx);
974 FTPTransaction *tx = vtx;
402eb645 975
6f364032
PA
976 if (!tx->done) {
977 if (direction == STREAM_TOSERVER &&
978 tx->command_descriptor->command == FTP_COMMAND_PORT) {
979 return FTP_STATE_PORT_DONE;
980 }
dc80d520 981 return FTP_STATE_IN_PROGRESS;
6f364032 982 }
402eb645 983
dc80d520 984 return FTP_STATE_FINISHED;
402eb645
VJ
985}
986
987
429c6388
AS
988static int FTPRegisterPatternsForProtocolDetection(void)
989{
8125f78f
MK
990 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
991 "220 (", 5, 0, STREAM_TOCLIENT) < 0)
992 {
993 return -1;
994 }
995 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
996 "FEAT", 4, 0, STREAM_TOSERVER) < 0)
997 {
998 return -1;
999 }
429c6388
AS
1000 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
1001 "USER ", 5, 0, STREAM_TOSERVER) < 0)
1002 {
1003 return -1;
1004 }
1005 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
1006 "PASS ", 5, 0, STREAM_TOSERVER) < 0)
1007 {
1008 return -1;
1009 }
1010 if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
1011 "PORT ", 5, 0, STREAM_TOSERVER) < 0)
1012 {
1013 return -1;
1014 }
1015
1016 return 0;
1017}
1018
b0a69344
EL
1019
1020static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
1021
1022/**
1023 * \brief This function is called to retrieve a ftp request
1024 * \param ftp_state the ftp state structure for the parser
1025 * \param input input line of the command
1026 * \param input_len length of the request
1027 * \param output the resulting output
1028 *
1029 * \retval 1 when the command is parsed, 0 otherwise
1030 */
44d3f264 1031static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
b0a69344 1032 AppLayerParserState *pstate,
579cc9f0 1033 const uint8_t *input, uint32_t input_len,
b0a69344
EL
1034 void *local_data, int direction)
1035{
1036 uint16_t flags = FileFlowToFlags(f, direction);
1037 int ret = 0;
1038 /* we depend on detection engine for file pruning */
1039 flags |= FILE_USE_DETECT;
1040 if (ftpdata_state->files == NULL) {
4b3be245
VJ
1041 struct FtpTransferCmd *data =
1042 (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId());
b0a69344 1043 if (data == NULL) {
44d3f264 1044 SCReturnStruct(APP_LAYER_ERROR);
b0a69344
EL
1045 }
1046
1047 ftpdata_state->files = FileContainerAlloc();
1048 if (ftpdata_state->files == NULL) {
4b3be245 1049 FlowFreeStorageById(f, AppLayerExpectationGetFlowId());
44d3f264 1050 SCReturnStruct(APP_LAYER_ERROR);
b0a69344
EL
1051 }
1052
1053 ftpdata_state->file_name = data->file_name;
1054 ftpdata_state->file_len = data->file_len;
1055 data->file_name = NULL;
1056 data->file_len = 0;
1057 f->parent_id = data->flow_id;
1058 ftpdata_state->command = data->cmd;
2515c892
EL
1059 switch (data->cmd) {
1060 case FTP_COMMAND_STOR:
1061 ftpdata_state->direction = STREAM_TOSERVER;
1062 break;
1063 case FTP_COMMAND_RETR:
1064 ftpdata_state->direction = STREAM_TOCLIENT;
1065 break;
1066 default:
1067 break;
1068 }
b0a69344 1069
9132e403
VJ
1070 /* open with fixed track_id 0 as we can have just one
1071 * file per ftp-data flow. */
1072 if (FileOpenFileWithId(ftpdata_state->files, &sbcfg,
1073 0ULL, (uint8_t *) ftpdata_state->file_name,
b0a69344 1074 ftpdata_state->file_len,
9132e403 1075 input, input_len, flags) != 0) {
b0a69344
EL
1076 SCLogDebug("Can't open file");
1077 ret = -1;
1078 }
4b3be245 1079 FlowFreeStorageById(f, AppLayerExpectationGetFlowId());
04ba6dc1 1080 ftpdata_state->tx_data.files_opened = 1;
b0a69344
EL
1081 } else {
1082 if (input_len != 0) {
1083 ret = FileAppendData(ftpdata_state->files, input, input_len);
1084 if (ret == -2) {
1085 ret = 0;
1086 SCLogDebug("FileAppendData() - file no longer being extracted");
1087 goto out;
1088 } else if (ret < 0) {
1089 SCLogDebug("FileAppendData() failed: %d", ret);
1090 ret = -2;
1091 goto out;
1092 }
1093 } else {
1094 ret = FileCloseFile(ftpdata_state->files, NULL, 0, flags);
1095 ftpdata_state->state = FTPDATA_STATE_FINISHED;
1096 if (ret < 0)
1097 goto out;
1098 }
1099 }
1100
4f73943d
VJ
1101 const bool eof_flag = flags & STREAM_TOSERVER ?
1102 AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) != 0 :
1103 AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) != 0;
1104 if (input_len && eof_flag) {
b0a69344
EL
1105 ret = FileCloseFile(ftpdata_state->files, (uint8_t *) NULL, 0, flags);
1106 ftpdata_state->state = FTPDATA_STATE_FINISHED;
1107 }
1108
1109out:
44d3f264
VJ
1110 if (ret < 0) {
1111 SCReturnStruct(APP_LAYER_ERROR);
1112 }
1113 SCReturnStruct(APP_LAYER_OK);
b0a69344
EL
1114}
1115
44d3f264 1116static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state,
b0a69344 1117 AppLayerParserState *pstate,
579cc9f0 1118 const uint8_t *input, uint32_t input_len,
7bc3c3ac 1119 void *local_data, const uint8_t flags)
b0a69344
EL
1120{
1121 return FTPDataParse(f, ftp_state, pstate, input, input_len,
1122 local_data, STREAM_TOSERVER);
1123}
1124
44d3f264 1125static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state,
b0a69344 1126 AppLayerParserState *pstate,
579cc9f0 1127 const uint8_t *input, uint32_t input_len,
7bc3c3ac 1128 void *local_data, const uint8_t flags)
b0a69344
EL
1129{
1130 return FTPDataParse(f, ftp_state, pstate, input, input_len,
1131 local_data, STREAM_TOCLIENT);
1132}
1133
1134#ifdef DEBUG
1135static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER;
1136static uint64_t ftpdata_state_memuse = 0;
1137static uint64_t ftpdata_state_memcnt = 0;
1138#endif
1139
547d6c2d 1140static void *FTPDataStateAlloc(void *orig_state, AppProto proto_orig)
b0a69344 1141{
1930b1f5 1142 void *s = FTPCalloc(1, sizeof(FtpDataState));
b0a69344
EL
1143 if (unlikely(s == NULL))
1144 return NULL;
1145
1930b1f5
JL
1146 FtpDataState *state = (FtpDataState *) s;
1147 state->state = FTPDATA_STATE_IN_PROGRESS;
b0a69344
EL
1148
1149#ifdef DEBUG
1150 SCMutexLock(&ftpdata_state_mem_lock);
1151 ftpdata_state_memcnt++;
1152 ftpdata_state_memuse+=sizeof(FtpDataState);
1153 SCMutexUnlock(&ftpdata_state_mem_lock);
1154#endif
1155 return s;
1156}
1157
1158static void FTPDataStateFree(void *s)
1159{
1160 FtpDataState *fstate = (FtpDataState *) s;
1161
9c67c634
JI
1162 if (fstate->tx_data.de_state != NULL) {
1163 DetectEngineStateFree(fstate->tx_data.de_state);
b0a69344
EL
1164 }
1165 if (fstate->file_name != NULL) {
aa3f784d 1166 FTPFree(fstate->file_name, fstate->file_len + 1);
b0a69344
EL
1167 }
1168
1169 FileContainerFree(fstate->files);
1170
aa3f784d 1171 FTPFree(s, sizeof(FtpDataState));
b0a69344
EL
1172#ifdef DEBUG
1173 SCMutexLock(&ftpdata_state_mem_lock);
1174 ftpdata_state_memcnt--;
1175 ftpdata_state_memuse-=sizeof(FtpDataState);
1176 SCMutexUnlock(&ftpdata_state_mem_lock);
1177#endif
1178}
1179
c98f5978 1180static AppLayerTxData *FTPDataGetTxData(void *vtx)
b1beb76f
JI
1181{
1182 FtpDataState *ftp_state = (FtpDataState *)vtx;
c98f5978 1183 return &ftp_state->tx_data;
b1beb76f
JI
1184}
1185
b0a69344
EL
1186static void FTPDataStateTransactionFree(void *state, uint64_t tx_id)
1187{
1188 /* do nothing */
1189}
1190
1191static void *FTPDataGetTx(void *state, uint64_t tx_id)
1192{
1193 FtpDataState *ftp_state = (FtpDataState *)state;
1194 return ftp_state;
1195}
1196
1197static uint64_t FTPDataGetTxCnt(void *state)
1198{
1199 /* ftp-data is single tx */
1200 return 1;
1201}
1202
b0a69344
EL
1203static int FTPDataGetAlstateProgress(void *tx, uint8_t direction)
1204{
1205 FtpDataState *ftpdata_state = (FtpDataState *)tx;
1206 return ftpdata_state->state;
1207}
1208
1209static FileContainer *FTPDataStateGetFiles(void *state, uint8_t direction)
1210{
1211 FtpDataState *ftpdata_state = (FtpDataState *)state;
1212
2515c892
EL
1213 if (direction != ftpdata_state->direction)
1214 SCReturnPtr(NULL, "FileContainer");
1215
b0a69344
EL
1216 SCReturnPtr(ftpdata_state->files, "FileContainer");
1217}
1218
09ab032a
JL
1219static void FTPSetMpmState(void)
1220{
1221 ftp_mpm_ctx = SCMalloc(sizeof(MpmCtx));
1222 if (unlikely(ftp_mpm_ctx == NULL)) {
1223 exit(EXIT_FAILURE);
1224 }
1225 memset(ftp_mpm_ctx, 0, sizeof(MpmCtx));
1226 MpmInitCtx(ftp_mpm_ctx, FTP_MPM);
1227
1228 uint32_t i = 0;
1229 for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) {
1230 const FtpCommand *cmd = &FtpCommands[i];
86deaefe
JL
1231 if (cmd->command_length == 0)
1232 continue;
1233
09ab032a
JL
1234 MpmAddPatternCI(ftp_mpm_ctx,
1235 (uint8_t *)cmd->command_name,
1236 cmd->command_length,
1237 0 /* defunct */, 0 /* defunct */,
1238 i /* id */, i /* rule id */ , 0 /* no flags */);
1239 }
1240
1241 mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx);
1242
1243}
1244
1245static void FTPFreeMpmState(void)
1246{
1247 if (ftp_mpm_ctx != NULL) {
1248 mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx);
1249 SCFree(ftp_mpm_ctx);
1250 ftp_mpm_ctx = NULL;
1251 }
1252}
1253
8f1d7503
KS
1254void RegisterFTPParsers(void)
1255{
ab1200fb 1256 const char *proto_name = "ftp";
b0a69344 1257 const char *proto_data_name = "ftp-data";
10966245 1258
000ce98c 1259 /** FTP */
429c6388
AS
1260 if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
1261 AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name);
1262 if (FTPRegisterPatternsForProtocolDetection() < 0 )
1263 return;
b0a69344 1264 AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name);
ddde572f
AS
1265 }
1266
429c6388
AS
1267 if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
1268 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER,
1269 FTPParseRequest);
1270 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT,
1271 FTPParseResponse);
1272 AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
1273 AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT);
402eb645
VJ
1274
1275 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree);
1276
402eb645 1277 AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx);
c98f5978 1278 AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxData);
402eb645 1279
09ab032a
JL
1280 AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc,
1281 FTPLocalStorageFree);
402eb645
VJ
1282 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt);
1283
1284 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress);
1285
efc9a7a3
VJ
1286 AppLayerParserRegisterStateProgressCompletionStatus(
1287 ALPROTO_FTP, FTP_STATE_FINISHED, FTP_STATE_FINISHED);
b0a69344
EL
1288
1289 AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA);
1290 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER,
1291 FTPDataParseRequest);
1292 AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT,
1293 FTPDataParseResponse);
1294 AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree);
1295 AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT);
1296 AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree);
b0a69344
EL
1297
1298 AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetFiles);
1299
1300 AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx);
c98f5978 1301 AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxData);
b0a69344
EL
1302
1303 AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt);
1304
1305 AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress);
1306
efc9a7a3
VJ
1307 AppLayerParserRegisterStateProgressCompletionStatus(
1308 ALPROTO_FTPDATA, FTPDATA_STATE_FINISHED, FTPDATA_STATE_FINISHED);
b0a69344
EL
1309
1310 sbcfg.buf_size = 4096;
711b6fb3
EL
1311 sbcfg.Malloc = FTPMalloc;
1312 sbcfg.Calloc = FTPCalloc;
1313 sbcfg.Realloc = FTPRealloc;
1314 sbcfg.Free = FTPFree;
b0a69344 1315
711b6fb3 1316 FTPParseMemcap();
ddde572f
AS
1317 } else {
1318 SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
1319 "still on.", proto_name);
1320 }
09ab032a
JL
1321
1322 FTPSetMpmState();
1323
9faa4b74 1324#ifdef UNITTESTS
429c6388 1325 AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests);
9faa4b74 1326#endif
f2f9b832
PR
1327}
1328
8f1d7503
KS
1329void FTPAtExitPrintStats(void)
1330{
f2f9b832
PR
1331#ifdef DEBUG
1332 SCMutexLock(&ftp_state_mem_lock);
1333 SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"",
1334 ftp_state_memcnt, ftp_state_memuse);
1335 SCMutexUnlock(&ftp_state_mem_lock);
1336#endif
1337}
1338
b0a69344 1339
3df2b343
JL
1340/*
1341 * \brief Returns the ending offset of the next line from a multi-line buffer.
1342 *
1343 * "Buffer" refers to a FTP response in a single buffer containing multiple lines.
1344 * Here, "next line" is defined as terminating on
1345 * - Newline character
1346 * - Null character
1347 *
1348 * \param buffer Contains zero or more characters.
1349 * \param len Size, in bytes, of buffer.
1350 *
1351 * \retval Offset from the start of buffer indicating the where the
1352 * next "line ends". The characters between the input buffer and this
1353 * value comprise the line.
1354 *
86fabef0 1355 * NULL is found first or a newline isn't found, then UINT16_MAX is returned.
3df2b343
JL
1356 */
1357uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len)
1358{
86fabef0
JL
1359 if (!buffer || *buffer == '\0') {
1360 return UINT16_MAX;
1361 }
3df2b343 1362
86fabef0
JL
1363 char *c = strchr(buffer, '\n');
1364 return c == NULL ? len : c - buffer + 1;
3df2b343
JL
1365}
1366
648bd5af 1367void EveFTPDataAddMetadata(const Flow *f, JsonBuilder *jb)
b0a69344
EL
1368{
1369 const FtpDataState *ftp_state = NULL;
1370 if (f->alstate == NULL)
03de315b
JL
1371 return;
1372
b0a69344 1373 ftp_state = (FtpDataState *)f->alstate;
03de315b 1374
b0a69344 1375 if (ftp_state->file_name) {
03de315b 1376 jb_set_string_from_bytes(jb, "filename", ftp_state->file_name, ftp_state->file_len);
b0a69344
EL
1377 }
1378 switch (ftp_state->command) {
1379 case FTP_COMMAND_STOR:
03de315b 1380 JB_SET_STRING(jb, "command", "STOR");
b0a69344
EL
1381 break;
1382 case FTP_COMMAND_RETR:
03de315b 1383 JB_SET_STRING(jb, "command", "RETR");
b0a69344
EL
1384 break;
1385 default:
1386 break;
1387 }
b0a69344 1388}
b0a69344 1389
09ab032a 1390/**
03de315b 1391 * \brief Free memory allocated for global FTP parser state.
09ab032a
JL
1392 */
1393void FTPParserCleanup(void)
1394{
1395 FTPFreeMpmState();
1396}
1397
f2f9b832
PR
1398/* UNITTESTS */
1399#ifdef UNITTESTS
1400
1401/** \test Send a get request in one chunk. */
ab1200fb 1402static int FTPParserTest01(void)
8f1d7503 1403{
f2f9b832
PR
1404 Flow f;
1405 uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n";
1406 uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */
1407 TcpSession ssn;
8dbf7a0d 1408 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
f2f9b832
PR
1409
1410 memset(&f, 0, sizeof(f));
1411 memset(&ssn, 0, sizeof(ssn));
cc76aa4b 1412
f2f9b832 1413 f.protoctx = (void *)&ssn;
429c6388 1414 f.proto = IPPROTO_TCP;
5c01b409 1415 f.alproto = ALPROTO_FTP;
f2f9b832 1416
1eeb9669 1417 StreamTcpInitConfig(true);
6a53ab9c 1418
675fa564
GL
1419 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1420 STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen);
b195ffbe 1421 FAIL_IF(r != 0);
f2f9b832 1422
06904c90 1423 FtpState *ftp_state = f.alstate;
b195ffbe
SB
1424 FAIL_IF_NULL(ftp_state);
1425 FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
f2f9b832 1426
b195ffbe 1427 AppLayerParserThreadCtxFree(alp_tctx);
1eeb9669 1428 StreamTcpFreeConfig(true);
b195ffbe 1429 PASS;
f2f9b832
PR
1430}
1431
1930b1f5 1432/** \test Send a split get request. */
ab1200fb 1433static int FTPParserTest03(void)
8f1d7503 1434{
f2f9b832
PR
1435 Flow f;
1436 uint8_t ftpbuf1[] = "POR";
1437 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1438 uint8_t ftpbuf2[] = "T 192,168,1";
1439 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
1440 uint8_t ftpbuf3[] = "1,1,10,20\r\n";
1441 uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */
1442 TcpSession ssn;
8dbf7a0d 1443 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
f2f9b832
PR
1444
1445 memset(&f, 0, sizeof(f));
1446 memset(&ssn, 0, sizeof(ssn));
48b3cb04 1447
f2f9b832 1448 f.protoctx = (void *)&ssn;
429c6388 1449 f.proto = IPPROTO_TCP;
5c01b409 1450 f.alproto = ALPROTO_FTP;
f2f9b832 1451
1eeb9669 1452 StreamTcpInitConfig(true);
6a53ab9c 1453
675fa564
GL
1454 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1455 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1456 ftplen1);
b195ffbe 1457 FAIL_IF(r != 0);
f2f9b832 1458
675fa564
GL
1459 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER,
1460 ftpbuf2, ftplen2);
b195ffbe 1461 FAIL_IF(r != 0);
f2f9b832 1462
675fa564
GL
1463 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1464 STREAM_TOSERVER | STREAM_EOF, ftpbuf3, ftplen3);
b195ffbe 1465 FAIL_IF(r != 0);
f2f9b832 1466
06904c90 1467 FtpState *ftp_state = f.alstate;
b195ffbe 1468 FAIL_IF_NULL(ftp_state);
f2f9b832 1469
b195ffbe 1470 FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
f2f9b832 1471
b195ffbe 1472 AppLayerParserThreadCtxFree(alp_tctx);
1eeb9669 1473 StreamTcpFreeConfig(true);
b195ffbe 1474 PASS;
f2f9b832
PR
1475}
1476
1477/** \test See how it deals with an incomplete request. */
ab1200fb 1478static int FTPParserTest06(void)
8f1d7503 1479{
f2f9b832
PR
1480 Flow f;
1481 uint8_t ftpbuf1[] = "PORT";
1482 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1483 TcpSession ssn;
8dbf7a0d 1484 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
f2f9b832
PR
1485
1486 memset(&f, 0, sizeof(f));
1487 memset(&ssn, 0, sizeof(ssn));
cc76aa4b 1488
f2f9b832 1489 f.protoctx = (void *)&ssn;
429c6388 1490 f.proto = IPPROTO_TCP;
5c01b409 1491 f.alproto = ALPROTO_FTP;
f2f9b832 1492
1eeb9669 1493 StreamTcpInitConfig(true);
6a53ab9c 1494
675fa564
GL
1495 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1496 STREAM_TOSERVER | STREAM_START | STREAM_EOF,
1497 ftpbuf1,
1498 ftplen1);
b195ffbe 1499 FAIL_IF(r != 0);
f2f9b832 1500
06904c90 1501 FtpState *ftp_state = f.alstate;
b195ffbe 1502 FAIL_IF_NULL(ftp_state);
f2f9b832 1503
b195ffbe 1504 FAIL_IF(ftp_state->command != FTP_COMMAND_UNKNOWN);
f2f9b832 1505
b195ffbe 1506 AppLayerParserThreadCtxFree(alp_tctx);
1eeb9669 1507 StreamTcpFreeConfig(true);
b195ffbe 1508 PASS;
f2f9b832
PR
1509}
1510
1511/** \test See how it deals with an incomplete request in multiple chunks. */
ab1200fb 1512static int FTPParserTest07(void)
8f1d7503 1513{
f2f9b832
PR
1514 Flow f;
1515 uint8_t ftpbuf1[] = "PO";
1516 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1517 uint8_t ftpbuf2[] = "RT\r\n";
1518 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
1519 TcpSession ssn;
8dbf7a0d 1520 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
f2f9b832
PR
1521
1522 memset(&f, 0, sizeof(f));
1523 memset(&ssn, 0, sizeof(ssn));
cc76aa4b 1524
f2f9b832 1525 f.protoctx = (void *)&ssn;
429c6388 1526 f.proto = IPPROTO_TCP;
5c01b409 1527 f.alproto = ALPROTO_FTP;
f2f9b832 1528
1eeb9669 1529 StreamTcpInitConfig(true);
6a53ab9c 1530
675fa564
GL
1531 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1532 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1533 ftplen1);
b195ffbe 1534 FAIL_IF(r != 0);
f2f9b832 1535
675fa564
GL
1536 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1537 STREAM_TOSERVER | STREAM_EOF, ftpbuf2, ftplen2);
b195ffbe 1538 FAIL_IF(r != 0);
f2f9b832 1539
06904c90 1540 FtpState *ftp_state = f.alstate;
b195ffbe 1541 FAIL_IF_NULL(ftp_state);
f2f9b832 1542
b195ffbe 1543 FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
f2f9b832 1544
b195ffbe 1545 AppLayerParserThreadCtxFree(alp_tctx);
1eeb9669 1546 StreamTcpFreeConfig(true);
b195ffbe 1547 PASS;
f2f9b832
PR
1548}
1549
1550/** \test Test case where chunks are smaller than the delim length and the
1551 * last chunk is supposed to match the delim. */
ab1200fb 1552static int FTPParserTest10(void)
8f1d7503 1553{
f2f9b832
PR
1554 Flow f;
1555 uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n";
1556 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
1557 TcpSession ssn;
8dbf7a0d 1558 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
f2f9b832
PR
1559 int r = 0;
1560 memset(&f, 0, sizeof(f));
1561 memset(&ssn, 0, sizeof(ssn));
cc76aa4b 1562
f2f9b832 1563 f.protoctx = (void *)&ssn;
429c6388 1564 f.proto = IPPROTO_TCP;
5c01b409 1565 f.alproto = ALPROTO_FTP;
f2f9b832 1566
1eeb9669 1567 StreamTcpInitConfig(true);
6a53ab9c 1568
f2f9b832
PR
1569 uint32_t u;
1570 for (u = 0; u < ftplen1; u++) {
1571 uint8_t flags = 0;
1572
1573 if (u == 0) flags = STREAM_TOSERVER|STREAM_START;
1574 else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF;
1575 else flags = STREAM_TOSERVER;
1576
675fa564
GL
1577 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, flags,
1578 &ftpbuf1[u], 1);
b195ffbe 1579 FAIL_IF(r != 0);
f2f9b832
PR
1580 }
1581
06904c90 1582 FtpState *ftp_state = f.alstate;
b195ffbe 1583 FAIL_IF_NULL(ftp_state);
f2f9b832 1584
b195ffbe 1585 FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
f2f9b832 1586
b195ffbe 1587 AppLayerParserThreadCtxFree(alp_tctx);
1eeb9669 1588 StreamTcpFreeConfig(true);
b195ffbe 1589 PASS;
354074ba
JL
1590}
1591
1592/** \test Supply RETR without a filename */
1593static int FTPParserTest11(void)
1594{
354074ba
JL
1595 Flow f;
1596 uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1597 uint8_t ftpbuf2[] = "RETR\r\n";
1598 uint8_t ftpbuf3[] = "227 OK\r\n";
1599 TcpSession ssn;
1600
1601 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1602
1603 memset(&f, 0, sizeof(f));
1604 memset(&ssn, 0, sizeof(ssn));
1605
354074ba
JL
1606 f.protoctx = (void *)&ssn;
1607 f.proto = IPPROTO_TCP;
1608 f.alproto = ALPROTO_FTP;
1609
1eeb9669 1610 StreamTcpInitConfig(true);
354074ba 1611
354074ba
JL
1612 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1613 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1614 sizeof(ftpbuf1) - 1);
b195ffbe 1615 FAIL_IF(r != 0);
354074ba
JL
1616
1617 /* Response */
354074ba
JL
1618 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1619 STREAM_TOCLIENT,
1620 ftpbuf3,
1621 sizeof(ftpbuf3) - 1);
b195ffbe 1622 FAIL_IF(r != 0);
354074ba 1623
354074ba
JL
1624 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1625 STREAM_TOSERVER, ftpbuf2,
1626 sizeof(ftpbuf2) - 1);
b195ffbe 1627 FAIL_IF(r == 0);
354074ba
JL
1628
1629 FtpState *ftp_state = f.alstate;
b195ffbe 1630 FAIL_IF_NULL(ftp_state);
354074ba 1631
b195ffbe 1632 FAIL_IF(ftp_state->command != FTP_COMMAND_RETR);
354074ba 1633
b195ffbe 1634 AppLayerParserThreadCtxFree(alp_tctx);
1eeb9669 1635 StreamTcpFreeConfig(true);
b195ffbe 1636 PASS;
354074ba
JL
1637}
1638
1639/** \test Supply STOR without a filename */
1640static int FTPParserTest12(void)
1641{
354074ba
JL
1642 Flow f;
1643 uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1644 uint8_t ftpbuf2[] = "STOR\r\n";
1645 uint8_t ftpbuf3[] = "227 OK\r\n";
1646 TcpSession ssn;
1647
1648 AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1649
1650 memset(&f, 0, sizeof(f));
1651 memset(&ssn, 0, sizeof(ssn));
1652
354074ba
JL
1653 f.protoctx = (void *)&ssn;
1654 f.proto = IPPROTO_TCP;
1655 f.alproto = ALPROTO_FTP;
1656
1eeb9669 1657 StreamTcpInitConfig(true);
354074ba 1658
354074ba
JL
1659 int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1660 STREAM_TOSERVER | STREAM_START, ftpbuf1,
1661 sizeof(ftpbuf1) - 1);
b195ffbe 1662 FAIL_IF(r != 0);
354074ba
JL
1663
1664 /* Response */
354074ba
JL
1665 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1666 STREAM_TOCLIENT,
1667 ftpbuf3,
1668 sizeof(ftpbuf3) - 1);
b195ffbe 1669 FAIL_IF(r != 0);
354074ba 1670
354074ba
JL
1671 r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1672 STREAM_TOSERVER, ftpbuf2,
1673 sizeof(ftpbuf2) - 1);
b195ffbe 1674 FAIL_IF(r == 0);
354074ba
JL
1675
1676 FtpState *ftp_state = f.alstate;
b195ffbe 1677 FAIL_IF_NULL(ftp_state);
354074ba 1678
b195ffbe 1679 FAIL_IF(ftp_state->command != FTP_COMMAND_STOR);
354074ba 1680
b195ffbe 1681 AppLayerParserThreadCtxFree(alp_tctx);
1eeb9669 1682 StreamTcpFreeConfig(true);
b195ffbe 1683 PASS;
f2f9b832
PR
1684}
1685#endif /* UNITTESTS */
1686
8f1d7503
KS
1687void FTPParserRegisterTests(void)
1688{
f2f9b832 1689#ifdef UNITTESTS
796dd522
JI
1690 UtRegisterTest("FTPParserTest01", FTPParserTest01);
1691 UtRegisterTest("FTPParserTest03", FTPParserTest03);
1692 UtRegisterTest("FTPParserTest06", FTPParserTest06);
1693 UtRegisterTest("FTPParserTest07", FTPParserTest07);
1694 UtRegisterTest("FTPParserTest10", FTPParserTest10);
354074ba
JL
1695 UtRegisterTest("FTPParserTest11", FTPParserTest11);
1696 UtRegisterTest("FTPParserTest12", FTPParserTest12);
f2f9b832
PR
1697#endif /* UNITTESTS */
1698}
1699