Coverage Report

Created: 2026-03-31 07:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/output-json-ftp.c
Line
Count
Source
1
/* Copyright (C) 2017-2021 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 Jeff Lucovsky <jeff@lucovsky.org>
22
 *
23
 * Implement JSON/eve logging app-layer FTP.
24
 */
25
26
#include "suricata-common.h"
27
#include "detect.h"
28
#include "pkt-var.h"
29
#include "conf.h"
30
31
#include "threads.h"
32
#include "threadvars.h"
33
#include "tm-threads.h"
34
35
#include "util-unittest.h"
36
#include "util-buffer.h"
37
#include "util-debug.h"
38
#include "util-mem.h"
39
40
#include "output.h"
41
#include "output-json.h"
42
43
#include "app-layer.h"
44
#include "app-layer-parser.h"
45
46
#include "app-layer-ftp.h"
47
#include "output-json-ftp.h"
48
49
static void EveFTPLogCommand(FTPTransaction *tx, JsonBuilder *jb)
50
24.6k
{
51
    /* Preallocate array objects to simplify failure case */
52
24.6k
    JsonBuilder *js_resplist = NULL;
53
24.6k
    if (!TAILQ_EMPTY(&tx->response_list)) {
54
17.2k
        js_resplist = jb_new_array();
55
56
17.2k
        if (unlikely(js_resplist == NULL)) {
57
0
            return;
58
0
        }
59
17.2k
    }
60
24.6k
    jb_set_string(jb, "command", tx->command_descriptor->command_name);
61
24.6k
    uint32_t min_length = tx->command_descriptor->command_length + 1; /* command + space */
62
24.6k
    if (tx->request_length > min_length) {
63
12.2k
        jb_set_string_from_bytes(jb,
64
12.2k
                "command_data",
65
12.2k
                (const uint8_t *)tx->request + min_length,
66
12.2k
                tx->request_length - min_length - 1);
67
12.2k
        if (tx->request_truncated) {
68
212
            JB_SET_TRUE(jb, "command_truncated");
69
12.0k
        } else {
70
12.0k
            JB_SET_FALSE(jb, "command_truncated");
71
12.0k
        }
72
12.2k
    }
73
74
24.6k
    bool reply_truncated = false;
75
76
24.6k
    if (!TAILQ_EMPTY(&tx->response_list)) {
77
17.2k
        int resp_cnt = 0;
78
17.2k
        FTPString *response;
79
17.2k
        bool is_cc_array_open = false;
80
69.0k
        TAILQ_FOREACH(response, &tx->response_list, next) {
81
            /* handle multiple lines within the response, \r\n delimited */
82
69.0k
            uint8_t *where = response->str;
83
69.0k
            uint16_t length = 0;
84
69.0k
            uint16_t pos;
85
69.0k
            if (response->len > 0 && response->len <= UINT16_MAX) {
86
66.5k
                length = (uint16_t)response->len - 1;
87
66.5k
            } else if (response->len > UINT16_MAX) {
88
0
                length = UINT16_MAX;
89
0
            }
90
69.0k
            if (!reply_truncated && response->truncated) {
91
365
                reply_truncated = true;
92
365
            }
93
132k
            while ((pos = JsonGetNextLineFromBuffer((const char *)where, length)) != UINT16_MAX) {
94
63.7k
                uint16_t offset = 0;
95
                /* Try to find a completion code for this line */
96
63.7k
                if (pos >= 3)  {
97
                    /* Gather the completion code if present */
98
53.9k
                    if (isdigit(where[0]) && isdigit(where[1]) && isdigit(where[2])) {
99
15.1k
                        if (!is_cc_array_open) {
100
4.56k
                            jb_open_array(jb, "completion_code");
101
4.56k
                            is_cc_array_open = true;
102
4.56k
                        }
103
15.1k
                        jb_append_string_from_bytes(jb, (const uint8_t *)where, 3);
104
15.1k
                        offset = 4;
105
15.1k
                    }
106
53.9k
                }
107
                /* move past 3 character completion code */
108
63.7k
                if (pos >= offset) {
109
63.2k
                    jb_append_string_from_bytes(js_resplist, (const uint8_t *)where + offset, pos - offset);
110
63.2k
                    resp_cnt++;
111
63.2k
                }
112
113
63.7k
                where += pos;
114
63.7k
                length -= pos;
115
63.7k
            }
116
69.0k
        }
117
118
17.2k
        if (is_cc_array_open) {
119
4.56k
            jb_close(jb);
120
4.56k
        }
121
17.2k
        if (resp_cnt) {
122
16.3k
            jb_close(js_resplist);
123
16.3k
            jb_set_object(jb, "reply", js_resplist);
124
16.3k
        }
125
17.2k
        jb_free(js_resplist);
126
17.2k
    }
127
128
24.6k
    if (tx->dyn_port) {
129
970
        jb_set_uint(jb, "dynamic_port", tx->dyn_port);
130
970
    }
131
132
24.6k
    if (tx->command_descriptor->command == FTP_COMMAND_PORT ||
133
19.5k
        tx->command_descriptor->command == FTP_COMMAND_EPRT) {
134
5.62k
        if (tx->active) {
135
621
            JB_SET_STRING(jb, "mode", "active");
136
5.00k
        } else {
137
5.00k
            JB_SET_STRING(jb, "mode", "passive");
138
5.00k
        }
139
5.62k
    }
140
141
24.6k
    if (tx->done) {
142
20.0k
        JB_SET_STRING(jb, "reply_received", "yes");
143
20.0k
    } else {
144
4.61k
        JB_SET_STRING(jb, "reply_received", "no");
145
4.61k
    }
146
147
24.6k
    if (reply_truncated) {
148
365
        JB_SET_TRUE(jb, "reply_truncated");
149
24.2k
    } else {
150
24.2k
        JB_SET_FALSE(jb, "reply_truncated");
151
24.2k
    }
152
24.6k
}
153
154
155
static int JsonFTPLogger(ThreadVars *tv, void *thread_data,
156
    const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id)
