]> git.ipfire.org Git - people/ms/suricata.git/blobdiff - src/app-layer-ftp.c
app-layer: include DetectEngineState in AppLayerTxData
[people/ms/suricata.git] / src / app-layer-ftp.c
index 7aac7273707bd1f21e471d60d99ea2fa4905d0f1..0a14e01437b61db19bd8c4e0edbe5ef21e6aa9ae 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2007-2013 Open Information Security Foundation
+/* Copyright (C) 2007-2020 Open Information Security Foundation
  *
  * You can copy, redistribute or modify this Program under the terms of
  * the GNU General Public License version 2 as published by the Free
@@ -19,6 +19,8 @@
  * \file
  *
  * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
+ * \author Eric Leblond <eric@regit.org>
+ * \author Jeff Lucovsky <jeff@lucovsky.org>
  *
  * App Layer Parser for FTP
  */
@@ -32,6 +34,7 @@
 #include "util-pool.h"
 
 #include "flow-util.h"
+#include "flow-storage.h"
 
 #include "detect-engine-state.h"
 
 #include "stream-tcp.h"
 #include "stream.h"
 
+#include "app-layer.h"
 #include "app-layer-protos.h"
 #include "app-layer-parser.h"
 #include "app-layer-ftp.h"
+#include "app-layer-expectation.h"
 
 #include "util-spm.h"
+#include "util-mpm.h"
 #include "util-unittest.h"
 #include "util-debug.h"
 #include "util-memcmp.h"
