1 /* vi: set et ts=4: */
2 /* Copyright (C) 2007-2021 Open Information Security Foundation
3 *
4 * You can copy, redistribute or modify this Program under the terms of
5 * the GNU General Public License version 2 as published by the Free
6 * Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * version 2 along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 * 02110-1301, USA.
17 */
18
19 /**
20 * \file
21 *
22 * \author Mike Pomraning <mpomraning@qualys.com>
23 *
24 * File-like output for logging: regular files and sockets.
25 */
26
27 #include "suricata-common.h" /* errno.h, string.h, etc. */
28 #include "conf.h" /* ConfNode, etc. */
29 #include "output.h" /* DEFAULT_LOG_* */
30 #include "util-byte.h"
31 #include "util-logopenfile.h"
32
33 #if defined(HAVE_SYS_UN_H) && defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SYS_TYPES_H)
34 #define BUILD_WITH_UNIXSOCKET
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <sys/un.h>
38 #endif
39
40 #ifdef HAVE_LIBHIREDIS
41 #include "util-log-redis.h"
42 #endif /* HAVE_LIBHIREDIS */
43
44 static bool LogFileNewThreadedCtx(LogFileCtx *parent_ctx, const char *log_path, const char *append, int i);
45 static bool SCLogOpenThreadedFileFp(const char *log_path, const char *append, LogFileCtx *parent_ctx, int slot_count);
46
47 // Threaded eve.json identifier
48 static SC_ATOMIC_DECL_AND_INIT_WITH_VAL(uint32_t, eve_file_id, 1);
49
50 #ifdef BUILD_WITH_UNIXSOCKET
51 /** \brief connect to the indicated local stream socket, logging any errors
52 * \param path filesystem path to connect to
53 * \param log_err, non-zero if connect failure should be logged.
54 * \retval FILE* on success (fdopen'd wrapper of underlying socket)
55 * \retval NULL on error
56 */
57 static FILE *
SCLogOpenUnixSocketFp(const char * path,int sock_type,int log_err)58 SCLogOpenUnixSocketFp(const char *path, int sock_type, int log_err)
59 {
60 struct sockaddr_un saun;
61 int s = -1;
62 FILE * ret = NULL;
63
64 memset(&saun, 0x00, sizeof(saun));
65
66 s = socket(PF_UNIX, sock_type, 0);
67 if (s < 0) goto err;
68
69 saun.sun_family = AF_UNIX;
70 strlcpy(saun.sun_path, path, sizeof(saun.sun_path));
71
72 if (connect(s, (const struct sockaddr *)&saun, sizeof(saun)) < 0)
73 goto err;
74
75 ret = fdopen(s, "w");
76 if (ret == NULL)
77 goto err;
78
79 return ret;
80
81 err:
82 if (log_err)
83 SCLogWarning(SC_ERR_SOCKET,
84 "Error connecting to socket \"%s\": %s (will keep trying)",
85 path, strerror(errno));
86
87 if (s >= 0)
88 close(s);
89
90 return NULL;
91 }
92
93 /**
94 * \brief Attempt to reconnect a disconnected (or never-connected) Unix domain socket.
95 * \retval 1 if it is now connected; otherwise 0
96 */
SCLogUnixSocketReconnect(LogFileCtx * log_ctx)97 static int SCLogUnixSocketReconnect(LogFileCtx *log_ctx)
98 {
99 int disconnected = 0;
100 if (log_ctx->fp) {
101 SCLogWarning(SC_ERR_SOCKET,
102 "Write error on Unix socket \"%s\": %s; reconnecting...",
103 log_ctx->filename, strerror(errno));
104 fclose(log_ctx->fp);
105 log_ctx->fp = NULL;
106 log_ctx->reconn_timer = 0;
107 disconnected = 1;
108 }
109
110 struct timeval tv;
111 uint64_t now;
112 gettimeofday(&tv, NULL);
113 now = (uint64_t)tv.tv_sec * 1000;
114 now += tv.tv_usec / 1000; /* msec resolution */
115 if (log_ctx->reconn_timer != 0 &&
116 (now - log_ctx->reconn_timer) < LOGFILE_RECONN_MIN_TIME) {
117 /* Don't bother to try reconnecting too often. */
118 return 0;
119 }
120 log_ctx->reconn_timer = now;
121
122 log_ctx->fp = SCLogOpenUnixSocketFp(log_ctx->filename, log_ctx->sock_type, 0);
123 if (log_ctx->fp) {
124 /* Connected at last (or reconnected) */
125 SCLogNotice("Reconnected socket \"%s\"", log_ctx->filename);
126 } else if (disconnected) {
127 SCLogWarning(SC_ERR_SOCKET, "Reconnect failed: %s (will keep trying)",
128 strerror(errno));
129 }
130
131 return log_ctx->fp ? 1 : 0;
132 }
133
SCLogFileWriteSocket(const char * buffer,int buffer_len,LogFileCtx * ctx)134 static int SCLogFileWriteSocket(const char *buffer, int buffer_len,
135 LogFileCtx *ctx)
136 {
137 int tries = 0;
138 int ret = 0;
139 bool reopen = false;
140 if (ctx->fp == NULL && ctx->is_sock) {
141 SCLogUnixSocketReconnect(ctx);
142 }
143 tryagain:
144 ret = -1;
145 reopen = 0;
146 errno = 0;
147 if (ctx->fp != NULL) {
148 int fd = fileno(ctx->fp);
149 ssize_t size = send(fd, buffer, buffer_len, ctx->send_flags);
150 if (size > -1) {
151 ret = 0;
152 } else {
153 if (errno == EAGAIN || errno == EWOULDBLOCK) {
154 SCLogDebug("Socket would block, dropping event.");
155 } else if (errno == EINTR) {
156 if (tries++ == 0) {
157 SCLogDebug("Interrupted system call, trying again.");
158 goto tryagain;
159 }
160 SCLogDebug("Too many interrupted system calls, "
161 "dropping event.");
162 } else {
163 /* Some other error. Assume badness and reopen. */
164 SCLogDebug("Send failed: %s", strerror(errno));
165 reopen = true;
166 }
167 }
168 }
169
170 if (reopen && tries++ == 0) {
171 if (SCLogUnixSocketReconnect(ctx)) {
172 goto tryagain;
173 }
174 }
175
176 if (ret == -1) {
177 ctx->dropped++;
178 }
179
180 return ret;
181 }
182 #endif /* BUILD_WITH_UNIXSOCKET */
OutputWriteLock(pthread_mutex_t * m)183 static inline void OutputWriteLock(pthread_mutex_t *m)
184 {
185 SCMutexLock(m);
186
187 }
188
189 /**
190 * \brief Write buffer to log file.
191 * \retval 0 on failure; otherwise, the return value of fwrite_unlocked (number of
192 * characters successfully written).
193 */
SCLogFileWriteNoLock(const char * buffer,int buffer_len,LogFileCtx * log_ctx)194 static int SCLogFileWriteNoLock(const char *buffer, int buffer_len, LogFileCtx *log_ctx)
195 {
196 int ret = 0;
197
198 BUG_ON(log_ctx->is_sock);
199
200 /* Check for rotation. */
201 if (log_ctx->rotation_flag) {
202 log_ctx->rotation_flag = 0;
203 SCConfLogReopen(log_ctx);
204 }
205
206 if (log_ctx->flags & LOGFILE_ROTATE_INTERVAL) {
207 time_t now = time(NULL);
208 if (now >= log_ctx->rotate_time) {
209 SCConfLogReopen(log_ctx);
210 log_ctx->rotate_time = now + log_ctx->rotate_interval;
211 }
212 }
213
214 if (log_ctx->fp) {
215 SCClearErrUnlocked(log_ctx->fp);
216 if (1 != SCFwriteUnlocked(buffer, buffer_len, 1, log_ctx->fp)) {
217 /* Only the first error is logged */
218 if (!log_ctx->output_errors) {
219 SCLogError(SC_ERR_LOG_OUTPUT, "%s error while writing to %s",
220 SCFerrorUnlocked(log_ctx->fp) ? strerror(errno) : "unknown error",
221 log_ctx->filename);
222 }
223 log_ctx->output_errors++;
224 } else {
225 SCFflushUnlocked(log_ctx->fp);
226 }
227 }
228
229 return ret;
230 }
231
232 /**
233 * \brief Write buffer to log file.
234 * \retval 0 on failure; otherwise, the return value of fwrite (number of
235 * characters successfully written).
236 */
SCLogFileWrite(const char * buffer,int buffer_len,LogFileCtx * log_ctx)237 static int SCLogFileWrite(const char *buffer, int buffer_len, LogFileCtx *log_ctx)
238 {
239 OutputWriteLock(&log_ctx->fp_mutex);
240 int ret = 0;
241
242 #ifdef BUILD_WITH_UNIXSOCKET
243 if (log_ctx->is_sock) {
244 ret = SCLogFileWriteSocket(buffer, buffer_len, log_ctx);
245 } else
246 #endif
247 {
248
249 /* Check for rotation. */
250 if (log_ctx->rotation_flag) {
251 log_ctx->rotation_flag = 0;
252 SCConfLogReopen(log_ctx);
253 }
254
255 if (log_ctx->flags & LOGFILE_ROTATE_INTERVAL) {
256 time_t now = time(NULL);
257 if (now >= log_ctx->rotate_time) {
258 SCConfLogReopen(log_ctx);
259 log_ctx->rotate_time = now + log_ctx->rotate_interval;
260 }
261 }
262
263 if (log_ctx->fp) {
264 clearerr(log_ctx->fp);
265 if (1 != fwrite(buffer, buffer_len, 1, log_ctx->fp)) {
266 /* Only the first error is logged */
267 if (!log_ctx->output_errors) {
268 SCLogError(SC_ERR_LOG_OUTPUT, "%s error while writing to %s",
269 ferror(log_ctx->fp) ? strerror(errno) : "unknown error",
270 log_ctx->filename);
271 }
272 log_ctx->output_errors++;
273 } else {
274 fflush(log_ctx->fp);
275 }
276 }
277 }
278
279 SCMutexUnlock(&log_ctx->fp_mutex);
280
281 return ret;
282 }
283
284 /** \brief generate filename based on pattern
285 * \param pattern pattern to use
286 * \retval char* on success
287 * \retval NULL on error
288 */
SCLogFilenameFromPattern(const char * pattern)289 static char *SCLogFilenameFromPattern(const char *pattern)
290 {
291 char *filename = SCMalloc(PATH_MAX);
292 if (filename == NULL) {
293 return NULL;
294 }
295
296 int rc = SCTimeToStringPattern(time(NULL), pattern, filename, PATH_MAX);
297 if (rc != 0) {
298 SCFree(filename);
299 return NULL;
300 }
301
302 return filename;
303 }
304
SCLogFileCloseNoLock(LogFileCtx * log_ctx)305 static void SCLogFileCloseNoLock(LogFileCtx *log_ctx)
306 {
307 SCLogDebug("Closing %s", log_ctx->filename);
308 if (log_ctx->fp)
309 fclose(log_ctx->fp);
310
311 if (log_ctx->output_errors) {
312 SCLogError(SC_ERR_LOG_OUTPUT, "There were %" PRIu64 " output errors to %s",
313 log_ctx->output_errors, log_ctx->filename);
314 }
315 }
316
SCLogFileClose(LogFileCtx * log_ctx)317 static void SCLogFileClose(LogFileCtx *log_ctx)
318 {
319 SCMutexLock(&log_ctx->fp_mutex);
320 SCLogFileCloseNoLock(log_ctx);
321 SCMutexUnlock(&log_ctx->fp_mutex);
322 }
323
324 static bool
SCLogOpenThreadedFileFp(const char * log_path,const char * append,LogFileCtx * parent_ctx,int slot_count)325 SCLogOpenThreadedFileFp(const char *log_path, const char *append, LogFileCtx *parent_ctx, int slot_count)
326 {
327 parent_ctx->threads = SCCalloc(1, sizeof(LogThreadedFileCtx));
328 if (!parent_ctx->threads) {
329 SCLogError(SC_ERR_MEM_ALLOC, "Unable to allocate threads container");
330 return false;
331 }
332 parent_ctx->threads->append = SCStrdup(append);
333 if (!parent_ctx->threads->append) {
334 SCLogError(SC_ERR_MEM_ALLOC, "Unable to allocate threads append setting");
335 goto error_exit;
336 }
337
338 parent_ctx->threads->slot_count = slot_count;
339 parent_ctx->threads->lf_slots = SCCalloc(slot_count, sizeof(LogFileCtx *));
340 if (!parent_ctx->threads->lf_slots) {
341 SCLogError(SC_ERR_MEM_ALLOC, "Unable to allocate thread slots");
342 goto error_exit;
343 }
344 SCLogDebug("Allocated %d file context pointers for threaded array",
345 parent_ctx->threads->slot_count);
346 int slot = 1;
347 for (; slot < parent_ctx->threads->slot_count; slot++) {
348 if (!LogFileNewThreadedCtx(parent_ctx, log_path, append, slot)) {
349 /* TODO: clear allocated entries [1, slot) */
350 goto error_exit;
351 }
352 }
353 SCMutexInit(&parent_ctx->threads->mutex, NULL);
354 return true;
355
356 error_exit:
357
358 if (parent_ctx->threads->lf_slots) {
359 SCFree(parent_ctx->threads->lf_slots);
360 }
361 if (parent_ctx->threads->append) {
362 SCFree(parent_ctx->threads->append);
363 }
364 SCFree(parent_ctx->threads);
365 parent_ctx->threads = NULL;
366 return false;
367 }
368
369 /** \brief open the indicated file, logging any errors
370 * \param path filesystem path to open
371 * \param append_setting open file with O_APPEND: "yes" or "no"
372 * \param mode permissions to set on file
373 * \retval FILE* on success
374 * \retval NULL on error
375 */
376 static FILE *
SCLogOpenFileFp(const char * path,const char * append_setting,uint32_t mode)377 SCLogOpenFileFp(const char *path, const char *append_setting, uint32_t mode)
378 {
379 FILE *ret = NULL;
380
381 char *filename = SCLogFilenameFromPattern(path);
382 if (filename == NULL) {
383 return NULL;
384 }
385
386 int rc = SCCreateDirectoryTree(filename, false);
387 if (rc < 0) {
388 SCFree(filename);
389 return NULL;
390 }
391
392 if (ConfValIsTrue(append_setting)) {
393 ret = fopen(filename, "a");
394 } else {
395 ret = fopen(filename, "w");
396 }
397
398 if (ret == NULL) {
399 SCLogError(SC_ERR_FOPEN, "Error opening file: \"%s\": %s",
400 filename, strerror(errno));
401 } else {
402 if (mode != 0) {
403 int r = chmod(filename, mode);
404 if (r < 0) {
405 SCLogWarning(SC_WARN_CHMOD, "Could not chmod %s to %o: %s",
406 filename, mode, strerror(errno));
407 }
408 }
409 }
410
411 SCFree(filename);
412 return ret;
413 }
414
415 /** \brief open a generic output "log file", which may be a regular file or a socket
416 * \param conf ConfNode structure for the output section in question
417 * \param log_ctx Log file context allocated by caller
418 * \param default_filename Default name of file to open, if not specified in ConfNode
419 * \param rotate Register the file for rotation in HUP.
420 * \retval 0 on success
421 * \retval -1 on error
422 */
423 int
SCConfLogOpenGeneric(ConfNode * conf,LogFileCtx * log_ctx,const char * default_filename,int rotate)424 SCConfLogOpenGeneric(ConfNode *conf,
425 LogFileCtx *log_ctx,
426 const char *default_filename,
427 int rotate)
428 {
429 char log_path[PATH_MAX];
430 const char *log_dir;
431 const char *filename, *filetype;
432
433 // Arg check
434 if (conf == NULL || log_ctx == NULL || default_filename == NULL) {
435 SCLogError(SC_ERR_INVALID_ARGUMENT,
436 "SCConfLogOpenGeneric(conf %p, ctx %p, default %p) "
437 "missing an argument",
438 conf, log_ctx, default_filename);
439 return -1;
440 }
441 if (log_ctx->fp != NULL) {
442 SCLogError(SC_ERR_INVALID_ARGUMENT,
443 "SCConfLogOpenGeneric: previously initialized Log CTX "
444 "encountered");
445 return -1;
446 }
447
448 // Resolve the given config
449 filename = ConfNodeLookupChildValue(conf, "filename");
450 if (filename == NULL)
451 filename = default_filename;
452
453 log_dir = ConfigGetLogDirectory();
454
455 if (PathIsAbsolute(filename)) {
456 snprintf(log_path, PATH_MAX, "%s", filename);
457 } else {
458 snprintf(log_path, PATH_MAX, "%s/%s", log_dir, filename);
459 }
460
461 /* Rotate log file based on time */
462 const char *rotate_int = ConfNodeLookupChildValue(conf, "rotate-interval");
463 if (rotate_int != NULL) {
464 time_t now = time(NULL);
465 log_ctx->flags |= LOGFILE_ROTATE_INTERVAL;
466
467 /* Use a specific time */
468 if (strcmp(rotate_int, "minute") == 0) {
469 log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now);
470 log_ctx->rotate_interval = 60;
471 } else if (strcmp(rotate_int, "hour") == 0) {
472 log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now);
473 log_ctx->rotate_interval = 3600;
474 } else if (strcmp(rotate_int, "day") == 0) {
475 log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now);
476 log_ctx->rotate_interval = 86400;
477 }
478
479 /* Use a timer */
480 else {
481 log_ctx->rotate_interval = SCParseTimeSizeString(rotate_int);
482 if (log_ctx->rotate_interval == 0) {
483 FatalError(SC_ERR_FATAL,
484 "invalid rotate-interval value");
485 }
486 log_ctx->rotate_time = now + log_ctx->rotate_interval;
487 }
488 }
489
490 filetype = ConfNodeLookupChildValue(conf, "filetype");
491 if (filetype == NULL)
492 filetype = DEFAULT_LOG_FILETYPE;
493
494 const char *filemode = ConfNodeLookupChildValue(conf, "filemode");
495 uint32_t mode = 0;
496 if (filemode != NULL &&
497 StringParseUint32(&mode, 8, strlen(filemode),
498 filemode) > 0) {
499 log_ctx->filemode = mode;
500 }
501
502 const char *append = ConfNodeLookupChildValue(conf, "append");
503 if (append == NULL)
504 append = DEFAULT_LOG_MODE_APPEND;
505
506 /* JSON flags */
507 log_ctx->json_flags = JSON_PRESERVE_ORDER|JSON_COMPACT|
508 JSON_ENSURE_ASCII|JSON_ESCAPE_SLASH;
509
510 ConfNode *json_flags = ConfNodeLookupChild(conf, "json");
511
512 if (json_flags != 0) {
513 const char *preserve_order = ConfNodeLookupChildValue(json_flags,
514 "preserve-order");
515 if (preserve_order != NULL && ConfValIsFalse(preserve_order))
516 log_ctx->json_flags &= ~(JSON_PRESERVE_ORDER);
517
518 const char *compact = ConfNodeLookupChildValue(json_flags, "compact");
519 if (compact != NULL && ConfValIsFalse(compact))
520 log_ctx->json_flags &= ~(JSON_COMPACT);
521
522 const char *ensure_ascii = ConfNodeLookupChildValue(json_flags,
523 "ensure-ascii");
524 if (ensure_ascii != NULL && ConfValIsFalse(ensure_ascii))
525 log_ctx->json_flags &= ~(JSON_ENSURE_ASCII);
526
527 const char *escape_slash = ConfNodeLookupChildValue(json_flags,
528 "escape-slash");
529 if (escape_slash != NULL && ConfValIsFalse(escape_slash))
530 log_ctx->json_flags &= ~(JSON_ESCAPE_SLASH);
531 }
532
533 // Now, what have we been asked to open?
534 if (strcasecmp(filetype, "unix_stream") == 0) {
535 #ifdef BUILD_WITH_UNIXSOCKET
536 /* Don't bail. May be able to connect later. */
537 log_ctx->is_sock = 1;
538 log_ctx->sock_type = SOCK_STREAM;
539 log_ctx->fp = SCLogOpenUnixSocketFp(log_path, SOCK_STREAM, 1);
540 #else
541 return -1;
542 #endif
543 } else if (strcasecmp(filetype, "unix_dgram") == 0) {
544 #ifdef BUILD_WITH_UNIXSOCKET
545 /* Don't bail. May be able to connect later. */
546 log_ctx->is_sock = 1;
547 log_ctx->sock_type = SOCK_DGRAM;
548 log_ctx->fp = SCLogOpenUnixSocketFp(log_path, SOCK_DGRAM, 1);
549 #else
550 return -1;
551 #endif
552 } else if (strcasecmp(filetype, DEFAULT_LOG_FILETYPE) == 0 ||
553 strcasecmp(filetype, "file") == 0) {
554 log_ctx->is_regular = 1;
555 if (!log_ctx->threaded) {
556 log_ctx->fp = SCLogOpenFileFp(log_path, append, log_ctx->filemode);
557 if (log_ctx->fp == NULL)
558 return -1; // Error already logged by Open...Fp routine
559 } else {
560 if (!SCLogOpenThreadedFileFp(log_path, append, log_ctx, 1)) {
561 return -1;
562 }
563 }
564 if (rotate) {
565 OutputRegisterFileRotationFlag(&log_ctx->rotation_flag);
566 }
567 #ifdef HAVE_LIBHIREDIS
568 } else if (strcasecmp(filetype, "redis") == 0) {
569 ConfNode *redis_node = ConfNodeLookupChild(conf, "redis");
570 if (SCConfLogOpenRedis(redis_node, log_ctx) < 0) {
571 SCLogError(SC_ERR_REDIS, "failed to open redis output");
572 return -1;
573 }
574 log_ctx->type = LOGFILE_TYPE_REDIS;
575 #endif
576 } else {
577 SCLogError(SC_ERR_INVALID_YAML_CONF_ENTRY, "Invalid entry for "
578 "%s.filetype. Expected \"regular\" (default), \"unix_stream\", "
579 "or \"unix_dgram\"",
580 conf->name);
581 }
582 log_ctx->filename = SCStrdup(log_path);
583 if (unlikely(log_ctx->filename == NULL)) {
584 SCLogError(SC_ERR_MEM_ALLOC,
585 "Failed to allocate memory for filename");
586 return -1;
587 }
588
589 #ifdef BUILD_WITH_UNIXSOCKET
590 /* If a socket and running live, do non-blocking writes. */
591 if (log_ctx->is_sock && !IsRunModeOffline(RunmodeGetCurrent())) {
592 SCLogInfo("Setting logging socket of non-blocking in live mode.");
593 log_ctx->send_flags |= MSG_DONTWAIT;
594 }
595 #endif
596 SCLogInfo("%s output device (%s) initialized: %s", conf->name, filetype,
597 filename);
598
599 return 0;
600 }
601
602 /**
603 * \brief Reopen a regular log file with the side-affect of truncating it.
604 *
605 * This is useful to clear the log file and start a new one, or to
606 * re-open the file after its been moved by something external
607 * (eg. logrotate).
608 */
SCConfLogReopen(LogFileCtx * log_ctx)609 int SCConfLogReopen(LogFileCtx *log_ctx)
610 {
611 if (!log_ctx->is_regular) {
612 /* Not supported and not needed on non-regular files. */
613 return 0;
614 }
615
616 if (log_ctx->filename == NULL) {
617 SCLogWarning(SC_ERR_INVALID_ARGUMENT,
618 "Can't re-open LogFileCtx without a filename.");
619 return -1;
620 }
621
622 if (log_ctx->fp != NULL) {
623 fclose(log_ctx->fp);
624 }
625
626 /* Reopen the file. Append is forced in case the file was not
627 * moved as part of a rotation process. */
628 SCLogDebug("Reopening log file %s.", log_ctx->filename);
629 log_ctx->fp = SCLogOpenFileFp(log_ctx->filename, "yes", log_ctx->filemode);
630 if (log_ctx->fp == NULL) {
631 return -1; // Already logged by Open..Fp routine.
632 }
633
634 return 0;
635 }
636
637 /** \brief LogFileNewCtx() Get a new LogFileCtx
638 * \retval LogFileCtx * pointer if successful, NULL if error
639 * */
LogFileNewCtx(void)640 LogFileCtx *LogFileNewCtx(void)
641 {
642 LogFileCtx* lf_ctx;
643 lf_ctx = (LogFileCtx*)SCCalloc(1, sizeof(LogFileCtx));
644
645 if (lf_ctx == NULL)
646 return NULL;
647
648 lf_ctx->Write = SCLogFileWrite;
649 lf_ctx->Close = SCLogFileClose;
650
651 return lf_ctx;
652 }
653
654 /** \brief LogFileEnsureExists() Ensure a log file context for the thread exists
655 * \param parent_ctx
656 * \param thread_id
657 * \retval LogFileCtx * pointer if successful; NULL otherwise
658 */
LogFileEnsureExists(LogFileCtx * parent_ctx,int thread_id)659 LogFileCtx *LogFileEnsureExists(LogFileCtx *parent_ctx, int thread_id)
660 {
661 /* threaded output disabled */
662 if (!parent_ctx->threaded)
663 return parent_ctx;
664
665 SCLogDebug("Adding reference %d to file ctx %p", thread_id, parent_ctx);
666 SCMutexLock(&parent_ctx->threads->mutex);
667 /* are there enough context slots already */
668 if (thread_id < parent_ctx->threads->slot_count) {
669 /* has it been opened yet? */
670 if (!parent_ctx->threads->lf_slots[thread_id]) {
671 SCLogDebug("Opening new file for %d reference to file ctx %p", thread_id, parent_ctx);
672 LogFileNewThreadedCtx(parent_ctx, parent_ctx->filename, parent_ctx->threads->append, thread_id);
673 }
674 SCLogDebug("Existing file for %d reference to file ctx %p", thread_id, parent_ctx);
675 SCMutexUnlock(&parent_ctx->threads->mutex);
676 return parent_ctx->threads->lf_slots[thread_id];
677 }
678
679 /* ensure there's a slot for the caller */
680 int new_size = MAX(parent_ctx->threads->slot_count << 1, thread_id + 1);
681 SCLogDebug("Increasing slot count; current %d, trying %d",
682 parent_ctx->threads->slot_count, new_size);
683 LogFileCtx **new_array = SCRealloc(parent_ctx->threads->lf_slots, new_size * sizeof(LogFileCtx *));
684 if (new_array == NULL) {
685 /* Try one more time */
686 SCLogDebug("Unable to increase file context array size to %d; trying %d",
687 new_size, thread_id + 1);
688 new_size = thread_id + 1;
689 new_array = SCRealloc(parent_ctx->threads->lf_slots, new_size * sizeof(LogFileCtx *));
690 }
691
692 if (new_array == NULL) {
693 SCLogError(SC_ERR_MEM_ALLOC, "Unable to increase file context array size to %d", new_size);
694 SCMutexUnlock(&parent_ctx->threads->mutex);
695 return NULL;
696 }
697
698 parent_ctx->threads->lf_slots = new_array;
699 /* initialize newly added slots */
700 for (int i = parent_ctx->threads->slot_count; i < new_size; i++) {
701 parent_ctx->threads->lf_slots[i] = NULL;
702 }
703 parent_ctx->threads->slot_count = new_size;
704 LogFileNewThreadedCtx(parent_ctx, parent_ctx->filename, parent_ctx->threads->append, thread_id);
705
706 SCMutexUnlock(&parent_ctx->threads->mutex);
707
708 return parent_ctx->threads->lf_slots[thread_id];
709 }
710
711 /** \brief LogFileThreadedName() Create file name for threaded EVE storage
712 *
713 */
LogFileThreadedName(const char * original_name,char * threaded_name,size_t len,uint32_t unique_id)714 static bool LogFileThreadedName(
715 const char *original_name, char *threaded_name, size_t len, uint32_t unique_id)
716 {
717 const char *base = SCBasename(original_name);
718 if (!base) {
719 FatalError(SC_ERR_FATAL,
720 "Invalid filename for threaded mode \"%s\"; "
721 "no basename found.",
722 original_name);
723 }
724
725 /* Check if basename has an extension */
726 char *dot = strrchr(base, '.');
727 if (dot) {
728 char *tname = SCStrdup(original_name);
729 if (!tname) {
730 return false;
731 }
732
733 /* Fetch extension location from original, not base
734 * for update
735 */
736 dot = strrchr(original_name, '.');
737 int dotpos = dot - original_name;
738 tname[dotpos] = '\0';
739 char *ext = tname + dotpos + 1;
740 if (strlen(tname) && strlen(ext)) {
741 snprintf(threaded_name, len, "%s.%d.%s", tname, unique_id, ext);
742 } else {
743 FatalError(SC_ERR_FATAL,
744 "Invalid filename for threaded mode \"%s\"; "
745 "filenames must include an extension, e.g: \"name.ext\"",
746 original_name);
747 }
748 SCFree(tname);
749 } else {
750 snprintf(threaded_name, len, "%s.%d", original_name, unique_id);
751 }
752 return true;
753 }
754
755 /** \brief LogFileNewThreadedCtx() Create file context for threaded output
756 * \param parent_ctx
757 * \param log_path
758 * \param append
759 * \param thread_id
760 */
LogFileNewThreadedCtx(LogFileCtx * parent_ctx,const char * log_path,const char * append,int thread_id)761 static bool LogFileNewThreadedCtx(LogFileCtx *parent_ctx, const char *log_path, const char *append, int thread_id)
762 {
763 LogFileCtx *thread = SCCalloc(1, sizeof(LogFileCtx));
764 if (!thread) {
765 SCLogError(SC_ERR_MEM_ALLOC, "Unable to allocate thread file context slot %d", thread_id);
766 return false;
767 }
768
769 *thread = *parent_ctx;
770 char fname[NAME_MAX];
771 if (!LogFileThreadedName(log_path, fname, sizeof(fname), SC_ATOMIC_ADD(eve_file_id, 1))) {
772 SCLogError(SC_ERR_MEM_ALLOC, "Unable to create threaded filename for log");
773 goto error;
774 }
775 SCLogDebug("Thread open -- using name %s [replaces %s]", fname, log_path);
776 thread->fp = SCLogOpenFileFp(fname, append, thread->filemode);
777 if (thread->fp == NULL) {
778 goto error;
779 }
780 thread->filename = SCStrdup(fname);
781 if (!thread->filename) {
782 SCLogError(SC_ERR_MEM_ALLOC, "Unable to duplicate filename for context slot %d", thread_id);
783 goto error;
784 }
785
786 thread->threaded = false;
787 thread->parent = parent_ctx;
788 thread->id = thread_id;
789 thread->is_regular = true;
790 thread->Write = SCLogFileWriteNoLock;
791 thread->Close = SCLogFileCloseNoLock;
792 OutputRegisterFileRotationFlag(&thread->rotation_flag);
793
794 parent_ctx->threads->lf_slots[thread_id] = thread;
795 return true;
796
797 error:
798 SC_ATOMIC_SUB(eve_file_id, 1);
799 if (thread->fp) {
800 thread->Close(thread);
801 }
802 if (thread) {
803 SCFree(thread);
804 }
805 parent_ctx->threads->lf_slots[thread_id] = NULL;
806 return false;
807 }
808
809 /** \brief LogFileFreeCtx() Destroy a LogFileCtx (Close the file and free memory)
810 * \param lf_ctx pointer to the OutputCtx
811 * \retval int 1 if successful, 0 if error
812 * */
LogFileFreeCtx(LogFileCtx * lf_ctx)813 int LogFileFreeCtx(LogFileCtx *lf_ctx)
814 {
815 if (lf_ctx == NULL) {
816 SCReturnInt(0);
817 }
818
819 if (lf_ctx->threaded) {
820 SCMutexDestroy(&lf_ctx->threads->mutex);
821 for(int i = 0; i < lf_ctx->threads->slot_count; i++) {
822 if (lf_ctx->threads->lf_slots[i]) {
823 OutputUnregisterFileRotationFlag(&lf_ctx->threads->lf_slots[i]->rotation_flag);
824 lf_ctx->threads->lf_slots[i]->Close(lf_ctx->threads->lf_slots[i]);
825 SCFree(lf_ctx->threads->lf_slots[i]->filename);
826 SCFree(lf_ctx->threads->lf_slots[i]);
827 }
828 }
829 SCFree(lf_ctx->threads->lf_slots);
830 SCFree(lf_ctx->threads->append);
831 SCFree(lf_ctx->threads);
832 } else {
833 if (lf_ctx->type == LOGFILE_TYPE_PLUGIN) {
834 if (lf_ctx->plugin->Close != NULL) {
835 lf_ctx->plugin->Close(lf_ctx->plugin_data);
836 }
837 } else if (lf_ctx->fp != NULL) {
838 lf_ctx->Close(lf_ctx);
839 }
840 if (lf_ctx->parent) {
841 SCMutexLock(&lf_ctx->parent->threads->mutex);
842 lf_ctx->parent->threads->lf_slots[lf_ctx->id] = NULL;
843 SCMutexUnlock(&lf_ctx->parent->threads->mutex);
844 }
845 SCMutexDestroy(&lf_ctx->fp_mutex);
846 }
847
848 if (lf_ctx->prefix != NULL) {
849 SCFree(lf_ctx->prefix);
850 lf_ctx->prefix_len = 0;
851 }
852
853 if(lf_ctx->filename != NULL)
854 SCFree(lf_ctx->filename);
855
856 if (lf_ctx->sensor_name)
857 SCFree(lf_ctx->sensor_name);
858
859 if (!lf_ctx->threaded) {
860 OutputUnregisterFileRotationFlag(&lf_ctx->rotation_flag);
861 }
862
863 SCFree(lf_ctx);
864
865 SCReturnInt(1);
866 }
867
LogFileWrite(LogFileCtx * file_ctx,MemBuffer * buffer)868 int LogFileWrite(LogFileCtx *file_ctx, MemBuffer *buffer)
869 {
870 if (file_ctx->type == LOGFILE_TYPE_SYSLOG) {
871 syslog(file_ctx->syslog_setup.alert_syslog_level, "%s",
872 (const char *)MEMBUFFER_BUFFER(buffer));
873 } else if (file_ctx->type == LOGFILE_TYPE_FILE ||
874 file_ctx->type == LOGFILE_TYPE_UNIX_DGRAM ||
875 file_ctx->type == LOGFILE_TYPE_UNIX_STREAM)
876 {
877 /* append \n for files only */
878 MemBufferWriteString(buffer, "\n");
879 file_ctx->Write((const char *)MEMBUFFER_BUFFER(buffer),
880 MEMBUFFER_OFFSET(buffer), file_ctx);
881 }
882 #ifdef HAVE_LIBHIREDIS
883 else if (file_ctx->type == LOGFILE_TYPE_REDIS) {
884 SCMutexLock(&file_ctx->fp_mutex);
885 LogFileWriteRedis(file_ctx, (const char *)MEMBUFFER_BUFFER(buffer),
886 MEMBUFFER_OFFSET(buffer));
887 SCMutexUnlock(&file_ctx->fp_mutex);
888 }
889 #endif
890 else if (file_ctx->type == LOGFILE_TYPE_PLUGIN) {
891 file_ctx->plugin->Write((const char *)MEMBUFFER_BUFFER(buffer),
892 MEMBUFFER_OFFSET(buffer), file_ctx->plugin_data);
893 }
894
895 return 0;
896 }
897