suricata
output-json-email-common.c
Go to the documentation of this file.
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"
54 
55 #define LOG_EMAIL_DEFAULT 0
56 #define LOG_EMAIL_EXTENDED (1<<0)
57 #define LOG_EMAIL_ARRAY (1<<1) /* require array handling */
58 #define LOG_EMAIL_COMMA (1<<2) /* require array handling */
59 #define LOG_EMAIL_BODY_MD5 (1<<3)
60 #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 {
88  char *sp = p;
89  if (unlikely(p == NULL)) {
90  return NULL;
91  }
92  while (((*sp == '\t') || (*sp == ' ')) && (sp < savep)) {
93  sp++;
94  }
95  return sp;
96 }
97 
98 static bool EveEmailJsonArrayFromCommaList(JsonBuilder *js, const uint8_t *val, size_t len)
99 {
100  char *savep = NULL;
101  char *p;
102  char *sp;
103  char *to_line = BytesToString((uint8_t *)val, len);
104  if (likely(to_line != NULL)) {
105  p = strtok_r(to_line, ",", &savep);
106  if (p == NULL) {
107  SCFree(to_line);
108  return false;
109  }
110  sp = SkipWhiteSpaceTill(p, savep);
111  jb_append_string(js, sp);
112  while ((p = strtok_r(NULL, ",", &savep)) != NULL) {
113  sp = SkipWhiteSpaceTill(p, savep);
114  jb_append_string(js, sp);
115  }
116  } else {
117  return false;
118  }
119  SCFree(to_line);
120  return true;
121 }
122 
123 static void EveEmailLogJSONMd5(OutputJsonEmailCtx *email_ctx, JsonBuilder *js, SMTPTransaction *tx)
124 {
125  if (email_ctx->flags & LOG_EMAIL_SUBJECT_MD5) {
126  MimeDecEntity *entity = tx->msg_tail;
127  if (entity == NULL) {
128  return;
129  }
130  MimeDecField *field = MimeDecFindField(entity, "subject");
131  if (field != NULL) {
132  char smd5[SC_MD5_HEX_LEN + 1];
133  SCMd5HashBufferToHex((uint8_t *)field->value, field->value_len, smd5, sizeof(smd5));
134  jb_set_string(js, "subject_md5", smd5);
135  }
136  }
137 
138  if (email_ctx->flags & LOG_EMAIL_BODY_MD5) {
139  MimeDecParseState *mime_state = tx->mime_state;
140  if (mime_state && mime_state->has_md5 && (mime_state->state_flag == PARSE_DONE)) {
141  jb_set_hex(js, "body_md5", mime_state->md5, (uint32_t)sizeof(mime_state->md5));
142  }
143  }
144 }
145 
146 static int JsonEmailAddToJsonArray(const uint8_t *val, size_t len, void *data)
147 {
148  JsonBuilder *ajs = data;
149 
150  if (ajs == NULL)
151  return 0;
152  jb_append_string_from_bytes(ajs, val, (uint32_t)len);
153  return 1;
154 }
155 
156 static void EveEmailLogJSONCustom(OutputJsonEmailCtx *email_ctx, JsonBuilder *js, SMTPTransaction *tx)
157 {
158  int f = 0;
159  JsonBuilderMark mark = { 0, 0, 0 };
160  MimeDecField *field;
161  MimeDecEntity *entity = tx->msg_tail;
162  if (entity == NULL) {
163  return;
164  }
165 
166  while(email_fields[f].config_field) {
167  if (((email_ctx->fields & (1ULL<<f)) != 0)
168  ||
169  ((email_ctx->flags & LOG_EMAIL_EXTENDED) && (email_fields[f].flags & LOG_EMAIL_EXTENDED))
170  ) {
172  jb_get_mark(js, &mark);
173  jb_open_array(js, email_fields[f].config_field);
174  int found = MimeDecFindFieldsForEach(entity, email_fields[f].email_field, JsonEmailAddToJsonArray, js);
175  if (found > 0) {
176  jb_close(js);
177  } else {
178  jb_restore_mark(js, &mark);
179  }
180  } else if (email_fields[f].flags & LOG_EMAIL_COMMA) {
181  field = MimeDecFindField(entity, email_fields[f].email_field);
182  if (field) {
183  jb_get_mark(js, &mark);
184  jb_open_array(js, email_fields[f].config_field);
185  if (EveEmailJsonArrayFromCommaList(js, field->value, field->value_len)) {
186  jb_close(js);
187  } else {
188  jb_restore_mark(js, &mark);
189  }
190  }
191  } else {
192  field = MimeDecFindField(entity, email_fields[f].email_field);
193  if (field != NULL) {
194  jb_set_string_from_bytes(
195  js, email_fields[f].config_field, field->value, field->value_len);
196  }
197  }
198 
199  }
200  f++;
201  }
202 }
203 
204 /* JSON format logging */
205 static bool EveEmailLogJsonData(const Flow *f, void *state, void *vtx, uint64_t tx_id, JsonBuilder *sjs)
206 {
207  SMTPState *smtp_state;
208  MimeDecParseState *mime_state;
209  MimeDecEntity *entity;
210  JsonBuilderMark mark = { 0, 0, 0 };
211 
212  /* check if we have SMTP state or not */
213  AppProto proto = FlowGetAppProtocol(f);
214  switch (proto) {
215  case ALPROTO_SMTP:
216  smtp_state = (SMTPState *)state;
217  if (smtp_state == NULL) {
218  SCLogDebug("no smtp state, so no request logging");
219  jb_free(sjs);
220  SCReturnPtr(NULL, "JsonBuilder");
221  }
222  SMTPTransaction *tx = vtx;
223  mime_state = tx->mime_state;
224  entity = tx->msg_tail;
225  SCLogDebug("lets go mime_state %p, entity %p, state_flag %u", mime_state, entity, mime_state ? mime_state->state_flag : 0);
226  break;
227  default:
228  /* don't know how we got here */
229  SCReturnBool(false);
230  }
231  if ((mime_state != NULL)) {
232  if (entity == NULL) {
233  SCReturnBool(false);
234  }
235 
236  jb_set_string(sjs, "status", MimeDecParseStateGetStatus(mime_state));
237 
238  MimeDecField *field;
239 
240  /* From: */
241  field = MimeDecFindField(entity, "from");
242  if (field != NULL) {
243  char *s = BytesToString((uint8_t *)field->value,
244  (size_t)field->value_len);
245  if (likely(s != NULL)) {
246  //printf("From: \"%s\"\n", s);
247  char * sp = SkipWhiteSpaceTill(s, s + strlen(s));
248  jb_set_string(sjs, "from", sp);
249  SCFree(s);
250  }
251  }
252 
253  /* To: */
254  field = MimeDecFindField(entity, "to");
255  if (field != NULL) {
256  jb_get_mark(sjs, &mark);
257  jb_open_array(sjs, "to");
258  if (EveEmailJsonArrayFromCommaList(sjs, field->value, field->value_len)) {
259  jb_close(sjs);
260  } else {
261  jb_restore_mark(sjs, &mark);
262  }
263  }
264 
265  /* Cc: */
266  field = MimeDecFindField(entity, "cc");
267  if (field != NULL) {
268  jb_get_mark(sjs, &mark);
269  jb_open_array(sjs, "cc");
270  if (EveEmailJsonArrayFromCommaList(sjs, field->value, field->value_len)) {
271  jb_close(sjs);
272  } else {
273  jb_restore_mark(sjs, &mark);
274  }
275  }
276 
277  if (mime_state->stack == NULL || mime_state->stack->top == NULL || mime_state->stack->top->data == NULL) {
278  SCReturnBool(false);
279  }
280 
281  entity = (MimeDecEntity *)mime_state->stack->top->data;
282  int attach_cnt = 0;
283  int url_cnt = 0;
284  JsonBuilder *js_attach = jb_new_array();
285  JsonBuilder *js_url = jb_new_array();
286  if (entity->url_list != NULL) {
287  MimeDecUrl *url;
288  bool has_ipv6_url = false;
289  bool has_ipv4_url = false;
290  bool has_exe_url = false;
291  for (url = entity->url_list; url != NULL; url = url->next) {
292  jb_append_string_from_bytes(js_url, url->url, url->url_len);
293  if (url->url_flags & URL_IS_EXE)
294  has_exe_url = true;
295  if (url->url_flags & URL_IS_IP6)
296  has_ipv6_url = true;
297  if (url->url_flags & URL_IS_IP4)
298  has_ipv6_url = true;
299  url_cnt += 1;
300  }
301  jb_set_bool(sjs, "has_ipv6_url", has_ipv6_url);
302  jb_set_bool(sjs, "has_ipv4_url", has_ipv4_url);
303  jb_set_bool(sjs, "has_exe_url", has_exe_url);
304  }
305  for (entity = entity->child; entity != NULL; entity = entity->next) {
306  if (entity->ctnt_flags & CTNT_IS_ATTACHMENT) {
307  jb_append_string_from_bytes(js_attach, entity->filename, entity->filename_len);
308  attach_cnt += 1;
309  }
310  if (entity->url_list != NULL) {
311  MimeDecUrl *url;
312  for (url = entity->url_list; url != NULL; url = url->next) {
313  jb_append_string_from_bytes(js_url, url->url, url->url_len);
314  url_cnt += 1;
315  }
316  }
317  }
318  if (attach_cnt > 0) {
319  jb_close(js_attach);
320  jb_set_object(sjs, "attachment", js_attach);
321  }
322  jb_free(js_attach);
323  if (url_cnt > 0) {
324  jb_close(js_url);
325  jb_set_object(sjs, "url", js_url);
326  }
327  jb_free(js_url);
328  SCReturnBool(true);
329  }
330 
331  SCReturnBool(false);
332 }
333 
334 /* JSON format logging */
335 TmEcode EveEmailLogJson(JsonEmailLogThread *aft, JsonBuilder *js, const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id)
336 {
337  OutputJsonEmailCtx *email_ctx = aft->emaillog_ctx;
338  SMTPTransaction *tx = (SMTPTransaction *) vtx;
339  JsonBuilderMark mark = { 0, 0, 0 };
340 
341  jb_get_mark(js, &mark);
342  jb_open_object(js, "email");
343  if (!EveEmailLogJsonData(f, state, vtx, tx_id, js)) {
344  jb_restore_mark(js, &mark);
346  }
347 
348  if ((email_ctx->flags & LOG_EMAIL_EXTENDED) || (email_ctx->fields != 0))
349  EveEmailLogJSONCustom(email_ctx, js, tx);
350 
351  if (!g_disable_hashing) {
352  EveEmailLogJSONMd5(email_ctx, js, tx);
353  }
354 
355  jb_close(js);
357 }
358 
359 bool EveEmailAddMetadata(const Flow *f, uint32_t tx_id, JsonBuilder *js)
360 {
361  SMTPState *smtp_state = (SMTPState *)FlowGetAppState(f);
362  if (smtp_state) {
363  SMTPTransaction *tx = AppLayerParserGetTx(IPPROTO_TCP, ALPROTO_SMTP, smtp_state, tx_id);
364  if (tx) {
365  return EveEmailLogJsonData(f, smtp_state, tx, tx_id, js);
366  }
367  }
368 
369  return false;
370 }
371 
373 {
374  if (conf) {
375  const char *extended = ConfNodeLookupChildValue(conf, "extended");
376 
377  if (extended != NULL) {
378  if (ConfValIsTrue(extended)) {
379  email_ctx->flags = LOG_EMAIL_EXTENDED;
380  }
381  }
382 
383  email_ctx->fields = 0;
384  ConfNode *custom;
385  if ((custom = ConfNodeLookupChild(conf, "custom")) != NULL) {
386  ConfNode *field;
387  TAILQ_FOREACH (field, &custom->head, next) {
388  int f = 0;
389  while (email_fields[f].config_field) {
390  if ((strcmp(email_fields[f].config_field, field->val) == 0) ||
391  (strcasecmp(email_fields[f].email_field, field->val) == 0)) {
392  email_ctx->fields |= (1ULL << f);
393  break;
394  }
395  f++;
396  }
397  }
398  }
399 
400  email_ctx->flags = 0;
401  ConfNode *md5_conf;
402  if ((md5_conf = ConfNodeLookupChild(conf, "md5")) != NULL) {
403  ConfNode *field;
404  TAILQ_FOREACH (field, &md5_conf->head, next) {
405  if (strcmp("body", field->val) == 0) {
406  SCLogInfo("Going to log the md5 sum of email body");
407  email_ctx->flags |= LOG_EMAIL_BODY_MD5;
408  }
409  if (strcmp("subject", field->val) == 0) {
410  SCLogInfo("Going to log the md5 sum of email subject");
411  email_ctx->flags |= LOG_EMAIL_SUBJECT_MD5;
412  }
413  }
414  }
415  }
416  return;
417 }
util-byte.h
MimeDecEntity::ctnt_flags
uint32_t ctnt_flags
Definition: util-decode-mime.h:136
tm-threads.h
SMTPState_
Definition: app-layer-smtp.h:116
MimeDecEntity::child
struct MimeDecEntity * child
Definition: util-decode-mime.h:145
len
uint8_t len
Definition: app-layer-dnp3.h:2
URL_IS_IP4
#define URL_IS_IP4
Definition: util-decode-mime.h:46
BytesToString
char * BytesToString(const uint8_t *bytes, size_t nbytes)
Turn byte array into string.
Definition: util-byte.c:41
LOG_EMAIL_COMMA
#define LOG_EMAIL_COMMA
Definition: output-json-email-common.c:58
SMTPTransaction_::msg_tail
MimeDecEntity * msg_tail
Definition: app-layer-smtp.h:87
ConfNode_::val
char * val
Definition: conf.h:34
JsonEmailLogThread_
Definition: output-json-email-common.h:33
MimeDecField::value
uint8_t * value
Definition: util-decode-mime.h:110
EveEmailLogJson
TmEcode EveEmailLogJson(JsonEmailLogThread *aft, JsonBuilder *js, const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id)
Definition: output-json-email-common.c:335
unlikely
#define unlikely(expr)
Definition: util-optimize.h:35
LOG_EMAIL_EXTENDED
#define LOG_EMAIL_EXTENDED
Definition: output-json-email-common.c:56
PARSE_DONE
#define PARSE_DONE
Definition: util-decode-mime.h:71
MimeDecEntity::filename
uint8_t * filename
Definition: util-decode-mime.h:139
SMTPTransaction_::mime_state
MimeDecParseState * mime_state
Definition: app-layer-smtp.h:89
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:269
next
struct HtpBodyChunk_ * next
Definition: app-layer-htp.h:0
AppProto
uint16_t AppProto
Definition: app-layer-protos.h:81
threads.h
Flow_
Flow data structure.
Definition: flow.h:351
MimeDecStack::top
MimeDecStackNode * top
Definition: util-decode-mime.h:167
MimeDecEntity::filename_len
uint32_t filename_len
Definition: util-decode-mime.h:138
TAILQ_FOREACH
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:252
tm-threads-common.h
util-privs.h
email_field
const char * email_field
Definition: output-json-email-common.c:64
MimeDecUrl::next
struct MimeDecUrl * next
Definition: util-decode-mime.h:125
MimeDecField
This represents a header field name and associated value.
Definition: util-decode-mime.h:106
flags
uint32_t flags
Definition: output-json-email-common.c:65
proto
uint8_t proto
Definition: decode-template.h:0
MimeDecEntity::url_list
MimeDecUrl * url_list
Definition: util-decode-mime.h:134
TM_ECODE_FAILED
@ TM_ECODE_FAILED
Definition: tm-threads-common.h:85
MimeDecUrl::url_len
uint32_t url_len
Definition: util-decode-mime.h:123
util-unittest.h
ConfValIsTrue
int ConfValIsTrue(const char *val)
Check if a value is true.
Definition: conf.c:537
TM_ECODE_OK
@ TM_ECODE_OK
Definition: tm-threads-common.h:84
SCReturnBool
#define SCReturnBool(x)
Definition: util-debug.h:289
util-debug.h
OutputJsonEmailCtx_::fields
uint64_t fields
Definition: output-json-email-common.h:29
output-json.h
ALPROTO_SMTP
@ ALPROTO_SMTP
Definition: app-layer-protos.h:32
MimeDecParseState::stack
MimeDecStack * stack
Definition: util-decode-mime.h:188
util-print.h
detect.h
pkt-var.h
output-json-email-common.h
app-layer-parser.h
OutputEmailInitConf
void OutputEmailInitConf(ConfNode *conf, OutputJsonEmailCtx *email_ctx)
Definition: output-json-email-common.c:372
Packet_
Definition: decode.h:437
SMTPTransaction_
Definition: app-layer-smtp.h:72
conf.h
TmEcode
TmEcode
Definition: tm-threads-common.h:83
SCReturnPtr
#define SCReturnPtr(x, type)
Definition: util-debug.h:287
SCLogInfo
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition: util-debug.h:224
AppLayerParserGetTx
void * AppLayerParserGetTx(uint8_t ipproto, AppProto alproto, void *alstate, uint64_t tx_id)
Definition: app-layer-parser.c:1114
EveEmailAddMetadata
bool EveEmailAddMetadata(const Flow *f, uint32_t tx_id, JsonBuilder *js)
Definition: output-json-email-common.c:359
MimeDecUrl::url_flags
uint32_t url_flags
Definition: util-decode-mime.h:124
ConfNodeLookupChild
ConfNode * ConfNodeLookupChild(const ConfNode *node, const char *name)
Lookup a child configuration node by name.
Definition: conf.c:786
LOG_EMAIL_DEFAULT
#define LOG_EMAIL_DEFAULT
Definition: output-json-email-common.c:55
MimeDecField::value_len
uint32_t value_len
Definition: util-decode-mime.h:109
MimeDecParseState::has_md5
bool has_md5
Definition: util-decode-mime.h:198
config_field
const char * config_field
Definition: output-json-email-common.c:63
suricata-common.h
email_fields
struct @131 email_fields[]
LOG_EMAIL_ARRAY
#define LOG_EMAIL_ARRAY
Definition: output-json-email-common.c:57
threadvars.h
URL_IS_IP6
#define URL_IS_IP6
Definition: util-decode-mime.h:47
MimeDecParseState
Structure contains the current state of the MIME parser.
Definition: util-decode-mime.h:186
MimeDecParseStateGetStatus
const char * MimeDecParseStateGetStatus(MimeDecParseState *state)
Definition: util-decode-mime.c:2326
MimeDecFindField
MimeDecField * MimeDecFindField(const MimeDecEntity *entity, const char *name)
Searches for a header field with the specified name.
Definition: util-decode-mime.c:325
SCFree
#define SCFree(p)
Definition: util-mem.h:61
ConfNode_
Definition: conf.h:32
util-logopenfile.h
MimeDecUrl
This represents a URL value node in a linked list.
Definition: util-decode-mime.h:121
util-buffer.h
MimeDecFindFieldsForEach
int MimeDecFindFieldsForEach(const MimeDecEntity *entity, const char *name, int(*DataCallback)(const uint8_t *val, const size_t, void *data), void *data)
Searches for header fields with the specified name.
Definition: util-decode-mime.c:297
suricata.h
MimeDecEntity::next
struct MimeDecEntity * next
Definition: util-decode-mime.h:144
LOG_EMAIL_SUBJECT_MD5
#define LOG_EMAIL_SUBJECT_MD5
Definition: output-json-email-common.c:60
likely
#define likely(expr)
Definition: util-optimize.h:32
CTNT_IS_ATTACHMENT
#define CTNT_IS_ATTACHMENT
Definition: util-decode-mime.h:39
app-layer-smtp.h
JsonEmailLogThread_::emaillog_ctx
OutputJsonEmailCtx * emaillog_ctx
Definition: output-json-email-common.h:34
OutputJsonEmailCtx_::flags
uint32_t flags
Definition: output-json-email-common.h:28
URL_IS_EXE
#define URL_IS_EXE
Definition: util-decode-mime.h:48
SCReturnInt
#define SCReturnInt(x)
Definition: util-debug.h:275
MimeDecEntity
This represents the MIME Entity (or also top level message) in a child-sibling tree.
Definition: util-decode-mime.h:132
OutputJsonEmailCtx_
Definition: output-json-email-common.h:27
MimeDecParseState::state_flag
uint8_t state_flag
Definition: util-decode-mime.h:199
LOG_EMAIL_BODY_MD5
#define LOG_EMAIL_BODY_MD5
Definition: output-json-email-common.c:59
output.h
MimeDecStackNode::data
MimeDecEntity * data
Definition: util-decode-mime.h:155
MimeDecParseState::md5
uint8_t md5[SC_MD5_LEN]
Definition: util-decode-mime.h:197
app-layer.h
MimeDecUrl::url
uint8_t * url
Definition: util-decode-mime.h:122
g_disable_hashing
bool g_disable_hashing
Definition: suricata.c:213
ConfNodeLookupChildValue
const char * ConfNodeLookupChildValue(const ConfNode *node, const char *name)
Lookup the value of a child configuration node by name.
Definition: conf.c:814