+#include "util-memrchr.h"
+#include "util-mem.h"
+#include "util-misc.h"
+
+#include "output-json.h"
+#include "rust.h"
+
+typedef struct FTPThreadCtx_ {
+    MpmThreadCtx *ftp_mpm_thread_ctx;
+    PrefilterRuleStore *pmq;
+} FTPThreadCtx;
+
+#define FTP_MPM mpm_default_matcher
+
+static MpmCtx *ftp_mpm_ctx = NULL;
+
+const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = {
+    /* Parsed and handled */
+    { FTP_COMMAND_PORT, "PORT", 4},
+    { FTP_COMMAND_EPRT, "EPRT", 4},
+    { FTP_COMMAND_AUTH_TLS, "AUTH TLS", 8},
+    { FTP_COMMAND_PASV, "PASV", 4},
+    { FTP_COMMAND_RETR, "RETR", 4},
+    { FTP_COMMAND_EPSV, "EPSV", 4},
+    { FTP_COMMAND_STOR, "STOR", 4},
+
+    /* Parsed, but not handled */
+    { FTP_COMMAND_ABOR, "ABOR", 4},
+    { FTP_COMMAND_ACCT, "ACCT", 4},
+    { FTP_COMMAND_ALLO, "ALLO", 4},
+    { FTP_COMMAND_APPE, "APPE", 4},
+    { FTP_COMMAND_CDUP, "CDUP", 4},
+    { FTP_COMMAND_CHMOD, "CHMOD", 5},
+    { FTP_COMMAND_CWD, "CWD", 3},
+    { FTP_COMMAND_DELE, "DELE", 4},
+    { FTP_COMMAND_HELP, "HELP", 4},
+    { FTP_COMMAND_IDLE, "IDLE", 4},
+    { FTP_COMMAND_LIST, "LIST", 4},
+    { FTP_COMMAND_MAIL, "MAIL", 4},
+    { FTP_COMMAND_MDTM, "MDTM", 4},
+    { FTP_COMMAND_MKD, "MKD", 3},
+    { FTP_COMMAND_MLFL, "MLFL", 4},
+    { FTP_COMMAND_MODE, "MODE", 4},
+    { FTP_COMMAND_MRCP, "MRCP", 4},
+    { FTP_COMMAND_MRSQ, "MRSQ", 4},
+    { FTP_COMMAND_MSAM, "MSAM", 4},
+    { FTP_COMMAND_MSND, "MSND", 4},
+    { FTP_COMMAND_MSOM, "MSOM", 4},
+    { FTP_COMMAND_NLST, "NLST", 4},
+    { FTP_COMMAND_NOOP, "NOOP", 4},
+    { FTP_COMMAND_PASS, "PASS", 4},
+    { FTP_COMMAND_PWD, "PWD", 3},
+    { FTP_COMMAND_QUIT, "QUIT", 4},
+    { FTP_COMMAND_REIN, "REIN", 4},
+    { FTP_COMMAND_REST, "REST", 4},
+    { FTP_COMMAND_RMD, "RMD", 3},
+    { FTP_COMMAND_RNFR, "RNFR", 4},
+    { FTP_COMMAND_RNTO, "RNTO", 4},
+    { FTP_COMMAND_SITE, "SITE", 4},
+    { FTP_COMMAND_SIZE, "SIZE", 4},
+    { FTP_COMMAND_SMNT, "SMNT", 4},
+    { FTP_COMMAND_STAT, "STAT", 4},
+    { FTP_COMMAND_STOU, "STOU", 4},
+    { FTP_COMMAND_STRU, "STRU", 4},
+    { FTP_COMMAND_SYST, "SYST", 4},
+    { FTP_COMMAND_TYPE, "TYPE", 4},
+    { FTP_COMMAND_UMASK, "UMASK", 5},
+    { FTP_COMMAND_USER, "USER", 4},
+    { FTP_COMMAND_UNKNOWN, NULL, 0}
+};
+uint64_t ftp_config_memcap = 0;
+
+SC_ATOMIC_DECLARE(uint64_t, ftp_memuse);
+SC_ATOMIC_DECLARE(uint64_t, ftp_memcap);
+
+static FTPTransaction *FTPGetOldestTx(FtpState *, FTPTransaction *);
+
+static void FTPParseMemcap(void)
+{
+    const char *conf_val;
+
+    /** set config values for memcap, prealloc and hash_size */
+    if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1)
+    {
+        if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) {
+            SCLogError(SC_ERR_SIZE_PARSE, "Error parsing ftp.memcap "
+                       "from conf file - %s.  Killing engine",
+                       conf_val);
+            exit(EXIT_FAILURE);
+        }
+        SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap);
+    } else {
+        /* default to unlimited */
+        ftp_config_memcap = 0;
+    }
+
+    SC_ATOMIC_INIT(ftp_memuse);
+    SC_ATOMIC_INIT(ftp_memcap);
+}
+
+static void FTPIncrMemuse(uint64_t size)
+{
+    (void) SC_ATOMIC_ADD(ftp_memuse, size);
+    return;
+}
+
+static void FTPDecrMemuse(uint64_t size)
+{
+    (void) SC_ATOMIC_SUB(ftp_memuse, size);
+    return;
+}
+
+uint64_t FTPMemuseGlobalCounter(void)
+{
+    uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse);
+    return tmpval;
+}
+
+uint64_t FTPMemcapGlobalCounter(void)
+{
+    uint64_t tmpval = SC_ATOMIC_GET(ftp_memcap);
+    return tmpval;
+}
+
+/**
+ *  \brief Check if alloc'ing "size" would mean we're over memcap
+ *
+ *  \retval 1 if in bounds
+ *  \retval 0 if not in bounds
+ */
+static int FTPCheckMemcap(uint64_t size)
+{
+    if (ftp_config_memcap == 0 || size + SC_ATOMIC_GET(ftp_memuse) <= ftp_config_memcap)
+        return 1;
+    (void) SC_ATOMIC_ADD(ftp_memcap, 1);
+    return 0;
+}
+
+static void *FTPMalloc(size_t size)
+{
+    void *ptr = NULL;
+
+    if (FTPCheckMemcap((uint32_t)size) == 0)
+        return NULL;
+
+    ptr = SCMalloc(size);
+
+    if (unlikely(ptr == NULL))
+        return NULL;
+
+    FTPIncrMemuse((uint64_t)size);
+
+    return ptr;
+}
+
+static void *FTPCalloc(size_t n, size_t size)
+{
+    if (FTPCheckMemcap((uint32_t)(n * size)) == 0)
+        return NULL;
+
+    void *ptr = SCCalloc(n, size);
+
+    if (unlikely(ptr == NULL))
+        return NULL;
+
+    FTPIncrMemuse((uint64_t)(n * size));
+    return ptr;
+}
+
+static void *FTPRealloc(void *ptr, size_t orig_size, size_t size)
+{
+    void *rptr = NULL;
+
+    if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0)
+        return NULL;
+
+    rptr = SCRealloc(ptr, size);
+    if (rptr == NULL)
+        return NULL;
+
+    if (size > orig_size) {
+        FTPIncrMemuse(size - orig_size);
+    } else {
+        FTPDecrMemuse(orig_size - size);
+    }
+
+    return rptr;
+}
+
+static void FTPFree(void *ptr, size_t size)
+{
+    SCFree(ptr);
+
+    FTPDecrMemuse((uint64_t)size);
+}
+
+static FTPString *FTPStringAlloc(void)
+{
+    return FTPCalloc(1, sizeof(FTPString));
+}
+
+static void FTPStringFree(FTPString *str)
+{
+    if (str->str) {
+        FTPFree(str->str, str->len);
+    }
+
+    FTPFree(str, sizeof(FTPString));
+}
+
+static void *FTPLocalStorageAlloc(void)
+{
+    /* needed by the mpm */
+    FTPThreadCtx *td = SCCalloc(1, sizeof(*td));
+    if (td == NULL) {
+        exit(EXIT_FAILURE);
+    }
+
+    td->pmq = SCCalloc(1, sizeof(*td->pmq));
+    if (td->pmq == NULL) {
+        exit(EXIT_FAILURE);
+    }
+    PmqSetup(td->pmq);
+
+    td->ftp_mpm_thread_ctx = SCCalloc(1, sizeof(MpmThreadCtx));
+    if (unlikely(td->ftp_mpm_thread_ctx == NULL)) {
+        exit(EXIT_FAILURE);
+    }
+    MpmInitThreadCtx(td->ftp_mpm_thread_ctx, FTP_MPM);
+    return td;
+}
+
+static void FTPLocalStorageFree(void *ptr)
+{
+    FTPThreadCtx *td = ptr;
+    if (td != NULL) {
+        if (td->pmq != NULL) {
+            PmqFree(td->pmq);
+            SCFree(td->pmq);
+        }
+
+        if (td->ftp_mpm_thread_ctx != NULL) {
+            mpm_table[FTP_MPM].DestroyThreadCtx(ftp_mpm_ctx, td->ftp_mpm_thread_ctx);
+            SCFree(td->ftp_mpm_thread_ctx);
+        }
+
+        SCFree(td);
+    }
+
+    return;
+}
+static FTPTransaction *FTPTransactionCreate(FtpState *state)
+{
+    SCEnter();
+    FTPTransaction *tx = FTPCalloc(1, sizeof(*tx));
+    if (tx == NULL) {
+        return NULL;
+    }
+
+    TAILQ_INSERT_TAIL(&state->tx_list, tx, next);
+    tx->tx_id = state->tx_cnt++;
+
+    TAILQ_INIT(&tx->response_list);
+
+    SCLogDebug("new transaction %p (state tx cnt %"PRIu64")", tx, state->tx_cnt);
+    return tx;
+}
+
+static void FTPTransactionFree(FTPTransaction *tx)
+{
+    SCEnter();
+
+    if (tx->tx_data.de_state != NULL) {
+        DetectEngineStateFree(tx->tx_data.de_state);
+    }
+
+    if (tx->request) {
+        FTPFree(tx->request, tx->request_length);
+    }
+
+    FTPString *str = NULL;
+    while ((str = TAILQ_FIRST(&tx->response_list))) {
+        TAILQ_REMOVE(&tx->response_list, str, next);
+        FTPStringFree(str);
+    }
+
+    FTPFree(tx, sizeof(*tx));
+}
 
 static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
 {
+    void *ptmp;
     if (line_state->current_line_lf_seen == 1) {
         /* we have seen the lf for the previous line.  Clear the parser
          * details to parse new line */
         line_state->current_line_lf_seen = 0;
         if (line_state->current_line_db == 1) {
             line_state->current_line_db = 0;
-            SCFree(line_state->db);
+            FTPFree(line_state->db, line_state->db_len);
             line_state->db = NULL;
             line_state->db_len = 0;
             state->current_line = NULL;
@@ -75,7 +370,7 @@ static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
          * if we see fragmentation then it's definitely something you
          * should alert about */
         if (line_state->current_line_db == 0) {
-            line_state->db = SCMalloc(state->input_len);
+            line_state->db = FTPMalloc(state->input_len);
             if (line_state->db == NULL) {
                 return -1;
             }
@@ -83,12 +378,16 @@ static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
             memcpy(line_state->db, state->input, state->input_len);
             line_state->db_len = state->input_len;
         } else {
-            line_state->db = SCRealloc(line_state->db,
-                                                (line_state->db_len +
-                                                 state->input_len));
-            if (line_state->db == NULL) {
+            ptmp = FTPRealloc(line_state->db, line_state->db_len,
+                             (line_state->db_len + state->input_len));
+            if (ptmp == NULL) {
+                FTPFree(line_state->db, line_state->db_len);
+                line_state->db = NULL;
+                line_state->db_len = 0;
                 return -1;
             }
+            line_state->db = ptmp;
+
             memcpy(line_state->db + line_state->db_len,
                    state->input, state->input_len);
             line_state->db_len += state->input_len;
@@ -102,12 +401,16 @@ static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
         line_state->current_line_lf_seen = 1;
 
         if (line_state->current_line_db == 1) {
-            line_state->db = SCRealloc(line_state->db,
-                                                (line_state->db_len +
-                                                 (lf_idx + 1 - state->input)));
-            if (line_state->db == NULL) {
+            ptmp = FTPRealloc(line_state->db, line_state->db_len,
+                             (line_state->db_len + (lf_idx + 1 - state->input)));
+            if (ptmp == NULL) {
+                FTPFree(line_state->db, line_state->db_len);
+                line_state->db = NULL;
+                line_state->db_len = 0;
                 return -1;
             }
+            line_state->db = ptmp;
+
             memcpy(line_state->db + line_state->db_len,
                    state->input, (lf_idx + 1 - state->input));
             line_state->db_len += (lf_idx + 1 - state->input);
@@ -162,32 +465,77 @@ static int FTPGetLine(FtpState *state)
 
 /**
  * \brief This function is called to determine and set which command is being
- * transfered to the ftp server
- * \param ftp_state the ftp state structure for the parser
+ * transferred to the ftp server
+ * \param thread context
  * \param input input line of the command
  * \param len of the command
+ * \param cmd_descriptor when the command has been parsed
  *
  * \retval 1 when the command is parsed, 0 otherwise
  */
-static int FTPParseRequestCommand(void *ftp_state, uint8_t *input,
-                                  uint32_t input_len) {
+static int FTPParseRequestCommand(FTPThreadCtx *td,
+                                  const uint8_t *input, uint32_t input_len,
+                                  const FtpCommand **cmd_descriptor)
+{
     SCEnter();
-    FtpState *fstate = (FtpState *)ftp_state;
-    fstate->command = FTP_COMMAND_UNKNOWN;
 
-    if (input_len >= 4) {
-        if (SCMemcmpLowercase("port", input, 4) == 0) {
-            fstate->command = FTP_COMMAND_PORT;
+    /* I don't like this pmq reset here.  We'll devise a method later, that
+     * should make the use of the mpm very efficient */
+    PmqReset(td->pmq);
+    int mpm_cnt = mpm_table[FTP_MPM].Search(ftp_mpm_ctx, td->ftp_mpm_thread_ctx,
+                                            td->pmq, input, input_len);
+    if (mpm_cnt) {
+        *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]];
+        SCReturnInt(1);
+    }
+
+    *cmd_descriptor = NULL;
+    SCReturnInt(0);
+}
+
+struct FtpTransferCmd {
+    /** Need to look like a ExpectationData so DFree must
+     *  be first field . */
+    void (*DFree)(void *);
+    uint64_t flow_id;
+    uint8_t *file_name;
+    uint16_t file_len;
+    FtpRequestCommand cmd;
+};
+
+static void FtpTransferCmdFree(void *data)
+{
+    struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data;
+    if (cmd == NULL)
+        return;
+    if (cmd->file_name) {
+        FTPFree(cmd->file_name, cmd->file_len + 1);
+    }
+    FTPFree(cmd, sizeof(struct FtpTransferCmd));
+}
+
+static uint32_t CopyCommandLine(uint8_t **dest, const uint8_t *src, uint32_t length)
+{
+    if (likely(length)) {
+        uint8_t *where = FTPCalloc(length + 1, sizeof(char));
+        if (unlikely(where == NULL)) {
+            return 0;
         }
+        memcpy(where, src, length);
 
-        /* else {
-         *     Add the ftp commands you need here
-         * }
-         */
+        /* Remove trailing newlines/carriage returns */
+        while (length && isspace((unsigned char) where[length - 1])) {
+            length--;
+        }
+
+        where[length] = '\0';
+        *dest = where;
     }
-    return 1;
+    /* either 0 or actual */
+    return length ? length + 1 : 0;
 }
 
+
 /**
  * \brief This function is called to retrieve a ftp request
  * \param ftp_state the ftp state structure for the parser
@@ -195,43 +543,184 @@ static int FTPParseRequestCommand(void *ftp_state, uint8_t *input,
  * \param input_len length of the request
  * \param output the resulting output
  *
- * \retval 1 when the command is parsed, 0 otherwise
+ * \retval APP_LAYER_OK when input was process successfully
+ * \retval APP_LAYER_ERROR when a unrecoverable error was encountered
  */
-static int FTPParseRequest(Flow *f, void *ftp_state,
+static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state,
                            AppLayerParserState *pstate,
-                           uint8_t *input, uint32_t input_len,
-                           void *local_data, AppLayerParserResult *output)
+                           const uint8_t *input, uint32_t input_len,
+                           void *local_data, const uint8_t flags)
 {
+    FTPThreadCtx *thread_data = local_data;
+
     SCEnter();
     /* PrintRawDataFp(stdout, input,input_len); */
 
     FtpState *state = (FtpState *)ftp_state;
+    void *ptmp;
+
+    if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) {
+        SCReturnStruct(APP_LAYER_OK);
+    } else if (input == NULL || input_len == 0) {
+        SCReturnStruct(APP_LAYER_ERROR);
+    }
 
     state->input = input;
     state->input_len = input_len;
     /* toserver stream */
     state->direction = 0;
 
+    int direction = STREAM_TOSERVER;
     while (FTPGetLine(state) >= 0) {
-        FTPParseRequestCommand(state,
-                               state->current_line, state->current_line_len);
-        if (state->command == FTP_COMMAND_PORT) {
-            if (state->current_line_len > state->port_line_size) {
-                state->port_line = SCRealloc(state->port_line,
-                                             state->current_line_len);
-                if (state->port_line == NULL) {
-                    state->port_line_size = 0;
-                    return 0;
+        const FtpCommand *cmd_descriptor;
+
+        if (!FTPParseRequestCommand(thread_data,
+                    state->current_line, state->current_line_len,
+                    &cmd_descriptor)) {
+            state->command = FTP_COMMAND_UNKNOWN;
+            continue;
+        }
+
+        state->command = cmd_descriptor->command;
+
+        FTPTransaction *tx = FTPTransactionCreate(state);
+        if (unlikely(tx == NULL))
+            SCReturnStruct(APP_LAYER_ERROR);
+        state->curr_tx = tx;
+
+        tx->command_descriptor = cmd_descriptor;
+        tx->request_length = CopyCommandLine(&tx->request,
+                state->current_line, state->current_line_len);
+
+        /* change direction (default to server) so expectation will handle
+         * the correct message when expectation will match.
+         * For ftp active mode, data connection direction is opposite to
+         * control direction.
+         */
+        if ((state->active && state->command == FTP_COMMAND_STOR) ||
+                (!state->active && state->command == FTP_COMMAND_RETR)) {
+            direction = STREAM_TOCLIENT;
+        }
+
+        switch (state->command) {
+            case FTP_COMMAND_EPRT:
+                // fallthrough
+            case FTP_COMMAND_PORT:
+                if (state->current_line_len + 1 > state->port_line_size) {
+                    /* Allocate an extra byte for a NULL terminator */
+                    ptmp = FTPRealloc(state->port_line, state->port_line_size,
+                                      state->current_line_len);
+                    if (ptmp == NULL) {
+                        if (state->port_line) {
+                            FTPFree(state->port_line, state->port_line_size);
+                            state->port_line = NULL;
+                            state->port_line_size = 0;
+                        }
+                        SCReturnStruct(APP_LAYER_OK);
+                    }
+                    state->port_line = ptmp;
+                    state->port_line_size = state->current_line_len;
                 }
-                state->port_line_size = state->current_line_len;
-            }
-            memcpy(state->port_line, state->current_line,
-                   state->current_line_len);
-            state->port_line_len = state->current_line_len;
+                memcpy(state->port_line, state->current_line,
+                        state->current_line_len);
+                state->port_line_len = state->current_line_len;
+                break;
+            case FTP_COMMAND_RETR:
+                // fallthrough
+            case FTP_COMMAND_STOR: {
+                    /* Ensure that there is a negotiated dyn port and a file
+                     * name -- need more than 5 chars: cmd [4], space, <filename>
+                     */
+                    if (state->dyn_port == 0 || state->current_line_len < 6) {
+                        SCReturnStruct(APP_LAYER_ERROR);
+                    }
+                    struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd));
+                    if (data == NULL)
+                        SCReturnStruct(APP_LAYER_ERROR);
+                    data->DFree = FtpTransferCmdFree;
+                    /*
+                     * Min size has been checked in FTPParseRequestCommand
+                     * PATH_MAX includes the null
+                     */
+                    int file_name_len = MIN(PATH_MAX - 1, state->current_line_len - 5);
+                    data->file_name = FTPCalloc(file_name_len + 1, sizeof(char));
+                    if (data->file_name == NULL) {
+                        FtpTransferCmdFree(data);
+                        SCReturnStruct(APP_LAYER_ERROR);
+                    }
+                    data->file_name[file_name_len] = 0;
+                    data->file_len = file_name_len;
+                    memcpy(data->file_name, state->current_line + 5, file_name_len);
+                    data->cmd = state->command;
+                    data->flow_id = FlowGetId(f);
+                    int ret = AppLayerExpectationCreate(f, direction,
+                                            0, state->dyn_port, ALPROTO_FTPDATA, data);
+                    if (ret == -1) {
+                        FtpTransferCmdFree(data);
+                        SCLogDebug("No expectation created.");
+                        SCReturnStruct(APP_LAYER_ERROR);
+                    } else {
+                        SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].",
+                            state->active ? "to server" : "to client",
+                            state->dyn_port);
+                    }
+
+                    /* reset the dyn port to avoid duplicate */
+                    state->dyn_port = 0;
+                    /* reset active/passive indicator */
+                    state->active = false;
+                }
+                break;
+            default:
+                break;
         }
     }
 
-    return 1;
+    SCReturnStruct(APP_LAYER_OK);
+}
+
+static int FTPParsePassiveResponse(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len)
+{
+    uint16_t dyn_port = rs_ftp_pasv_response(input, input_len);
+    if (dyn_port == 0) {
+        return -1;
+    }
+    SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port);
+    state->active = false;
+    state->dyn_port = dyn_port;
+    state->curr_tx->dyn_port = dyn_port;
+    state->curr_tx->active = false;
+
+    return 0;
+}
+
+static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len)
+{
+    uint16_t dyn_port = rs_ftp_epsv_response(input, input_len);
+    if (dyn_port == 0) {
+        return -1;
+    }
+    SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port);
+    state->active = false;
+    state->dyn_port = dyn_port;
+    state->curr_tx->dyn_port = dyn_port;
+    state->curr_tx->active = false;
+    return 0;
+}
+
+/**
+ * \brief  Handle preliminary replies -- keep tx open
+ * \retval bool True for a positive preliminary reply; false otherwise
+ *
+ * 1yz   Positive Preliminary reply
+ *
+ *                The requested action is being initiated; expect another
+ *                               reply before proceeding with a new command
+ */
+static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len)
+{
+    return input_len >= 4 && isdigit(input[0]) && input[0] == '1' &&
+           isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]);
 }
 
 /**
@@ -243,25 +732,117 @@ static int FTPParseRequest(Flow *f, void *ftp_state,
  *
  * \retval 1 when the command is parsed, 0 otherwise
  */
