/src/suricata7/src/output-json-email-common.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2007-2015 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 Tom DeCanio <td@npulsetech.com> |
22 | | * \author Eric Leblond <eric@regit.org> |
23 | | * |
24 | | * Implements json common email logging portion of the engine. |
25 | | */ |
26 | | |
27 | | #include "suricata-common.h" |
28 | | #include "detect.h" |
29 | | #include "pkt-var.h" |
30 | | #include "conf.h" |
31 | | #include "suricata.h" |
32 | | |
33 | | #include "threads.h" |
34 | | #include "threadvars.h" |
35 | | #include "tm-threads.h" |
36 | | #include "tm-threads-common.h" |
37 | | |
38 | | #include "util-print.h" |
39 | | #include "util-unittest.h" |
40 | | |
41 | | #include "util-debug.h" |
42 | | #include "app-layer-parser.h" |
43 | | #include "output.h" |
44 | | #include "app-layer-smtp.h" |
45 | | #include "app-layer.h" |
46 | | #include "util-privs.h" |
47 | | #include "util-buffer.h" |
48 | | #include "util-byte.h" |
49 | | |
50 | | #include "util-logopenfile.h" |
51 | | |
52 | | #include "output-json.h" |
53 | | #include "output-json-email-common.h" |
54 | | |
55 | | #define LOG_EMAIL_DEFAULT 0 |
56 | 6.53k | #define LOG_EMAIL_EXTENDED (1<<0) |
57 | 0 | #define LOG_EMAIL_ARRAY (1<<1) /* require array handling */ |
58 | 0 | #define LOG_EMAIL_COMMA (1<<2) /* require array handling */ |
59 | 561 | #define LOG_EMAIL_BODY_MD5 (1<<3) |
60 | 561 | #define LOG_EMAIL_SUBJECT_MD5 (1<<4) |
61 | | |
62 | | struct { |
63 | | const char *config_field; |
64 | | const char *email_field; |
65 | | uint32_t flags; |
66 | | } email_fields[] = { |
67 | | { "reply_to", "reply-to", LOG_EMAIL_DEFAULT }, |
68 | | { "bcc", "bcc", LOG_EMAIL_COMMA|LOG_EMAIL_EXTENDED }, |
69 | | { "message_id", "message-id", LOG_EMAIL_EXTENDED }, |
70 | | { "subject", "subject", LOG_EMAIL_EXTENDED }, |
71 | | { "x_mailer", "x-mailer", LOG_EMAIL_EXTENDED }, |
72 | | { "user_agent", "user-agent", LOG_EMAIL_EXTENDED }, |
73 | | { "received", "received", LOG_EMAIL_ARRAY }, |
74 | | { "x_originating_ip", "x-originating-ip", LOG_EMAIL_DEFAULT }, |
75 | | { "in_reply_to", "in-reply-to", LOG_EMAIL_DEFAULT }, |
76 | | { "references", "references", LOG_EMAIL_DEFAULT }, |
77 | | { "importance", "importance", LOG_EMAIL_DEFAULT }, |
78 | | { "priority", "priority", LOG_EMAIL_DEFAULT }, |
79 | | { "sensitivity", "sensitivity", LOG_EMAIL_DEFAULT }, |
80 | | { "organization", "organization", LOG_EMAIL_DEFAULT }, |
81 | | { "content_md5", "content-md5", LOG_EMAIL_DEFAULT }, |
82 | | { "date", "date", LOG_EMAIL_DEFAULT }, |
83 | | { NULL, NULL, LOG_EMAIL_DEFAULT}, |
84 | | }; |
85 | | |
86 | | static inline char *SkipWhiteSpaceTill(char *p, char *savep) |
87 | 1.46k | { |
88 | 1.46k | char *sp = p; |
89 | 1.46k | if (unlikely(p == NULL)) { |
90 | 0 | return NULL; |
91 | 0 | } |
92 | 1.46k | while (((*sp == '\t') || (*sp == ' ')) && (sp < savep)) { |
93 | 0 | sp++; |
94 | 0 | } |
95 | 1.46k | return sp; |
96 | 1.46k | } |
97 | | |
98 | | static bool EveEmailJsonArrayFromCommaList(JsonBuilder *js, const uint8_t *val, size_t len) |
99 | 1.47k | { |
100 | 1.47k | bool has_not_empty_field = false; |
101 | 1.47k | size_t start = 0; |
102 | 1.47k | int state = 0; |
103 | | |
104 | 35.3k | for (size_t i = 0; i < len; i++) { |
105 | 33.8k | switch (state) { |
106 | 1.47k | case 0: |
107 | 1.47k | if (val[i] == ' ' || val[i] == '\t') { |
108 | | // skip leading space |
109 | 0 | start += 1; |
110 | 1.47k | } else if (val[i] == '"') { |
111 | | // quoted state |
112 | 0 | state = 2; |
113 | 1.47k | } else { |
114 | | // field |
115 | 1.47k | state = 1; |
116 | 1.47k | } |
117 | 1.47k | break; |
118 | 32.4k | case 1: // field |
119 | 32.4k | if (val[i] == ',') { |
120 | 3 | if (i > start) { |
121 | 3 | jb_append_string_from_bytes(js, val + start, i - start); |
122 | 3 | has_not_empty_field = true; |
123 | 3 | } |
124 | 3 | start = i + 1; |
125 | 3 | state = 0; |
126 | 32.4k | } else if (val[i] == '"') { |
127 | | // quoted |
128 | 0 | state = 2; |
129 | 0 | } |
130 | 32.4k | break; |
131 | 0 | case 2: // quoted |
132 | 0 | if (val[i] == '"') { |
133 | | // out of quotes, back to field |
134 | 0 | state = 1; |
135 | 0 | } |
136 | 33.8k | } |
137 | 33.8k | } |
138 | 1.47k | if (len > start) { |
139 | 1.47k | jb_append_string_from_bytes(js, val + start, len - start); |
140 | 1.47k | has_not_empty_field = true; |
141 | 1.47k | } |
142 | 1.47k | return has_not_empty_field; |
143 | 1.47k | } |
144 | | |
145 | | static void EveEmailLogJSONMd5(OutputJsonEmailCtx *email_ctx, JsonBuilder *js, SMTPTransaction *tx) |
146 | 561 | { |
147 | 561 | if (email_ctx->flags & LOG_EMAIL_SUBJECT_MD5) { |
148 | 0 | MimeDecEntity *entity = tx->msg_tail; |
149 | 0 | if (entity == NULL) { |
150 | 0 | return; |
151 | 0 | } |
152 | 0 | MimeDecField *field = MimeDecFindField(entity, "subject"); |
153 | 0 | if (field != NULL) { |
154 | 0 | char smd5[SC_MD5_HEX_LEN + 1]; |
155 | 0 | SCMd5HashBufferToHex((uint8_t *)field->value, field->value_len, smd5, sizeof(smd5)); |
156 | 0 | jb_set_string(js, "subject_md5", smd5); |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | 561 | if (email_ctx->flags & LOG_EMAIL_BODY_MD5) { |
161 | 0 | MimeDecParseState *mime_state = tx->mime_state; |
162 | 0 | if (mime_state && mime_state->has_md5 && (mime_state->state_flag == PARSE_DONE)) { |
163 | 0 | jb_set_hex(js, "body_md5", mime_state->md5, (uint32_t)sizeof(mime_state->md5)); |
164 | 0 | } |
165 | 0 | } |
166 | 561 | } |
167 | | |
168 | | static int JsonEmailAddToJsonArray(const uint8_t *val, size_t len, void *data) |
169 | 0 | { |
170 | 0 | JsonBuilder *ajs = data; |
171 | |
|
172 | 0 | if (ajs == NULL) |
173 | 0 | return 0; |
174 | 0 | jb_append_string_from_bytes(ajs, val, (uint32_t)len); |
175 | 0 | return 1; |
176 | 0 | } |
177 | | |
178 | | static void EveEmailLogJSONCustom(OutputJsonEmailCtx *email_ctx, JsonBuilder *js, SMTPTransaction *tx) |
179 | 0 | { |
180 | 0 | int f = 0; |
181 | 0 | JsonBuilderMark mark = { 0, 0, 0 }; |
182 | 0 | MimeDecField *field; |
183 | 0 | MimeDecEntity *entity = tx->msg_tail; |
184 | 0 | if (entity == NULL) { |
185 | 0 | return; |
186 | 0 | } |
187 | | |
188 | 0 | while(email_fields[f].config_field) { |
189 | 0 | if (((email_ctx->fields & (1ULL<<f)) != 0) |
190 | 0 | || |
191 | 0 | ((email_ctx->flags & LOG_EMAIL_EXTENDED) && (email_fields[f].flags & LOG_EMAIL_EXTENDED)) |
192 | 0 | ) { |
193 | 0 | if (email_fields[f].flags & LOG_EMAIL_ARRAY) { |
194 | 0 | jb_get_mark(js, &mark); |
195 | 0 | jb_open_array(js, email_fields[f].config_field); |
196 | 0 | int found = MimeDecFindFieldsForEach(entity, email_fields[f].email_field, JsonEmailAddToJsonArray, js); |
197 | 0 | if (found > 0) { |
198 | 0 | jb_close(js); |
199 | 0 | } else { |
200 | 0 | jb_restore_mark(js, &mark); |
201 | 0 | } |
202 | 0 | } else if (email_fields[f].flags & LOG_EMAIL_COMMA) { |
203 | 0 | field = MimeDecFindField(entity, email_fields[f].email_field); |
204 | 0 | if (field) { |
205 | 0 | jb_get_mark(js, &mark); |
206 | 0 | jb_open_array(js, email_fields[f].config_field); |
207 | 0 | if (EveEmailJsonArrayFromCommaList(js, field->value, field->value_len)) { |
208 | 0 | jb_close(js); |
209 | 0 | } else { |
210 | 0 | jb_restore_mark(js, &mark); |
211 | 0 | } |
212 | 0 | } |
213 | 0 | } else { |
214 | 0 | field = MimeDecFindField(entity, email_fields[f].email_field); |
215 | 0 | if (field != NULL) { |
216 | 0 | jb_set_string_from_bytes( |
217 | 0 | js, email_fields[f].config_field, field->value, field->value_len); |
218 | 0 | } |
219 | 0 | } |
220 | |
|
221 | 0 | } |
222 | 0 | f++; |
223 | 0 | } |
224 | 0 | } |
225 | | |
226 | | /* JSON format logging */ |
227 | | static bool EveEmailLogJsonData(const Flow *f, void *state, void *vtx, uint64_t tx_id, JsonBuilder *sjs) |
228 | 2.51k | { |
229 | 2.51k | SMTPState *smtp_state; |
230 | 2.51k | MimeDecParseState *mime_state; |
231 | 2.51k | MimeDecEntity *entity; |
232 | 2.51k | JsonBuilderMark mark = { 0, 0, 0 }; |
233 | | |
234 | | /* check if we have SMTP state or not */ |
235 | 2.51k | AppProto proto = FlowGetAppProtocol(f); |
236 | 2.51k | switch (proto) { |
237 | 2.51k | case ALPROTO_SMTP: |
238 | 2.51k | smtp_state = (SMTPState *)state; |
239 | 2.51k | if (smtp_state == NULL) { |
240 | 0 | SCLogDebug("no smtp state, so no request logging"); |
241 | 0 | jb_free(sjs); |
242 | 0 | SCReturnPtr(NULL, "JsonBuilder"); |
243 | 0 | } |
244 | 2.51k | SMTPTransaction *tx = vtx; |
245 | 2.51k | mime_state = tx->mime_state; |
246 | 2.51k | entity = tx->msg_tail; |
247 | 2.51k | SCLogDebug("lets go mime_state %p, entity %p, state_flag %u", mime_state, entity, mime_state ? mime_state->state_flag : 0); |
248 | 2.51k | break; |
249 | 0 | default: |
250 | | /* don't know how we got here */ |
251 | 0 | SCReturnBool(false); |
252 | 2.51k | } |
253 | 2.51k | if ((mime_state != NULL)) { |
254 | 2.05k | if (entity == NULL) { |
255 | 0 | SCReturnBool(false); |
256 | 0 | } |
257 | | |
258 | 2.05k | jb_set_string(sjs, "status", MimeDecParseStateGetStatus(mime_state)); |
259 | | |
260 | 2.05k | MimeDecField *field; |
261 | | |
262 | | /* From: */ |
263 | 2.05k | field = MimeDecFindField(entity, "from"); |
264 | 2.05k | if (field != NULL) { |
265 | 1.46k | char *s = BytesToString((uint8_t *)field->value, |
266 | 1.46k | (size_t)field->value_len); |
267 | 1.46k | if (likely(s != NULL)) { |
268 | | //printf("From: \"%s\"\n", s); |
269 | 1.46k | char * sp = SkipWhiteSpaceTill(s, s + strlen(s)); |
270 | 1.46k | jb_set_string(sjs, "from", sp); |
271 | 1.46k | SCFree(s); |
272 | 1.46k | } |
273 | 1.46k | } |
274 | | |
275 | | /* To: */ |
276 | 2.05k | field = MimeDecFindField(entity, "to"); |
277 | 2.05k | if (field != NULL) { |
278 | 1.47k | jb_get_mark(sjs, &mark); |
279 | 1.47k | jb_open_array(sjs, "to"); |
280 | 1.47k | if (EveEmailJsonArrayFromCommaList(sjs, field->value, field->value_len)) { |
281 | 1.47k | jb_close(sjs); |
282 | 1.47k | } else { |
283 | 0 | jb_restore_mark(sjs, &mark); |
284 | 0 | } |
285 | 1.47k | } |
286 | | |
287 | | /* Cc: */ |
288 | 2.05k | field = MimeDecFindField(entity, "cc"); |
289 | 2.05k | if (field != NULL) { |
290 | 0 | jb_get_mark(sjs, &mark); |
291 | 0 | jb_open_array(sjs, "cc"); |
292 | 0 | if (EveEmailJsonArrayFromCommaList(sjs, field->value, field->value_len)) { |
293 | 0 | jb_close(sjs); |
294 | 0 | } else { |
295 | 0 | jb_restore_mark(sjs, &mark); |
296 | 0 | } |
297 | 0 | } |
298 | | |
299 | 2.05k | if (mime_state->stack == NULL || mime_state->stack->top == NULL || mime_state->stack->top->data == NULL) { |
300 | 0 | SCReturnBool(false); |
301 | 0 | } |
302 | | |
303 | 2.05k | entity = (MimeDecEntity *)mime_state->stack->top->data; |
304 | 2.05k | int attach_cnt = 0; |
305 | 2.05k | int url_cnt = 0; |
306 | 2.05k | JsonBuilder *js_attach = jb_new_array(); |
307 | 2.05k | JsonBuilder *js_url = jb_new_array(); |
308 | 2.05k | if (entity->url_list != NULL) { |
309 | 0 | MimeDecUrl *url; |
310 | 0 | bool has_ipv6_url = false; |
311 | 0 | bool has_ipv4_url = false; |
312 | 0 | bool has_exe_url = false; |
313 | 0 | for (url = entity->url_list; url != NULL; url = url->next) { |
314 | 0 | jb_append_string_from_bytes(js_url, url->url, url->url_len); |
315 | 0 | if (url->url_flags & URL_IS_EXE) |
316 | 0 | has_exe_url = true; |
317 | 0 | if (url->url_flags & URL_IS_IP6) |
318 | 0 | has_ipv6_url = true; |
319 | 0 | if (url->url_flags & URL_IS_IP4) |
320 | 0 | has_ipv6_url = true; |
321 | 0 | url_cnt += 1; |
322 | 0 | } |
323 | 0 | jb_set_bool(sjs, "has_ipv6_url", has_ipv6_url); |
324 | 0 | jb_set_bool(sjs, "has_ipv4_url", has_ipv4_url); |
325 | 0 | jb_set_bool(sjs, "has_exe_url", has_exe_url); |
326 | 0 | } |
327 | 2.05k | for (entity = entity->child; entity != NULL; entity = entity->next) { |
328 | 0 | if (entity->ctnt_flags & CTNT_IS_ATTACHMENT) { |
329 | 0 | jb_append_string_from_bytes(js_attach, entity->filename, entity->filename_len); |
330 | 0 | attach_cnt += 1; |
331 | 0 | } |
332 | 0 | if (entity->url_list != NULL) { |
333 | 0 | MimeDecUrl *url; |
334 | 0 | for (url = entity->url_list; url != NULL; url = url->next) { |
335 | 0 | jb_append_string_from_bytes(js_url, url->url, url->url_len); |
336 | 0 | url_cnt += 1; |
337 | 0 | } |
338 | 0 | } |
339 | 0 | } |
340 | 2.05k | if (attach_cnt > 0) { |
341 | 0 | jb_close(js_attach); |
342 | 0 | jb_set_object(sjs, "attachment", js_attach); |
343 | 0 | } |
344 | 2.05k | jb_free(js_attach); |
345 | 2.05k | if (url_cnt > 0) { |
346 | 0 | jb_close(js_url); |
347 | 0 | jb_set_object(sjs, "url", js_url); |
348 | 0 | } |
349 | 2.05k | jb_free(js_url); |
350 | 2.05k | SCReturnBool(true); |
351 | 2.05k | } |
352 | | |
353 | 2.51k | SCReturnBool(false); |
354 | 2.51k | } |
355 | | |
356 | | /* JSON format logging */ |
357 | | TmEcode EveEmailLogJson(JsonEmailLogThread *aft, JsonBuilder *js, const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id) |
358 | 8.72k | { |
359 | 8.72k | OutputJsonEmailCtx *email_ctx = aft->emaillog_ctx; |
360 | 8.72k | SMTPTransaction *tx = (SMTPTransaction *) vtx; |
361 | 8.72k | JsonBuilderMark mark = { 0, 0, 0 }; |
362 | | |
363 | 8.72k | jb_get_mark(js, &mark); |
364 | 8.72k | jb_open_object(js, "email"); |
365 | 8.72k | if (!EveEmailLogJsonData(f, state, vtx, tx_id, js)) { |
366 | 2.19k | jb_restore_mark(js, &mark); |
367 | 2.19k | SCReturnInt(TM_ECODE_FAILED); |
368 | 2.19k | } |
369 | | |
370 | 6.53k | if ((email_ctx->flags & LOG_EMAIL_EXTENDED) || (email_ctx->fields != 0)) |
371 | 0 | EveEmailLogJSONCustom(email_ctx, js, tx); |
372 | | |
373 | 6.53k | if (!g_disable_hashing) { |
374 | 6.53k | EveEmailLogJSONMd5(email_ctx, js, tx); |
375 | 6.53k | } |
376 | | |
377 | 6.53k | jb_close(js); |
378 | 6.53k | SCReturnInt(TM_ECODE_OK); |
379 | 8.72k | } |
380 | | |
381 | | bool EveEmailAddMetadata(const Flow *f, uint32_t tx_id, JsonBuilder *js) |
382 | 3.86k | { |
383 | 3.86k | SMTPState *smtp_state = (SMTPState *)FlowGetAppState(f); |
384 | 3.86k | if (smtp_state) { |
385 | 3.86k | SMTPTransaction *tx = AppLayerParserGetTx(IPPROTO_TCP, ALPROTO_SMTP, smtp_state, tx_id); |
386 | 3.86k | if (tx) { |
387 | 3.83k | return EveEmailLogJsonData(f, smtp_state, tx, tx_id, js); |
388 | 3.83k | } |
389 | 3.86k | } |
390 | | |
391 | 28 | return false; |
392 | 3.86k | } |
393 | | |
394 | | void OutputEmailInitConf(ConfNode *conf, OutputJsonEmailCtx *email_ctx) |
395 | 2 | { |
396 | 2 | if (conf) { |
397 | 2 | const char *extended = ConfNodeLookupChildValue(conf, "extended"); |
398 | | |
399 | 2 | if (extended != NULL) { |
400 | 2 | if (ConfValIsTrue(extended)) { |
401 | 2 | email_ctx->flags = LOG_EMAIL_EXTENDED; |
402 | 2 | } |
403 | 2 | } |
404 | | |
405 | 2 | email_ctx->fields = 0; |
406 | 2 | ConfNode *custom; |
407 | 2 | if ((custom = ConfNodeLookupChild(conf, "custom")) != NULL) { |
408 | 0 | ConfNode *field; |
409 | 0 | TAILQ_FOREACH (field, &custom->head, next) { |
410 | 0 | int f = 0; |
411 | 0 | while (email_fields[f].config_field) { |
412 | 0 | if ((strcmp(email_fields[f].config_field, field->val) == 0) || |
413 | 0 | (strcasecmp(email_fields[f].email_field, field->val) == 0)) { |
414 | 0 | email_ctx->fields |= (1ULL << f); |
415 | 0 | break; |
416 | 0 | } |
417 | 0 | f++; |
418 | 0 | } |
419 | 0 | } |
420 | 0 | } |
421 | | |
422 | 2 | email_ctx->flags = 0; |
423 | 2 | ConfNode *md5_conf; |
424 | 2 | if ((md5_conf = ConfNodeLookupChild(conf, "md5")) != NULL) { |
425 | 0 | ConfNode *field; |
426 | 0 | TAILQ_FOREACH (field, &md5_conf->head, next) { |
427 | 0 | if (strcmp("body", field->val) == 0) { |
428 | 0 | SCLogInfo("Going to log the md5 sum of email body"); |
429 | 0 | email_ctx->flags |= LOG_EMAIL_BODY_MD5; |
430 | 0 | } |
431 | 0 | if (strcmp("subject", field->val) == 0) { |
432 | 0 | SCLogInfo("Going to log the md5 sum of email subject"); |
433 | 0 | email_ctx->flags |= LOG_EMAIL_SUBJECT_MD5; |
434 | 0 | } |
435 | 0 | } |
436 | 0 | } |
437 | 2 | } |
438 | 2 | return; |
439 | 2 | } |