Coverage Report

Created: 2026-06-07 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/app-layer-ftp.c
Line
Count
Source
1
/* Copyright (C) 2007-2022 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 "app-layer-ftp.h"
30
#include "app-layer.h"
31
#include "app-layer-parser.h"
32
#include "app-layer-expectation.h"
33
#include "app-layer-detect-proto.h"
34
35
#include "rust.h"
36
37
#include "util-misc.h"
38
#include "util-mpm.h"
39
#include "util-validate.h"
40
41
typedef struct FTPThreadCtx_ {
42
    MpmThreadCtx *ftp_mpm_thread_ctx;
43
    PrefilterRuleStore *pmq;
44
} FTPThreadCtx;
45
46
1.57M
#define FTP_MPM mpm_default_matcher
47
48
static MpmCtx *ftp_mpm_ctx = NULL;
49
50
// clang-format off
51
const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = {
52
    /* Parsed and handled */
53
    { "PORT",   FTP_COMMAND_PORT,   4 },
54
    { "EPRT",   FTP_COMMAND_EPRT,   4 },
55
    { "AUTH TLS",   FTP_COMMAND_AUTH_TLS,   8 },
56
    { "PASV",   FTP_COMMAND_PASV,   4 },
57
    { "RETR",   FTP_COMMAND_RETR,   4 },
58
    { "EPSV",   FTP_COMMAND_EPSV,   4 },
59
    { "STOR",   FTP_COMMAND_STOR,   4 },
60
61
    /* Parsed, but not handled */
62
    { "ABOR",   FTP_COMMAND_ABOR,   4 },
63
    { "ACCT",   FTP_COMMAND_ACCT,   4 },
64
    { "ALLO",   FTP_COMMAND_ALLO,   4 },
65
    { "APPE",   FTP_COMMAND_APPE,   4 },
66
    { "CDUP",   FTP_COMMAND_CDUP,   4 },
67
    { "CHMOD",  FTP_COMMAND_CHMOD,  5 },
68
    { "CWD",    FTP_COMMAND_CWD,    3 },
69
    { "DELE",   FTP_COMMAND_DELE,   4 },
70
    { "HELP",   FTP_COMMAND_HELP,   4 },
71
    { "IDLE",   FTP_COMMAND_IDLE,   4 },
72
    { "LIST",   FTP_COMMAND_LIST,   4 },
73
    { "MAIL",   FTP_COMMAND_MAIL,   4 },
74
    { "MDTM",   FTP_COMMAND_MDTM,   4 },
75
    { "MKD",    FTP_COMMAND_MKD,    3 },
76
    { "MLFL",   FTP_COMMAND_MLFL,   4 },
77
    { "MODE",   FTP_COMMAND_MODE,   4 },
78
    { "MRCP",   FTP_COMMAND_MRCP,   4 },
79
    { "MRSQ",   FTP_COMMAND_MRSQ,   4 },
80
    { "MSAM",   FTP_COMMAND_MSAM,   4 },
81
    { "MSND",   FTP_COMMAND_MSND,   4 },
82
    { "MSOM",   FTP_COMMAND_MSOM,   4 },
83
    { "NLST",   FTP_COMMAND_NLST,   4 },
84
    { "NOOP",   FTP_COMMAND_NOOP,   4 },
85
    { "PASS",   FTP_COMMAND_PASS,   4 },
86
    { "PWD",    FTP_COMMAND_PWD,    3 },
87
    { "QUIT",   FTP_COMMAND_QUIT,   4 },
88
    { "REIN",   FTP_COMMAND_REIN,   4 },
89
    { "REST",   FTP_COMMAND_REST,   4 },
90
    { "RMD",    FTP_COMMAND_RMD,    3 },
91
    { "RNFR",   FTP_COMMAND_RNFR,   4 },
92
    { "RNTO",   FTP_COMMAND_RNTO,   4 },
93
    { "SITE",   FTP_COMMAND_SITE,   4 },
94
    { "SIZE",   FTP_COMMAND_SIZE,   4 },
95
    { "SMNT",   FTP_COMMAND_SMNT,   4 },
96
    { "STAT",   FTP_COMMAND_STAT,   4 },
97
    { "STOU",   FTP_COMMAND_STOU,   4 },
98
    { "STRU",   FTP_COMMAND_STRU,   4 },
99
    { "SYST",   FTP_COMMAND_SYST,   4 },
100
    { "TYPE",   FTP_COMMAND_TYPE,   4 },
101
    { "UMASK",  FTP_COMMAND_UMASK,  5 },
102
    { "USER",   FTP_COMMAND_USER,   4 },
103
    { NULL,     FTP_COMMAND_UNKNOWN,    0 }
104
};
105
// clang-format on
106
107
uint64_t ftp_config_memcap = 0;
108
uint32_t ftp_config_maxtx = 1024;
109
uint32_t ftp_max_line_len = 4096;
110
111
SC_ATOMIC_DECLARE(uint64_t, ftp_memuse);
112
SC_ATOMIC_DECLARE(uint64_t, ftp_memcap);
113
114
static FTPTransaction *FTPGetOldestTx(const FtpState *, FTPTransaction *);
115
116
static void FTPParseMemcap(void)
117
34
{
118
34
    const char *conf_val;
119
120
    /** set config values for memcap, prealloc and hash_size */
121
34
    if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1)
122
0
    {
123
0
        if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) {
124
0
            SCLogError("Error parsing ftp.memcap "
125
0
                       "from conf file - %s.  Killing engine",
126
0
                    conf_val);
127
0
            exit(EXIT_FAILURE);
128
0
        }
129
0
        SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap);
130
34
    } else {
131
        /* default to unlimited */
132
34
        ftp_config_memcap = 0;
133
34
    }
134
135
34
    SC_ATOMIC_INIT(ftp_memuse);
136
34
    SC_ATOMIC_INIT(ftp_memcap);
137
138
34
    if ((ConfGet("app-layer.protocols.ftp.max-tx", &conf_val)) == 1) {
139
0
        if (ParseSizeStringU32(conf_val, &ftp_config_maxtx) < 0) {
140
0
            SCLogError("Error parsing ftp.max-tx "
141
0
                       "from conf file - %s.",
142
0
                    conf_val);
143
0
        }
144
0
        SCLogInfo("FTP max tx: %" PRIu32, ftp_config_maxtx);
145
0
    }
146
147
34
    if ((ConfGet("app-layer.protocols.ftp.max-line-length", &conf_val)) == 1) {
148
0
        if (ParseSizeStringU32(conf_val, &ftp_max_line_len) < 0) {
149
0
            SCLogError("Error parsing ftp.max-line-length from conf file - %s.", conf_val);
150
0
        }
151
0
        SCLogConfig("FTP max line length: %" PRIu32, ftp_max_line_len);
152
0
    }
153
34
}
154
155
static void FTPIncrMemuse(uint64_t size)
156
1.31M
{
157
1.31M
    (void) SC_ATOMIC_ADD(ftp_memuse, size);
158
1.31M
    return;
159
1.31M
}
160
161
static void FTPDecrMemuse(uint64_t size)
162
1.31M
{
163
1.31M
    (void) SC_ATOMIC_SUB(ftp_memuse, size);
164
1.31M
    return;
165
1.31M
}
166
167
uint64_t FTPMemuseGlobalCounter(void)
168
0
{
169
0
    uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse);
170
0
    return tmpval;
171
0
}
172
173
uint64_t FTPMemcapGlobalCounter(void)
174
0
{
175
0
    uint64_t tmpval = SC_ATOMIC_GET(ftp_memcap);
176
0
    return tmpval;
177
0
}
178
179
/**
180
 *  \brief Check if alloc'ing "size" would mean we're over memcap
181
 *
182
 *  \retval 1 if in bounds
183
 *  \retval 0 if not in bounds
184
 */