-static int FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
-                            uint8_t *input, uint32_t input_len,
-                            void *local_data, AppLayerParserResult *output)
+static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
+                            const uint8_t *input, uint32_t input_len,
+                            void *local_data, const uint8_t flags)
 {
-    return 1;
+    FtpState *state = (FtpState *)ftp_state;
+
+    if (unlikely(input_len == 0)) {
+        SCReturnStruct(APP_LAYER_OK);
+    }
+    state->input = input;
+    state->input_len = input_len;
+    /* toclient stream */
+    state->direction = 1;
+
+    FTPTransaction *lasttx = TAILQ_FIRST(&state->tx_list);
+    while (FTPGetLine(state) >= 0) {
+        FTPTransaction *tx = FTPGetOldestTx(state, lasttx);
+        if (tx == NULL) {
+            tx = FTPTransactionCreate(state);
+        }
+        if (unlikely(tx == NULL)) {
+            SCReturnStruct(APP_LAYER_ERROR);
+        }
+        lasttx = tx;
+        if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) {
+            /* unknown */
+            tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX -1];
+        }
+
+        state->curr_tx = tx;
+        uint16_t dyn_port;
+        switch (state->command) {
+            case FTP_COMMAND_AUTH_TLS:
+                if (state->current_line_len >= 4 && SCMemcmp("234 ", state->current_line, 4) == 0) {
+                    AppLayerRequestProtocolTLSUpgrade(f);
+                }
+                break;
+
+            case FTP_COMMAND_EPRT:
+                dyn_port = rs_ftp_active_eprt(state->port_line, state->port_line_len);
+                if (dyn_port == 0) {
+                    goto tx_complete;
+                }
+                state->dyn_port = dyn_port;
+                state->active = true;
+                tx->dyn_port = dyn_port;
+                tx->active = true;
+                SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16"", dyn_port);
+                break;
+
+            case FTP_COMMAND_PORT:
+                dyn_port = rs_ftp_active_port(state->port_line, state->port_line_len);
+                if (dyn_port == 0) {
+                    goto tx_complete;
+                }
+                state->dyn_port = dyn_port;
+                state->active = true;
+                tx->dyn_port = state->dyn_port;
+                tx->active = true;
+                SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16"", dyn_port);
+                break;
+
+            case FTP_COMMAND_PASV:
+                if (state->current_line_len >= 4 && SCMemcmp("227 ", state->current_line, 4) == 0) {
+                    FTPParsePassiveResponse(f, ftp_state, state->current_line, state->current_line_len);
+                }
+                break;
+
+            case FTP_COMMAND_EPSV:
+                if (state->current_line_len >= 4 && SCMemcmp("229 ", state->current_line, 4) == 0) {
+                    FTPParsePassiveResponseV6(f, ftp_state, state->current_line, state->current_line_len);
+                }
+                break;
+            default:
+                break;
+        }
+
+        if (likely(state->current_line_len)) {
+            FTPString *response = FTPStringAlloc();
+            if (likely(response)) {
+                response->len = CopyCommandLine(&response->str, state->current_line, state->current_line_len);
+                TAILQ_INSERT_TAIL(&tx->response_list, response, next);
+            }
+        }
+
+        /* Handle preliminary replies -- keep tx open */
+        if (FTPIsPPR(state->current_line, state->current_line_len)) {
+            continue;
+        }
+    tx_complete:
+        tx->done = true;
+    }
+
+    SCReturnStruct(APP_LAYER_OK);
 }
 
