/src/suricata/src/output-json-file.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-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 Tom DeCanio <td@npulsetech.com> |
22 | | * |
23 | | * Log files we track. |
24 | | * |
25 | | */ |
26 | | |
27 | | #include "suricata-common.h" |
28 | | #include "detect.h" |
29 | | #include "pkt-var.h" |
30 | | #include "conf.h" |
31 | | |
32 | | #include "threadvars.h" |
33 | | #include "tm-modules.h" |
34 | | |
35 | | #include "threads.h" |
36 | | |
37 | | #include "app-layer-parser.h" |
38 | | |
39 | | #include "detect-filemagic.h" |
40 | | |
41 | | #include "stream.h" |
42 | | |
43 | | #include "util-print.h" |
44 | | #include "util-unittest.h" |
45 | | #include "util-privs.h" |
46 | | #include "util-debug.h" |
47 | | #include "util-atomic.h" |
48 | | #include "util-file.h" |
49 | | #include "util-time.h" |
50 | | #include "util-buffer.h" |
51 | | #include "util-byte.h" |
52 | | #include "util-validate.h" |
53 | | |
54 | | #include "util-logopenfile.h" |
55 | | |
56 | | #include "output.h" |
57 | | #include "output-json.h" |
58 | | #include "output-json-file.h" |
59 | | #include "output-json-http.h" |
60 | | #include "output-json-smtp.h" |
61 | | #include "output-json-email-common.h" |
62 | | #include "output-json-nfs.h" |
63 | | #include "output-json-smb.h" |
64 | | |
65 | | #include "app-layer-htp.h" |
66 | | #include "app-layer-htp-xff.h" |
67 | | #include "util-memcmp.h" |
68 | | #include "stream-tcp-reassemble.h" |
69 | | |
70 | | typedef struct OutputFileCtx_ { |
71 | | uint32_t file_cnt; |
72 | | HttpXFFCfg *xff_cfg; |
73 | | HttpXFFCfg *parent_xff_cfg; |
74 | | OutputJsonCtx *eve_ctx; |
75 | | } OutputFileCtx; |
76 | | |
77 | | typedef struct JsonFileLogThread_ { |
78 | | OutputFileCtx *filelog_ctx; |
79 | | OutputJsonThreadCtx *ctx; |
80 | | } JsonFileLogThread; |
81 | | |
82 | | SCJsonBuilder *JsonBuildFileInfoRecord(const Packet *p, const File *ff, void *tx, |
83 | | const uint64_t tx_id, const bool stored, uint8_t dir, HttpXFFCfg *xff_cfg, |
84 | | OutputJsonCtx *eve_ctx) |
85 | 113k | { |
86 | 113k | enum SCOutputJsonLogDirection fdir = LOG_DIR_FLOW; |
87 | | |
88 | 113k | switch(dir) { |
89 | 103k | case STREAM_TOCLIENT: |
90 | 103k | fdir = LOG_DIR_FLOW_TOCLIENT; |
91 | 103k | break; |
92 | 9.36k | case STREAM_TOSERVER: |
93 | 9.36k | fdir = LOG_DIR_FLOW_TOSERVER; |
94 | 9.36k | break; |
95 | 0 | default: |
96 | 0 | DEBUG_VALIDATE_BUG_ON(1); |
97 | 0 | break; |
98 | 113k | } |
99 | | |
100 | 113k | JsonAddrInfo addr = json_addr_info_zero; |
101 | 113k | JsonAddrInfoInit(p, fdir, &addr); |
102 | | |
103 | | /* Overwrite address info with XFF if needed. */ |
104 | 113k | int have_xff_ip = 0; |
105 | 113k | char xff_buffer[XFF_MAXLEN]; |
106 | 113k | if ((xff_cfg != NULL) && !(xff_cfg->flags & XFF_DISABLED)) { |
107 | 113k | if (FlowGetAppProtocol(p->flow) == ALPROTO_HTTP1) { |
108 | 75.7k | have_xff_ip = HttpXFFGetIPFromTx(p->flow, tx_id, xff_cfg, xff_buffer, XFF_MAXLEN); |
109 | 75.7k | } |
110 | 113k | if (have_xff_ip && xff_cfg->flags & XFF_OVERWRITE) { |
111 | 0 | if (p->flowflags & FLOW_PKT_TOCLIENT) { |
112 | 0 | strlcpy(addr.dst_ip, xff_buffer, JSON_ADDR_LEN); |
113 | 0 | } else { |
114 | 0 | strlcpy(addr.src_ip, xff_buffer, JSON_ADDR_LEN); |
115 | 0 | } |
116 | 0 | have_xff_ip = 0; |
117 | 0 | } |
118 | 113k | } |
119 | | |
120 | 113k | SCJsonBuilder *js = CreateEveHeader(p, fdir, "fileinfo", &addr, eve_ctx); |
121 | 113k | if (unlikely(js == NULL)) |
122 | 0 | return NULL; |
123 | | |
124 | 113k | SCJsonBuilderMark mark = { 0, 0, 0 }; |
125 | 113k | EveJsonSimpleAppLayerLogger *al; |
126 | 113k | switch (p->flow->alproto) { |
127 | 75.7k | case ALPROTO_HTTP1: |
128 | 75.7k | SCJbOpenObject(js, "http"); |
129 | 75.7k | EveHttpAddMetadata(p->flow, tx_id, js); |
130 | 75.7k | SCJbClose(js); |
131 | 75.7k | break; |
132 | 1.96k | case ALPROTO_SMTP: |
133 | 1.96k | SCJbGetMark(js, &mark); |
134 | 1.96k | SCJbOpenObject(js, "smtp"); |
135 | 1.96k | if (EveSMTPAddMetadata(p->flow, tx_id, js)) { |
136 | 1.96k | SCJbClose(js); |
137 | 1.96k | } else { |
138 | 0 | SCJbRestoreMark(js, &mark); |
139 | 0 | } |
140 | 1.96k | SCJbGetMark(js, &mark); |
141 | 1.96k | SCJbOpenObject(js, "email"); |
142 | 1.96k | if (EveEmailAddMetadata(p->flow, tx_id, js)) { |
143 | 1.96k | SCJbClose(js); |
144 | 1.96k | } else { |
145 | 0 | SCJbRestoreMark(js, &mark); |
146 | 0 | } |
147 | 1.96k | break; |
148 | 2.46k | case ALPROTO_NFS: |
149 | | /* rpc */ |
150 | 2.46k | SCJbGetMark(js, &mark); |
151 | 2.46k | SCJbOpenObject(js, "rpc"); |
152 | 2.46k | if (EveNFSAddMetadataRPC(p->flow, tx_id, js)) { |
153 | 2.46k | SCJbClose(js); |
154 | 2.46k | } else { |
155 | 0 | SCJbRestoreMark(js, &mark); |
156 | 0 | } |
157 | | /* nfs */ |
158 | 2.46k | SCJbGetMark(js, &mark); |
159 | 2.46k | SCJbOpenObject(js, "nfs"); |
160 | 2.46k | if (EveNFSAddMetadata(p->flow, tx_id, js)) { |
161 | 2.46k | SCJbClose(js); |
162 | 2.46k | } else { |
163 | 0 | SCJbRestoreMark(js, &mark); |
164 | 0 | } |
165 | 2.46k | break; |
166 | 8.97k | case ALPROTO_SMB: |
167 | 8.97k | SCJbGetMark(js, &mark); |
168 | 8.97k | SCJbOpenObject(js, "smb"); |
169 | 8.97k | if (EveSMBAddMetadata(p->flow, tx_id, js)) { |
170 | 8.97k | SCJbClose(js); |
171 | 8.97k | } else { |
172 | 0 | SCJbRestoreMark(js, &mark); |
173 | 0 | } |
174 | 8.97k | break; |
175 | 23.8k | default: |
176 | 23.8k | al = SCEveJsonSimpleGetLogger(p->flow->alproto); |
177 | 23.8k | if (al && al->LogTx) { |
178 | 23.8k | void *state = FlowGetAppState(p->flow); |
179 | 23.8k | if (state) { |
180 | 23.8k | tx = AppLayerParserGetTx(p->flow->proto, p->flow->alproto, state, tx_id); |
181 | 23.8k | if (tx) { |
182 | 23.8k | SCJbGetMark(js, &mark); |
183 | 23.8k | if (!al->LogTx(tx, js)) { |
184 | 1.93k | SCJbRestoreMark(js, &mark); |
185 | 1.93k | } |
186 | 23.8k | } |
187 | 23.8k | } |
188 | 23.8k | } |
189 | 23.8k | break; |
190 | 113k | } |
191 | | |
192 | 113k | SCJbSetString(js, "app_proto", AppProtoToString(p->flow->alproto)); |
193 | | |
194 | 113k | SCJbOpenObject(js, "fileinfo"); |
195 | 113k | if (stored) { |
196 | | // the file has just been stored on disk cf OUTPUT_FILEDATA_FLAG_CLOSE |
197 | | // but the flag is not set until the loggers have been called |
198 | 0 | EveFileInfo(js, ff, tx_id, ff->flags | FILE_STORED); |
199 | 113k | } else { |
200 | 113k | EveFileInfo(js, ff, tx_id, ff->flags); |
201 | 113k | } |
202 | 113k | SCJbClose(js); |
203 | | |
204 | | /* xff header */ |
205 | 113k | if (have_xff_ip && xff_cfg->flags & XFF_EXTRADATA) { |
206 | 232 | SCJbSetString(js, "xff", xff_buffer); |
207 | 232 | } |
208 | | |
209 | 113k | return js; |
210 | 113k | } |
211 | | |
212 | | /** |
213 | | * \internal |
214 | | * \brief Write meta data on a single line json record |
215 | | */ |
216 | | static void FileWriteJsonRecord(ThreadVars *tv, JsonFileLogThread *aft, const Packet *p, |
217 | | const File *ff, void *tx, const uint64_t tx_id, uint8_t dir, OutputJsonCtx *eve_ctx) |
218 | 163k | { |
219 | 163k | HttpXFFCfg *xff_cfg = aft->filelog_ctx->xff_cfg != NULL ? aft->filelog_ctx->xff_cfg |
220 | 163k | : aft->filelog_ctx->parent_xff_cfg; |
221 | 163k | SCJsonBuilder *js = JsonBuildFileInfoRecord(p, ff, tx, tx_id, false, dir, xff_cfg, eve_ctx); |
222 | 163k | if (unlikely(js == NULL)) { |
223 | 0 | return; |
224 | 0 | } |
225 | | |
226 | 163k | OutputJsonBuilderBuffer(tv, p, p->flow, js, aft->ctx); |
227 | 163k | SCJbFree(js); |
228 | 163k | } |
229 | | |
230 | | static int JsonFileLogger(ThreadVars *tv, void *thread_data, const Packet *p, const File *ff, |
231 | | void *tx, const uint64_t tx_id, uint8_t dir) |
232 | 163k | { |
233 | 163k | SCEnter(); |
234 | 163k | JsonFileLogThread *aft = (JsonFileLogThread *)thread_data; |
235 | | |
236 | 163k | DEBUG_VALIDATE_BUG_ON(ff->flags & FILE_LOGGED); |
237 | | |
238 | 163k | SCLogDebug("ff %p", ff); |
239 | | |
240 | 163k | FileWriteJsonRecord(tv, aft, p, ff, tx, tx_id, dir, aft->filelog_ctx->eve_ctx); |
241 | 163k | return 0; |
242 | 163k | } |
243 | | |
244 | | |
245 | | static TmEcode JsonFileLogThreadInit(ThreadVars *t, const void *initdata, void **data) |
246 | 4 | { |
247 | 4 | JsonFileLogThread *aft = SCCalloc(1, sizeof(JsonFileLogThread)); |
248 | 4 | if (unlikely(aft == NULL)) |
249 | 0 | return TM_ECODE_FAILED; |
250 | | |
251 | 4 | if(initdata == NULL) |
252 | 0 | { |
253 | 0 | SCLogDebug("Error getting context for EveLogFile. \"initdata\" argument NULL"); |
254 | 0 | goto error_exit; |
255 | 0 | } |
256 | | |
257 | | /* Use the Output Context (file pointer and mutex) */ |
258 | 4 | aft->filelog_ctx = ((OutputCtx *)initdata)->data; |
259 | 4 | aft->ctx = CreateEveThreadCtx(t, aft->filelog_ctx->eve_ctx); |
260 | 4 | if (!aft->ctx) { |
261 | 0 | goto error_exit; |
262 | 0 | } |
263 | | |
264 | 4 | *data = (void *)aft; |
265 | 4 | return TM_ECODE_OK; |
266 | | |
267 | 0 | error_exit: |
268 | 0 | SCFree(aft); |
269 | 0 | return TM_ECODE_FAILED; |
270 | 4 | } |
271 | | |
272 | | static TmEcode JsonFileLogThreadDeinit(ThreadVars *t, void *data) |
273 | 0 | { |
274 | 0 | JsonFileLogThread *aft = (JsonFileLogThread *)data; |
275 | 0 | if (aft == NULL) { |
276 | 0 | return TM_ECODE_OK; |
277 | 0 | } |
278 | | |
279 | 0 | FreeEveThreadCtx(aft->ctx); |
280 | | |
281 | | /* clear memory */ |
282 | 0 | memset(aft, 0, sizeof(JsonFileLogThread)); |
283 | |
|
284 | 0 | SCFree(aft); |
285 | 0 | return TM_ECODE_OK; |
286 | 0 | } |
287 | | |
288 | | static void OutputFileLogDeinitSub(OutputCtx *output_ctx) |
289 | 0 | { |
290 | 0 | OutputFileCtx *ff_ctx = output_ctx->data; |
291 | 0 | if (ff_ctx->xff_cfg != NULL) { |
292 | 0 | SCFree(ff_ctx->xff_cfg); |
293 | 0 | } |
294 | 0 | SCFree(ff_ctx); |
295 | 0 | SCFree(output_ctx); |
296 | 0 | } |
297 | | |
298 | | /** \brief Create a new http log LogFileCtx. |
299 | | * \param conf Pointer to ConfNode containing this loggers configuration. |
300 | | * \return NULL if failure, LogFileCtx* to the file_ctx if succesful |
301 | | * */ |
302 | | static OutputInitResult OutputFileLogInitSub(SCConfNode *conf, OutputCtx *parent_ctx) |
303 | 4 | { |
304 | 4 | OutputInitResult result = { NULL, false }; |
305 | 4 | OutputJsonCtx *ojc = parent_ctx->data; |
306 | | |
307 | 4 | OutputFileCtx *output_file_ctx = SCCalloc(1, sizeof(OutputFileCtx)); |
308 | 4 | if (unlikely(output_file_ctx == NULL)) |
309 | 0 | return result; |
310 | | |
311 | 4 | OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); |
312 | 4 | if (unlikely(output_ctx == NULL)) { |
313 | 0 | SCFree(output_file_ctx); |
314 | 0 | return result; |
315 | 0 | } |
316 | | |
317 | 4 | if (conf) { |
318 | 0 | const char *force_filestore = SCConfNodeLookupChildValue(conf, "force-filestore"); |
319 | 0 | if (force_filestore != NULL && SCConfValIsTrue(force_filestore)) { |
320 | 0 | FileForceFilestoreEnable(); |
321 | 0 | SCLogConfig("forcing filestore of all files"); |
322 | 0 | } |
323 | |
|
324 | 0 | const char *force_magic = SCConfNodeLookupChildValue(conf, "force-magic"); |
325 | 0 | if (force_magic != NULL && SCConfValIsTrue(force_magic)) { |
326 | 0 | FileForceMagicEnable(); |
327 | 0 | SCLogConfig("forcing magic lookup for logged files"); |
328 | 0 | } |
329 | |
|
330 | 0 | FileForceHashParseCfg(conf); |
331 | 0 | } |
332 | | |
333 | 4 | if (conf != NULL && SCConfNodeLookupChild(conf, "xff") != NULL) { |
334 | 0 | output_file_ctx->xff_cfg = SCCalloc(1, sizeof(HttpXFFCfg)); |
335 | 0 | if (output_file_ctx->xff_cfg != NULL) { |
336 | 0 | HttpXFFGetCfg(conf, output_file_ctx->xff_cfg); |
337 | 0 | } |
338 | 4 | } else if (ojc->xff_cfg) { |
339 | 4 | output_file_ctx->parent_xff_cfg = ojc->xff_cfg; |
340 | 4 | } |
341 | | |
342 | 4 | output_file_ctx->eve_ctx = ojc; |
343 | 4 | output_ctx->data = output_file_ctx; |
344 | 4 | output_ctx->DeInit = OutputFileLogDeinitSub; |
345 | | |
346 | 4 | FileForceTrackingEnable(); |
347 | 4 | result.ctx = output_ctx; |
348 | 4 | result.ok = true; |
349 | 4 | return result; |
350 | 4 | } |
351 | | |
352 | | void JsonFileLogRegister (void) |
353 | 71 | { |
354 | | /* register as child of eve-log */ |
355 | 71 | OutputRegisterFileSubModule(LOGGER_JSON_FILE, "eve-log", "JsonFileLog", "eve-log.files", |
356 | 71 | OutputFileLogInitSub, JsonFileLogger, JsonFileLogThreadInit, JsonFileLogThreadDeinit); |
357 | 71 | } |