/src/suricata8/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(SCJsonBuilder *js, uint8_t lc) |
56 | 94.9k | { |
57 | 94.9k | SCJbSetBool(js, "dir", DNP3_LINK_DIR(lc)); |
58 | 94.9k | SCJbSetBool(js, "pri", DNP3_LINK_PRI(lc)); |
59 | 94.9k | SCJbSetBool(js, "fcb", DNP3_LINK_FCB(lc)); |
60 | 94.9k | SCJbSetBool(js, "fcv", DNP3_LINK_FCV(lc)); |
61 | 94.9k | SCJbSetUint(js, "function_code", DNP3_LINK_FC(lc)); |
62 | 94.9k | } |
63 | | |
64 | | static void JsonDNP3LogIin(SCJsonBuilder *js, uint16_t iin) |
65 | 429 | { |
66 | 429 | if (iin) { |
67 | 426 | SCJbOpenArray(js, "indicators"); |
68 | | |
69 | 426 | int mapping = 0; |
70 | 6.81k | do { |
71 | 6.81k | if (iin & DNP3IndicatorsMap[mapping].value) { |
72 | 3.43k | SCJbAppendString(js, DNP3IndicatorsMap[mapping].name); |
73 | 3.43k | } |
74 | 6.81k | mapping++; |
75 | 6.81k | } while (DNP3IndicatorsMap[mapping].name != NULL); |
76 | 426 | SCJbClose(js); |
77 | 426 | } |
78 | 429 | } |
79 | | |
80 | | static void JsonDNP3LogApplicationControl(SCJsonBuilder *js, uint8_t ac) |
81 | 94.9k | { |
82 | 94.9k | SCJbSetBool(js, "fir", DNP3_APP_FIR(ac)); |
83 | 94.9k | SCJbSetBool(js, "fin", DNP3_APP_FIN(ac)); |
84 | 94.9k | SCJbSetBool(js, "con", DNP3_APP_CON(ac)); |
85 | 94.9k | SCJbSetBool(js, "uns", DNP3_APP_UNS(ac)); |
86 | 94.9k | SCJbSetUint(js, "sequence", DNP3_APP_SEQ(ac)); |
87 | 94.9k | } |
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(SCJsonBuilder *js, DNP3Object *object) |
95 | 42.0k | { |
96 | 42.0k | DNP3Point *item; |
97 | | |
98 | 301k | TAILQ_FOREACH(item, object->points, next) { |
99 | 301k | SCJbStartObject(js); |
100 | | |
101 | 301k | SCJbSetUint(js, "prefix", item->prefix); |
102 | 301k | SCJbSetUint(js, "index", item->index); |
103 | 301k | if (DNP3PrefixIsSize(object->prefix_code)) { |
104 | 31.2k | SCJbSetUint(js, "size", item->size); |
105 | 31.2k | } |
106 | | |
107 | 301k | OutputJsonDNP3SetItem(js, object, item); |
108 | 301k | SCJbClose(js); |
109 | 301k | } |
110 | 42.0k | } |
111 | | |
112 | | /** |
113 | | * \brief Log the application layer objects. |
114 | | * |
115 | | * \param objects A list of DNP3 objects. |
116 | | * \param jb A SCJsonBuilder instance with an open array. |
117 | | */ |
118 | | static void JsonDNP3LogObjects(SCJsonBuilder *js, DNP3ObjectList *objects) |
119 | 57.8k | { |
120 | 57.8k | DNP3Object *object; |
121 | | |
122 | 158k | TAILQ_FOREACH(object, objects, next) { |
123 | 158k | SCJbStartObject(js); |
124 | 158k | SCJbSetUint(js, "group", object->group); |
125 | 158k | SCJbSetUint(js, "variation", object->variation); |
126 | 158k | SCJbSetUint(js, "qualifier", object->qualifier); |
127 | 158k | SCJbSetUint(js, "prefix_code", object->prefix_code); |
128 | 158k | SCJbSetUint(js, "range_code", object->range_code); |
129 | 158k | SCJbSetUint(js, "start", object->start); |
130 | 158k | SCJbSetUint(js, "stop", object->stop); |
131 | 158k | SCJbSetUint(js, "count", object->count); |
132 | | |
133 | 158k | if (object->points != NULL && !TAILQ_EMPTY(object->points)) { |
134 | 42.0k | SCJbOpenArray(js, "points"); |
135 | 42.0k | JsonDNP3LogObjectItems(js, object); |
136 | 42.0k | SCJbClose(js); |
137 | 42.0k | } |
138 | | |
139 | 158k | SCJbClose(js); |
140 | 158k | } |
141 | 57.8k | } |
142 | | |
143 | | static void JsonDNP3LogRequest(SCJsonBuilder *js, DNP3Transaction *dnp3tx) |
144 | 47.7k | { |
145 | 47.7k | JB_SET_STRING(js, "type", "request"); |
146 | | |
147 | 47.7k | SCJbOpenObject(js, "control"); |
148 | 47.7k | JsonDNP3LogLinkControl(js, dnp3tx->lh.control); |
149 | 47.7k | SCJbClose(js); |
150 | | |
151 | 47.7k | SCJbSetUint(js, "src", DNP3_SWAP16(dnp3tx->lh.src)); |
152 | 47.7k | SCJbSetUint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst)); |
153 | | |
154 | 47.7k | SCJbOpenObject(js, "application"); |
155 | | |
156 | 47.7k | SCJbOpenObject(js, "control"); |
157 | 47.7k | JsonDNP3LogApplicationControl(js, dnp3tx->ah.control); |
158 | 47.7k | SCJbClose(js); |
159 | | |
160 | 47.7k | SCJbSetUint(js, "function_code", dnp3tx->ah.function_code); |
161 | | |
162 | 47.7k | if (!TAILQ_EMPTY(&dnp3tx->objects)) { |
163 | 47.6k | SCJbOpenArray(js, "objects"); |
164 | 47.6k | JsonDNP3LogObjects(js, &dnp3tx->objects); |
165 | 47.6k | SCJbClose(js); |
166 | 47.6k | } |
167 | | |
168 | 47.7k | SCJbSetBool(js, "complete", dnp3tx->complete); |
169 | | |
170 | | /* Close application. */ |
171 | 47.7k | SCJbClose(js); |
172 | 47.7k | } |
173 | | |
174 | | static void JsonDNP3LogResponse(SCJsonBuilder *js, DNP3Transaction *dnp3tx) |
175 | 47.1k | { |
176 | 47.1k | if (dnp3tx->ah.function_code == DNP3_APP_FC_UNSOLICITED_RESP) { |
177 | 12.5k | JB_SET_STRING(js, "type", "unsolicited_response"); |
178 | 34.6k | } else { |
179 | 34.6k | JB_SET_STRING(js, "type", "response"); |
180 | 34.6k | } |
181 | | |
182 | 47.1k | SCJbOpenObject(js, "control"); |
183 | 47.1k | JsonDNP3LogLinkControl(js, dnp3tx->lh.control); |
184 | 47.1k | SCJbClose(js); |
185 | | |
186 | 47.1k | SCJbSetUint(js, "src", DNP3_SWAP16(dnp3tx->lh.src)); |
187 | 47.1k | SCJbSetUint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst)); |
188 | | |
189 | 47.1k | SCJbOpenObject(js, "application"); |
190 | | |
191 | 47.1k | SCJbOpenObject(js, "control"); |
192 | 47.1k | JsonDNP3LogApplicationControl(js, dnp3tx->ah.control); |
193 | 47.1k | SCJbClose(js); |
194 | | |
195 | 47.1k | SCJbSetUint(js, "function_code", dnp3tx->ah.function_code); |
196 | | |
197 | 47.1k | if (!TAILQ_EMPTY(&dnp3tx->objects)) { |
198 | 10.1k | SCJbOpenArray(js, "objects"); |
199 | 10.1k | JsonDNP3LogObjects(js, &dnp3tx->objects); |
200 | 10.1k | SCJbClose(js); |
201 | 10.1k | } |
202 | | |
203 | 47.1k | SCJbSetBool(js, "complete", dnp3tx->complete); |
204 | | |
205 | | /* Close application. */ |
206 | 47.1k | SCJbClose(js); |
207 | | |
208 | 47.1k | SCJbOpenObject(js, "iin"); |
209 | 47.1k | JsonDNP3LogIin(js, (uint16_t)(dnp3tx->iin.iin1 << 8 | dnp3tx->iin.iin2)); |
210 | 47.1k | SCJbClose(js); |
211 | 47.1k | } |
212 | | |
213 | | bool AlertJsonDnp3(void *vtx, SCJsonBuilder *js) |
214 | 303 | { |
215 | 303 | DNP3Transaction *tx = (DNP3Transaction *)vtx; |
216 | 303 | if (tx->done && tx->buffer == NULL) { |
217 | | /* Carrier TX for app-layer event, don't log. */ |
218 | 0 | return false; |
219 | 0 | } |
220 | 303 | bool logged = false; |
221 | 303 | SCJbOpenObject(js, "dnp3"); |
222 | 303 | if (tx->is_request && tx->done) { |
223 | 23 | SCJbOpenObject(js, "request"); |
224 | 23 | JsonDNP3LogRequest(js, tx); |
225 | 23 | SCJbClose(js); |
226 | 23 | logged = true; |
227 | 23 | } |
228 | 303 | if (!tx->is_request && tx->done) { |
229 | 279 | SCJbOpenObject(js, "response"); |
230 | 279 | JsonDNP3LogResponse(js, tx); |
231 | 279 | SCJbClose(js); |
232 | 279 | logged = true; |
233 | 279 | } |
234 | 303 | SCJbClose(js); |
235 | 303 | return logged; |
236 | 303 | } |
237 | | |
238 | | static int JsonDNP3LoggerToServer(ThreadVars *tv, void *thread_data, |
239 | | const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id) |
240 | 47.7k | { |
241 | 47.7k | SCEnter(); |
242 | 47.7k | LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data; |
243 | 47.7k | DNP3Transaction *tx = vtx; |
244 | | |
245 | 47.7k | SCJsonBuilder *js = |
246 | 47.7k | CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx); |
247 | 47.7k | if (unlikely(js == NULL)) { |
248 | 0 | return TM_ECODE_OK; |
249 | 0 | } |
250 | | |
251 | 47.7k | SCJbOpenObject(js, "dnp3"); |
252 | 47.7k | JsonDNP3LogRequest(js, tx); |
253 | 47.7k | SCJbClose(js); |
254 | 47.7k | OutputJsonBuilderBuffer(tv, p, p->flow, js, thread->ctx); |
255 | 47.7k | SCJbFree(js); |
256 | | |
257 | 47.7k | SCReturnInt(TM_ECODE_OK); |
258 | 47.7k | } |
259 | | |
260 | | static int JsonDNP3LoggerToClient(ThreadVars *tv, void *thread_data, |
261 | | const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id) |
262 | 46.8k | { |
263 | 46.8k | SCEnter(); |
264 | 46.8k | LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data; |
265 | 46.8k | DNP3Transaction *tx = vtx; |
266 | | |
267 | 46.8k | SCJsonBuilder *js = |
268 | 46.8k | CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx); |
269 | 46.8k | if (unlikely(js == NULL)) { |
270 | 0 | return TM_ECODE_OK; |
271 | 0 | } |
272 | | |
273 | 46.8k | SCJbOpenObject(js, "dnp3"); |
274 | 46.8k | JsonDNP3LogResponse(js, tx); |
275 | 46.8k | SCJbClose(js); |
276 | 46.8k | OutputJsonBuilderBuffer(tv, p, p->flow, js, thread->ctx); |
277 | 46.8k | SCJbFree(js); |
278 | | |
279 | 46.8k | SCReturnInt(TM_ECODE_OK); |
280 | 46.8k | } |
281 | | |
282 | | static int JsonDNP3Logger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, |
283 | | void *vtx, uint64_t tx_id) |
284 | 102k | { |
285 | 102k | SCEnter(); |
286 | 102k | DNP3Transaction *tx = vtx; |
287 | 102k | if (tx->done && tx->buffer == NULL) { |
288 | | /* Carrier TX for app-layer event, don't log. */ |
289 | 88 | SCReturnInt(TM_ECODE_OK); |
290 | 88 | } |
291 | 102k | if (tx->is_request && tx->done) { |
292 | 47.7k | JsonDNP3LoggerToServer(tv, thread_data, p, f, state, vtx, tx_id); |
293 | 54.6k | } else if (!tx->is_request && tx->done) { |
294 | 46.8k | JsonDNP3LoggerToClient(tv, thread_data, p, f, state, vtx, tx_id); |
295 | 46.8k | } |
296 | 102k | SCReturnInt(TM_ECODE_OK); |
297 | 102k | } |
298 | | |
299 | | static void OutputDNP3LogDeInitCtxSub(OutputCtx *output_ctx) |
300 | 0 | { |
301 | 0 | SCLogDebug("cleaning up sub output_ctx %p", output_ctx); |
302 | 0 | LogDNP3FileCtx *dnp3log_ctx = (LogDNP3FileCtx *)output_ctx->data; |
303 | 0 | SCFree(dnp3log_ctx); |
304 | 0 | SCFree(output_ctx); |
305 | 0 | } |
306 | | |
307 | | static OutputInitResult OutputDNP3LogInitSub(SCConfNode *conf, OutputCtx *parent_ctx) |
308 | 4 | { |
309 | 4 | OutputInitResult result = { NULL, false }; |
310 | 4 | OutputJsonCtx *json_ctx = parent_ctx->data; |
311 | | |
312 | 4 | LogDNP3FileCtx *dnp3log_ctx = SCCalloc(1, sizeof(*dnp3log_ctx)); |
313 | 4 | if (unlikely(dnp3log_ctx == NULL)) { |
314 | 0 | return result; |
315 | 0 | } |
316 | 4 | dnp3log_ctx->eve_ctx = json_ctx; |
317 | | |
318 | 4 | OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); |
319 | 4 | if (unlikely(output_ctx == NULL)) { |
320 | 0 | SCFree(dnp3log_ctx); |
321 | 0 | return result; |
322 | 0 | } |
323 | 4 | output_ctx->data = dnp3log_ctx; |
324 | 4 | output_ctx->DeInit = OutputDNP3LogDeInitCtxSub; |
325 | | |
326 | 4 | SCLogInfo("DNP3 log sub-module initialized."); |
327 | | |
328 | 4 | SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_DNP3); |
329 | | |
330 | 4 | result.ctx = output_ctx; |
331 | 4 | result.ok = true; |
332 | 4 | return result; |
333 | 4 | } |
334 | | |
335 | | |
336 | | static TmEcode JsonDNP3LogThreadInit(ThreadVars *t, const void *initdata, void **data) |
337 | 4 | { |
338 | 4 | LogDNP3LogThread *thread = SCCalloc(1, sizeof(*thread)); |
339 | 4 | if (unlikely(thread == NULL)) { |
340 | 0 | return TM_ECODE_FAILED; |
341 | 0 | } |
342 | | |
343 | 4 | if (initdata == NULL) { |
344 | 0 | SCLogDebug("Error getting context for DNP3. \"initdata\" is NULL."); |
345 | 0 | goto error_exit; |
346 | 0 | } |
347 | | |
348 | 4 | thread->dnp3log_ctx = ((OutputCtx *)initdata)->data; |
349 | 4 | thread->ctx = CreateEveThreadCtx(t, thread->dnp3log_ctx->eve_ctx); |
350 | 4 | if (thread->ctx == NULL) { |
351 | 0 | goto error_exit; |
352 | 0 | } |
353 | | |
354 | 4 | *data = (void *)thread; |
355 | | |
356 | 4 | return TM_ECODE_OK; |
357 | | |
358 | 0 | error_exit: |
359 | 0 | SCFree(thread); |
360 | 0 | return TM_ECODE_FAILED; |
361 | 4 | } |
362 | | |
363 | | static TmEcode JsonDNP3LogThreadDeinit(ThreadVars *t, void *data) |
364 | 0 | { |
365 | 0 | LogDNP3LogThread *thread = (LogDNP3LogThread *)data; |
366 | 0 | if (thread == NULL) { |
367 | 0 | return TM_ECODE_OK; |
368 | 0 | } |
369 | 0 | FreeEveThreadCtx(thread->ctx); |
370 | 0 | SCFree(thread); |
371 | 0 | return TM_ECODE_OK; |
372 | 0 | } |
373 | | |
374 | | void JsonDNP3LogRegister(void) |
375 | 78 | { |
376 | 78 | OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonDNP3Log", "eve-log.dnp3", |
377 | 78 | OutputDNP3LogInitSub, ALPROTO_DNP3, JsonDNP3Logger, JsonDNP3LogThreadInit, |
378 | 78 | JsonDNP3LogThreadDeinit); |
379 | 78 | } |