+
 #ifdef DEBUG
 static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER;
 static uint64_t ftp_state_memuse = 0;
 static uint64_t ftp_state_memcnt = 0;
 #endif
 
-static void *FTPStateAlloc(void) {
-    void *s = SCMalloc(sizeof(FtpState));
+static void *FTPStateAlloc(void *orig_state, AppProto proto_orig)
+{
+    void *s = FTPCalloc(1, sizeof(FtpState));
     if (unlikely(s == NULL))
         return NULL;
 
-    memset(s, 0, sizeof(FtpState));
+    FtpState *ftp_state = (FtpState *) s;
+    TAILQ_INIT(&ftp_state->tx_list);
 
 #ifdef DEBUG
     SCMutexLock(&ftp_state_mem_lock);
@@ -272,11 +853,29 @@ static void *FTPStateAlloc(void) {
     return s;
 }
 
-static void FTPStateFree(void *s) {
+static void FTPStateFree(void *s)
+{
     FtpState *fstate = (FtpState *) s;
     if (fstate->port_line != NULL)
-        SCFree(fstate->port_line);
-    SCFree(s);
+        FTPFree(fstate->port_line, fstate->port_line_size);
+    if (fstate->line_state[0].db)
+        FTPFree(fstate->line_state[0].db, fstate->line_state[0].db_len);
+    if (fstate->line_state[1].db)
+        FTPFree(fstate->line_state[1].db, fstate->line_state[1].db_len);
+
+    //AppLayerDecoderEventsFreeEvents(&s->decoder_events);
+
+    FTPTransaction *tx = NULL;
+    while ((tx = TAILQ_FIRST(&fstate->tx_list))) {
+        TAILQ_REMOVE(&fstate->tx_list, tx, next);
+        SCLogDebug("[%s] state %p id %"PRIu64", Freeing %d bytes at %p",
+            tx->command_descriptor->command_name,
+            s, tx->tx_id,
+            tx->request_length, tx->request);
+        FTPTransactionFree(tx);
+    }
+
+    FTPFree(s, sizeof(FtpState));
 #ifdef DEBUG
     SCMutexLock(&ftp_state_mem_lock);
     ftp_state_memcnt--;
@@ -285,33 +884,452 @@ static void FTPStateFree(void *s) {
 #endif
 }
 
-void RegisterFTPParsers(void) {
-    char *proto_name = "ftp";
+/**
+ * \brief This function returns the oldest open transaction; if none
+ * are open, then the oldest transaction is returned
+ * \param ftp_state the ftp state structure for the parser
+ * \param starttx the ftp transaction where to start looking
+ *
+ * \retval transaction pointer when a transaction was found; NULL otherwise.
+ */
+static FTPTransaction *FTPGetOldestTx(FtpState *ftp_state, FTPTransaction *starttx)
+{
+    if (unlikely(!ftp_state)) {
+        SCLogDebug("NULL state object; no transactions available");
+        return NULL;
+    }
+    FTPTransaction *tx = starttx;
+    FTPTransaction *lasttx = NULL;
+    while(tx != NULL) {
+        /* Return oldest open tx */
+        if (!tx->done) {
+            SCLogDebug("Returning tx %p id %"PRIu64, tx, tx->tx_id);
+            return tx;
+        }
+        /* save for the end */
+        lasttx = tx;
+        tx = TAILQ_NEXT(tx, next);
+    }
+    /* All tx are closed; return last element */
+    if (lasttx)
+        SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id);
+    return lasttx;
+}
+
+static void *FTPGetTx(void *state, uint64_t tx_id)
+{
+    FtpState *ftp_state = (FtpState *)state;
+    if (ftp_state) {
+        FTPTransaction *tx = NULL;
+
+        if (ftp_state->curr_tx == NULL)
+            return NULL;
+        if (ftp_state->curr_tx->tx_id == tx_id)
+            return ftp_state->curr_tx;
+
+        TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
+            if (tx->tx_id == tx_id)
+                return tx;
+        }
+    }
+    return NULL;
+}
+
+static AppLayerTxData *FTPGetTxData(void *vtx)
+{
+    FTPTransaction *tx = (FTPTransaction *)vtx;
+    return &tx->tx_data;
+}
+
+static void FTPStateTransactionFree(void *state, uint64_t tx_id)
+{
+    FtpState *ftp_state = state;
+    FTPTransaction *tx = NULL;
+    TAILQ_FOREACH(tx, &ftp_state->tx_list, next) {
+        if (tx_id < tx->tx_id)
+            break;
+        else if (tx_id > tx->tx_id)
+            continue;
+
+        if (tx == ftp_state->curr_tx)
+            ftp_state->curr_tx = NULL;
+        TAILQ_REMOVE(&ftp_state->tx_list, tx, next);
+        FTPTransactionFree(tx);
+        break;
+    }
+}
+
+static uint64_t FTPGetTxCnt(void *state)
+{
+    uint64_t cnt = 0;
+    FtpState *ftp_state = state;
+    if (ftp_state) {
+        cnt = ftp_state->tx_cnt;
+    }
+    SCLogDebug("returning state %p %"PRIu64, state, cnt);
+    return cnt;
+}
+
+static int FTPGetAlstateProgress(void *vtx, uint8_t direction)
+{
+    SCLogDebug("tx %p", vtx);
+    FTPTransaction *tx = vtx;
+
+    if (!tx->done) {
+        if (direction == STREAM_TOSERVER &&
+            tx->command_descriptor->command == FTP_COMMAND_PORT) {
+            return FTP_STATE_PORT_DONE;
+        }
+        return FTP_STATE_IN_PROGRESS;
+    }
+
+    return FTP_STATE_FINISHED;
+}
+
+
+static int FTPRegisterPatternsForProtocolDetection(void)
+{
+    if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
+                                              "220 (", 5, 0, STREAM_TOCLIENT) < 0)
+    {
+        return -1;
+    }
+    if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
+                                               "FEAT", 4, 0, STREAM_TOSERVER) < 0)
+    {
+        return -1;
+    }
+    if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
+                                               "USER ", 5, 0, STREAM_TOSERVER) < 0)
+    {
+        return -1;
+    }
+    if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
+                                               "PASS ", 5, 0, STREAM_TOSERVER) < 0)
+    {
+        return -1;
+    }
+    if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP,
+                                               "PORT ", 5, 0, STREAM_TOSERVER) < 0)
+    {
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
+
+/**
+ * \brief This function is called to retrieve a ftp request
+ * \param ftp_state the ftp state structure for the parser
+ * \param input input line of the command
+ * \param input_len length of the request
+ * \param output the resulting output
+ *
+ * \retval 1 when the command is parsed, 0 otherwise
+ */
+static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state,
+        AppLayerParserState *pstate,
+        const uint8_t *input, uint32_t input_len,
+        void *local_data, int direction)
+{
+    uint16_t flags = FileFlowToFlags(f, direction);
+    int ret = 0;
+    /* we depend on detection engine for file pruning */
+    flags |= FILE_USE_DETECT;
+    if (ftpdata_state->files == NULL) {
+        struct FtpTransferCmd *data =
+                (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId());
+        if (data == NULL) {
+            SCReturnStruct(APP_LAYER_ERROR);
+        }
+
+        ftpdata_state->files = FileContainerAlloc();
+        if (ftpdata_state->files == NULL) {
+            FlowFreeStorageById(f, AppLayerExpectationGetFlowId());
+            SCReturnStruct(APP_LAYER_ERROR);
+        }
+
+        ftpdata_state->file_name = data->file_name;
+        ftpdata_state->file_len = data->file_len;
+        data->file_name = NULL;
+        data->file_len = 0;
+        f->parent_id = data->flow_id;
+        ftpdata_state->command = data->cmd;
+        switch (data->cmd) {
+            case FTP_COMMAND_STOR:
+                ftpdata_state->direction = STREAM_TOSERVER;
+                break;
+            case FTP_COMMAND_RETR:
+                ftpdata_state->direction = STREAM_TOCLIENT;
+                break;
+            default:
+                break;
+        }
+
+        /* open with fixed track_id 0 as we can have just one
+         * file per ftp-data flow. */
+        if (FileOpenFileWithId(ftpdata_state->files, &sbcfg,
+                         0ULL, (uint8_t *) ftpdata_state->file_name,
+                         ftpdata_state->file_len,
+                         input, input_len, flags) != 0) {
+            SCLogDebug("Can't open file");
+            ret = -1;
+        }
+        FlowFreeStorageById(f, AppLayerExpectationGetFlowId());
+        ftpdata_state->tx_data.files_opened = 1;
+    } else {
+        if (input_len != 0) {
+            ret = FileAppendData(ftpdata_state->files, input, input_len);
+            if (ret == -2) {
+                ret = 0;
+                SCLogDebug("FileAppendData() - file no longer being extracted");
+                goto out;
+            } else if (ret < 0) {
+                SCLogDebug("FileAppendData() failed: %d", ret);
+                ret = -2;
+                goto out;
+            }
+        } else {
+            ret = FileCloseFile(ftpdata_state->files, NULL, 0, flags);
+            ftpdata_state->state = FTPDATA_STATE_FINISHED;
+            if (ret < 0)
+                goto out;
+        }
+    }
+
+    const bool eof_flag = flags & STREAM_TOSERVER ?
+        AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) != 0 :
+        AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) != 0;
+    if (input_len && eof_flag) {
+        ret = FileCloseFile(ftpdata_state->files, (uint8_t *) NULL, 0, flags);
+        ftpdata_state->state = FTPDATA_STATE_FINISHED;
+    }
+
+out:
+    if (ret < 0) {
+        SCReturnStruct(APP_LAYER_ERROR);
+    }
+    SCReturnStruct(APP_LAYER_OK);
+}
+
+static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state,
+        AppLayerParserState *pstate,
+        const uint8_t *input, uint32_t input_len,
+        void *local_data, const uint8_t flags)
+{
+    return FTPDataParse(f, ftp_state, pstate, input, input_len,
+                               local_data, STREAM_TOSERVER);
+}
+
+static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state,
+        AppLayerParserState *pstate,
+        const uint8_t *input, uint32_t input_len,
+        void *local_data, const uint8_t flags)
+{
+    return FTPDataParse(f, ftp_state, pstate, input, input_len,
+                               local_data, STREAM_TOCLIENT);
+}
+
+#ifdef DEBUG
+static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER;
+static uint64_t ftpdata_state_memuse = 0;
+static uint64_t ftpdata_state_memcnt = 0;
+#endif
+
+static void *FTPDataStateAlloc(void *orig_state, AppProto proto_orig)
+{
+    void *s = FTPCalloc(1, sizeof(FtpDataState));
+    if (unlikely(s == NULL))
+        return NULL;
+
+    FtpDataState *state = (FtpDataState *) s;
+    state->state = FTPDATA_STATE_IN_PROGRESS;
+
+#ifdef DEBUG
+    SCMutexLock(&ftpdata_state_mem_lock);
+    ftpdata_state_memcnt++;
+    ftpdata_state_memuse+=sizeof(FtpDataState);
+    SCMutexUnlock(&ftpdata_state_mem_lock);
+#endif
+    return s;
+}
+
+static void FTPDataStateFree(void *s)
+{
+    FtpDataState *fstate = (FtpDataState *) s;
+
+    if (fstate->tx_data.de_state != NULL) {
+        DetectEngineStateFree(fstate->tx_data.de_state);
+    }
+    if (fstate->file_name != NULL) {
+        FTPFree(fstate->file_name, fstate->file_len + 1);
+    }
+
+    FileContainerFree(fstate->files);
+
+    FTPFree(s, sizeof(FtpDataState));
+#ifdef DEBUG
+    SCMutexLock(&ftpdata_state_mem_lock);
+    ftpdata_state_memcnt--;
+    ftpdata_state_memuse-=sizeof(FtpDataState);
+    SCMutexUnlock(&ftpdata_state_mem_lock);
+#endif
+}
+
+static AppLayerTxData *FTPDataGetTxData(void *vtx)
+{
+    FtpDataState *ftp_state = (FtpDataState *)vtx;
+    return &ftp_state->tx_data;
+}
+
+static void FTPDataStateTransactionFree(void *state, uint64_t tx_id)
+{
+    /* do nothing */
+}
+
+static void *FTPDataGetTx(void *state, uint64_t tx_id)
+{
+    FtpDataState *ftp_state = (FtpDataState *)state;
+    return ftp_state;
+}
+
+static uint64_t FTPDataGetTxCnt(void *state)
+{
+    /* ftp-data is single tx */
+    return 1;
+}
+
+static int FTPDataGetAlstateProgress(void *tx, uint8_t direction)
+{
+    FtpDataState *ftpdata_state = (FtpDataState *)tx;
+    return ftpdata_state->state;
+}
+
+static FileContainer *FTPDataStateGetFiles(void *state, uint8_t direction)
+{
+    FtpDataState *ftpdata_state = (FtpDataState *)state;
+
+    if (direction != ftpdata_state->direction)
+        SCReturnPtr(NULL, "FileContainer");
+
+    SCReturnPtr(ftpdata_state->files, "FileContainer");
+}
+
+static void FTPSetMpmState(void)
+{
+    ftp_mpm_ctx = SCMalloc(sizeof(MpmCtx));
+    if (unlikely(ftp_mpm_ctx == NULL)) {
+        exit(EXIT_FAILURE);
+    }
+    memset(ftp_mpm_ctx, 0, sizeof(MpmCtx));
+    MpmInitCtx(ftp_mpm_ctx, FTP_MPM);
+
+    uint32_t i = 0;
+    for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) {
+        const FtpCommand *cmd = &FtpCommands[i];
+        if (cmd->command_length == 0)
+            continue;
+
+        MpmAddPatternCI(ftp_mpm_ctx,
+                       (uint8_t *)cmd->command_name,
+                       cmd->command_length,
+                       0 /* defunct */, 0 /* defunct */,
+                       i /*  id */, i /* rule id */ , 0 /* no flags */);
+    }
+
+    mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx);
+
+}
+
+static void FTPFreeMpmState(void)
+{
+    if (ftp_mpm_ctx != NULL) {
+        mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx);
+        SCFree(ftp_mpm_ctx);
+        ftp_mpm_ctx = NULL;
+    }
+}
+
+void RegisterFTPParsers(void)
+{
+    const char *proto_name = "ftp";
+    const char *proto_data_name = "ftp-data";
 
     /** FTP */
-    if (AppLayerProtoDetectionEnabled(proto_name)) {
-        AlpProtoAddCI(&alp_proto_ctx, proto_name, IPPROTO_TCP, ALPROTO_FTP, "USER ", 5, 0, STREAM_TOSERVER);
-        AlpProtoAddCI(&alp_proto_ctx, proto_name, IPPROTO_TCP, ALPROTO_FTP, "PASS ", 5, 0, STREAM_TOSERVER);
-        AlpProtoAddCI(&alp_proto_ctx, proto_name, IPPROTO_TCP, ALPROTO_FTP, "PORT ", 5, 0, STREAM_TOSERVER);
-        AppLayerRegisterParserAcceptableDataDirection(ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT);
-    }
-
-    if (AppLayerParserEnabled(proto_name)) {
-        AppLayerRegisterProto(proto_name, ALPROTO_FTP, STREAM_TOSERVER,
-                              FTPParseRequest);
-        AppLayerRegisterProto(proto_name, ALPROTO_FTP, STREAM_TOCLIENT,
-                              FTPParseResponse);
-        AppLayerRegisterStateFuncs(ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
+    if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
+        AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name);
+        if (FTPRegisterPatternsForProtocolDetection() < 0 )
+            return;
+        AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name);
+    }
+
+    if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
+        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER,
+                                     FTPParseRequest);
+        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT,
+                                     FTPParseResponse);
+        AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
+        AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT);
+
+        AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree);
+
+        AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx);
+        AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxData);
+
+        AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc,
+                                               FTPLocalStorageFree);
+        AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt);
+
+        AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress);
+
+        AppLayerParserRegisterStateProgressCompletionStatus(
+                ALPROTO_FTP, FTP_STATE_FINISHED, FTP_STATE_FINISHED);
+
+        AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA);
+        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER,
+                                     FTPDataParseRequest);
+        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT,
+                                     FTPDataParseResponse);
+        AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree);
+        AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT);
+        AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree);
+
+        AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetFiles);
+
+        AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx);
+        AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxData);
+
+        AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt);
+
+        AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress);
+
+        AppLayerParserRegisterStateProgressCompletionStatus(
+                ALPROTO_FTPDATA, FTPDATA_STATE_FINISHED, FTPDATA_STATE_FINISHED);
+
+        sbcfg.buf_size = 4096;
+        sbcfg.Malloc = FTPMalloc;
+        sbcfg.Calloc = FTPCalloc;
+        sbcfg.Realloc = FTPRealloc;
+        sbcfg.Free = FTPFree;
+
+        FTPParseMemcap();
     } else {
         SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
                   "still on.", proto_name);
     }