157
24.6k
{
158
24.6k
    SCEnter();
159
24.6k
    OutputJsonThreadCtx *thread = thread_data;
160
161
24.6k
    const char *event_type;
162
24.6k
    if (f->alproto == ALPROTO_FTPDATA) {
163
35
        event_type = "ftp_data";
164
24.6k
    } else {
165
24.6k
        event_type = "ftp";
166
24.6k
    }
167
24.6k
    FTPTransaction *tx = vtx;
168
169
24.6k
    JsonBuilder *jb =
170
24.6k
            CreateEveHeaderWithTxId(p, LOG_DIR_FLOW, event_type, NULL, tx_id, thread->ctx);
171
24.6k
    if (likely(jb)) {
172
24.6k
        jb_open_object(jb, event_type);
173
24.6k
        if (f->alproto == ALPROTO_FTPDATA) {
174
35
            EveFTPDataAddMetadata(f, jb);
175
24.6k
        } else {
176
24.6k
            EveFTPLogCommand(tx, jb);
177
24.6k
        }
178
179
24.6k
        if (!jb_close(jb)) {
180
0
            goto fail;
181
0
        }
182
183
24.6k
        OutputJsonBuilderBuffer(jb, thread);
184
185
24.6k
        jb_free(jb);
186
24.6k
    }
187
24.6k
    return TM_ECODE_OK;
188
189
0
fail:
190
0
    jb_free(jb);
191
0
    return TM_ECODE_FAILED;
192
24.6k
}
193
194
static OutputInitResult OutputFTPLogInitSub(ConfNode *conf,
195
    OutputCtx *parent_ctx)
196
4
{
197
4
    AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_FTP);
198
4
    AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_FTPDATA);
199
4
    return OutputJsonLogInitSub(conf, parent_ctx);
200
4
}
201
202
void JsonFTPLogRegister(void)
203
33
{
204
    /* Register as an eve sub-module. */
205
33
    OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonFTPLog", "eve-log.ftp",
206
33
            OutputFTPLogInitSub, ALPROTO_FTP, JsonFTPLogger, JsonLogThreadInit, JsonLogThreadDeinit,
207
33
            NULL);
208
33
    OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonFTPLog", "eve-log.ftp",
209
33
            OutputFTPLogInitSub, ALPROTO_FTPDATA, JsonFTPLogger, JsonLogThreadInit,
210
33
            JsonLogThreadDeinit, NULL);
211
212
33
    SCLogDebug("FTP JSON logger registered.");
213
33
}