/src/suricata7/src/output-json-dnp3.c
Line | Count | Source |
1 | | /* Copyright (C) 2015-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 | | #include "suricata-common.h" |
19 | | #include "detect.h" |
20 | | #include "pkt-var.h" |
21 | | #include "conf.h" |
22 | | |
23 | | #include "threads.h" |
24 | | #include "threadvars.h" |
25 | | #include "tm-threads.h" |
26 | | |
27 | | #include "util-print.h" |
28 | | #include "util-unittest.h" |
29 | | #include "util-buffer.h" |
30 | | #include "util-debug.h" |
31 | | |
32 | | #include "app-layer.h" |
33 | | #include "app-layer-parser.h" |
34 | | #include "app-layer-dnp3.h" |
35 | | #include "app-layer-dnp3-objects.h" |
36 | | |
37 | | #include "detect-dnp3.h" |
38 | | |
39 | | #include "output.h" |
40 | | #include "output-json.h" |
41 | | #include "output-json-dnp3.h" |
42 | | #include "output-json-dnp3-objects.h" |
43 | | |
44 | | typedef struct LogDNP3FileCtx_ { |
45 | | uint32_t flags; |
46 | | uint8_t include_object_data; |
47 | | OutputJsonCtx *eve_ctx; |
48 | | } LogDNP3FileCtx; |
49 | | |
50 | | typedef struct LogDNP3LogThread_ { |
51 | | LogDNP3FileCtx *dnp3log_ctx; |
52 | | OutputJsonThreadCtx *ctx; |
53 | | } LogDNP3LogThread; |
54 | | |
55 | | static void JsonDNP3LogLinkControl(JsonBuilder *js, uint8_t lc) |
56 | 118k | { |
57 | 118k | jb_set_bool(js, "dir", DNP3_LINK_DIR(lc)); |
58 | 118k | jb_set_bool(js, "pri", DNP3_LINK_PRI(lc)); |
59 | 118k | jb_set_bool(js, "fcb", DNP3_LINK_FCB(lc)); |
60 | 118k | jb_set_bool(js, "fcv", DNP3_LINK_FCV(lc)); |
61 | 118k | jb_set_uint(js, "function_code", DNP3_LINK_FC(lc)); |
62 | 118k | } |
63 | | |
64 | | static void JsonDNP3LogIin(JsonBuilder *js, uint16_t iin) |
65 | 27.9k | { |
66 | 27.9k | if (iin) { |
67 | 8.10k | jb_open_array(js, "indicators"); |
68 | | |
69 | 8.10k | int mapping = 0; |
70 | 129k | do { |
71 | 129k | if (iin & DNP3IndicatorsMap[mapping].value) { |
72 | 12.3k | jb_append_string(js, DNP3IndicatorsMap[mapping].name); |
73 | 12.3k | } |
74 | 129k | mapping++; |
75 | 129k | } while (DNP3IndicatorsMap[mapping].name != NULL); |
76 | 8.10k | jb_close(js); |
77 | 8.10k | } |
78 | 27.9k | } |
79 | | |
80 | | static void JsonDNP3LogApplicationControl(JsonBuilder *js, uint8_t ac) |
81 | 118k | { |
82 | 118k | jb_set_bool(js, "fir", DNP3_APP_FIR(ac)); |
83 | 118k | jb_set_bool(js, "fin", DNP3_APP_FIN(ac)); |
84 | 118k | jb_set_bool(js, "con", DNP3_APP_CON(ac)); |
85 | 118k | jb_set_bool(js, "uns", DNP3_APP_UNS(ac)); |
86 | 118k | jb_set_uint(js, "sequence", DNP3_APP_SEQ(ac)); |
87 | 118k | } |
88 | | |
89 | | /** |
90 | | * \brief Log the items (points) for an object. |
91 | | * |
92 | | * TODO: Autogenerate this function based on object definitions. |
93 | | */ |
94 | | static void JsonDNP3LogObjectItems(JsonBuilder *js, DNP3Object *object) |
95 | 51.6k | { |
96 | 51.6k | DNP3Point *item; |
97 | | |
98 | 369k | TAILQ_FOREACH(item, object->points, next) { |
99 | 369k | jb_start_object(js); |
100 | | |
101 | 369k | jb_set_uint(js, "prefix", item->prefix); |
102 | 369k | jb_set_uint(js, "index", item->index); |
103 | 369k | if (DNP3PrefixIsSize(object->prefix_code)) { |
104 | 6.46k | jb_set_uint(js, "size", item->size); |
105 | 6.46k | } |
106 | | |
107 | 369k | OutputJsonDNP3SetItem(js, object, item); |
108 | 369k | jb_close(js); |
109 | 369k | } |
110 | 51.6k | } |
111 | | |
112 | | /** |
113 | | * \brief Log the application layer objects. |
114 | | * |
115 | | * \param objects A list of DNP3 objects. |
116 | | * \param jb A JsonBuilder instance with an open array. |
117 | | */ |
118 | | static void JsonDNP3LogObjects(JsonBuilder *js, DNP3ObjectList *objects) |
119 | 67.7k | { |
120 | 67.7k | DNP3Object *object; |
121 | | |
122 | 165k | TAILQ_FOREACH(object, objects, next) { |
123 | 165k | jb_start_object(js); |
124 | 165k | jb_set_uint(js, "group", object->group); |
125 | 165k | jb_set_uint(js, "variation", object->variation); |
126 | 165k | jb_set_uint(js, "qualifier", object->qualifier); |
127 | 165k | jb_set_uint(js, "prefix_code", object->prefix_code); |
128 | 165k | jb_set_uint(js, "range_code", object->range_code); |
129 | 165k | jb_set_uint(js, "start", object->start); |
130 | 165k | jb_set_uint(js, "stop", object->stop); |
131 | 165k | jb_set_uint(js, "count", object->count); |
132 | | |
133 | 165k | if (object->points != NULL && !TAILQ_EMPTY(object->points)) { |
134 | 51.6k | jb_open_array(js, "points"); |
135 | 51.6k | JsonDNP3LogObjectItems(js, object); |
136 | 51.6k | jb_close(js); |
137 | 51.6k | } |
138 | | |
139 | 165k | jb_close(js); |
140 | 165k | } |
141 | 67.7k | } |
142 | | |
143 | | void JsonDNP3LogRequest(JsonBuilder *js, DNP3Transaction *dnp3tx) |
144 | 21.3k | { |
145 | 21.3k | JB_SET_STRING(js, "type", "request"); |
146 | | |
147 | 21.3k | jb_open_object(js, "control"); |
148 | 21.3k | JsonDNP3LogLinkControl(js, dnp3tx->lh.control); |
149 | 21.3k | jb_close(js); |
150 | | |
151 | 21.3k | jb_set_uint(js, "src", DNP3_SWAP16(dnp3tx->lh.src)); |
152 | 21.3k | jb_set_uint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst)); |
153 | | |
154 | 21.3k | jb_open_object(js, "application"); |
155 | | |
156 | 21.3k | jb_open_object(js, "control"); |
157 | 21.3k | JsonDNP3LogApplicationControl(js, dnp3tx->ah.control); |
158 | 21.3k | jb_close(js); |
159 | | |
160 | 21.3k | jb_set_uint(js, "function_code", dnp3tx->ah.function_code); |
161 | | |
162 | 21.3k | if (!TAILQ_EMPTY(&dnp3tx->objects)) { |
163 | 21.3k | jb_open_array(js, "objects"); |
164 | 21.3k | JsonDNP3LogObjects(js, &dnp3tx->objects); |
165 | 21.3k | jb_close(js); |
166 | 21.3k | } |
167 | | |
168 | 21.3k | jb_set_bool(js, "complete", dnp3tx->complete); |
169 | | |
170 | | /* Close application. */ |
171 | 21.3k | jb_close(js); |
172 | 21.3k | } |
173 | | |
174 | | void JsonDNP3LogResponse(JsonBuilder *js, DNP3Transaction *dnp3tx) |
175 | 27.9k | { |
176 | 27.9k | if (dnp3tx->ah.function_code == DNP3_APP_FC_UNSOLICITED_RESP) { |
177 | 6.52k | JB_SET_STRING(js, "type", "unsolicited_response"); |
178 | 21.4k | } else { |
179 | 21.4k | JB_SET_STRING(js, "type", "response"); |
180 | 21.4k | } |
181 | | |
182 | 27.9k | jb_open_object(js, "control"); |
183 | 27.9k | JsonDNP3LogLinkControl(js, dnp3tx->lh.control); |
184 | 27.9k | jb_close(js); |
185 | | |
186 | 27.9k | jb_set_uint(js, "src", DNP3_SWAP16(dnp3tx->lh.src)); |
187 | 27.9k | jb_set_uint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst)); |
188 | | |
189 | 27.9k | jb_open_object(js, "application"); |
190 | | |
191 | 27.9k | jb_open_object(js, "control"); |
192 | 27.9k | JsonDNP3LogApplicationControl(js, dnp3tx->ah.control); |
193 | 27.9k | jb_close(js); |
194 | | |
195 | 27.9k | jb_set_uint(js, "function_code", dnp3tx->ah.function_code); |
196 | | |
197 | 27.9k | if (!TAILQ_EMPTY(&dnp3tx->objects)) { |
198 | 6.20k | jb_open_array(js, "objects"); |
199 | 6.20k | JsonDNP3LogObjects(js, &dnp3tx->objects); |
200 | 6.20k | jb_close(js); |
201 | 6.20k | } |
202 | | |
203 | 27.9k | jb_set_bool(js, "complete", dnp3tx->complete); |
204 | | |
205 | | /* Close application. */ |
206 | 27.9k | jb_close(js); |
207 | | |
208 | 27.9k | jb_open_object(js, "iin"); |
209 | 27.9k | JsonDNP3LogIin(js, (uint16_t)(dnp3tx->iin.iin1 << 8 | dnp3tx->iin.iin2)); |
210 | 27.9k | jb_close(js); |
211 | 27.9k | } |
212 | | |
213 | | static int JsonDNP3LoggerToServer(ThreadVars *tv, void *thread_data, |
214 | | const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id) |
215 | 50.5k | { |
216 | 50.5k | SCEnter(); |
217 | 50.5k | LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data; |
218 | 50.5k | DNP3Transaction *tx = vtx; |
219 | | |
220 | 50.5k | JsonBuilder *js = CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx); |
221 | 50.5k | if (unlikely(js == NULL)) { |
222 | 0 | return TM_ECODE_OK; |
223 | 0 | } |
224 | | |
225 | 50.5k | jb_open_object(js, "dnp3"); |
226 | 50.5k | JsonDNP3LogRequest(js, tx); |
227 | 50.5k | jb_close(js); |
228 | 50.5k | OutputJsonBuilderBuffer(js, thread->ctx); |
229 | 50.5k | jb_free(js); |
230 | | |
231 | 50.5k | SCReturnInt(TM_ECODE_OK); |
232 | 50.5k | } |
233 | | |
234 | | static int JsonDNP3LoggerToClient(ThreadVars *tv, void *thread_data, |
235 | | const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id) |
236 | 63.4k | { |
237 | 63.4k | SCEnter(); |
238 | 63.4k | LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data; |
239 | 63.4k | DNP3Transaction *tx = vtx; |
240 | | |
241 | 63.4k | JsonBuilder *js = CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx); |
242 | 63.4k | if (unlikely(js == NULL)) { |
243 | 0 | return TM_ECODE_OK; |
244 | 0 | } |
245 | | |
246 | 63.4k | jb_open_object(js, "dnp3"); |
247 | 63.4k | JsonDNP3LogResponse(js, tx); |
248 | 63.4k | jb_close(js); |
249 | 63.4k | OutputJsonBuilderBuffer(js, thread->ctx); |
250 | 63.4k | jb_free(js); |
251 | | |
252 | 63.4k | SCReturnInt(TM_ECODE_OK); |
253 | 63.4k | } |
254 | | |
255 | | static int JsonDNP3Logger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, |
256 | | void *vtx, uint64_t tx_id) |
257 | 118k | { |
258 | 118k | SCEnter(); |
259 | 118k | DNP3Transaction *tx = vtx; |
260 | 118k | if (tx->is_request && tx->done) { |
261 | 50.5k | JsonDNP3LoggerToServer(tv, thread_data, p, f, state, vtx, tx_id); |
262 | 68.4k | } else if (!tx->is_request && tx->done) { |
263 | 63.4k | JsonDNP3LoggerToClient(tv, thread_data, p, f, state, vtx, tx_id); |
264 | 63.4k | } |
265 | 118k | SCReturnInt(TM_ECODE_OK); |
266 | 118k | } |
267 | | |
268 | | static void OutputDNP3LogDeInitCtxSub(OutputCtx *output_ctx) |
269 | 0 | { |
270 | 0 | SCLogDebug("cleaning up sub output_ctx %p", output_ctx); |
271 | 0 | LogDNP3FileCtx *dnp3log_ctx = (LogDNP3FileCtx *)output_ctx->data; |
272 | 0 | SCFree(dnp3log_ctx); |
273 | 0 | SCFree(output_ctx); |
274 | 0 | } |
275 | | |
276 | | #define DEFAULT_LOG_FILENAME "dnp3.json" |
277 | | |
278 | | static OutputInitResult OutputDNP3LogInitSub(ConfNode *conf, OutputCtx *parent_ctx) |
279 | 4 | { |
280 | 4 | OutputInitResult result = { NULL, false }; |
281 | 4 | OutputJsonCtx *json_ctx = parent_ctx->data; |
282 | | |
283 | 4 | LogDNP3FileCtx *dnp3log_ctx = SCCalloc(1, sizeof(*dnp3log_ctx)); |
284 | 4 | if (unlikely(dnp3log_ctx == NULL)) { |
285 | 0 | return result; |
286 | 0 | } |
287 | 4 | dnp3log_ctx->eve_ctx = json_ctx; |
288 | | |
289 | 4 | OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); |
290 | 4 | if (unlikely(output_ctx == NULL)) { |
291 | 0 | SCFree(dnp3log_ctx); |
292 | 0 | return result; |
293 | 0 | } |
294 | 4 | output_ctx->data = dnp3log_ctx; |
295 | 4 | output_ctx->DeInit = OutputDNP3LogDeInitCtxSub; |
296 | | |
297 | 4 | SCLogInfo("DNP3 log sub-module initialized."); |
298 | | |
299 | 4 | AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_DNP3); |
300 | | |
301 | 4 | result.ctx = output_ctx; |
302 | 4 | result.ok = true; |
303 | 4 | return result; |
304 | 4 | } |
305 | | |
306 | | |
307 | | static TmEcode JsonDNP3LogThreadInit(ThreadVars *t, const void *initdata, void **data) |
308 | 4 | { |
309 | 4 | LogDNP3LogThread *thread = SCCalloc(1, sizeof(*thread)); |
310 | 4 | if (unlikely(thread == NULL)) { |
311 | 0 | return TM_ECODE_FAILED; |
312 | 0 | } |
313 | | |
314 | 4 | if (initdata == NULL) { |
315 | 0 | SCLogDebug("Error getting context for DNP3. \"initdata\" is NULL."); |
316 | 0 | goto error_exit; |
317 | 0 | } |
318 | | |
319 | 4 | thread->dnp3log_ctx = ((OutputCtx *)initdata)->data; |
320 | 4 | thread->ctx = CreateEveThreadCtx(t, thread->dnp3log_ctx->eve_ctx); |
321 | 4 | if (thread->ctx == NULL) { |
322 | 0 | goto error_exit; |
323 | 0 | } |
324 | | |
325 | 4 | *data = (void *)thread; |
326 | | |
327 | 4 | return TM_ECODE_OK; |
328 | | |
329 | 0 | error_exit: |
330 | 0 | SCFree(thread); |
331 | 0 | return TM_ECODE_FAILED; |
332 | 4 | } |
333 | | |
334 | | static TmEcode JsonDNP3LogThreadDeinit(ThreadVars *t, void *data) |
335 | 0 | { |
336 | 0 | LogDNP3LogThread *thread = (LogDNP3LogThread *)data; |
337 | 0 | if (thread == NULL) { |
338 | 0 | return TM_ECODE_OK; |
339 | 0 | } |
340 | 0 | FreeEveThreadCtx(thread->ctx); |
341 | 0 | SCFree(thread); |
342 | 0 | return TM_ECODE_OK; |
343 | 0 | } |
344 | | |
345 | | void JsonDNP3LogRegister(void) |
346 | 74 | { |
347 | 74 | OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonDNP3Log", "eve-log.dnp3", |
348 | 74 | OutputDNP3LogInitSub, ALPROTO_DNP3, JsonDNP3Logger, JsonDNP3LogThreadInit, |
349 | | JsonDNP3LogThreadDeinit, NULL); |
350 | 74 | } |