+
+    FTPSetMpmState();
+
 #ifdef UNITTESTS
-    AppLayerParserRegisterUnittests(ALPROTO_FTP, FTPParserRegisterTests);
+    AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests);
 #endif
 }
 
-void FTPAtExitPrintStats(void) {
+void FTPAtExitPrintStats(void)
+{
 #ifdef DEBUG
     SCMutexLock(&ftp_state_mem_lock);
     SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"",
@@ -320,57 +1338,102 @@ void FTPAtExitPrintStats(void) {
 #endif
 }
 
+
+/*
+ * \brief Returns the ending offset of the next line from a multi-line buffer.
+ *
+ * "Buffer" refers to a FTP response in a single buffer containing multiple lines.
+ * Here, "next line" is defined as terminating on
+ * - Newline character
+ * - Null character
+ *
+ * \param buffer Contains zero or more characters.
+ * \param len Size, in bytes, of buffer.
+ *
+ * \retval Offset from the start of buffer indicating the where the
+ * next "line ends". The characters between the input buffer and this
+ * value comprise the line.
+ *
+ * NULL is found first or a newline isn't found, then UINT16_MAX is returned.
+ */
+uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len)
+{
+    if (!buffer || *buffer == '\0') {
+        return UINT16_MAX;
+    }
+
+    char *c = strchr(buffer, '\n');
+    return c == NULL ? len : c - buffer + 1;
+}
+
+void EveFTPDataAddMetadata(const Flow *f, JsonBuilder *jb)
+{
+    const FtpDataState *ftp_state = NULL;
+    if (f->alstate == NULL)
+        return;
+
+    ftp_state = (FtpDataState *)f->alstate;
+
+    if (ftp_state->file_name) {
+        jb_set_string_from_bytes(jb, "filename", ftp_state->file_name, ftp_state->file_len);
+    }
+    switch (ftp_state->command) {
+        case FTP_COMMAND_STOR:
+            JB_SET_STRING(jb, "command", "STOR");
+            break;
+        case FTP_COMMAND_RETR:
+            JB_SET_STRING(jb, "command", "RETR");
+            break;
+        default:
+            break;
+    }
+}
+
+/**
+ * \brief Free memory allocated for global FTP parser state.
+ */
+void FTPParserCleanup(void)
+{
+    FTPFreeMpmState();
+}
+
 /* UNITTESTS */
 #ifdef UNITTESTS
 
 /** \test Send a get request in one chunk. */
-int FTPParserTest01(void) {
-    int result = 1;
+static int FTPParserTest01(void)
+{
     Flow f;
     uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n";
     uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */
     TcpSession ssn;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
 
-    FLOW_INITIALIZE(&f);
     f.protoctx = (void *)&ssn;
+    f.proto = IPPROTO_TCP;
+    f.alproto = ALPROTO_FTP;
 
-    StreamTcpInitConfig(TRUE);
+    StreamTcpInitConfig(true);
 
-    SCMutexLock(&f.m);
-    int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf, ftplen);
-    if (r != 0) {
-        SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        result = 0;
-        SCMutexUnlock(&f.m);
-        goto end;
-    }
-    SCMutexUnlock(&f.m);
+    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen);
+    FAIL_IF(r != 0);
 
     FtpState *ftp_state = f.alstate;
-    if (ftp_state == NULL) {
-        SCLogDebug("no ftp state: ");
-        result = 0;
-        goto end;
-    }
+    FAIL_IF_NULL(ftp_state);
+    FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
 
-    if (ftp_state->command != FTP_COMMAND_PORT) {
-        SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
-        result = 0;
-        goto end;
-    }
-
-end:
-    StreamTcpFreeConfig(TRUE);
-    FLOW_DESTROY(&f);
-    return result;
+    AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(true);
+    PASS;
 }
 
