Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata/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
bool EveFTPLogCommand(void *vtx, SCJsonBuilder *jb)
50
88.8k
{
51
88.8k
    FTPTransaction *tx = vtx;
52
    /* Preallocate array objects to simplify failure case */
53
88.8k
    SCJsonBuilder *js_resplist = NULL;
54
88.8k
    if (!TAILQ_EMPTY(&tx->response_list)) {
55
72.5k
        js_resplist = SCJbNewArray();
56
57
72.5k
        if (unlikely(js_resplist == NULL)) {
58
0
            return false;
59
0
        }
60
72.5k
    }
61
88.8k
    const char *command_name = NULL;
62
88.8k
    uint8_t command_name_length;
63
88.8k
    if (tx->command_descriptor.command_code != FTP_COMMAND_UNKNOWN) {
64
52.7k
        if (!SCGetFtpCommandInfo(tx->command_descriptor.command_index, &command_name, NULL,
65
52.7k
                    &command_name_length)) {
66
0
            SCLogDebug("Unable to fetch info for FTP command code %d [index %d]",
67
0
                    tx->command_descriptor.command_code, tx->command_descriptor.command_index);
68
0
            return false;
69
0
        }
70
52.7k
    }
71
88.8k
    SCJbOpenObject(jb, "ftp");
72
88.8k
    if (command_name) {
73
52.7k
        SCJbSetString(jb, "command", command_name);
74
52.7k
        uint32_t min_length = command_name_length + 1; /* command + space */
75
52.7k
        if (tx->request_length > min_length) {
76
41.6k
            SCJbSetStringFromBytes(jb, "command_data", (const uint8_t *)tx->request + min_length,
77
41.6k
                    tx->request_length - min_length - 1);
78
41.6k
            if (tx->request_truncated) {
79
403
                JB_SET_TRUE(jb, "command_truncated");
80
41.2k
            } else {
81
41.2k
                JB_SET_FALSE(jb, "command_truncated");
82
41.2k
            }
83
41.6k
        }
84
52.7k
    }
85
86
88.8k
    bool reply_truncated = false;
87
88
88.8k
    if (!TAILQ_EMPTY(&tx->response_list)) {
89
72.5k
        int resp_cnt = 0;
90
72.5k
        FTPResponseWrapper *wrapper;
91
72.5k
        bool is_cc_array_open = false;
92
449k
        TAILQ_FOREACH (wrapper, &tx->response_list, next) {
93
            /* handle multiple lines within the response, \r\n delimited */
94
449k
            if (!wrapper->response) {
95
0
                continue;
96
0
            }
97
449k
            FTPResponseLine *response = wrapper->response;
98
99
449k
            if (!reply_truncated && response->truncated) {
100
656
                reply_truncated = true;
101
656
            }
102
449k
            if (response->code_length > 0) {
103
37.4k
                if (!is_cc_array_open) {
104
31.9k
                    SCJbOpenArray(jb, "completion_code");
105
31.9k
                    is_cc_array_open = true;
106
31.9k
                }
107
37.4k
                SCJbAppendStringFromBytes(
108
37.4k
                        jb, (const uint8_t *)response->code, (uint32_t)response->code_length);
109
37.4k
            }
110
449k
            if (response->length) {
111
449k
                SCJbAppendStringFromBytes(js_resplist, (const uint8_t *)response->response,
112
449k
                        (uint32_t)response->length);
113
449k
                resp_cnt++;
114
449k
            }
115
449k
        }
116
117
72.5k
        if (is_cc_array_open) {
118
31.9k
            SCJbClose(jb);
119
31.9k
        }
120
72.5k
        if (resp_cnt) {
121
72.5k
            SCJbClose(js_resplist);
122
72.5k
            SCJbSetObject(jb, "reply", js_resplist);
123
72.5k
        }
124
72.5k
        SCJbFree(js_resplist);
125
72.5k
    }
126
127
88.8k
    if (tx->dyn_port) {
128
10.8k
        SCJbSetUint(jb, "dynamic_port", tx->dyn_port);
129
10.8k
    }
130
131
88.8k
    switch (tx->command_descriptor.command_code) {
132
9.54k
        case FTP_COMMAND_PORT:
133
12.8k
        case FTP_COMMAND_EPRT:
134
17.6k
        case FTP_COMMAND_PASV:
135
21.9k
        case FTP_COMMAND_EPSV:
136
21.9k
            if (tx->active) {
137
4.27k
                JB_SET_STRING(jb, "mode", "active");
138
17.6k
            } else {
139
17.6k
                JB_SET_STRING(jb, "mode", "passive");
140
17.6k
            }
141
88.8k
        default:
142
88.8k
            break;
143
88.8k
    }
144
145
88.8k
    if (tx->done) {
146
86.6k
        JB_SET_STRING(jb, "reply_received", "yes");
147
86.6k
    } else {
148
2.27k
        JB_SET_STRING(jb, "reply_received", "no");
149
2.27k
    }
150
151
88.8k
    if (reply_truncated) {
152
656
        JB_SET_TRUE(jb, "reply_truncated");
153
88.2k
    } else {
154
88.2k
        JB_SET_FALSE(jb, "reply_truncated");
155
88.2k
    }
156
88.8k
    SCJbClose(jb);
157
    return true;
158
88.8k
}