185
static int FTPCheckMemcap(uint64_t size)
186
3.19M
{
187
3.19M
    if (ftp_config_memcap == 0 || size + SC_ATOMIC_GET(ftp_memuse) <= ftp_config_memcap)
188
3.19M
        return 1;
189
0
    (void) SC_ATOMIC_ADD(ftp_memcap, 1);
190
0
    return 0;
191
3.19M
}
192
193
static void *FTPCalloc(size_t n, size_t size)
194
3.17M
{
195
3.17M
    if (FTPCheckMemcap((uint32_t)(n * size)) == 0) {
196
0
        sc_errno = SC_ELIMIT;
197
0
        return NULL;
198
0
    }
199
200
3.17M
    void *ptr = SCCalloc(n, size);
201
202
3.17M
    if (unlikely(ptr == NULL)) {
203
0
        sc_errno = SC_ENOMEM;
204
0
        return NULL;
205
0
    }
206
207
3.17M
    FTPIncrMemuse((uint64_t)(n * size));
208
3.17M
    return ptr;
209
3.17M
}
210
211
static void *FTPRealloc(void *ptr, size_t orig_size, size_t size)
212
12.5k
{
213
12.5k
    void *rptr = NULL;
214
215
12.5k
    if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0) {
216
0
        sc_errno = SC_ELIMIT;
217
0
        return NULL;
218
0
    }
219
220
12.5k
    rptr = SCRealloc(ptr, size);
221
12.5k
    if (rptr == NULL) {
222
0
        sc_errno = SC_ENOMEM;
223
0
        return NULL;
224
0
    }
225
226
12.5k
    if (size > orig_size) {
227
8.44k
        FTPIncrMemuse(size - orig_size);
228
8.44k
    } else {
229
4.08k
        FTPDecrMemuse(orig_size - size);
230
4.08k
    }
231
232
12.5k
    return rptr;
233
12.5k
}
234
235
static void FTPFree(void *ptr, size_t size)
236
3.18M
{
237
3.18M
    SCFree(ptr);
238
239
3.18M
    FTPDecrMemuse((uint64_t)size);
240
3.18M
}
241
242
static FTPString *FTPStringAlloc(void)
243
297k
{
244
297k
    return FTPCalloc(1, sizeof(FTPString));
245
297k
}
246
247
static void FTPStringFree(FTPString *str)
248
297k
{
249
297k
    if (str->str) {
250
297k
        FTPFree(str->str, str->len);
251
297k
    }
252
253
297k
    FTPFree(str, sizeof(FTPString));
254
297k
}
255
256
static void *FTPLocalStorageAlloc(void)
257
80
{
258
    /* needed by the mpm */
259
80
    FTPThreadCtx *td = SCCalloc(1, sizeof(*td));
260
80
    if (td == NULL) {
261
0
        exit(EXIT_FAILURE);
262
0
    }
263
264
80
    td->pmq = SCCalloc(1, sizeof(*td->pmq));
265
80
    if (td->pmq == NULL) {
266
0
        exit(EXIT_FAILURE);
267
0
    }
268
80
    PmqSetup(td->pmq);
269
270
80
    td->ftp_mpm_thread_ctx = SCCalloc(1, sizeof(MpmThreadCtx));
271
80
    if (unlikely(td->ftp_mpm_thread_ctx == NULL)) {
272
0
        exit(EXIT_FAILURE);
273
0
    }
274
80
    MpmInitThreadCtx(td->ftp_mpm_thread_ctx, FTP_MPM);
275
80
    return td;
276
80
}
277
278
static void FTPLocalStorageFree(void *ptr)
279
0
{
280
0
    FTPThreadCtx *td = ptr;
281
0
    if (td != NULL) {
282
0
        if (td->pmq != NULL) {
283
0
            PmqFree(td->pmq);
284
0
            SCFree(td->pmq);
285
0
        }
286
287
0
        if (td->ftp_mpm_thread_ctx != NULL) {
288
0
            mpm_table[FTP_MPM].DestroyThreadCtx(ftp_mpm_ctx, td->ftp_mpm_thread_ctx);
289
0
            SCFree(td->ftp_mpm_thread_ctx);
290
0
        }
291
292
0
        SCFree(td);
293
0
    }
294
295
0
    return;
296
0
}
297
static FTPTransaction *FTPTransactionCreate(FtpState *state)
298
367k
{
299
367k
    SCEnter();
300
367k
    FTPTransaction *firsttx = TAILQ_FIRST(&state->tx_list);
301
367k
    if (firsttx && state->tx_cnt - firsttx->tx_id > ftp_config_maxtx) {
302
        // FTP does not set events yet...
303
4
        return NULL;
304
4
    }
305
367k
    FTPTransaction *tx = FTPCalloc(1, sizeof(*tx));
306
367k
    if (tx == NULL) {
307
0
        return NULL;
308
0
    }
309
310
367k
    TAILQ_INSERT_TAIL(&state->tx_list, tx, next);
311
367k
    tx->tx_id = state->tx_cnt++;
312
313
367k
    TAILQ_INIT(&tx->response_list);
314
315
367k
    SCLogDebug("new transaction %p (state tx cnt %"PRIu64")", tx, state->tx_cnt);
316
367k
    return tx;
317
367k
}
318
319
static void FTPTransactionFree(FTPTransaction *tx)
320
367k
{
321
367k
    SCEnter();
322
323
367k
    if (tx->tx_data.de_state != NULL) {
324
760
        DetectEngineStateFree(tx->tx_data.de_state);
325
760
    }
326
327
367k
    if (tx->request) {
328
340k
        FTPFree(tx->request, tx->request_length);
329
340k
    }
330
331
367k
    FTPString *str = NULL;
332
664k
    while ((str = TAILQ_FIRST(&tx->response_list))) {
333
297k
        TAILQ_REMOVE(&tx->response_list, str, next);
334
297k
        FTPStringFree(str);
335
297k
    }
336
337
367k
    if (tx->tx_data.events) {
338
2.37k
        AppLayerDecoderEventsFreeEvents(&tx->tx_data.events);
339
2.37k
    }
340
341
367k
    FTPFree(tx, sizeof(*tx));
342
367k
}
343
344
typedef struct FtpInput_ {
345
    const uint8_t *buf;
346
    int32_t consumed;
347
    int32_t len;
348
    int32_t orig_len;
349
} FtpInput;
350
351
static AppLayerResult FTPGetLineForDirection(
352
        FtpState *state, FtpLineState *line, FtpInput *input, bool *current_line_truncated)
353
5.11M
{
354
5.11M
    SCEnter();
355
356
    /* we have run out of input */
357
5.11M
    if (input->len <= 0)
358
202k
        return APP_LAYER_ERROR;
359
360
4.90M
    const uint8_t *lf_idx = memchr(input->buf + input->consumed, 0x0a, input->len);
361
362
4.90M
    if (lf_idx == NULL) {
363
161k
        if (!(*current_line_truncated) && (uint32_t)input->len >= ftp_max_line_len) {
364
3.07k
            *current_line_truncated = true;
365
3.07k
            line->buf = input->buf;
366
3.07k
            line->len = ftp_max_line_len;
367
3.07k
            line->delim_len = 0;
368
3.07k
            input->len = 0;
369
3.07k
            SCReturnStruct(APP_LAYER_OK);
370
3.07k
        }
371
161k
        SCReturnStruct(APP_LAYER_INCOMPLETE(input->consumed, input->len + 1));
372
4.74M
    } else if (*current_line_truncated) {
373
        // Whatever came in with first LF should also get discarded
374
2.17k
        *current_line_truncated = false;
375
2.17k
        line->len = 0;
376
2.17k
        line->delim_len = 0;
377
2.17k
        input->len = 0;
378
2.17k
        SCReturnStruct(APP_LAYER_ERROR);
379
4.74M
    } else {
380
        // There could be one chunk of command data that has LF but post the line limit
381
        // e.g. input_len = 5077
382
        //      lf_idx = 5010
383
        //      max_line_len = 4096
384
4.74M
        uint32_t o_consumed = input->consumed;
385
4.74M
        input->consumed = lf_idx - input->buf + 1;
386
4.74M
        line->len = input->consumed - o_consumed;
387
4.74M
        input->len -= line->len;
388
4.74M
        line->lf_found = true;
389
4.74M
        DEBUG_VALIDATE_BUG_ON((input->consumed + input->len) != input->orig_len);
390
4.74M
        line->buf = input->buf + o_consumed;
391
4.74M
        if (line->len >= ftp_max_line_len) {
392
8.25k
            *current_line_truncated = true;
393
8.25k
            line->len = ftp_max_line_len;
394
8.25k
            SCReturnStruct(APP_LAYER_OK);
395
8.25k
        }
396
4.73M
        if (input->consumed >= 2 && input->buf[input->consumed - 2] == 0x0D) {
397
207k
            line->delim_len = 2;
398
207k
            line->len -= 2;
399
4.52M
        } else {
400
4.52M
            line->delim_len = 1;
401
4.52M
            line->len -= 1;
402
4.52M
        }
403
4.73M
        SCReturnStruct(APP_LAYER_OK);
404
4.74M
    }
405
4.90M
}
406
407
/**
408
 * \brief This function is called to determine and set which command is being
409
 * transferred to the ftp server
410
 * \param thread context
411
 * \param input input line of the command
412
 * \param len of the command
413
 * \param cmd_descriptor when the command has been parsed
414
 *
415
 * \retval 1 when the command is parsed, 0 otherwise
416
 */