-/** \test Send a splitted get request. */
-int FTPParserTest03(void) {
-    int result = 1;
+/** \test Send a split get request. */
+static int FTPParserTest03(void)
+{
     Flow f;
     uint8_t ftpbuf1[] = "POR";
     uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
@@ -379,180 +1442,131 @@ int FTPParserTest03(void) {
     uint8_t ftpbuf3[] = "1,1,10,20\r\n";
     uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */
     TcpSession ssn;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
+
     f.protoctx = (void *)&ssn;
+    f.proto = IPPROTO_TCP;
+    f.alproto = ALPROTO_FTP;
 
-    StreamTcpInitConfig(TRUE);
+    StreamTcpInitConfig(true);
 
-    SCMutexLock(&f.m);
-    int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START, ftpbuf1, ftplen1);
-    if (r != 0) {
-        SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        result = 0;
-        SCMutexUnlock(&f.m);
-        goto end;
-    }
-    SCMutexUnlock(&f.m);
+    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOSERVER | STREAM_START, ftpbuf1,
+                                ftplen1);
+    FAIL_IF(r != 0);
 
-    SCMutexLock(&f.m);
-    r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER, ftpbuf2, ftplen2);
-    if (r != 0) {
-        SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
-        result = 0;
-        SCMutexUnlock(&f.m);
-        goto end;
-    }
-    SCMutexUnlock(&f.m);
+    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER,
+                            ftpbuf2, ftplen2);
+    FAIL_IF(r != 0);
 
