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