417
static int FTPParseRequestCommand(
418
        FTPThreadCtx *td, FtpLineState *line, const FtpCommand **cmd_descriptor)
419
1.57M
{
420
1.57M
    SCEnter();
421
422
    /* I don't like this pmq reset here.  We'll devise a method later, that
423
     * should make the use of the mpm very efficient */
424
1.57M
    PmqReset(td->pmq);
425
1.57M
    int mpm_cnt = mpm_table[FTP_MPM].Search(
426
1.57M
            ftp_mpm_ctx, td->ftp_mpm_thread_ctx, td->pmq, line->buf, line->len);
427
1.57M
    if (mpm_cnt) {
428
340k
        *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]];
429
340k
        SCReturnInt(1);
430
340k
    }
431
432
1.23M
    *cmd_descriptor = NULL;
433
1.23M
    SCReturnInt(0);
434
1.57M
}
435
436
struct FtpTransferCmd {
437
    /** Need to look like a ExpectationData so DFree must
438
     *  be first field . */
439
    void (*DFree)(void *);
440
    uint64_t flow_id;
441
    uint8_t *file_name;
442
    uint16_t file_len;
443
    uint8_t direction; /**< direction in which the data will flow */
444
    FtpRequestCommand cmd;
445
};
446
447
static void FtpTransferCmdFree(void *data)
448
8.24k
{
449
8.24k
    struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data;
450
8.24k
    if (cmd == NULL)
451
0
        return;
452
8.24k
    if (cmd->file_name) {
453
7.98k
        FTPFree(cmd->file_name, cmd->file_len + 1);
454
7.98k
    }
455
8.24k
    FTPFree(cmd, sizeof(struct FtpTransferCmd));
456
8.24k
}
457
458
static uint32_t CopyCommandLine(uint8_t **dest, FtpLineState *line)
459
1.16M
{
460
1.16M
    if (likely(line->len)) {
461
1.16M
        uint8_t *where = FTPCalloc(line->len + 1, sizeof(char));
462
1.16M
        if (unlikely(where == NULL)) {
463
0
            return 0;
464
0
        }
465
1.16M
        memcpy(where, line->buf, line->len);
466
467
        /* Remove trailing newlines/carriage returns */
468
1.83M
        while (line->len && isspace((unsigned char)where[line->len - 1])) {
469
669k
            line->len--;
470
669k
        }
471
472
1.16M
        where[line->len] = '\0';
473
1.16M
        *dest = where;
474
1.16M
    }
475
    /* either 0 or actual */
476
1.16M
    return line->len ? line->len + 1 : 0;
477
1.16M
}
478
479
#include "util-print.h"
480
481
/**
482
 * \brief This function is called to retrieve a ftp request
483
 * \param ftp_state the ftp state structure for the parser
484
 *
485
 * \retval APP_LAYER_OK when input was process successfully
486
 * \retval APP_LAYER_ERROR when a unrecoverable error was encountered
487
 */
488
static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserState *pstate,
489
        StreamSlice stream_slice, void *local_data)
490
172k
{
491
172k
    FTPThreadCtx *thread_data = local_data;
492
493
172k
    SCEnter();
494
    /* PrintRawDataFp(stdout, input,input_len); */
495
496
172k
    FtpState *state = (FtpState *)ftp_state;
497
172k
    void *ptmp;
498
499
172k
    const uint8_t *input = StreamSliceGetData(&stream_slice);
500
172k
    uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
501
502
172k
    if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) {
503
438
        SCReturnStruct(APP_LAYER_OK);
504
171k
    } else if (input == NULL || input_len == 0) {
505
0
        SCReturnStruct(APP_LAYER_ERROR);
506
0
    }
507
508
171k
    FtpInput ftpi = { .buf = input, .len = input_len, .orig_len = input_len, .consumed = 0 };
509
171k
    FtpLineState line = { .buf = NULL, .len = 0, .delim_len = 0, .lf_found = false };
510
511
171k
    uint8_t direction = STREAM_TOSERVER;
512
171k
    AppLayerResult res;
513
3.03M
    while (1) {
514
3.03M
        res = FTPGetLineForDirection(state, &line, &ftpi, &state->current_line_truncated_ts);
515
3.03M
        if (res.status == 1) {
516
85.7k
            return res;
517
2.94M
        } else if (res.status == -1) {
518
84.3k
            break;
519
84.3k
        }
520
2.86M
        const FtpCommand *cmd_descriptor;
521
522
2.86M
        if (!FTPParseRequestCommand(thread_data, &line, &cmd_descriptor)) {
523
1.99M
            state->command = FTP_COMMAND_UNKNOWN;
524
1.99M
            continue;
525
1.99M
        }
526
527
872k
        state->command = cmd_descriptor->command;
528
872k
        FTPTransaction *tx = FTPTransactionCreate(state);
529
872k
        if (unlikely(tx == NULL))
530
4
            SCReturnStruct(APP_LAYER_ERROR);
531
872k
        tx->tx_data.updated_ts = true;
532
872k
        state->curr_tx = tx;
533
534
872k
        tx->command_descriptor = cmd_descriptor;
535
872k
        tx->request_length = CopyCommandLine(&tx->request, &line);
536
872k
        tx->request_truncated = state->current_line_truncated_ts;
537
538
872k
        if (line.lf_found) {
539
871k
            state->current_line_truncated_ts = false;
540
871k
        }
541
872k
        if (tx->request_truncated) {
542
2.36k
            AppLayerDecoderEventsSetEventRaw(&tx->tx_data.events, FtpEventRequestCommandTooLong);
543
2.36k
        }
544
545
        /* change direction (default to server) so expectation will handle
546
         * the correct message when expectation will match.
547
         * For ftp active mode, data connection direction is opposite to
548
         * control direction.
549
         */
550
872k
        if ((state->active && state->command == FTP_COMMAND_STOR) ||
551
871k
                (!state->active && state->command == FTP_COMMAND_RETR)) {
552
5.80k
            direction = STREAM_TOCLIENT;
553
5.80k
        }
554
555
872k
        switch (state->command) {
556
11.3k
            case FTP_COMMAND_EPRT:
557
                // fallthrough
558
34.6k
            case FTP_COMMAND_PORT:
559
34.6k
                if (line.len + 1 > state->port_line_size) {
560
                    /* Allocate an extra byte for a NULL terminator */
561
10.8k
                    ptmp = FTPRealloc(state->port_line, state->port_line_size, line.len);
562
10.8k
                    if (ptmp == NULL) {
563
0
                        if (state->port_line) {
564
0
                            FTPFree(state->port_line, state->port_line_size);
565
0
                            state->port_line = NULL;
566
0
                            state->port_line_size = 0;
567
0
                            state->port_line_len = 0;
568
0
                        }
569
0
                        SCReturnStruct(APP_LAYER_OK);
570
0
                    }
571
10.8k
                    state->port_line = ptmp;
572
10.8k
                    state->port_line_size = line.len;
573
10.8k
                }
574
34.6k
                memcpy(state->port_line, line.buf, line.len);
575
34.6k
                state->port_line_len = line.len;
576
34.6k
                break;
577
6.52k
            case FTP_COMMAND_RETR:
578
                // fallthrough
579
8.56k
            case FTP_COMMAND_STOR: {
580
                /* Ensure that there is a negotiated dyn port and a file
581
                 * name -- need more than 5 chars: cmd [4], space, <filename>
582
                 */
583
8.56k
                if (state->dyn_port == 0 || line.len < 6) {
584
273
                    SCReturnStruct(APP_LAYER_ERROR);
585
273
                }
586
8.28k
                struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd));
587
8.28k
                if (data == NULL)
588
0
                    SCReturnStruct(APP_LAYER_ERROR);
589
8.28k
                data->DFree = FtpTransferCmdFree;
590
                /*
591
                 * Min size has been checked in FTPParseRequestCommand
592
                 * SC_FILENAME_MAX includes the null
593
                 */
594
8.28k
                uint32_t file_name_len = MIN(SC_FILENAME_MAX - 1, line.len - 5);
595
#if SC_FILENAME_MAX > UINT16_MAX
596
#error SC_FILENAME_MAX is greater than UINT16_MAX
597
#endif
598
8.28k
                    data->file_name = FTPCalloc(file_name_len + 1, sizeof(char));
599
8.28k
                    if (data->file_name == NULL) {
600
0
                        FtpTransferCmdFree(data);
601
0
                        SCReturnStruct(APP_LAYER_ERROR);
602
0
                    }
603
8.28k
                    data->file_name[file_name_len] = 0;
604
8.28k
                    data->file_len = (uint16_t)file_name_len;
605
8.28k
                    memcpy(data->file_name, line.buf + 5, file_name_len);
606
8.28k
                    data->cmd = state->command;
607
8.28k
                    data->flow_id = FlowGetId(f);
608
8.28k
                    data->direction = direction;
609
8.28k
                    int ret = AppLayerExpectationCreate(f, direction,
610
8.28k
                                            0, state->dyn_port, ALPROTO_FTPDATA, data);
611
8.28k
                    if (ret == -1) {
612
0
                        FtpTransferCmdFree(data);
613
0
                        SCLogDebug("No expectation created.");
614
0
                        SCReturnStruct(APP_LAYER_ERROR);
615
8.28k
                    } else {
616
8.28k
                        SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].",
617
8.28k
                            state->active ? "to server" : "to client",
618
8.28k
                            state->dyn_port);