-    SCMutexLock(&f.m);
-    r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf3, ftplen3);
-    if (r != 0) {
-        SCLogDebug("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
-        result = 0;
-        SCMutexUnlock(&f.m);
-        goto end;
-    }
-    SCMutexUnlock(&f.m);
+    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                            STREAM_TOSERVER | STREAM_EOF, ftpbuf3, ftplen3);
+    FAIL_IF(r != 0);
 
     FtpState *ftp_state = f.alstate;
-    if (ftp_state == NULL) {
-        SCLogDebug("no ftp state: ");
-        result = 0;
-        goto end;
-    }
+    FAIL_IF_NULL(ftp_state);
 
-    if (ftp_state->command != FTP_COMMAND_PORT) {
-        SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
-        result = 0;
-        goto end;
-    }
+    FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
 
-end:
-    StreamTcpFreeConfig(TRUE);
-    return result;
+    AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(true);
+    PASS;
 }
 
 /** \test See how it deals with an incomplete request. */
-int FTPParserTest06(void) {
-    int result = 1;
+static int FTPParserTest06(void)
+{
     Flow f;
     uint8_t ftpbuf1[] = "PORT";
     uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
     TcpSession ssn;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
 
-    FLOW_INITIALIZE(&f);
     f.protoctx = (void *)&ssn;
+    f.proto = IPPROTO_TCP;
+    f.alproto = ALPROTO_FTP;
 
-    StreamTcpInitConfig(TRUE);
+    StreamTcpInitConfig(true);
 
-    SCMutexLock(&f.m);
-    int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START|STREAM_EOF, ftpbuf1, ftplen1);
-    if (r != 0) {
-        SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        result = 0;
-        SCMutexUnlock(&f.m);
-        goto end;
-    }
-    SCMutexUnlock(&f.m);
+    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOSERVER | STREAM_START | STREAM_EOF,
+                                ftpbuf1,
+                                ftplen1);
+    FAIL_IF(r != 0);
 
     FtpState *ftp_state = f.alstate;
-    if (ftp_state == NULL) {
-        SCLogDebug("no ftp state: ");
-        result = 0;
-        goto end;
-    }
+    FAIL_IF_NULL(ftp_state);
 
-    if (ftp_state->command != FTP_COMMAND_UNKNOWN) {
-        SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command);
-        result = 0;
-        goto end;
-    }
+    FAIL_IF(ftp_state->command != FTP_COMMAND_UNKNOWN);
 
-end:
-    StreamTcpFreeConfig(TRUE);
-    FLOW_DESTROY(&f);
-    return result;
+    AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(true);
+    PASS;
 }
 
 /** \test See how it deals with an incomplete request in multiple chunks. */
