suricata
output-filestore.c
Go to the documentation of this file.
1/* Copyright (C) 2018-2022 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 "output-filestore.h"
20
21#include "stream-tcp.h"
22
23#include "feature.h"
24
25#include "output.h"
26#include "output-json-file.h"
27
28#include "util-conf.h"
29#include "util-misc.h"
30#include "util-path.h"
31#include "util-print.h"
32
33#define MODULE_NAME "OutputFilestore"
34
35/* Create a filestore specific PATH_MAX that is less than the system
36 * PATH_MAX to prevent newer gcc truncation warnings with snprint. */
37#define SHA256_STRING_LEN (SC_SHA256_LEN * 2)
38#define LEAF_DIR_MAX_LEN 4
39#define FILESTORE_PREFIX_MAX (PATH_MAX - SHA256_STRING_LEN - LEAF_DIR_MAX_LEN)
40
41/* The default log directory, relative to the default log
42 * directory. */
43static const char *default_log_dir = "filestore";
44
45/* Atomic counter of simultaneously open files. */
46static SC_ATOMIC_DECLARE(uint32_t, filestore_open_file_cnt);
47
54
60
70
71/* For WARN_ONCE, a record of warnings that have already been
72 * issued. */
73static thread_local bool once_errs[WOT_MAX];
74
75#define WARN_ONCE(wot_type, ...) \
76 do { \
77 if (!once_errs[wot_type]) { \
78 once_errs[wot_type] = true; \
79 SCLogWarning(__VA_ARGS__); \
80 } \
81 } while (0)
82
83static uint64_t OutputFilestoreOpenFilesCounter(void)
84{
85 return SC_ATOMIC_GET(filestore_open_file_cnt);
86}
87
88static uint32_t g_file_store_max_open_files = 0;
89
90static void FileSetMaxOpenFiles(uint32_t count)
91{
92 g_file_store_max_open_files = count;
93}
94
95static uint32_t FileGetMaxOpenFiles(void)
96{
97 return g_file_store_max_open_files;
98}
99
100/**
101 * \brief Update the timestamps on a file to match those of another
102 * file.
103 *
104 * \param src_filename Filename to use as timestamp source.
105 * \param filename Filename to apply timestamps to.
106 */
107static void OutputFilestoreUpdateFileTime(const char *src_filename,
108 const char *filename)
109{
110 struct stat sb;
111 if (stat(src_filename, &sb) != 0) {
112 SCLogDebug("Failed to stat %s: %s", filename, strerror(errno));
113 return;
114 }
115 struct utimbuf utimbuf = {
116 .actime = sb.st_atime,
117 .modtime = sb.st_mtime,
118 };
119 if (utime(filename, &utimbuf) != 0) {
120 SCLogDebug("Failed to update file timestamps: %s: %s", filename,
121 strerror(errno));
122 }
123}
124
125static void OutputFilestoreFinalizeFiles(ThreadVars *tv, const OutputFilestoreLogThread *oft,
126 const OutputFilestoreCtx *ctx, const Packet *p, File *ff, void *tx, const uint64_t tx_id,
127 uint8_t dir)
128{
129 /* Stringify the SHA256 which will be used in the final
130 * filename. */
131 char sha256string[(SC_SHA256_LEN * 2) + 1];
132 PrintHexString(sha256string, sizeof(sha256string), ff->sha256,
133 sizeof(ff->sha256));
134
135 char tmp_filename[PATH_MAX] = "";
136 snprintf(tmp_filename, sizeof(tmp_filename), "%s/file.%u", ctx->tmpdir,
137 ff->file_store_id);
138
139 char final_filename[PATH_MAX] = "";
140 snprintf(final_filename, sizeof(final_filename), "%s/%c%c/%s",
141 ctx->prefix, sha256string[0], sha256string[1], sha256string);
142
143 if (SCPathExists(final_filename)) {
144 OutputFilestoreUpdateFileTime(tmp_filename, final_filename);
145 if (unlink(tmp_filename) != 0) {
147 WARN_ONCE(WOT_UNLINK, "Failed to remove temporary file %s: %s", tmp_filename,
148 strerror(errno));
149 }
150 } else if (rename(tmp_filename, final_filename) != 0) {
152 WARN_ONCE(WOT_RENAME, "Failed to rename %s to %s: %s", tmp_filename, final_filename,
153 strerror(errno));
154 if (unlink(tmp_filename) != 0) {
155 /* Just increment, don't log as has_fs_errors would
156 * already be set above. */
158 }
159 return;
160 }
161
162 if (ctx->fileinfo) {
163 char js_metadata_filename[PATH_MAX];
164 if (snprintf(js_metadata_filename, sizeof(js_metadata_filename), "%s.%" PRIuMAX ".%u.json",
165 final_filename, (uintmax_t)SCTIME_SECS(p->ts),
166 ff->file_store_id) == (int)sizeof(js_metadata_filename)) {
167 WARN_ONCE(WOT_SNPRINTF, "Failed to write file info record. Output filename truncated.");
168 } else {
169 SCJsonBuilder *js_fileinfo =
170 JsonBuildFileInfoRecord(p, ff, tx, tx_id, true, dir, ctx->xff_cfg, NULL);
171 if (likely(js_fileinfo != NULL)) {
172 SCJbClose(js_fileinfo);
173 FILE *out = fopen(js_metadata_filename, "w");
174 if (out != NULL) {
175 size_t js_len = SCJbLen(js_fileinfo);
176 fwrite(SCJbPtr(js_fileinfo), js_len, 1, out);
177 fclose(out);
178 }
179 SCJbFree(js_fileinfo);
180 }
181 }
182 }
183}
184
185static int OutputFilestoreLogger(ThreadVars *tv, void *thread_data, const Packet *p, File *ff,
186 void *tx, const uint64_t tx_id, const uint8_t *data, uint32_t data_len, uint8_t flags,
187 uint8_t dir)
188{
189 SCEnter();
191 OutputFilestoreCtx *ctx = aft->ctx;
192 char filename[PATH_MAX] = "";
193 int file_fd = -1;
194
195 SCLogDebug("ff %p, data %p, data_len %u", ff, data, data_len);
196
198 snprintf(filename, sizeof(filename), "%s/file.%u", ctx->tmpdir, ff->file_store_id);
199 file_fd = open(filename, O_CREAT | O_TRUNC | O_NOFOLLOW | O_WRONLY,
200 0644);
201 if (file_fd == -1) {
203 SCLogWarning("Filestore (v2) failed to create %s: %s", filename, strerror(errno));
204 return -1;
205 }
206
207 if (SC_ATOMIC_GET(filestore_open_file_cnt) < FileGetMaxOpenFiles()) {
208 SC_ATOMIC_ADD(filestore_open_file_cnt, 1);
209 ff->fd = file_fd;
210 } else {
211 if (FileGetMaxOpenFiles() > 0) {
213 }
214 ff->fd = -1;
215 }
216 /* we can get called with a NULL ffd when we need to close */
217 } else if (data != NULL) {
218 if (ff->fd == -1) {
219 snprintf(filename, sizeof(filename), "%s/file.%u", ctx->tmpdir, ff->file_store_id);
220 file_fd = open(filename, O_APPEND | O_NOFOLLOW | O_WRONLY);
221 if (file_fd == -1) {
223 WARN_ONCE(WOT_OPEN, "Filestore (v2) failed to open file %s: %s", filename,
224 strerror(errno));
225 return -1;
226 }
227 } else {
228 file_fd = ff->fd;
229 }
230 }
231
232 if (file_fd != -1) {
233 ssize_t r = write(file_fd, (const void *)data, (size_t)data_len);
234 if (r == -1) {
235 snprintf(filename, sizeof(filename), "%s/file.%u", ctx->tmpdir, ff->file_store_id);
237 WARN_ONCE(WOT_WRITE, "Filestore (v2) failed to write to %s: %s", filename,
238 strerror(errno));
239 if (ff->fd != -1) {
240 SC_ATOMIC_SUB(filestore_open_file_cnt, 1);
241 }
242 ff->fd = -1;
243 }
244 if (ff->fd == -1) {
245 close(file_fd);
246 }
247 }
248
250 if (ff->fd != -1) {
251 close(ff->fd);
252 ff->fd = -1;
253 SC_ATOMIC_SUB(filestore_open_file_cnt, 1);
254 }
255 OutputFilestoreFinalizeFiles(tv, aft, ctx, p, ff, tx, tx_id, dir);
256 }
257
258 return 0;
259}
260
261static TmEcode OutputFilestoreLogThreadInit(ThreadVars *t, const void *initdata,
262 void **data)
263{
265 if (unlikely(aft == NULL))
266 return TM_ECODE_FAILED;
267
268 if (initdata == NULL) {
269 SCLogDebug("Error getting context for LogFileStore. \"initdata\" argument NULL");
270 SCFree(aft);
271 return TM_ECODE_FAILED;
272 }
273
274 OutputFilestoreCtx *ctx = ((OutputCtx *)initdata)->data;
275 aft->ctx = ctx;
276
277 aft->counter_max_hits =
278 StatsRegisterCounter("file_store.open_files_max_hit", t);
279
280 /* File system type errors (open, write, rename) will only be
281 * logged once. But this stat will be incremented for every
282 * occurrence. */
283 aft->fs_error_counter = StatsRegisterCounter("file_store.fs_errors", t);
284
285 *data = (void *)aft;
286 return TM_ECODE_OK;
287}
288
289static TmEcode OutputFilestoreLogThreadDeinit(ThreadVars *t, void *data)
290{
292 if (aft == NULL) {
293 return TM_ECODE_OK;
294 }
295
296 /* clear memory */
297 memset(aft, 0, sizeof(OutputFilestoreLogThread));
298
299 SCFree(aft);
300 return TM_ECODE_OK;
301}
302
303static void OutputFilestoreLogDeInitCtx(OutputCtx *output_ctx)
304{
306 if (ctx->xff_cfg != NULL) {
307 SCFree(ctx->xff_cfg);
308 }
309 SCFree(ctx);
310 SCFree(output_ctx);
311}
312
313static void GetLogDirectory(const SCConfNode *conf, char *out, size_t out_size)
314{
315 const char *log_base_dir = SCConfNodeLookupChildValue(conf, "dir");
316 if (log_base_dir == NULL) {
317 SCLogConfig("Filestore (v2) default log directory %s", default_log_dir);
318 log_base_dir = default_log_dir;
319 }
320 if (PathIsAbsolute(log_base_dir)) {
321 strlcpy(out, log_base_dir, out_size);
322 } else {
323 const char *default_log_prefix = SCConfigGetLogDirectory();
324 snprintf(out, out_size, "%s/%s", default_log_prefix, log_base_dir);
325 }
326}
327
328static bool InitFilestoreDirectory(const char *dir)
329{
330 const uint8_t dir_count = 0xff;
331
332 if (!SCPathExists(dir)) {
333 SCLogInfo("Filestore (v2) creating directory %s", dir);
334 if (SCCreateDirectoryTree(dir, true) != 0) {
335 SCLogError("Filestore (v2) failed to create directory %s: %s", dir, strerror(errno));
336 return false;
337 }
338 }
339
340 for (int i = 0; i <= dir_count; i++) {
341 char leaf[PATH_MAX];
342 int n = snprintf(leaf, sizeof(leaf), "%s/%02x", dir, i);
343 if (n < 0 || n >= PATH_MAX) {
344 SCLogError("Filestore (v2) failed to create leaf directory: "
345 "path too long");
346 return false;
347 }
348 if (!SCPathExists(leaf)) {
349 SCLogInfo("Filestore (v2) creating directory %s", leaf);
350 if (SCDefaultMkDir(leaf) != 0) {
352 "Filestore (v2) failed to create directory %s: %s", leaf, strerror(errno));
353 return false;
354 }
355 }
356 }
357
358 /* Make sure the tmp directory exists. */
359 char tmpdir[PATH_MAX];
360 int n = snprintf(tmpdir, sizeof(tmpdir), "%s/tmp", dir);
361 if (n < 0 || n >= PATH_MAX) {
362 SCLogError("Filestore (v2) failed to create tmp directory: path too long");
363 return false;
364 }
365 if (!SCPathExists(tmpdir)) {
366 SCLogInfo("Filestore (v2) creating directory %s", tmpdir);
367 if (SCDefaultMkDir(tmpdir) != 0) {
368 SCLogError("Filestore (v2) failed to create directory %s: %s", tmpdir, strerror(errno));
369 return false;
370 }
371 }
372
373 return true;
374}
375
376/** \brief Create a new http log OutputFilestoreCtx.
377 * \param conf Pointer to ConfNode containing this loggers configuration.
378 * \return NULL if failure, OutputFilestoreCtx* to the file_ctx if succesful
379 * */
380static OutputInitResult OutputFilestoreLogInitCtx(SCConfNode *conf)
381{
382 OutputInitResult result = { NULL, false };
383
384 intmax_t version = 0;
385 if (!SCConfGetChildValueInt(conf, "version", &version) || version < 2) {
386 SCLogWarning("File-store v1 has been removed. Please update to file-store v2.");
387 return result;
388 }
389
391 SCLogWarning("A file data logger is already enabled. Filestore (v2) "
392 "will not be enabled.");
393 return result;
394 }
395
396 char log_directory[PATH_MAX] = "";
397 GetLogDirectory(conf, log_directory, sizeof(log_directory));
398 if (!InitFilestoreDirectory(log_directory)) {
399 return result;
400 }
401
402 OutputFilestoreCtx *ctx = SCCalloc(1, sizeof(*ctx));
403 if (unlikely(ctx == NULL)) {
404 return result;
405 }
406
407 strlcpy(ctx->prefix, log_directory, sizeof(ctx->prefix));
408 int written = snprintf(ctx->tmpdir, sizeof(ctx->tmpdir) - 1, "%s/tmp",
409 log_directory);
410 if (written == sizeof(ctx->tmpdir)) {
411 SCLogError("File-store output directory overflow.");
412 SCFree(ctx);
413 return result;
414 }
415
416 ctx->xff_cfg = SCCalloc(1, sizeof(HttpXFFCfg));
417 if (ctx->xff_cfg != NULL) {
418 HttpXFFGetCfg(conf, ctx->xff_cfg);
419 }
420
421 OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
422 if (unlikely(output_ctx == NULL)) {
423 SCFree(ctx->xff_cfg);
424 SCFree(ctx);
425 return result;
426 }
427
428 output_ctx->data = ctx;
429 output_ctx->DeInit = OutputFilestoreLogDeInitCtx;
430
431 const char *write_fileinfo = SCConfNodeLookupChildValue(conf, "write-fileinfo");
432 if (write_fileinfo != NULL && SCConfValIsTrue(write_fileinfo)) {
433 SCLogConfig("Filestore (v2) will output fileinfo records.");
434 ctx->fileinfo = true;
435 }
436
437 const char *force_filestore = SCConfNodeLookupChildValue(conf, "force-filestore");
438 if (force_filestore != NULL && SCConfValIsTrue(force_filestore)) {
440 SCLogInfo("forcing filestore of all files");
441 }
442
443 const char *force_magic = SCConfNodeLookupChildValue(conf, "force-magic");
444 if (force_magic != NULL && SCConfValIsTrue(force_magic)) {
446 SCLogConfig("Filestore (v2) forcing magic lookup for stored files");
447 }
448
450
451 /* The new filestore requires SHA256. */
453
455
456 const char *stream_depth_str = SCConfNodeLookupChildValue(conf, "stream-depth");
457 if (stream_depth_str != NULL && strcmp(stream_depth_str, "no")) {
458 uint32_t stream_depth = 0;
459 if (ParseSizeStringU32(stream_depth_str,
460 &stream_depth) < 0) {
461 SCLogError("Error parsing "
462 "file-store.stream-depth "
463 "from conf file - %s. Killing engine",
464 stream_depth_str);
465 exit(EXIT_FAILURE);
466 }
467 if (stream_depth) {
468 if (stream_depth <= stream_config.reassembly_depth) {
469 SCLogWarning("file-store.stream-depth value %" PRIu32 " has "
470 "no effect since it's less than stream.reassembly.depth "
471 "value.",
472 stream_depth);
473 } else {
474 FileReassemblyDepthEnable(stream_depth);
475 }
476 }
477 }
478
479 const char *file_count_str = SCConfNodeLookupChildValue(conf, "max-open-files");
480 if (file_count_str != NULL) {
481 uint32_t file_count = 0;
482 if (ParseSizeStringU32(file_count_str,
483 &file_count) < 0) {
484 SCLogError("Error parsing "
485 "file-store.max-open-files "
486 "from conf file - %s. Killing engine",
487 file_count_str);
488 exit(EXIT_FAILURE);
489 } else {
490 if (file_count != 0) {
491 FileSetMaxOpenFiles(file_count);
492 SCLogConfig("Filestore (v2) will keep a max of %d "
493 "simultaneously open files", file_count);
494 }
495 }
496 }
497
498 result.ctx = output_ctx;
499 result.ok = true;
500 SCReturnCT(result, "OutputInitResult");
501}
502
504{
506 OutputFilestoreLogInitCtx, OutputFilestoreLogger, OutputFilestoreLogThreadInit,
507 OutputFilestoreLogThreadDeinit);
508
509 SC_ATOMIC_INIT(filestore_open_file_cnt);
510 SC_ATOMIC_SET(filestore_open_file_cnt, 0);
511}
512
514{
515 StatsRegisterGlobalCounter("file_store.open_files", OutputFilestoreOpenFilesCounter);
516}
void HttpXFFGetCfg(SCConfNode *conf, HttpXFFCfg *result)
Function to return XFF configuration from a configuration node.
int SCConfValIsTrue(const char *val)
Check if a value is true.
Definition conf.c:551
int SCConfGetChildValueInt(const SCConfNode *base, const char *name, intmax_t *val)
Definition conf.c:449
const char * SCConfNodeLookupChildValue(const SCConfNode *node, const char *name)
Lookup the value of a child configuration node by name.
Definition conf.c:824
uint16_t StatsRegisterGlobalCounter(const char *name, uint64_t(*Func)(void))
Registers a counter, which represents a global value.
Definition counters.c:1010
uint16_t StatsRegisterCounter(const char *name, struct ThreadVars_ *tv)
Registers a normal, unqualified counter.
Definition counters.c:952
void StatsIncr(ThreadVars *tv, uint16_t id)
Increments the local counter.
Definition counters.c:166
uint8_t flags
Definition decode-gre.h:0
uint8_t version
Definition decode-gre.h:1
void ProvidesFeature(const char *feature_name)
Definition feature.c:111
#define FEATURE_OUTPUT_FILESTORE
Definition feature.h:28
TcpStreamCnf stream_config
Definition stream-tcp.c:219
ThreadVars * tv
struct Thresholds ctx
#define OUTPUT_FILEDATA_FLAG_CLOSE
#define OUTPUT_FILEDATA_FLAG_OPEN
#define MODULE_NAME
#define FILESTORE_PREFIX_MAX
#define WARN_ONCE(wot_type,...)
struct OutputFilestoreLogThread_ OutputFilestoreLogThread
void OutputFilestoreRegister(void)
struct OutputFilestoreCtx_ OutputFilestoreCtx
WarnOnceTypes
@ WOT_UNLINK
@ WOT_MAX
@ WOT_WRITE
@ WOT_RENAME
@ WOT_OPEN
@ WOT_SNPRINTF
void OutputFilestoreRegisterGlobalCounters(void)
SCJsonBuilder * JsonBuildFileInfoRecord(const Packet *p, const File *ff, void *tx, const uint64_t tx_id, const bool stored, uint8_t dir, HttpXFFCfg *xff_cfg, OutputJsonCtx *eve_ctx)
void OutputRegisterFiledataModule(LoggerId id, const char *name, const char *conf_name, OutputInitFunc InitFunc, SCFiledataLogger FiledataLogFunc, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit)
Register a file data output module.
Definition output.c:459
int RunModeOutputFiledataEnabled(void)
Definition runmodes.c:543
uint8_t sha256[SC_SHA256_LEN]
Definition util-file.h:98
int fd
Definition util-file.h:86
uint32_t file_store_id
Definition util-file.h:85
void * data
Definition tm-modules.h:91
void(* DeInit)(struct OutputCtx_ *)
Definition tm-modules.h:94
char tmpdir[FILESTORE_PREFIX_MAX]
char prefix[FILESTORE_PREFIX_MAX]
OutputFilestoreCtx * ctx
OutputCtx * ctx
Definition output.h:47
SCTime_t ts
Definition decode.h:555
uint32_t reassembly_depth
Definition stream-tcp.h:75
Per thread variable structure.
Definition threadvars.h:58
@ LOGGER_FILE_STORE
size_t strlcpy(char *dst, const char *src, size_t siz)
@ TM_ECODE_FAILED
@ TM_ECODE_OK
#define SC_ATOMIC_ADD(name, val)
add a value to our atomic variable
#define SC_ATOMIC_INIT(name)
wrapper for initializing an atomic variable.
#define SC_ATOMIC_DECLARE(type, name)
wrapper for declaring atomic variables.
#define SC_ATOMIC_SUB(name, val)
sub a value from our atomic variable
#define SC_ATOMIC_GET(name)
Get the value from the atomic variable.
#define SC_ATOMIC_SET(name, val)
Set the value for the atomic variable.
const char * SCConfigGetLogDirectory(void)
Definition util-conf.c:38
#define SCEnter(...)
Definition util-debug.h:277
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCReturnCT(x, type)
Definition util-debug.h:291
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition util-debug.h:255
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition util-debug.h:225
#define SCLogError(...)
Macro used to log ERROR messages.
Definition util-debug.h:267
#define SCLogConfig(...)
Definition util-debug.h:229
void FileForceSha256Enable(void)
Definition util-file.c:116
void FileForceMagicEnable(void)
Definition util-file.c:98
void FileReassemblyDepthEnable(uint32_t size)
Definition util-file.c:127
void FileForceFilestoreEnable(void)
Definition util-file.c:92
void FileForceHashParseCfg(SCConfNode *conf)
Function to parse forced file hashing configuration.
Definition util-file.c:170
#define SC_SHA256_LEN
Definition util-file.h:37
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
int ParseSizeStringU32(const char *size, uint32_t *res)
Definition util-misc.c:173
#define likely(expr)
#define unlikely(expr)
bool SCPathExists(const char *path)
Check if a path exists.
Definition util-path.c:183
int PathIsAbsolute(const char *path)
Check if a path is absolute.
Definition util-path.c:44
int SCCreateDirectoryTree(const char *path, const bool final)
Recursively create a directory.
Definition util-path.c:137
int SCDefaultMkDir(const char *path)
Wrapper around SCMkDir with default mode arguments.
Definition util-path.c:123
void PrintHexString(char *str, size_t size, uint8_t *buf, size_t buf_len)
Definition util-print.c:255
#define SCTIME_SECS(t)
Definition util-time.h:57
#define O_NOFOLLOW
Definition win32-misc.h:30