619
8.28k
                    }
620
621
                    /* reset the dyn port to avoid duplicate */
622
8.28k
                    state->dyn_port = 0;
623
                    /* reset active/passive indicator */
624
8.28k
                    state->active = false;
625
8.28k
            } break;
626
829k
            default:
627
829k
                break;
628
872k
        }
629
872k
        if (line.len >= ftp_max_line_len) {
630
1.26k
            ftpi.consumed = ftpi.len + 1;
631
1.26k
            break;
632
1.26k
        }
633
872k
    }
634
635
171k
    SCReturnStruct(APP_LAYER_OK);
636
171k
}
637
638
static int FTPParsePassiveResponse(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len)
639
236k
{
640
236k
    uint16_t dyn_port = rs_ftp_pasv_response(input, input_len);
641
236k
    if (dyn_port == 0) {
642
195k
        return -1;
643
195k
    }
644
40.2k
    SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port);
645
40.2k
    state->active = false;
646
40.2k
    state->dyn_port = dyn_port;
647
40.2k
    state->curr_tx->dyn_port = dyn_port;
648
40.2k
    state->curr_tx->active = false;
649
650
40.2k
    return 0;
651
236k
}
652
653
static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len)
654
110k
{
655
110k
    uint16_t dyn_port = rs_ftp_epsv_response(input, input_len);
656
110k
    if (dyn_port == 0) {
657
35.6k
        return -1;
658
35.6k
    }
659
75.0k
    SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port);
660
75.0k
    state->active = false;
661
75.0k
    state->dyn_port = dyn_port;
662
75.0k
    state->curr_tx->dyn_port = dyn_port;
663
75.0k
    state->curr_tx->active = false;
664
75.0k
    return 0;
665
110k
}
666
667
/**
668
 * \brief  Handle preliminary replies -- keep tx open
669
 * \retval bool True for a positive preliminary reply; false otherwise
670
 *
671
 * 1yz   Positive Preliminary reply
672
 *
673
 *                The requested action is being initiated; expect another
674
 *                               reply before proceeding with a new command
675
 */
676
static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len)
677
1.38M
{
678
1.38M
    return input_len >= 4 && isdigit(input[0]) && input[0] == '1' &&
679
1.38M
           isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]);
680
1.38M
}
681
682
/**
683
 * \brief This function is called to retrieve a ftp response
684
 * \param ftp_state the ftp state structure for the parser
685
 * \param input input line of the command
686
 * \param input_len length of the request
687
 * \param output the resulting output
688
 *
689
 * \retval 1 when the command is parsed, 0 otherwise
690
 */
691
static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
692
        StreamSlice stream_slice, void *local_data)
693
78.2k
{
694
78.2k
    FtpState *state = (FtpState *)ftp_state;
695
696
78.2k
    const uint8_t *input = StreamSliceGetData(&stream_slice);
697
78.2k
    uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
698
699
78.2k
    if (unlikely(input_len == 0)) {
700
151
        SCReturnStruct(APP_LAYER_OK);
701
151
    }
702
78.1k
    FtpInput ftpi = { .buf = input, .len = input_len, .orig_len = input_len, .consumed = 0 };
703
78.1k
    FtpLineState line = { .buf = NULL, .len = 0, .delim_len = 0, .lf_found = false };
704
705
78.1k
    FTPTransaction *lasttx = TAILQ_FIRST(&state->tx_list);
706
78.1k
    AppLayerResult res;
707
814k
    while (1) {
708
814k
        res = FTPGetLineForDirection(state, &line, &ftpi, &state->current_line_truncated_tc);
709
814k
        if (res.status == 1) {
710
36.7k
            return res;
711
777k
        } else if (res.status == -1) {
712
40.4k
            break;
713
40.4k
        }
714
737k
        FTPTransaction *tx = FTPGetOldestTx(state, lasttx);
715
737k
        if (tx == NULL) {
716
27.2k
            tx = FTPTransactionCreate(state);
717
27.2k
        }
718
737k
        if (unlikely(tx == NULL)) {
719
0
            SCReturnStruct(APP_LAYER_ERROR);
720
0
        }
721
737k
        lasttx = tx;
722
737k
        tx->tx_data.updated_tc = true;
723
737k
        if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) {
724
            /* unknown */
725
218k
            tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX - 1];
726
218k
        }
727
728
737k
        state->curr_tx = tx;
729
737k
        uint16_t dyn_port;
730
737k
        switch (state->command) {
731
0
            case FTP_COMMAND_AUTH_TLS:
732
0
                if (line.len >= 4 && SCMemcmp("234 ", line.buf, 4) == 0) {
733
0
                    AppLayerRequestProtocolTLSUpgrade(f);
734
0
                }
735
0
                break;
736
737
199k
            case FTP_COMMAND_EPRT:
738
199k
                dyn_port = rs_ftp_active_eprt(state->port_line, state->port_line_len);
739
199k
                if (dyn_port == 0) {
740
197k
                    goto tx_complete;
741
197k
                }
742
1.65k
                state->dyn_port = dyn_port;
743
1.65k
                state->active = true;
744
1.65k
                tx->dyn_port = dyn_port;
745
1.65k
                tx->active = true;
746
1.65k
                SCLogDebug("FTP active mode (v6): dynamic port %" PRIu16 "", dyn_port);
747
1.65k
                break;
748
749
144k
            case FTP_COMMAND_PORT:
750
144k
                dyn_port = rs_ftp_active_port(state->port_line, state->port_line_len);
751
144k
                if (dyn_port == 0) {
752
138k
                    goto tx_complete;
753
138k
                }
754
6.17k
                state->dyn_port = dyn_port;
755
6.17k
                state->active = true;
756
6.17k
                tx->dyn_port = state->dyn_port;
757
6.17k
                tx->active = true;
758
6.17k
                SCLogDebug("FTP active mode (v4): dynamic port %" PRIu16 "", dyn_port);
759
6.17k
                break;
760
761
145k
            case FTP_COMMAND_PASV:
762
145k
                if (line.len >= 4 && SCMemcmp("227 ", line.buf, 4) == 0) {
763
103k
                    FTPParsePassiveResponse(f, ftp_state, line.buf, line.len);
764
103k
                }
765
145k
                break;
766
767
21.1k
            case FTP_COMMAND_EPSV:
768
21.1k
                if (line.len >= 4 && SCMemcmp("229 ", line.buf, 4) == 0) {
769
11.2k
                    FTPParsePassiveResponseV6(f, ftp_state, line.buf, line.len);
770
11.2k
                }
771
21.1k
                break;
772
226k
            default:
773
226k
                break;
774
737k
        }
775
776
401k
        if (likely(line.len)) {
777
297k
            FTPString *response = FTPStringAlloc();
778
297k
            if (likely(response)) {
779
297k
                response->len = CopyCommandLine(&response->str, &line);
780
297k
                response->truncated = state->current_line_truncated_tc;
781
297k
                if (response->truncated) {
782
5.07k
                    AppLayerDecoderEventsSetEventRaw(
783
5.07k
                            &tx->tx_data.events, FtpEventResponseCommandTooLong);
784
5.07k
                }
785
297k
                if (line.lf_found) {
786
296k
                    state->current_line_truncated_tc = false;
787
296k
                }
788
297k
                TAILQ_INSERT_TAIL(&tx->response_list, response, next);
789
297k
            }
790
297k
        }