-int FTPParserTest07(void) {
-    int result = 1;
+static int FTPParserTest07(void)
+{
     Flow f;
     uint8_t ftpbuf1[] = "PO";
     uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
     uint8_t ftpbuf2[] = "RT\r\n";
     uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
     TcpSession ssn;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
 
-    FLOW_INITIALIZE(&f);
     f.protoctx = (void *)&ssn;
+    f.proto = IPPROTO_TCP;
+    f.alproto = ALPROTO_FTP;
 
-    StreamTcpInitConfig(TRUE);
+    StreamTcpInitConfig(true);
 
-    SCMutexLock(&f.m);
-    int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START, ftpbuf1, ftplen1);
-    if (r != 0) {
-        SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        result = 0;
-        SCMutexUnlock(&f.m);
-        goto end;
-    }
-    SCMutexUnlock(&f.m);
+    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOSERVER | STREAM_START, ftpbuf1,
+                                ftplen1);
+    FAIL_IF(r != 0);
 
-    SCMutexLock(&f.m);
-    r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf2, ftplen2);
-    if (r != 0) {
-        SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
-        result = 0;
-        SCMutexUnlock(&f.m);
-        goto end;
-    }
-    SCMutexUnlock(&f.m);
+    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                            STREAM_TOSERVER | STREAM_EOF, ftpbuf2, ftplen2);
+    FAIL_IF(r != 0);
 
     FtpState *ftp_state = f.alstate;
-    if (ftp_state == NULL) {
-        SCLogDebug("no ftp state: ");
-        result = 0;
-        goto end;
-    }
+    FAIL_IF_NULL(ftp_state);
 
-    if (ftp_state->command != FTP_COMMAND_PORT) {
-        SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ",
-                   FTP_COMMAND_PORT, ftp_state->command);
-        result = 0;
-        goto end;
-    }
+    FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
 
-end:
-    StreamTcpFreeConfig(TRUE);
-    FLOW_DESTROY(&f);
-    return result;
+    AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(true);
+    PASS;
 }
 
 /** \test Test case where chunks are smaller than the delim length and the
   *       last chunk is supposed to match the delim. */
-int FTPParserTest10(void) {
-    int result = 1;
+static int FTPParserTest10(void)
+{
     Flow f;
     uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n";
     uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
     TcpSession ssn;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
     int r = 0;
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
 
-    FLOW_INITIALIZE(&f);
     f.protoctx = (void *)&ssn;
+    f.proto = IPPROTO_TCP;
+    f.alproto = ALPROTO_FTP;
 
-    StreamTcpInitConfig(TRUE);
+    StreamTcpInitConfig(true);
 
     uint32_t u;
     for (u = 0; u < ftplen1; u++) {
@@ -562,44 +1576,126 @@ int FTPParserTest10(void) {
         else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF;
         else flags = STREAM_TOSERVER;
 
-        SCMutexLock(&f.m);
-        r = AppLayerParse(NULL, &f, ALPROTO_FTP, flags, &ftpbuf1[u], 1);
-        if (r != 0) {
-            SCLogDebug("toserver chunk %" PRIu32 " returned %" PRId32 ", expected 0: ", u, r);
-            result = 0;
-            SCMutexUnlock(&f.m);
-            goto end;
-        }
-        SCMutexUnlock(&f.m);
+        r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, flags,
+                                &ftpbuf1[u], 1);
+        FAIL_IF(r != 0);
     }
 
     FtpState *ftp_state = f.alstate;
-    if (ftp_state == NULL) {
-        SCLogDebug("no ftp state: ");
-        result = 0;
-        goto end;
-    }
+    FAIL_IF_NULL(ftp_state);
 
-    if (ftp_state->command != FTP_COMMAND_PORT) {
-        SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
-        result = 0;
-        goto end;
-    }
+    FAIL_IF(ftp_state->command != FTP_COMMAND_PORT);
+
+    AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(true);
+    PASS;
+}
+
+/** \test Supply RETR without a filename */
+static int FTPParserTest11(void)
+{
+    Flow f;
+    uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
+    uint8_t ftpbuf2[] = "RETR\r\n";
+    uint8_t ftpbuf3[] = "227 OK\r\n";
+    TcpSession ssn;
+
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+
+    memset(&f, 0, sizeof(f));
+    memset(&ssn, 0, sizeof(ssn));
+
+    f.protoctx = (void *)&ssn;
+    f.proto = IPPROTO_TCP;
+    f.alproto = ALPROTO_FTP;
 
-end:
-    StreamTcpFreeConfig(TRUE);
-    FLOW_DESTROY(&f);
-    return result;
+    StreamTcpInitConfig(true);
+
+    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOSERVER | STREAM_START, ftpbuf1,
+                                sizeof(ftpbuf1) - 1);
+    FAIL_IF(r != 0);
+
+    /* Response */
+    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOCLIENT,
+                                ftpbuf3,
+                                sizeof(ftpbuf3) - 1);
+    FAIL_IF(r != 0);
+
+    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOSERVER, ftpbuf2,
+                                sizeof(ftpbuf2) - 1);
+    FAIL_IF(r == 0);
+
+    FtpState *ftp_state = f.alstate;
+    FAIL_IF_NULL(ftp_state);
+
+    FAIL_IF(ftp_state->command != FTP_COMMAND_RETR);
+
+    AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(true);
+    PASS;
+}
+
+/** \test Supply STOR without a filename */
+static int FTPParserTest12(void)
+{
+    Flow f;
+    uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n";
+    uint8_t ftpbuf2[] = "STOR\r\n";
+    uint8_t ftpbuf3[] = "227 OK\r\n";
+    TcpSession ssn;
+
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+
+    memset(&f, 0, sizeof(f));
+    memset(&ssn, 0, sizeof(ssn));
+
+    f.protoctx = (void *)&ssn;
+    f.proto = IPPROTO_TCP;
+    f.alproto = ALPROTO_FTP;
+
+    StreamTcpInitConfig(true);
+
+    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOSERVER | STREAM_START, ftpbuf1,
+                                sizeof(ftpbuf1) - 1);
+    FAIL_IF(r != 0);
+
+    /* Response */
+    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOCLIENT,
+                                ftpbuf3,
+                                sizeof(ftpbuf3) - 1);
+    FAIL_IF(r != 0);
+
+    r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP,
+                                STREAM_TOSERVER, ftpbuf2,
+                                sizeof(ftpbuf2) - 1);
+    FAIL_IF(r == 0);
+
+    FtpState *ftp_state = f.alstate;
+    FAIL_IF_NULL(ftp_state);
+
+    FAIL_IF(ftp_state->command != FTP_COMMAND_STOR);
+
+    AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(true);
+    PASS;
 }
 #endif /* UNITTESTS */
 
-void FTPParserRegisterTests(void) {
+void FTPParserRegisterTests(void)
+{
 #ifdef UNITTESTS
-    UtRegisterTest("FTPParserTest01", FTPParserTest01, 1);
-    UtRegisterTest("FTPParserTest03", FTPParserTest03, 1);
-    UtRegisterTest("FTPParserTest06", FTPParserTest06, 1);
-    UtRegisterTest("FTPParserTest07", FTPParserTest07, 1);
-    UtRegisterTest("FTPParserTest10", FTPParserTest10, 1);
+    UtRegisterTest("FTPParserTest01", FTPParserTest01);
+    UtRegisterTest("FTPParserTest03", FTPParserTest03);
+    UtRegisterTest("FTPParserTest06", FTPParserTest06);
+    UtRegisterTest("FTPParserTest07", FTPParserTest07);
+    UtRegisterTest("FTPParserTest10", FTPParserTest10);
+    UtRegisterTest("FTPParserTest11", FTPParserTest11);
+    UtRegisterTest("FTPParserTest12", FTPParserTest12);
 #endif /* UNITTESTS */
 }