791
792
        /* Handle preliminary replies -- keep tx open */
793
401k
        if (FTPIsPPR(line.buf, line.len)) {
794
11.7k
            continue;
795
11.7k
        }
796
725k
    tx_complete:
797
725k
        tx->done = true;
798
799
725k
        if (line.len >= ftp_max_line_len) {
800
925
            ftpi.consumed = ftpi.len + 1;
801
925
            break;
802
925
        }
803
725k
    }
804
805
78.1k
    SCReturnStruct(APP_LAYER_OK);
806
78.1k
}
807
808
809
#ifdef DEBUG
810
static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER;
811
static uint64_t ftp_state_memuse = 0;
812
static uint64_t ftp_state_memcnt = 0;
813
#endif
814
815
static void *FTPStateAlloc(void *orig_state, AppProto proto_orig)
816
11.6k
{
817
11.6k
    void *s = FTPCalloc(1, sizeof(FtpState));
818
11.6k
    if (unlikely(s == NULL))
819
0
        return NULL;
820
821
11.6k
    FtpState *ftp_state = (FtpState *) s;
822
11.6k
    TAILQ_INIT(&ftp_state->tx_list);
823
824
#ifdef DEBUG
825
    SCMutexLock(&ftp_state_mem_lock);
826
    ftp_state_memcnt++;
827
    ftp_state_memuse+=sizeof(FtpState);
828
    SCMutexUnlock(&ftp_state_mem_lock);
829
#endif
830
11.6k
    return s;
831
11.6k
}
832
833
static void FTPStateFree(void *s)
834
5.44k
{
835
5.44k
    FtpState *fstate = (FtpState *) s;
836
5.44k
    if (fstate->port_line != NULL)
837
3.09k
        FTPFree(fstate->port_line, fstate->port_line_size);
838
839
5.44k
    FTPTransaction *tx = NULL;
840
36.3k
    while ((tx = TAILQ_FIRST(&fstate->tx_list))) {
841
30.9k
        TAILQ_REMOVE(&fstate->tx_list, tx, next);
842
30.9k
        SCLogDebug("[%s] state %p id %" PRIu64 ", Freeing %d bytes at %p",
843
30.9k
                tx->command_descriptor->command_name, s, tx->tx_id, tx->request_length,
844
30.9k
                tx->request);
845
30.9k
        FTPTransactionFree(tx);
846
30.9k
    }
847
848
5.44k
    FTPFree(s, sizeof(FtpState));
849
#ifdef DEBUG
850
    SCMutexLock(&ftp_state_mem_lock);
851
    ftp_state_memcnt--;
852
    ftp_state_memuse-=sizeof(FtpState);
853
    SCMutexUnlock(&ftp_state_mem_lock);
854
#endif
855
5.44k
}
856
857
/**
858
 * \brief This function returns the oldest open transaction; if none
859
 * are open, then the oldest transaction is returned
860
 * \param ftp_state the ftp state structure for the parser
861
 * \param starttx the ftp transaction where to start looking
862
 *
863
 * \retval transaction pointer when a transaction was found; NULL otherwise.
864
 */
865
static FTPTransaction *FTPGetOldestTx(const FtpState *ftp_state, FTPTransaction *starttx)
866
1.88M
{
867
1.88M
    if (unlikely(!ftp_state)) {
868
0
        SCLogDebug("NULL state object; no transactions available");
869
0
        return NULL;
870
0
    }
871
1.88M
    FTPTransaction *tx = starttx;
872
1.88M
    FTPTransaction *lasttx = NULL;
873
3.61M
    while(tx != NULL) {
874
        /* Return oldest open tx */
875
2.25M
        if (!tx->done) {
876
523k
            SCLogDebug("Returning tx %p id %"PRIu64, tx, tx->tx_id);
877
523k
            return tx;
878
523k
        }
879
        /* save for the end */
880
1.73M
        lasttx = tx;
881
1.73M
        tx = TAILQ_NEXT(tx, next);
882
1.73M
    }
883
    /* All tx are closed; return last element */
884
1.36M
    if (lasttx)
885
1.28M
        SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id);
886
1.36M
    return lasttx;
887
1.88M
}
888
889
static void *FTPGetTx(void *state, uint64_t tx_id)
890
2.34k
{
891
2.34k
    FtpState *ftp_state = (FtpState *)state;
892
2.34k
    if (ftp_state) {
893
2.34k
        FTPTransaction *tx = NULL;
894
895
2.34k
        if (ftp_state->curr_tx == NULL)
896
257
            return NULL;
897
2.08k
        if (ftp_state->curr_tx->tx_id == tx_id)
898
1.10k
            return ftp_state->curr_tx;
899
900
2.08k
        TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
901
1.26k
            if (tx->tx_id == tx_id)
902
802
                return tx;
903
1.26k
        }
904
981
    }
905
179
    return NULL;
906
2.34k
}
907
908
static AppLayerTxData *FTPGetTxData(void *vtx)
909
4.30M
{
910
4.30M
    FTPTransaction *tx = (FTPTransaction *)vtx;
911
4.30M
    return &tx->tx_data;
912
4.30M
}
913
914
static AppLayerStateData *FTPGetStateData(void *vstate)
915
4.43k
{
916
4.43k
    FtpState *s = (FtpState *)vstate;
917
4.43k
    return &s->state_data;
918
4.43k
}
919
920
static void FTPStateTransactionFree(void *state, uint64_t tx_id)
921
750k
{
922
750k
    FtpState *ftp_state = state;
923
750k
    FTPTransaction *tx = NULL;
924
750k
    TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
925
750k
        if (tx_id < tx->tx_id)
926
0
            break;
927
750k
        else if (tx_id > tx->tx_id)
928
753
            continue;
929
930
750k
        if (tx == ftp_state->curr_tx)
931
122k
            ftp_state->curr_tx = NULL;
932
750k
        TAILQ_REMOVE(&ftp_state->tx_list, tx, next);
933
750k
        FTPTransactionFree(tx);
934
750k
        break;
935
750k
    }
936
750k
}
937
938
static uint64_t FTPGetTxCnt(void *state)
939
1.46M
{
940
1.46M
    uint64_t cnt = 0;
941
1.46M
    FtpState *ftp_state = state;
942
1.46M
    if (ftp_state) {
943
1.46M
        cnt = ftp_state->tx_cnt;
944
1.46M
    }
945
1.46M
    SCLogDebug("returning state %p %"PRIu64, state, cnt);
946
1.46M
    return cnt;
947
1.46M
}
948
949
static int FTPGetAlstateProgress(void *vtx, uint8_t direction)
950
2.13M
{
951
2.13M
    SCLogDebug("tx %p", vtx);
952
2.13M
    FTPTransaction *tx = vtx;
953
954
2.13M
    if (!tx->done) {
955
1.33M
        if (direction == STREAM_TOSERVER && tx->command_descriptor->command == FTP_COMMAND_PORT) {
956
20.4k
            return FTP_STATE_PORT_DONE;
957
20.4k
        }
958
1.30M
        return FTP_STATE_IN_PROGRESS;
959
1.33M
    }
960
961
807k
    return FTP_STATE_FINISHED;
962
2.13M
}
963
964
965
static int FTPRegisterPatternsForProtocolDetection(void)
966
34
{
967
34
    if (AppLayerProtoDetectPMRegisterPatternCI(
968
34
                IPPROTO_TCP, ALPROTO_FTP, "220 (", 5, 0, STREAM_TOCLIENT) < 0) {
969
0
        return -1;
970
0
    }
971
34
    if (AppLayerProtoDetectPMRegisterPatternCI(
972
34
                IPPROTO_TCP, ALPROTO_FTP, "FEAT", 4, 0, STREAM_TOSERVER) < 0) {
973
0
        return -1;
974
0
    }
975
34
    if (AppLayerProtoDetectPMRegisterPatternCI(
976
34
                IPPROTO_TCP, ALPROTO_FTP, "USER ", 5, 0, STREAM_TOSERVER) < 0) {
977
0
        return -1;
978
0
    }
979
34
    if (AppLayerProtoDetectPMRegisterPatternCI(
980
34
                IPPROTO_TCP, ALPROTO_FTP, "PASS ", 5, 0, STREAM_TOSERVER) < 0) {
981
0
        return -1;
982
0
    }
983
34
    if (AppLayerProtoDetectPMRegisterPatternCI(
984
34
                IPPROTO_TCP, ALPROTO_FTP, "PORT ", 5, 0, STREAM_TOSERVER) < 0) {
985
0
        return -1;
986
0
    }
987
988
34
    return 0;
989
34
}
990
991
992
static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
993
994
/**
995
 * \brief This function is called to retrieve a ftp request
996
 * \param ftp_state the ftp state structure for the parser
997
 * \param output the resulting output
998
 *
999
 * \retval 1 when the command is parsed, 0 otherwise
1000
 */
1001
static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
1002
        AppLayerParserState *pstate, StreamSlice stream_slice, void *local_data, uint8_t direction)
1003
980
{
1004
980
    const uint8_t *input = StreamSliceGetData(&stream_slice);
1005
980
    uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
1006
980
    const bool eof = (direction & STREAM_TOSERVER)
1007
980
                             ? AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) != 0
1008
980
                             : AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) != 0;
1009
1010
980
    SCTxDataUpdateFileFlags(&ftpdata_state->tx_data, ftpdata_state->state_data.file_flags);
1011
980
    if (ftpdata_state->tx_data.file_tx == 0)
1012
103
        ftpdata_state->tx_data.file_tx = direction & (STREAM_TOSERVER | STREAM_TOCLIENT);
1013
980
    if (direction & STREAM_TOSERVER) {
1014
813
        ftpdata_state->tx_data.updated_ts = true;
1015
813
    } else {
1016
167
        ftpdata_state->tx_data.updated_tc = true;
1017
167
    }
1018
    /* we depend on detection engine for file pruning */
1019
980
    const uint16_t flags = FileFlowFlagsToFlags(ftpdata_state->tx_data.file_flags, direction);
1020
980
    int ret = 0;
1021
1022
980
    SCLogDebug("FTP-DATA input_len %u flags %04x dir %d/%s EOF %s", input_len, flags, direction,
1023
980
            (direction & STREAM_TOSERVER) ? "toserver" : "toclient", eof ? "true" : "false");
1024
1025
980
    SCLogDebug("FTP-DATA flags %04x dir %d", flags, direction);
1026
980
    if (input_len && ftpdata_state->files == NULL) {
1027
103
        struct FtpTransferCmd *data =
1028
103
                (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId());
1029
103
        if (data == NULL) {
1030
32
            SCReturnStruct(APP_LAYER_ERROR);
1031
32
        }
1032
1033
        /* we shouldn't get data in the wrong dir. Don't set things up for this dir */
1034
71
        if ((direction & data->direction) == 0) {
1035
            // TODO set event for data in wrong direction
1036
0
            SCLogDebug("input %u not for our direction (%s): %s/%s", input_len,
1037
0
                    (direction & STREAM_TOSERVER) ? "toserver" : "toclient",
1038
0
                    data->cmd == FTP_COMMAND_STOR ? "STOR" : "RETR",
1039
0
                    (data->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1040
0
            SCReturnStruct(APP_LAYER_OK);
1041
0
        }
1042
1043
71
        ftpdata_state->files = FileContainerAlloc();
1044
71
        if (ftpdata_state->files == NULL) {
1045
0
            FlowFreeStorageById(f, AppLayerExpectationGetFlowId());
1046
0
            SCReturnStruct(APP_LAYER_ERROR);
1047
0
        }
1048
1049
71
        ftpdata_state->file_name = data->file_name;
1050
71
        ftpdata_state->file_len = data->file_len;
1051
71
        data->file_name = NULL;
1052
71
        data->file_len = 0;
1053
71
        f->parent_id = data->flow_id;
1054
71
        ftpdata_state->command = data->cmd;
1055
71
        switch (data->cmd) {
1056
23
            case FTP_COMMAND_STOR:
1057
23
                ftpdata_state->direction = data->direction;
1058
23
                SCLogDebug("STOR data to %s",
1059
23
                        (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1060
23
                break;
1061
48
            case FTP_COMMAND_RETR:
1062
48
                ftpdata_state->direction = data->direction;
1063
48
                SCLogDebug("RETR data to %s",
1064
48
                        (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1065
48
                break;
1066
0
            default:
1067
0
                break;
1068
71
        }
1069
1070
        /* open with fixed track_id 0 as we can have just one
1071
         * file per ftp-data flow. */
1072
71
        if (FileOpenFileWithId(ftpdata_state->files, &sbcfg,
1073
71
                         0ULL, (uint8_t *) ftpdata_state->file_name,
1074
71
                         ftpdata_state->file_len,
1075
71
                         input, input_len, flags) != 0) {
1076
0
            SCLogDebug("Can't open file");
1077
0
            ret = -1;
1078
0
        }
1079
71
        FlowFreeStorageById(f, AppLayerExpectationGetFlowId());
1080
71
        ftpdata_state->tx_data.files_opened = 1;
1081
877
    } else {
1082
877
        if (ftpdata_state->state == FTPDATA_STATE_FINISHED) {
1083
4
            SCLogDebug("state is already finished");
1084
4
            DEBUG_VALIDATE_BUG_ON(input_len); // data after state finished is a bug.
1085
4
            SCReturnStruct(APP_LAYER_OK);
1086
4
        }
1087
873
        if ((direction & ftpdata_state->direction) == 0) {
1088
9
            if (input_len) {
1089
                // TODO set event for data in wrong direction
1090
0
            }
1091
9
            SCLogDebug("input %u not for us (%s): %s/%s", input_len,
1092
9
                    (direction & STREAM_TOSERVER) ? "toserver" : "toclient",
1093
9
                    ftpdata_state->command == FTP_COMMAND_STOR ? "STOR" : "RETR",
1094
9
                    (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient");
1095
9
            SCReturnStruct(APP_LAYER_OK);
1096
9
        }
1097
864
        if (input_len != 0) {
1098
854
            ret = FileAppendData(ftpdata_state->files, &sbcfg, input, input_len);
1099
854
            if (ret == -2) {
1100
0
                ret = 0;
1101
0
                SCLogDebug("FileAppendData() - file no longer being extracted");
1102
0
                goto out;
1103
854
            } else if (ret < 0) {
1104
0
                SCLogDebug("FileAppendData() failed: %d", ret);
1105
0
                ret = -2;
1106
0
                goto out;
1107
0
            }
1108
854
        }
1109
864
    }
1110
1111
935
    BUG_ON((direction & ftpdata_state->direction) == 0); // should be unreachable
1112
935
    if (eof) {
1113
14
        ret = FileCloseFile(ftpdata_state->files, &sbcfg, NULL, 0, flags);
1114
14
        ftpdata_state->state = FTPDATA_STATE_FINISHED;
1115
14
        SCLogDebug("closed because of eof: state now FTPDATA_STATE_FINISHED");
1116
14
    }
1117
935
out:
1118
935
    if (ret < 0) {
1119
0
        SCReturnStruct(APP_LAYER_ERROR);
1120
0
    }
1121
935
    SCReturnStruct(APP_LAYER_OK);
1122
935
}
1123
1124
static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state, AppLayerParserState *pstate,
1125
        StreamSlice stream_slice, void *local_data)
1126
2.75k
{
1127
2.75k
    return FTPDataParse(f, ftp_state, pstate, stream_slice, local_data, STREAM_TOSERVER);
1128
2.75k
}
1129
1130
static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
1131
        StreamSlice stream_slice, void *local_data)
1132
1.65k
{
1133
1.65k
    return FTPDataParse(f, ftp_state, pstate, stream_slice, local_data, STREAM_TOCLIENT);
1134
1.65k
}
1135
1136
#ifdef DEBUG
1137
static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER;
1138
static uint64_t ftpdata_state_memuse = 0;
1139
static uint64_t ftpdata_state_memcnt = 0;
1140
#endif
1141
1142
static void *FTPDataStateAlloc(void *orig_state, AppProto proto_orig)
1143
321
{
1144
321
    void *s = FTPCalloc(1, sizeof(FtpDataState));
1145
321
    if (unlikely(s == NULL))
1146
0
        return NULL;
1147
1148
321
    FtpDataState *state = (FtpDataState *) s;
1149
321
    state->state = FTPDATA_STATE_IN_PROGRESS;
1150
1151
#ifdef DEBUG
1152
    SCMutexLock(&ftpdata_state_mem_lock);
1153
    ftpdata_state_memcnt++;
1154
    ftpdata_state_memuse+=sizeof(FtpDataState);
1155
    SCMutexUnlock(&ftpdata_state_mem_lock);
1156
#endif
1157
321
    return s;
1158
321
}
1159
1160
static void FTPDataStateFree(void *s)
1161
103
{
1162
103
    FtpDataState *fstate = (FtpDataState *) s;
1163
1164
103
    if (fstate->tx_data.de_state != NULL) {
1165
22
        DetectEngineStateFree(fstate->tx_data.de_state);
1166
22
    }
1167
103
    if (fstate->file_name != NULL) {
1168
71
        FTPFree(fstate->file_name, fstate->file_len + 1);
1169
71
    }
1170
1171
103
    FileContainerFree(fstate->files, &sbcfg);
1172
1173
103
    FTPFree(s, sizeof(FtpDataState));
1174
#ifdef DEBUG
1175
    SCMutexLock(&ftpdata_state_mem_lock);
1176
    ftpdata_state_memcnt--;
1177
    ftpdata_state_memuse-=sizeof(FtpDataState);
1178
    SCMutexUnlock(&ftpdata_state_mem_lock);
1179
#endif
1180
103
}
1181
1182
static AppLayerTxData *FTPDataGetTxData(void *vtx)
1183
21.3k
{
1184
21.3k
    FtpDataState *ftp_state = (FtpDataState *)vtx;
1185
21.3k
    return &ftp_state->tx_data;
1186
21.3k
}
1187
1188
static AppLayerStateData *FTPDataGetStateData(void *vstate)
1189
263
{
1190
263
    FtpDataState *ftp_state = (FtpDataState *)vstate;
1191
263
    return &ftp_state->state_data;
1192
263
}
1193
1194
static void FTPDataStateTransactionFree(void *state, uint64_t tx_id)
1195
98
{
1196
    /* do nothing */
1197
98
}
1198
1199
static void *FTPDataGetTx(void *state, uint64_t tx_id)
1200
16.3k
{
1201
16.3k
    FtpDataState *ftp_state = (FtpDataState *)state;
1202
16.3k
    return ftp_state;
1203
16.3k
}
1204
1205
static uint64_t FTPDataGetTxCnt(void *state)
1206
30.1k
{
1207
    /* ftp-data is single tx */
1208
30.1k
    return 1;
1209
30.1k
}
1210
1211
static int FTPDataGetAlstateProgress(void *tx, uint8_t direction)
1212
26.5k
{
1213
26.5k
    FtpDataState *ftpdata_state = (FtpDataState *)tx;
1214
26.5k
    if (direction == ftpdata_state->direction)
1215
15.6k
        return ftpdata_state->state;
1216
10.8k
    else
1217
10.8k
        return FTPDATA_STATE_FINISHED;
1218
26.5k
}
1219
1220
static AppLayerGetFileState FTPDataStateGetTxFiles(void *_state, void *tx, uint8_t direction)
1221
16.1k
{
1222
16.1k
    FtpDataState *ftpdata_state = (FtpDataState *)tx;
1223
16.1k
    AppLayerGetFileState files = { .fc = NULL, .cfg = &sbcfg };
1224
1225
16.1k
    if (direction == ftpdata_state->direction)
1226
9.45k
        files.fc = ftpdata_state->files;
1227
1228
16.1k
    return files;
1229
16.1k
}
1230
1231
static void FTPSetMpmState(void)
1232
34
{
1233
34
    ftp_mpm_ctx = SCMalloc(sizeof(MpmCtx));
1234
34
    if (unlikely(ftp_mpm_ctx == NULL)) {
1235
0
        exit(EXIT_FAILURE);
1236
0
    }
1237
34
    memset(ftp_mpm_ctx, 0, sizeof(MpmCtx));
1238
34
    MpmInitCtx(ftp_mpm_ctx, FTP_MPM);
1239
1240
34
    uint32_t i = 0;
1241
1.70k
    for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) {
1242
1.66k
        const FtpCommand *cmd = &FtpCommands[i];
1243
1.66k
        if (cmd->command_length == 0)
1244
34
            continue;
1245
1246
1.63k
        MpmAddPatternCI(ftp_mpm_ctx,
1247
1.63k
                       (uint8_t *)cmd->command_name,
1248
1.63k
                       cmd->command_length,
1249
1.63k
                       0 /* defunct */, 0 /* defunct */,
1250
1.63k
                       i /*  id */, i /* rule id */ , 0 /* no flags */);
1251
1.63k
    }
1252
1253
34
    mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx);
1254
1255
34
}
1256
1257
static void FTPFreeMpmState(void)
1258
0
{
1259
0
    if (ftp_mpm_ctx != NULL) {
1260
0
        mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx);
1261
0
        SCFree(ftp_mpm_ctx);
1262
0
        ftp_mpm_ctx = NULL;
1263
0
    }
1264
0
}
1265
1266
/** \brief FTP tx iterator, specialized for its linked list
1267
 *
1268
 *  \retval txptr or NULL if no more txs in list
1269
 */
1270
static AppLayerGetTxIterTuple FTPGetTxIterator(const uint8_t ipproto, const AppProto alproto,
1271
        void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state)
1272
4.28M
{
1273
4.28M
    FtpState *ftp_state = (FtpState *)alstate;
1274
4.28M
    AppLayerGetTxIterTuple no_tuple = { NULL, 0, false };
1275
4.28M
    if (ftp_state) {
1276
4.28M
        FTPTransaction *tx_ptr;
1277
4.28M
        if (state->un.ptr == NULL) {
1278
729k
            tx_ptr = TAILQ_FIRST(&ftp_state->tx_list);
1279
3.56M
        } else {
1280
3.56M
            tx_ptr = (FTPTransaction *)state->un.ptr;
1281
3.56M
        }
1282
4.28M
        if (tx_ptr) {
1283
4.18M
            while (tx_ptr->tx_id < min_tx_id) {
1284
93.4k
                tx_ptr = TAILQ_NEXT(tx_ptr, next);
1285
93.4k
                if (!tx_ptr) {
1286
18.3k
                    return no_tuple;
1287
18.3k
                }
1288
93.4k
            }
1289
4.08M
            if (tx_ptr->tx_id >= max_tx_id) {
1290
0
                return no_tuple;
1291
0
            }
1292
4.08M
            state->un.ptr = TAILQ_NEXT(tx_ptr, next);
1293
4.08M
            AppLayerGetTxIterTuple tuple = {
1294
4.08M
                .tx_ptr = tx_ptr,
1295
4.08M
                .tx_id = tx_ptr->tx_id,
1296
4.08M
                .has_next = (state->un.ptr != NULL),
1297
4.08M
            };
1298
4.08M
            return tuple;
1299
4.08M
        }
1300
4.28M
    }
1301
182k
    return no_tuple;
1302
4.28M
}
1303
1304
void RegisterFTPParsers(void)
1305
76
{
1306
76
    const char *proto_name = "ftp";
1307
76
    const char *proto_data_name = "ftp-data";
1308
1309
    /** FTP */
1310
76
    if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
1311
76
        AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name);
1312
76
        if (FTPRegisterPatternsForProtocolDetection() < 0 )
1313
0
            return;
1314
76
        AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name);
1315
76
    }
1316
1317
76
    if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
1318
76
        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER,
1319
76
                                     FTPParseRequest);
1320
76
        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT,
1321
76
                                     FTPParseResponse);
1322
76
        AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
1323
76
        AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT);
1324
1325
76
        AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree);
1326
1327
76
        AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx);
1328
76
        AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxData);
1329
76
        AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxIterator);
1330
76
        AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetStateData);
1331
1332
76
        AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc,
1333
76
                                               FTPLocalStorageFree);
1334
76
        AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt);
1335
1336
76
        AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress);
1337
1338
76
        AppLayerParserRegisterStateProgressCompletionStatus(
1339
76
                ALPROTO_FTP, FTP_STATE_FINISHED, FTP_STATE_FINISHED);
1340
1341
76
        AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA);
1342
76
        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER,
1343
76
                                     FTPDataParseRequest);
1344
76
        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT,
1345
76
                                     FTPDataParseResponse);
1346
76
        AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree);
1347
76
        AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT);
1348
76
        AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree);
1349
1350
76
        AppLayerParserRegisterGetTxFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetTxFiles);
1351
1352
76
        AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx);
1353
76
        AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxData);
1354
76
        AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetStateData);
1355
1356
76
        AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt);
1357
1358
76
        AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress);
1359
1360
76
        AppLayerParserRegisterStateProgressCompletionStatus(
1361
76
                ALPROTO_FTPDATA, FTPDATA_STATE_FINISHED, FTPDATA_STATE_FINISHED);
1362
1363
76
        AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_FTP, ftp_get_event_info);
1364
76
        AppLayerParserRegisterGetEventInfoById(IPPROTO_TCP, ALPROTO_FTP, ftp_get_event_info_by_id);
1365
1366
76
        sbcfg.buf_size = 4096;
1367
76
        sbcfg.Calloc = FTPCalloc;
1368
76
        sbcfg.Realloc = FTPRealloc;
1369
76
        sbcfg.Free = FTPFree;
1370
1371
76
        FTPParseMemcap();
1372
76
    } else {
1373
0
        SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
1374
0
                  "still on.", proto_name);
1375
0
    }
1376
1377
76
    FTPSetMpmState();
1378
1379
#ifdef UNITTESTS
1380
    AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests);
1381
#endif
1382
76
}
1383
1384
void FTPAtExitPrintStats(void)
1385
0
{
1386
#ifdef DEBUG
1387
    SCMutexLock(&ftp_state_mem_lock);
1388
    SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"",
1389
               ftp_state_memcnt, ftp_state_memuse);
1390
    SCMutexUnlock(&ftp_state_mem_lock);
1391
#endif
1392
0
}
1393
1394
1395
/*
1396
 * \brief Returns the ending offset of the next line from a multi-line buffer.
1397
 *
1398
 * "Buffer" refers to a FTP response in a single buffer containing multiple lines.
1399
 * Here, "next line" is defined as terminating on
1400
 * - Newline character
1401
 * - Null character
1402
 *
1403
 * \param buffer Contains zero or more characters.
1404
 * \param len Size, in bytes, of buffer.
1405
 *
1406
 * \retval Offset from the start of buffer indicating the where the
1407
 * next "line ends". The characters between the input buffer and this
1408
 * value comprise the line.
1409
 *
1410
 * NULL is found first or a newline isn't found, then UINT16_MAX is returned.
1411
 */
1412
uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len)
1413
161k
{
1414
161k
    if (!buffer || *buffer == '\0') {
1415
87.6k
        return UINT16_MAX;
1416
87.6k
    }
1417
1418
74.3k
    const char *c = strchr(buffer, '\n');
1419
74.3k
    return c == NULL ? len : (uint16_t)(c - buffer + 1);
1420
161k
}
1421
1422
void EveFTPDataAddMetadata(const Flow *f, JsonBuilder *jb)
1423
46
{
1424
46
    const FtpDataState *ftp_state = NULL;
1425
46
    if (f->alstate == NULL)
1426
0
        return;
1427
1428
46
    ftp_state = (FtpDataState *)f->alstate;
1429
1430
46
    if (ftp_state->file_name) {
1431
46
        jb_set_string_from_bytes(jb, "filename", ftp_state->file_name, ftp_state->file_len);
1432
46
    }
1433
46
    switch (ftp_state->command) {
1434
9
        case FTP_COMMAND_STOR:
1435
9
            JB_SET_STRING(jb, "command", "STOR");
1436
9
            break;
1437
37
        case FTP_COMMAND_RETR:
1438
37
            JB_SET_STRING(jb, "command", "RETR");
1439
37
            break;
1440
0
        default:
1441
0
            break;
1442
46
    }
1443
46
}
1444
1445
/**
1446
 * \brief Free memory allocated for global FTP parser state.
1447
 */
1448
void FTPParserCleanup(void)
1449
0
{
1450
0
    FTPFreeMpmState();
1451
0
}
1452
1453
/* UNITTESTS */
1454
#ifdef UNITTESTS
1455
#include "stream-tcp.h"
1456
1457
/** \test Send a get request in one chunk. */
1458
static int FTPParserTest01(void)
1459
{
1460
    Flow f;
1461
    uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n";
1462
    uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */
1463
    TcpSession ssn;
1464
    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1465
1466
    memset(&f, 0, sizeof(f));
1467
    memset(&ssn, 0, sizeof(ssn));
1468
1469
    f.protoctx = (void *)&ssn;
1470
    f.proto = IPPROTO_TCP;
1471
    f.alproto = ALPROTO_FTP;
1472
1473
    StreamTcpInitConfig(true);
1474
1475
    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1476
                                STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen);
1477
    FAIL_IF(r != 0);
1478
1479
    FtpState *ftp_state = f.alstate;
1480
    FAIL_IF_NULL(ftp_state);
1481
    FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
1482
1483
    AppLayerParserThreadCtxFree(alp_tctx);
1484
    StreamTcpFreeConfig(true);
1485
    PASS;
1486
}
1487
1488
/** \test Supply RETR without a filename */
1489
static int FTPParserTest11(void)
1490
{
1491
    Flow f;
1492
    uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1493
    uint8_t ftpbuf2[] = "RETR\r\n";
1494
    uint8_t ftpbuf3[] = "227 OK\r\n";
1495
    TcpSession ssn;
1496
1497
    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1498
1499
    memset(&f, 0, sizeof(f));
1500
    memset(&ssn, 0, sizeof(ssn));
1501
1502
    f.protoctx = (void *)&ssn;
1503
    f.proto = IPPROTO_TCP;
1504
    f.alproto = ALPROTO_FTP;
1505
1506
    StreamTcpInitConfig(true);
1507
1508
    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1509
                                STREAM_TOSERVER | STREAM_START, ftpbuf1,
1510
                                sizeof(ftpbuf1) - 1);
1511
    FAIL_IF(r != 0);
1512
1513
    /* Response */
1514
    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1515
                                STREAM_TOCLIENT,
1516
                                ftpbuf3,
1517
                                sizeof(ftpbuf3) - 1);
1518
    FAIL_IF(r != 0);
1519
1520
    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1521
                                STREAM_TOSERVER, ftpbuf2,
1522
                                sizeof(ftpbuf2) - 1);
1523
    FAIL_IF(r == 0);
1524
1525
    FtpState *ftp_state = f.alstate;
1526
    FAIL_IF_NULL(ftp_state);
1527
1528
    FAIL_IF(ftp_state->command != FTP_COMMAND_RETR);
1529
1530
    AppLayerParserThreadCtxFree(alp_tctx);
1531
    StreamTcpFreeConfig(true);
1532
    PASS;
1533
}
1534
1535
/** \test Supply STOR without a filename */
1536
static int FTPParserTest12(void)
1537
{
1538
    Flow f;
1539
    uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
1540
    uint8_t ftpbuf2[] = "STOR\r\n";
1541
    uint8_t ftpbuf3[] = "227 OK\r\n";
1542
    TcpSession ssn;
1543
1544
    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
1545
1546
    memset(&f, 0, sizeof(f));
1547
    memset(&ssn, 0, sizeof(ssn));
1548
1549
    f.protoctx = (void *)&ssn;
1550
    f.proto = IPPROTO_TCP;
1551
    f.alproto = ALPROTO_FTP;
1552
1553
    StreamTcpInitConfig(true);
1554
1555
    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1556
                                STREAM_TOSERVER | STREAM_START, ftpbuf1,
1557
                                sizeof(ftpbuf1) - 1);
1558
    FAIL_IF(r != 0);
1559
1560
    /* Response */
1561
    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1562
                                STREAM_TOCLIENT,
1563
                                ftpbuf3,
1564
                                sizeof(ftpbuf3) - 1);
1565
    FAIL_IF(r != 0);
1566
1567
    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
1568
                                STREAM_TOSERVER, ftpbuf2,
1569
                                sizeof(ftpbuf2) - 1);
1570
    FAIL_IF(r == 0);
1571
1572
    FtpState *ftp_state = f.alstate;
1573
    FAIL_IF_NULL(ftp_state);
1574
1575
    FAIL_IF(ftp_state->command != FTP_COMMAND_STOR);
1576
1577
    AppLayerParserThreadCtxFree(alp_tctx);
1578
    StreamTcpFreeConfig(true);
1579
    PASS;
1580
}
1581
#endif /* UNITTESTS */
1582
1583
void FTPParserRegisterTests(void)
1584
0
{
1585
#ifdef UNITTESTS
1586
    UtRegisterTest("FTPParserTest01", FTPParserTest01);
1587
    UtRegisterTest("FTPParserTest11", FTPParserTest11);
1588
    UtRegisterTest("FTPParserTest12", FTPParserTest12);
1589
#endif /* UNITTESTS */
1590
0
}
1591