summaryrefslogtreecommitdiffstats
path: root/src/util-debug.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util-debug.c1835
1 files changed, 1835 insertions, 0 deletions
diff --git a/src/util-debug.c b/src/util-debug.c
new file mode 100644
index 0000000..80509ae
--- /dev/null
+++ b/src/util-debug.c
@@ -0,0 +1,1835 @@
+/* Copyright (C) 2007-2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Anoop Saldanha <anoopsaldanha@gmail.com>
+ *
+ * Debug utility functions
+ */
+
+#include "suricata-common.h"
+#include "util-debug.h"
+
+#include "output.h"
+
+#include "suricata.h"
+
+#include "util-conf.h"
+#include "util-enum.h"
+#include "util-path.h"
+#include "util-syslog.h"
+#include "util-time.h"
+
+// clang-format off
+/* holds the string-enum mapping for the enums held in the table SCLogLevel */
+SCEnumCharMap sc_log_level_map[] = {
+ { "Not set", SC_LOG_NOTSET },
+ { "None", SC_LOG_NONE },
+ { "Error", SC_LOG_ERROR },
+ { "Warning", SC_LOG_WARNING },
+ { "Notice", SC_LOG_NOTICE },
+ { "Info", SC_LOG_INFO },
+ { "Perf", SC_LOG_PERF },
+ { "Config", SC_LOG_CONFIG },
+ { "Debug", SC_LOG_DEBUG },
+ { NULL, -1 }
+};
+
+SCEnumCharMap sc_log_slevel_map[] = {
+ { "Not set", SC_LOG_NOTSET },
+ { "None", SC_LOG_NONE },
+ { "E", SC_LOG_ERROR },
+ { "W", SC_LOG_WARNING },
+ { "i", SC_LOG_NOTICE },
+ { "i", SC_LOG_INFO },
+ { "i", SC_LOG_PERF },
+ { "i", SC_LOG_CONFIG },
+ { "d", SC_LOG_DEBUG },
+ { NULL, -1 }
+};
+
+/* holds the string-enum mapping for the enums held in the table SCLogOPIface */
+SCEnumCharMap sc_log_op_iface_map[ ] = {
+ { "Console", SC_LOG_OP_IFACE_CONSOLE },
+ { "File", SC_LOG_OP_IFACE_FILE },
+ { "Syslog", SC_LOG_OP_IFACE_SYSLOG },
+ { NULL, -1 }
+};
+// clang-format on
+
+#if defined (OS_WIN32)
+/**
+ * \brief Used for synchronous output on WIN32
+ */
+static SCMutex sc_log_stream_lock;
+#endif /* OS_WIN32 */
+
+/**
+ * \brief Transform the module name into display module name for logging
+ */
+static const char *SCTransformModule(const char *module_name, int *dn_len);
+
+/**
+ * \brief Holds the config state for the logging module
+ */
+static SCLogConfig *sc_log_config = NULL;
+
+/**
+ * \brief Returns the full path given a file and configured log dir
+ */
+static char *SCLogGetLogFilename(const char *);
+
+/**
+ * \brief Holds the global log level. Is the same as sc_log_config->log_level
+ */
+SCLogLevel sc_log_global_log_level;
+
+/**
+ * \brief Used to indicate whether the logging module has been init or not
+ */
+int sc_log_module_initialized = 0;
+
+/**
+ * \brief Used to indicate whether the logging module has been cleaned or not
+ */
+int sc_log_module_cleaned = 0;
+
+/**
+ * \brief Maps the SC logging level to the syslog logging level
+ *
+ * \param The SC logging level that has to be mapped to the syslog_log_level
+ *
+ * \retval syslog_log_level The mapped syslog_api_log_level, for the logging
+ * module api's internal log_level
+ */
+static inline int SCLogMapLogLevelToSyslogLevel(int log_level)
+{
+ int syslog_log_level = 0;
+
+ switch (log_level) {
+ case SC_LOG_ERROR:
+ syslog_log_level = LOG_ERR;
+ break;
+ case SC_LOG_WARNING:
+ syslog_log_level = LOG_WARNING;
+ break;
+ case SC_LOG_NOTICE:
+ syslog_log_level = LOG_NOTICE;
+ break;
+ case SC_LOG_INFO:
+ syslog_log_level = LOG_INFO;
+ break;
+ case SC_LOG_CONFIG:
+ case SC_LOG_DEBUG:
+ case SC_LOG_PERF:
+ syslog_log_level = LOG_DEBUG;
+ break;
+ default:
+ syslog_log_level = LOG_EMERG;
+ break;
+ }
+
+ return syslog_log_level;
+}
+
+/**
+ * \brief Output function that logs a character string out to a file descriptor
+ *
+ * \param fd Pointer to the file descriptor
+ * \param msg Pointer to the character string that should be logged
+ */
+static inline void SCLogPrintToStream(FILE *fd, char *msg)
+{
+ /* Would only happen if the log file failed to re-open during rotation. */
+ if (fd == NULL) {
+ return;
+ }
+
+#if defined (OS_WIN32)
+ SCMutexLock(&sc_log_stream_lock);
+#endif /* OS_WIN32 */
+
+ if (fprintf(fd, "%s\n", msg) < 0)
+ printf("Error writing to stream using fprintf\n");
+
+ fflush(fd);
+
+#if defined (OS_WIN32)
+ SCMutexUnlock(&sc_log_stream_lock);
+#endif /* OS_WIN32 */
+
+ return;
+}
+
+/**
+ * \brief Output function that logs a character string through the syslog iface
+ *
+ * \param syslog_log_level Holds the syslog_log_level that the message should be
+ * logged as
+ * \param msg Pointer to the char string, that should be logged
+ *
+ * \todo syslog is thread-safe according to POSIX manual and glibc code, but we
+ * we will have to look into non POSIX compliant boxes like freeBSD
+ */
+static inline void SCLogPrintToSyslog(int syslog_log_level, const char *msg)
+{
+ //static struct syslog_data data = SYSLOG_DATA_INIT;
+ //syslog_r(syslog_log_level, NULL, "%s", msg);
+
+ syslog(syslog_log_level, "%s", msg);
+
+ return;
+}
+
+/**
+ */
+static int SCLogMessageJSON(SCTime_t tval, char *buffer, size_t buffer_size, SCLogLevel log_level,
+ const char *file, unsigned line, const char *function, const char *module,
+ const char *message)
+{
+ JsonBuilder *js = jb_new_object();
+ if (unlikely(js == NULL))
+ goto error;
+
+ char timebuf[64];
+ CreateIsoTimeString(tval, timebuf, sizeof(timebuf));
+ jb_set_string(js, "timestamp", timebuf);
+
+ const char *s = SCMapEnumValueToName(log_level, sc_log_level_map);
+ if (s != NULL) {
+ jb_set_string(js, "log_level", s);
+ } else {
+ JB_SET_STRING(js, "log_level", "INVALID");
+ }
+
+ JB_SET_STRING(js, "event_type", "engine");
+ jb_open_object(js, "engine");
+
+ if (message)
+ jb_set_string(js, "message", message);
+
+ if (t_thread_name[0] != '\0') {
+ jb_set_string(js, "thread_name", t_thread_name);
+ }
+
+ if (module) {
+ /* Determine how much of module name to display */
+ int dn_len = 0;
+ const char *dn_name;
+ dn_name = SCTransformModule(module, &dn_len);
+ jb_set_string(js, "module", dn_name);
+ }
+
+ if (log_level >= SC_LOG_DEBUG) {
+ if (function)
+ jb_set_string(js, "function", function);
+
+ if (file)
+ jb_set_string(js, "file", file);
+
+ if (line > 0)
+ jb_set_uint(js, "line", line);
+ }
+ jb_close(js); // engine
+
+ jb_close(js);
+ memcpy(buffer, jb_ptr(js), MIN(buffer_size, jb_len(js)));
+
+ jb_free(js);
+
+ return 0;
+
+error:
+ return -1;
+}
+
+static const int transform_max_segs = 2; /* The maximum segment count to display */
+/*
+ * \brief Return a display name for the given module name for logging.
+ *
+ * The transformation is dependent upon the source code module names
+ * that use the dash character to separate incremental refinements of
+ * the subsystem.
+ *
+ * The transformation uses the local constant "transform_max_segs" to determine
+ * how many segments to display; the transformed name will never consist
+ * of more than this many segments.
+ *
+ * E.g., "detect-http-content-len" ==> "detect-http" when the max is 2
+ *
+ * \param module_name The source code module name to be transformed.
+ * \param dn_len The number of characters in the display name to print.
+ *
+ * \retval Pointer to the display name
+ */
+static const char *SCTransformModule(const char *module_name, int *dn_len)
+{
+ /*
+ * special case for source code module names beginning with:
+ * Prefixes skipped
+ * tm-*
+ * util-*
+ * source-*
+ * No transformation
+ * app-layer-*
+ */
+ if (strncmp("tm-", module_name, 3) == 0) {
+ *dn_len = strlen(module_name) - 3;
+ return module_name + 3;
+ } else if (strncmp("util-", module_name, 5) == 0) {
+ *dn_len = strlen(module_name) - 5;
+ return module_name + 5;
+ } else if (strncmp("source-pcap-file", module_name, 16) == 0) {
+ *dn_len = strlen("pcap");
+ return "pcap";
+ } else if (strncmp("source-", module_name, 7) == 0) {
+ *dn_len = strlen(module_name) - 7;
+ return module_name + 7;
+ } else if (strncmp("runmode-", module_name, 8) == 0) {
+ *dn_len = strlen(module_name) - 8;
+ return module_name + 8;
+ } else if (strncmp("app-layer-", module_name, 10) == 0) {
+ *dn_len = strlen(module_name);
+ return module_name;
+ } else if (strncmp("detect-engine", module_name, 13) == 0) {
+ *dn_len = strlen("detect");
+ return "detect";
+ }
+
+ int seg_cnt = 0;
+
+ char *last;
+ char *w = (char *)module_name;
+ while (w && (w = strchr(w, '-')) != NULL && seg_cnt < transform_max_segs) {
+ seg_cnt++;
+ last = w;
+ w++; /* skip past '-' */
+ }
+
+ if (seg_cnt < transform_max_segs)
+ *dn_len = strlen(module_name);
+ else
+ *dn_len = last - module_name;
+
+ return module_name;
+}
+
+/**
+ * \brief Adds the global log_format to the outgoing buffer
+ *
+ * \param log_level log_level of the message that has to be logged
+ * \param msg Buffer containing the outgoing message
+ * \param file File_name from where the message originated
+ * \param function Function_name from where the message originated
+ * \param line Line_no from where the messaged originated
+ *
+ * \retval 0 on success; else a negative value on error
+ */
+static SCError SCLogMessageGetBuffer(SCTime_t tval, int color, SCLogOPType type, char *buffer,
+ size_t buffer_size, const char *log_format, const SCLogLevel log_level, const char *file,
+ const unsigned int line, const char *function, const char *module, const char *message)
+{
+ if (type == SC_LOG_OP_TYPE_JSON)
+ return SCLogMessageJSON(
+ tval, buffer, buffer_size, log_level, file, line, function, module, message);
+
+ char *temp = buffer;
+ const char *s = NULL;
+ struct tm *tms = NULL;
+
+ const char *redb = "";
+ const char *red = "";
+ const char *yellowb = "";
+ const char *yellow = "";
+ const char *green = "";
+ const char *blue = "";
+ const char *reset = "";
+ if (color) {
+ redb = "\x1b[1;31m";
+ red = "\x1b[31m";
+ yellowb = "\x1b[1;33m";
+ yellow = "\x1b[33m";
+ green = "\x1b[32m";
+ blue = "\x1b[34m";
+ reset = "\x1b[0m";
+ }
+ /* no of characters_written(cw) by snprintf */
+ int cw = 0;
+
+ BUG_ON(sc_log_module_initialized != 1);
+
+ /* make a copy of the format string as it will be modified below */
+ const int add_M = strstr(log_format, "%M") == NULL;
+ char local_format[strlen(log_format) + add_M * 2 + 1];
+ strlcpy(local_format, log_format, sizeof(local_format));
+ if (add_M)
+ strlcat(local_format, "%M", sizeof(local_format));
+ char *temp_fmt = local_format;
+ char *substr = temp_fmt;
+ struct tm local_tm;
+
+ while ((temp_fmt = strchr(temp_fmt, SC_LOG_FMT_PREFIX))) {
+ if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) {
+ return 0;
+ }
+ switch(temp_fmt[1]) {
+ case SC_LOG_FMT_TIME:
+ temp_fmt[0] = '\0';
+
+ tms = SCLocalTime(SCTIME_SECS(tval), &local_tm);
+
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%04d-%02d-%02d %02d:%02d:%02d%s", substr, green, tms->tm_year + 1900,
+ tms->tm_mon + 1, tms->tm_mday, tms->tm_hour, tms->tm_min, tms->tm_sec,
+ reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_TIME_LEGACY:
+ temp_fmt[0] = '\0';
+
+ tms = SCLocalTime(SCTIME_SECS(tval), &local_tm);
+
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%d/%d/%04d -- %02d:%02d:%02d%s",
+ substr, green, tms->tm_mday, tms->tm_mon + 1,
+ tms->tm_year + 1900, tms->tm_hour, tms->tm_min,
+ tms->tm_sec, reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_PID:
+ temp_fmt[0] = '\0';
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%u%s", substr, yellow, getpid(), reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_TID:
+ temp_fmt[0] = '\0';
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%lu%s", substr, yellow, SCGetThreadIdLong(), reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_THREAD_NAME:
+ case SC_LOG_FMT_TM:
+ temp_fmt[0] = '\0';
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s", substr,
+ yellow, t_thread_name, reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_LOG_LEVEL:
+ temp_fmt[0] = '\0';
+ s = SCMapEnumValueToName(log_level, sc_log_level_map);
+ if (s != NULL) {
+ if (log_level <= SC_LOG_ERROR)
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%s%s", substr, redb, s, reset);
+ else if (log_level == SC_LOG_WARNING)
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%s%s", substr, red, s, reset);
+ else if (log_level == SC_LOG_NOTICE)
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%s%s", substr, yellowb, s, reset);
+ else
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s",
+ substr, yellow, s, reset);
+ } else {
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s", substr,
+ "INVALID");
+ }
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_LOG_SLEVEL:
+ temp_fmt[0] = '\0';
+ s = SCMapEnumValueToName(log_level, sc_log_slevel_map);
+ if (s != NULL) {
+ if (log_level <= SC_LOG_ERROR)
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s",
+ substr, redb, s, reset);
+ else if (log_level == SC_LOG_WARNING)
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s",
+ substr, red, s, reset);
+ else if (log_level == SC_LOG_NOTICE)
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s",
+ substr, yellowb, s, reset);
+ else
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%s%s", substr, yellow, s, reset);
+ } else {
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s", substr, "INVALID");
+ }
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_FILE_NAME:
+ temp_fmt[0] = '\0';
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%s%s", substr, blue, file, reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_LINE:
+ temp_fmt[0] = '\0';
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%u%s", substr, green, line, reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_SUBSYSTEM:
+ temp_fmt[0] = '\0';
+
+ /* Determine how much of module name to display */
+ int dn_len = 0;
+ const char *dn_name = "unknown";
+ if (module) {
+ dn_name = SCTransformModule(module, &dn_len);
+ }
+
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s%s", substr,
+ green, dn_name, reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_FUNCTION:
+ temp_fmt[0] = '\0';
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer),
+ "%s%s%s%s", substr, green, function, reset);
+ if (cw < 0)
+ return -1;
+ temp += cw;
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+
+ case SC_LOG_FMT_MESSAGE: {
+ temp_fmt[0] = '\0';
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s", substr);
+ if (cw < 0) {
+ return -1;
+ }
+ temp += cw;
+ if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) {
+ return 0;
+ }
+ const char *hi = "";
+ if (log_level <= SC_LOG_ERROR)
+ hi = red;
+ else if (log_level <= SC_LOG_NOTICE)
+ hi = yellow;
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s", hi, message,
+ reset);
+ if (cw < 0) {
+ return -1;
+ }
+ temp += cw;
+ if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) {
+ return 0;
+ }
+ temp_fmt++;
+ substr = temp_fmt;
+ substr++;
+ break;
+ }
+ }
+ temp_fmt++;
+ }
+ if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) {
+ return 0;
+ }
+ cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s", substr);
+ if (cw < 0) {
+ return -1;
+ }
+ if (sc_log_config->op_filter_regex != NULL) {
+ if (pcre2_match(sc_log_config->op_filter_regex, (PCRE2_SPTR8)buffer, strlen(buffer), 0, 0,
+ sc_log_config->op_filter_regex_match, NULL) < 0) {
+ return -1; // bit hacky, but just return !0
+ }
+ }
+
+ return 0;
+}
+
+/** \internal
+ * \brief try to reopen file
+ * \note no error reporting here, as we're called by SCLogMessage
+ * \retval status 0 ok, -1 error */
+static int SCLogReopen(SCLogOPIfaceCtx *op_iface_ctx)
+{
+ if (op_iface_ctx->file == NULL) {
+ return 0;
+ }
+
+ if (op_iface_ctx->file_d != NULL) {
+ fclose(op_iface_ctx->file_d);
+ }
+ op_iface_ctx->file_d = fopen(op_iface_ctx->file, "a");
+ if (op_iface_ctx->file_d == NULL) {
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * \brief Adds the global log_format to the outgoing buffer
+ *
+ * \param log_level log_level of the message that has to be logged
+ * \param msg Buffer containing the outgoing message
+ * \param file File_name from where the message originated
+ * \param function Function_name from where the message originated
+ * \param line Line_no from where the messaged originated
+ *
+ * \retval SC_OK on success; else an error code
+ */
+SCError SCLogMessage(const SCLogLevel log_level, const char *file, const unsigned int line,
+ const char *function, const char *module, const char *message)
+{
+ char buffer[SC_LOG_MAX_LOG_MSG_LEN] = "";
+ SCLogOPIfaceCtx *op_iface_ctx = NULL;
+
+ if (sc_log_module_initialized != 1) {
+ printf("Logging module not initialized. Call SCLogInitLogModule() "
+ "first before using the debug API\n");
+ return SC_OK;
+ }
+
+ /* get ts here so we log the same ts to each output */
+ struct timeval tval;
+ gettimeofday(&tval, NULL);
+ SCTime_t ts = SCTIME_FROM_TIMEVAL(&tval);
+
+ op_iface_ctx = sc_log_config->op_ifaces;
+ while (op_iface_ctx != NULL) {
+ if (log_level != SC_LOG_NOTSET && log_level > op_iface_ctx->log_level) {
+ op_iface_ctx = op_iface_ctx->next;
+ continue;
+ }
+
+ switch (op_iface_ctx->iface) {
+ case SC_LOG_OP_IFACE_CONSOLE:
+ if (SCLogMessageGetBuffer(ts, op_iface_ctx->use_color, op_iface_ctx->type, buffer,
+ sizeof(buffer),
+ op_iface_ctx->log_format ? op_iface_ctx->log_format
+ : sc_log_config->log_format,
+ log_level, file, line, function, module, message) == 0) {
+ SCLogPrintToStream((log_level == SC_LOG_ERROR)? stderr: stdout, buffer);
+ }
+ break;
+ case SC_LOG_OP_IFACE_FILE:
+ if (SCLogMessageGetBuffer(ts, 0, op_iface_ctx->type, buffer, sizeof(buffer),
+ op_iface_ctx->log_format ? op_iface_ctx->log_format
+ : sc_log_config->log_format,
+ log_level, file, line, function, module, message) == 0) {
+ int r = 0;
+ SCMutexLock(&op_iface_ctx->fp_mutex);
+ if (op_iface_ctx->rotation_flag) {
+ r = SCLogReopen(op_iface_ctx);
+ op_iface_ctx->rotation_flag = 0;
+ }
+ SCLogPrintToStream(op_iface_ctx->file_d, buffer);
+ SCMutexUnlock(&op_iface_ctx->fp_mutex);
+
+ /* report error outside of lock to avoid recursion */
+ if (r == -1) {
+ SCLogError("re-opening file \"%s\" failed: %s", op_iface_ctx->file,
+ strerror(errno));
+ }
+ }
+ break;
+ case SC_LOG_OP_IFACE_SYSLOG:
+ if (SCLogMessageGetBuffer(ts, 0, op_iface_ctx->type, buffer, sizeof(buffer),
+ op_iface_ctx->log_format ? op_iface_ctx->log_format
+ : sc_log_config->log_format,
+ log_level, file, line, function, module, message) == 0) {
+ SCLogPrintToSyslog(SCLogMapLogLevelToSyslogLevel(log_level), buffer);
+ }
+ break;
+ default:
+ break;
+ }
+ op_iface_ctx = op_iface_ctx->next;
+ }
+ return SC_OK;
+}
+
+void SCLog(int x, const char *file, const char *func, const int line, const char *module,
+ const char *fmt, ...)
+{
+ if (sc_log_global_log_level >= x &&
+ (sc_log_fg_filters_present == 0 ||
+ SCLogMatchFGFilterWL(file, func, line) == 1 ||
+ SCLogMatchFGFilterBL(file, func, line) == 1) &&
+ (sc_log_fd_filters_present == 0 ||
+ SCLogMatchFDFilter(func) == 1))
+ {
+ char msg[SC_LOG_MAX_LOG_MSG_LEN];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+ SCLogMessage(x, file, line, func, module, msg);
+ }
+}
+
+void SCLogErr(int x, const char *file, const char *func, const int line, const char *module,
+ const char *fmt, ...)
+{
+ if (sc_log_global_log_level >= x &&
+ (sc_log_fg_filters_present == 0 ||
+ SCLogMatchFGFilterWL(file, func, line) == 1 ||
+ SCLogMatchFGFilterBL(file, func, line) == 1) &&
+ (sc_log_fd_filters_present == 0 ||
+ SCLogMatchFDFilter(func) == 1))
+ {
+ char msg[SC_LOG_MAX_LOG_MSG_LEN];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+ SCLogMessage(x, file, line, func, module, msg);
+ }
+}
+
+/**
+ * \brief Returns whether debug messages are enabled to be logged or not
+ *
+ * \retval 1 if debug messages are enabled to be logged
+ * \retval 0 if debug messages are not enabled to be logged
+ */
+int SCLogDebugEnabled(void)
+{
+#ifdef DEBUG
+ if (sc_log_global_log_level == SC_LOG_DEBUG)
+ return 1;
+ else
+ return 0;
+#else
+ return 0;
+#endif
+}
+
+/**
+ * \brief Allocates an output buffer for an output interface. Used when we
+ * want the op_interface log_format to override the global_log_format.
+ * Currently not used.
+ *
+ * \retval buffer Pointer to the newly created output_buffer
+ */
+SCLogOPBuffer *SCLogAllocLogOPBuffer(void)
+{
+ SCLogOPBuffer *buffer = NULL;
+
+ if ( (buffer = SCMalloc(sc_log_config->op_ifaces_cnt *
+ sizeof(SCLogOPBuffer))) == NULL) {
+ FatalError("Fatal error encountered in SCLogAllocLogOPBuffer. Exiting...");
+ }
+
+ SCLogOPIfaceCtx *op_iface_ctx = sc_log_config->op_ifaces;
+ for (int i = 0; i < sc_log_config->op_ifaces_cnt; i++, op_iface_ctx = op_iface_ctx->next) {
+ buffer[i].log_format = op_iface_ctx->log_format;
+ buffer[i].temp = buffer[i].msg;
+ }
+
+ return buffer;
+}
+
+/*----------------------The logging module initialization code--------------- */
+
+/**
+ * \brief Returns a new output_interface_context
+ *
+ * \retval iface_ctx Pointer to a newly allocated output_interface_context
+ * \initonly
+ */
+static inline SCLogOPIfaceCtx *SCLogAllocLogOPIfaceCtx(void)
+{
+ SCLogOPIfaceCtx *iface_ctx = NULL;
+
+ if ((iface_ctx = SCCalloc(1, sizeof(SCLogOPIfaceCtx))) == NULL) {
+ FatalError("Fatal error encountered in SCLogallocLogOPIfaceCtx. Exiting...");
+ }
+
+ return iface_ctx;
+}
+
+/**
+ * \brief Initializes the file output interface
+ *
+ * \param file Path to the file used for logging purposes
+ * \param log_format Pointer to the log_format for this op interface, that
+ * overrides the global_log_format
+ * \param log_level Override of the global_log_level by this interface
+ *
+ * \retval iface_ctx Pointer to the file output interface context created
+ * \initonly
+ */
+static inline SCLogOPIfaceCtx *SCLogInitFileOPIface(const char *file, uint32_t userid,
+ uint32_t groupid, const char *log_format, int log_level, SCLogOPType type)
+{
+ SCLogOPIfaceCtx *iface_ctx = SCLogAllocLogOPIfaceCtx();
+ if (iface_ctx == NULL) {
+ FatalError("Fatal error encountered in SCLogInitFileOPIface. Exiting...");
+ }
+
+ if (file == NULL) {
+ goto error;
+ }
+
+ iface_ctx->iface = SC_LOG_OP_IFACE_FILE;
+ iface_ctx->type = type;
+
+ if ( (iface_ctx->file_d = fopen(file, "a")) == NULL) {
+ SCLogWarning("error opening file %s: %s", file, strerror(errno));
+ goto error;
+ }
+
+#ifndef OS_WIN32
+ if (userid != 0 || groupid != 0) {
+ if (fchown(fileno(iface_ctx->file_d), userid, groupid) == -1) {
+ SCLogWarning("Failed to change ownership of file %s: %s", file, strerror(errno));
+ }
+ }
+#endif
+
+ if ((iface_ctx->file = SCStrdup(file)) == NULL) {
+ goto error;
+ }
+
+ if (log_format != NULL && (iface_ctx->log_format = SCStrdup(log_format)) == NULL) {
+ goto error;
+ }
+
+ SCMutexInit(&iface_ctx->fp_mutex, NULL);
+ OutputRegisterFileRotationFlag(&iface_ctx->rotation_flag);
+
+ iface_ctx->log_level = log_level;
+
+ return iface_ctx;
+
+error:
+ if (iface_ctx->file != NULL) {
+ SCFree((char *)iface_ctx->file);
+ iface_ctx->file = NULL;
+ }
+ if (iface_ctx->log_format != NULL) {
+ SCFree((char *)iface_ctx->log_format);
+ iface_ctx->log_format = NULL;
+ }
+ if (iface_ctx->file_d != NULL) {
+ fclose(iface_ctx->file_d);
+ iface_ctx->file_d = NULL;
+ }
+ SCFree(iface_ctx);
+ return NULL;
+}
+
+/**
+ * \brief Initializes the console output interface and deals with possible
+ * env var overrides.
+ *
+ * \param log_format Pointer to the log_format for this op interface, that
+ * overrides the global_log_format
+ * \param log_level Override of the global_log_level by this interface
+ *
+ * \retval iface_ctx Pointer to the console output interface context created
+ * \initonly
+ */
+static inline SCLogOPIfaceCtx *SCLogInitConsoleOPIface(const char *log_format,
+ SCLogLevel log_level, SCLogOPType type)
+{
+ SCLogOPIfaceCtx *iface_ctx = SCLogAllocLogOPIfaceCtx();
+
+ if (iface_ctx == NULL) {
+ FatalError("Fatal error encountered in SCLogInitConsoleOPIface. Exiting...");
+ }
+
+ iface_ctx->iface = SC_LOG_OP_IFACE_CONSOLE;
+ iface_ctx->type = type;
+
+ /* console log format is overridden by envvars */
+ const char *tmp_log_format = log_format;
+ const char *s = getenv(SC_LOG_ENV_LOG_FORMAT);
+ if (s != NULL) {
+#if 0
+ printf("Overriding setting for \"console.format\" because of env "
+ "var SC_LOG_FORMAT=\"%s\".\n", s);
+#endif
+ tmp_log_format = s;
+ }
+
+ if (tmp_log_format != NULL &&
+ (iface_ctx->log_format = SCStrdup(tmp_log_format)) == NULL) {
+ printf("Error allocating memory\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* console log level is overridden by envvars */
+ SCLogLevel tmp_log_level = log_level;
+ s = getenv(SC_LOG_ENV_LOG_LEVEL);
+ if (s != NULL) {
+ SCLogLevel l = SCMapEnumNameToValue(s, sc_log_level_map);
+ if (l > SC_LOG_NOTSET && l < SC_LOG_LEVEL_MAX) {
+#if 0
+ printf("Overriding setting for \"console.level\" because of env "
+ "var SC_LOG_LEVEL=\"%s\".\n", s);
+#endif
+ tmp_log_level = l;
+ }
+ }
+ iface_ctx->log_level = tmp_log_level;
+
+#ifndef OS_WIN32
+ if (isatty(fileno(stdout)) && isatty(fileno(stderr))) {
+ iface_ctx->use_color = TRUE;
+ }
+#endif
+
+ return iface_ctx;
+}
+
+/**
+ * \brief Initializes the syslog output interface
+ *
+ * \param facility The facility code for syslog
+ * \param log_format Pointer to the log_format for this op interface, that
+ * overrides the global_log_format
+ * \param log_level Override of the global_log_level by this interface
+ *
+ * \retval iface_ctx Pointer to the syslog output interface context created
+ */
+static inline SCLogOPIfaceCtx *SCLogInitSyslogOPIface(int facility,
+ const char *log_format,
+ SCLogLevel log_level,
+ SCLogOPType type)
+{
+ SCLogOPIfaceCtx *iface_ctx = SCLogAllocLogOPIfaceCtx();
+
+ if ( iface_ctx == NULL) {
+ FatalError("Fatal error encountered in SCLogInitSyslogOPIface. Exiting...");
+ }
+
+ iface_ctx->iface = SC_LOG_OP_IFACE_SYSLOG;
+ iface_ctx->type = type;
+
+ if (facility == -1)
+ facility = SC_LOG_DEF_SYSLOG_FACILITY;
+ iface_ctx->facility = facility;
+
+ if (log_format != NULL &&
+ (iface_ctx->log_format = SCStrdup(log_format)) == NULL) {
+ printf("Error allocating memory\n");
+ exit(EXIT_FAILURE);
+ }
+
+ iface_ctx->log_level = log_level;
+
+ openlog(NULL, LOG_NDELAY, iface_ctx->facility);
+
+ return iface_ctx;
+}
+
+/**
+ * \brief Frees the output_interface context supplied as an argument
+ *
+ * \param iface_ctx Pointer to the op_interface_context to be freed
+ */
+static inline void SCLogFreeLogOPIfaceCtx(SCLogOPIfaceCtx *iface_ctx)
+{
+ SCLogOPIfaceCtx *temp = NULL;
+
+ while (iface_ctx != NULL) {
+ temp = iface_ctx;
+
+ if (iface_ctx->file_d != NULL) {
+ fclose(iface_ctx->file_d);
+ SCMutexDestroy(&iface_ctx->fp_mutex);
+ }
+
+ if (iface_ctx->file != NULL)
+ SCFree((void *)iface_ctx->file);
+
+ if (iface_ctx->log_format != NULL)
+ SCFree((void *)iface_ctx->log_format);
+
+ if (iface_ctx->iface == SC_LOG_OP_IFACE_SYSLOG) {
+ closelog();
+ }
+
+ iface_ctx = iface_ctx->next;
+
+ SCFree(temp);
+ }
+
+ return;
+}
+
+/**
+ * \brief Internal function used to set the logging module global_log_level
+ * during the initialization phase
+ *
+ * \param sc_lid The initialization data supplied.
+ * \param sc_lc The logging module context which has to be updated.
+ */
+static inline void SCLogSetLogLevel(SCLogInitData *sc_lid, SCLogConfig *sc_lc)
+{
+ SCLogLevel log_level = SC_LOG_NOTSET;
+ const char *s = NULL;
+
+ /* envvar overrides config */
+ s = getenv(SC_LOG_ENV_LOG_LEVEL);
+ if (s != NULL) {
+ log_level = SCMapEnumNameToValue(s, sc_log_level_map);
+ } else if (sc_lid != NULL) {
+ log_level = sc_lid->global_log_level;
+ }
+
+ /* deal with the global_log_level to be used */
+ if (log_level > SC_LOG_NOTSET && log_level < SC_LOG_LEVEL_MAX)
+ sc_lc->log_level = log_level;
+ else {
+ sc_lc->log_level = SC_LOG_DEF_LOG_LEVEL;
+#ifndef UNITTESTS
+ if (sc_lid != NULL) {
+ printf("Warning: Invalid/No global_log_level assigned by user. Falling "
+ "back on the default_log_level \"%s\"\n",
+ SCMapEnumValueToName(sc_lc->log_level, sc_log_level_map));
+ }
+#endif
+ }
+
+ /* we also set it to a global var, as it is easier to access it */
+ sc_log_global_log_level = sc_lc->log_level;
+
+ return;
+}
+
+SCLogLevel SCLogGetLogLevel(void)
+{
+ return sc_log_global_log_level;
+}
+
+static inline const char *SCLogGetDefaultLogFormat(const SCLogLevel lvl)
+{
+ const char *prog_ver = GetProgramVersion();
+ if (strstr(prog_ver, "RELEASE") != NULL) {
+ if (lvl <= SC_LOG_NOTICE)
+ return SC_LOG_DEF_LOG_FORMAT_REL_NOTICE;
+ else if (lvl <= SC_LOG_INFO)
+ return SC_LOG_DEF_LOG_FORMAT_REL_INFO;
+ else if (lvl <= SC_LOG_CONFIG)
+ return SC_LOG_DEF_LOG_FORMAT_REL_CONFIG;
+ }
+ return SC_LOG_DEF_LOG_FORMAT_DEBUG;
+}
+
+/**
+ * \brief Internal function used to set the logging module global_log_format
+ * during the initialization phase
+ *
+ * \param sc_lid The initialization data supplied.
+ * \param sc_lc The logging module context which has to be updated.
+ */
+static inline void SCLogSetLogFormat(SCLogInitData *sc_lid, SCLogConfig *sc_lc)
+{
+ const char *format = NULL;
+
+ /* envvar overrides config */
+ format = getenv(SC_LOG_ENV_LOG_FORMAT);
+ if (format == NULL) {
+ if (sc_lid != NULL) {
+ format = sc_lid->global_log_format;
+ }
+ }
+
+ /* deal with the global log format to be used */
+ if (format == NULL || strlen(format) > SC_LOG_MAX_LOG_FORMAT_LEN) {
+ format = SCLogGetDefaultLogFormat(sc_lc->log_level);
+#ifndef UNITTESTS
+ if (sc_lid != NULL) {
+ printf("Warning: Invalid/No global_log_format supplied by user or format "
+ "length exceeded limit of \"%d\" characters. Falling back on "
+ "default log_format \"%s\"\n", SC_LOG_MAX_LOG_FORMAT_LEN,
+ format);
+ }
+#endif
+ }
+
+ if (format != NULL && (sc_lc->log_format = SCStrdup(format)) == NULL) {
+ printf("Error allocating memory\n");
+ exit(EXIT_FAILURE);
+ }
+
+ return;
+}
+
+/**
+ * \brief Internal function used to set the logging module global_op_ifaces
+ * during the initialization phase
+ *
+ * \param sc_lid The initialization data supplied.
+ * \param sc_lc The logging module context which has to be updated.
+ */
+static inline void SCLogSetOPIface(SCLogInitData *sc_lid, SCLogConfig *sc_lc)
+{
+ SCLogOPIfaceCtx *op_ifaces_ctx = NULL;
+ int op_iface = 0;
+ const char *s = NULL;
+
+ if (sc_lid != NULL && sc_lid->op_ifaces != NULL) {
+ sc_lc->op_ifaces = sc_lid->op_ifaces;
+ sc_lid->op_ifaces = NULL;
+ sc_lc->op_ifaces_cnt = sc_lid->op_ifaces_cnt;
+ } else {
+ s = getenv(SC_LOG_ENV_LOG_OP_IFACE);
+ if (s != NULL) {
+ op_iface = SCMapEnumNameToValue(s, sc_log_op_iface_map);
+
+ if(op_iface < 0 || op_iface >= SC_LOG_OP_IFACE_MAX) {
+ op_iface = SC_LOG_DEF_LOG_OP_IFACE;
+#ifndef UNITTESTS
+ printf("Warning: Invalid output interface supplied by user. "
+ "Falling back on default_output_interface \"%s\"\n",
+ SCMapEnumValueToName(op_iface, sc_log_op_iface_map));
+#endif
+ }
+ }
+ else {
+ op_iface = SC_LOG_DEF_LOG_OP_IFACE;
+#ifndef UNITTESTS
+ if (sc_lid != NULL) {
+ printf("Warning: Output_interface not supplied by user. Falling "
+ "back on default_output_interface \"%s\"\n",
+ SCMapEnumValueToName(op_iface, sc_log_op_iface_map));
+ }
+#endif
+ }
+
+ switch (op_iface) {
+ case SC_LOG_OP_IFACE_CONSOLE:
+ op_ifaces_ctx = SCLogInitConsoleOPIface(NULL, SC_LOG_LEVEL_MAX,0);
+ break;
+ case SC_LOG_OP_IFACE_FILE:
+ s = getenv(SC_LOG_ENV_LOG_FILE);
+ if (s == NULL) {
+ char *str = SCLogGetLogFilename(SC_LOG_DEF_LOG_FILE);
+ if (str != NULL) {
+ op_ifaces_ctx = SCLogInitFileOPIface(str, 0, 0, NULL, SC_LOG_LEVEL_MAX, 0);
+ SCFree(str);
+ }
+ } else {
+ op_ifaces_ctx = SCLogInitFileOPIface(s, 0, 0, NULL, SC_LOG_LEVEL_MAX, 0);
+ }
+ break;
+ case SC_LOG_OP_IFACE_SYSLOG:
+ s = getenv(SC_LOG_ENV_LOG_FACILITY);
+ if (s == NULL)
+ s = SC_LOG_DEF_SYSLOG_FACILITY_STR;
+
+ op_ifaces_ctx = SCLogInitSyslogOPIface(SCMapEnumNameToValue(s, SCSyslogGetFacilityMap()), NULL, -1,0);
+ break;
+ }
+ sc_lc->op_ifaces = op_ifaces_ctx;
+ sc_lc->op_ifaces_cnt++;
+ }
+ return;
+}
+
+/**
+ * \brief Internal function used to set the logging module op_filter
+ * during the initialization phase
+ *
+ * \param sc_lid The initialization data supplied.
+ * \param sc_lc The logging module context which has to be updated.
+ */
+static inline void SCLogSetOPFilter(SCLogInitData *sc_lid, SCLogConfig *sc_lc)
+{
+ const char *filter = NULL;
+
+ int opts = 0;
+ int en;
+ PCRE2_SIZE eo = 0;
+
+ /* envvar overrides */
+ filter = getenv(SC_LOG_ENV_LOG_OP_FILTER);
+ if (filter == NULL) {
+ if (sc_lid != NULL) {
+ filter = sc_lid->op_filter;
+ }
+ }
+
+ if (filter != NULL && strcmp(filter, "") != 0) {
+ sc_lc->op_filter = SCStrdup(filter);
+ if (sc_lc->op_filter == NULL) {
+ printf("pcre filter alloc failed\n");
+ return;
+ }
+ sc_lc->op_filter_regex =
+ pcre2_compile((PCRE2_SPTR8)filter, PCRE2_ZERO_TERMINATED, opts, &en, &eo, NULL);
+ if (sc_lc->op_filter_regex == NULL) {
+ SCFree(sc_lc->op_filter);
+ PCRE2_UCHAR errbuffer[256];
+ pcre2_get_error_message(en, errbuffer, sizeof(errbuffer));
+ printf("pcre2 compile of \"%s\" failed at offset %d : %s\n", filter, (int)eo,
+ errbuffer);
+ return;
+ }
+ sc_lc->op_filter_regex_match =
+ pcre2_match_data_create_from_pattern(sc_lc->op_filter_regex, NULL);
+ }
+
+ return;
+}
+
+/**
+ * \brief Returns a pointer to a new SCLogInitData. This is a public interface
+ * intended to be used after the logging parameters are read from the
+ * conf file
+ *
+ * \retval sc_lid Pointer to the newly created SCLogInitData
+ * \initonly
+ */
+SCLogInitData *SCLogAllocLogInitData(void)
+{
+ SCLogInitData *sc_lid = NULL;
+
+ if ((sc_lid = SCCalloc(1, sizeof(SCLogInitData))) == NULL)
+ return NULL;
+
+ return sc_lid;
+}
+
+#ifdef UNITTESTS
+#ifndef OS_WIN32
+/**
+ * \brief Frees a SCLogInitData
+ *
+ * \param sc_lid Pointer to the SCLogInitData to be freed
+ */
+static void SCLogFreeLogInitData(SCLogInitData *sc_lid)
+{
+ if (sc_lid != NULL) {
+ SCLogFreeLogOPIfaceCtx(sc_lid->op_ifaces);
+ SCFree(sc_lid);
+ }
+
+ return;
+}
+#endif
+#endif
+
+/**
+ * \brief Frees the logging module context
+ */
+static inline void SCLogFreeLogConfig(SCLogConfig *sc_lc)
+{
+ if (sc_lc != NULL) {
+ if (sc_lc->startup_message != NULL)
+ SCFree(sc_lc->startup_message);
+ if (sc_lc->log_format != NULL)
+ SCFree(sc_lc->log_format);
+ if (sc_lc->op_filter != NULL)
+ SCFree(sc_lc->op_filter);
+
+ if (sc_lc->op_filter_regex != NULL)
+ pcre2_code_free(sc_lc->op_filter_regex);
+ if (sc_lc->op_filter_regex_match)
+ pcre2_match_data_free(sc_lc->op_filter_regex_match);
+
+ SCLogFreeLogOPIfaceCtx(sc_lc->op_ifaces);
+ SCFree(sc_lc);
+ }
+
+ return;
+}
+
+/**
+ * \brief Appends an output_interface to the output_interface list sent in head
+ *
+ * \param iface_ctx Pointer to the output_interface that has to be added to head
+ * \param head Pointer to the output_interface list
+ */
+void SCLogAppendOPIfaceCtx(SCLogOPIfaceCtx *iface_ctx, SCLogInitData *sc_lid)
+{
+ SCLogOPIfaceCtx *temp = NULL, *prev = NULL;
+ SCLogOPIfaceCtx **head = &sc_lid->op_ifaces;
+
+ if (iface_ctx == NULL) {
+#ifdef DEBUG
+ printf("Argument(s) to SCLogAppendOPIfaceCtx() NULL\n");
+#endif
+ return;
+ }
+
+ temp = *head;
+ while (temp != NULL) {
+ prev = temp;
+ temp = temp->next;
+ }
+
+ if (prev == NULL)
+ *head = iface_ctx;
+ else
+ prev->next = iface_ctx;
+
+ sc_lid->op_ifaces_cnt++;
+
+ return;
+}
+
+#ifdef UNITTESTS
+#ifndef OS_WIN32
+/**
+ * \internal
+ * \brief Creates a new output interface based on the arguments sent. The kind
+ * of output interface to be created is decided by the iface_name arg.
+ * If iface_name is "file", the arg argument will hold the filename to be
+ * used for logging purposes. If iface_name is "syslog", the arg
+ * argument holds the facility code. If iface_name is "console", arg is
+ * NULL.
+ *
+ * \param iface_name Interface name. Can be "console", "file" or "syslog"
+ * \param log_format Override for the global_log_format
+ * \param log_level Override for the global_log_level
+ * \param log_level Parameter required by a particular interface. Explained in
+ * the function description
+ *
+ * \retval iface_ctx Pointer to the newly created output interface
+ */
+static SCLogOPIfaceCtx *SCLogInitOPIfaceCtx(
+ const char *iface_name, const char *log_format, int log_level, const char *arg)
+{
+ int iface = SCMapEnumNameToValue(iface_name, sc_log_op_iface_map);
+
+ if (log_level < SC_LOG_NONE || log_level > SC_LOG_DEBUG) {
+ printf("Warning: Supplied log_level_override for op_interface \"%s\" "
+ "is invalid. Defaulting to not specifying an override\n",
+ iface_name);
+ log_level = SC_LOG_NOTSET;
+ }
+
+ switch (iface) {
+ case SC_LOG_OP_IFACE_CONSOLE:
+ return SCLogInitConsoleOPIface(log_format, log_level, SC_LOG_OP_TYPE_REGULAR);
+ case SC_LOG_OP_IFACE_FILE:
+ return SCLogInitFileOPIface(arg, 0, 0, log_format, log_level, SC_LOG_OP_TYPE_REGULAR);
+ case SC_LOG_OP_IFACE_SYSLOG:
+ return SCLogInitSyslogOPIface(SCMapEnumNameToValue(arg, SCSyslogGetFacilityMap()),
+ log_format, log_level, SC_LOG_OP_TYPE_REGULAR);
+ default:
+#ifdef DEBUG
+ printf("Output Interface \"%s\" not supported by the logging module",
+ iface_name);
+#endif
+ return NULL;
+ }
+}
+#endif
+#endif
+
+/**
+ * \brief Initializes the logging module.
+ *
+ * \param sc_lid The initialization data for the logging module. If sc_lid is
+ * NULL, we would stick to the default configuration for the
+ * logging subsystem.
+ * \initonly
+ */
+void SCLogInitLogModule(SCLogInitData *sc_lid)
+{
+ /* De-initialize the logging context, if it has already init by the
+ * environment variables at the start of the engine */
+ SCLogDeInitLogModule();
+
+#if defined (OS_WIN32)
+ if (SCMutexInit(&sc_log_stream_lock, NULL) != 0) {
+ FatalError("Failed to initialize log mutex.");
+ }
+#endif /* OS_WIN32 */
+
+ /* sc_log_config is a global variable */
+ if ((sc_log_config = SCCalloc(1, sizeof(SCLogConfig))) == NULL) {
+ FatalError("Fatal error encountered in SCLogInitLogModule. Exiting...");
+ }
+
+ SCLogSetLogLevel(sc_lid, sc_log_config);
+ SCLogSetLogFormat(sc_lid, sc_log_config);
+ SCLogSetOPIface(sc_lid, sc_log_config);
+ SCLogSetOPFilter(sc_lid, sc_log_config);
+
+ sc_log_module_initialized = 1;
+ sc_log_module_cleaned = 0;
+
+ //SCOutputPrint(sc_did->startup_message);
+
+ rs_log_set_level(sc_log_global_log_level);
+ return;
+}
+
+void SCLogLoadConfig(int daemon, int verbose, uint32_t userid, uint32_t groupid)
+{
+ ConfNode *outputs;
+ SCLogInitData *sc_lid;
+ int have_logging = 0;
+ int max_level = 0;
+ SCLogLevel min_level = 0;
+
+ /* If verbose logging was requested, set the minimum as
+ * SC_LOG_NOTICE plus the extra verbosity. */
+ if (verbose) {
+ min_level = SC_LOG_NOTICE + verbose;
+ }
+
+ outputs = ConfGetNode("logging.outputs");
+ if (outputs == NULL) {
+ SCLogDebug("No logging.output configuration section found.");
+ return;
+ }
+
+ sc_lid = SCLogAllocLogInitData();
+ if (sc_lid == NULL) {
+ SCLogDebug("Could not allocate memory for log init data");
+ return;
+ }
+
+ /* Get default log level and format. */
+ const char *default_log_level_s = NULL;
+ if (ConfGet("logging.default-log-level", &default_log_level_s) == 1) {
+ SCLogLevel default_log_level =
+ SCMapEnumNameToValue(default_log_level_s, sc_log_level_map);
+ if (default_log_level == -1) {
+ SCLogError("Invalid default log level: %s", default_log_level_s);
+ exit(EXIT_FAILURE);
+ }
+ sc_lid->global_log_level = MAX(min_level, default_log_level);
+ }
+ else {
+ sc_lid->global_log_level = MAX(min_level, SC_LOG_NOTICE);
+ }
+
+ if (ConfGet("logging.default-log-format", &sc_lid->global_log_format) != 1)
+ sc_lid->global_log_format = SCLogGetDefaultLogFormat(sc_lid->global_log_level);
+
+ (void)ConfGet("logging.default-output-filter", &sc_lid->op_filter);
+
+ ConfNode *seq_node, *output;
+ TAILQ_FOREACH(seq_node, &outputs->head, next) {
+ SCLogLevel level = sc_lid->global_log_level;
+ SCLogOPIfaceCtx *op_iface_ctx = NULL;
+ const char *format;
+ const char *level_s;
+
+ output = ConfNodeLookupChild(seq_node, seq_node->val);
+ if (output == NULL)
+ continue;
+
+ /* By default an output is enabled. */
+ const char *enabled = ConfNodeLookupChildValue(output, "enabled");
+ if (enabled != NULL && ConfValIsFalse(enabled))
+ continue;
+
+ SCLogOPType type = SC_LOG_OP_TYPE_REGULAR;
+ const char *type_s = ConfNodeLookupChildValue(output, "type");
+ if (type_s != NULL) {
+ if (strcmp(type_s, "regular") == 0)
+ type = SC_LOG_OP_TYPE_REGULAR;
+ else if (strcmp(type_s, "json") == 0) {
+ type = SC_LOG_OP_TYPE_JSON;
+ }
+ }
+
+ format = ConfNodeLookupChildValue(output, "format");
+
+ level_s = ConfNodeLookupChildValue(output, "level");
+ if (level_s != NULL) {
+ level = SCMapEnumNameToValue(level_s, sc_log_level_map);
+ if (level == -1) {
+ SCLogError("Invalid log level: %s", level_s);
+ exit(EXIT_FAILURE);
+ }
+ max_level = MAX(max_level, level);
+ }
+
+ /* Increase the level of extra verbosity was requested. */
+ level = MAX(min_level, level);
+
+ if (strcmp(output->name, "console") == 0) {
+ op_iface_ctx = SCLogInitConsoleOPIface(format, level, type);
+ }
+ else if (strcmp(output->name, "file") == 0) {
+ if (format == NULL) {
+ format = SC_LOG_DEF_FILE_FORMAT;
+ }
+
+ const char *filename = ConfNodeLookupChildValue(output, "filename");
+ if (filename == NULL) {
+ FatalError("Logging to file requires a filename");
+ }
+ char *path = NULL;
+ if (!(PathIsAbsolute(filename))) {
+ path = SCLogGetLogFilename(filename);
+ } else {
+ path = SCStrdup(filename);
+ }
+ if (path == NULL)
+ FatalError("failed to setup output to file");
+ have_logging = 1;
+ op_iface_ctx = SCLogInitFileOPIface(path, userid, groupid, format, level, type);
+ SCFree(path);
+ }
+ else if (strcmp(output->name, "syslog") == 0) {
+ int facility = SC_LOG_DEF_SYSLOG_FACILITY;
+ const char *facility_s = ConfNodeLookupChildValue(output,
+ "facility");
+ if (facility_s != NULL) {
+ facility = SCMapEnumNameToValue(facility_s, SCSyslogGetFacilityMap());
+ if (facility == -1) {
+ SCLogWarning("Invalid syslog "
+ "facility: \"%s\", now using \"%s\" as syslog "
+ "facility",
+ facility_s, SC_LOG_DEF_SYSLOG_FACILITY_STR);
+ facility = SC_LOG_DEF_SYSLOG_FACILITY;
+ }
+ }
+ SCLogDebug("Initializing syslog logging with format \"%s\"", format);
+ have_logging = 1;
+ op_iface_ctx = SCLogInitSyslogOPIface(facility, format, level, type);
+ }
+ else {
+ SCLogWarning("invalid logging method: %s, ignoring", output->name);
+ }
+ if (op_iface_ctx != NULL) {
+ SCLogAppendOPIfaceCtx(op_iface_ctx, sc_lid);
+ }
+ }
+
+ if (daemon && (have_logging == 0)) {
+ SCLogWarning("no logging compatible with daemon mode selected,"
+ " suricata won't be able to log. Please update "
+ " 'logging.outputs' in the YAML.");
+ }
+
+ /* Set the global log level to that of the max level used. */
+ sc_lid->global_log_level = MAX(sc_lid->global_log_level, max_level);
+ SCLogInitLogModule(sc_lid);
+
+ SCLogDebug("sc_log_global_log_level: %d", sc_log_global_log_level);
+ SCLogDebug("sc_lc->log_format: %s", sc_log_config->log_format);
+ SCLogDebug("SCLogSetOPFilter: filter: %s", sc_log_config->op_filter);
+
+ if (sc_lid != NULL)
+ SCFree(sc_lid);
+}
+
+/**
+ * \brief Returns a full file path given a filename uses log dir specified in
+ * conf or DEFAULT_LOG_DIR
+ *
+ * \param filearg The relative filename for which we want a full path include
+ * log directory
+ *
+ * \retval log_filename The fullpath of the logfile to open
+ */
+static char *SCLogGetLogFilename(const char *filearg)
+{
+ const char *log_dir = ConfigGetLogDirectory();
+ char *log_filename = SCMalloc(PATH_MAX);
+ if (unlikely(log_filename == NULL))
+ return NULL;
+ snprintf(log_filename, PATH_MAX, "%s/%s", log_dir, filearg);
+ return log_filename;
+}
+
+/**
+ * \brief De-Initializes the logging module
+ */
+void SCLogDeInitLogModule(void)
+{
+ SCLogFreeLogConfig(sc_log_config);
+
+ /* reset the global logging_module variables */
+ sc_log_global_log_level = 0;
+ sc_log_module_initialized = 0;
+ sc_log_module_cleaned = 1;
+ sc_log_config = NULL;
+
+ /* de-init the FD filters */
+ SCLogReleaseFDFilters();
+ /* de-init the FG filters */
+ SCLogReleaseFGFilters();
+
+#if defined (OS_WIN32)
+ SCMutexDestroy(&sc_log_stream_lock);
+#endif /* OS_WIN32 */
+
+ return;
+}
+
+//------------------------------------Unit_Tests--------------------------------
+
+/* The logging engine should be tested to the maximum extent possible, since
+ * logging code would be used throughout the codebase, and hence we can't afford
+ * to have a single bug here(not that you can afford to have a bug
+ * elsewhere ;) ). Please report a bug, if you get a slightest hint of a bug
+ * from the logging module.
+ */
+
+#ifdef UNITTESTS
+
+static int SCLogTestInit01(void)
+{
+#ifndef OS_WIN32
+ /* unset any environment variables set for the logging module */
+ unsetenv(SC_LOG_ENV_LOG_LEVEL);
+ unsetenv(SC_LOG_ENV_LOG_OP_IFACE);
+ unsetenv(SC_LOG_ENV_LOG_FORMAT);
+
+ SCLogInitLogModule(NULL);
+
+ FAIL_IF_NULL(sc_log_config);
+
+ FAIL_IF_NOT(SC_LOG_DEF_LOG_LEVEL == sc_log_config->log_level);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ SC_LOG_DEF_LOG_OP_IFACE == sc_log_config->op_ifaces->iface);
+ FAIL_IF_NOT(sc_log_config->log_format != NULL &&
+ strcmp(SCLogGetDefaultLogFormat(sc_log_config->log_level),
+ sc_log_config->log_format) == 0);
+
+ SCLogDeInitLogModule();
+
+ setenv(SC_LOG_ENV_LOG_LEVEL, "Debug", 1);
+ setenv(SC_LOG_ENV_LOG_OP_IFACE, "Console", 1);
+ setenv(SC_LOG_ENV_LOG_FORMAT, "%n- %l", 1);
+
+ SCLogInitLogModule(NULL);
+
+ FAIL_IF_NOT(SC_LOG_DEBUG == sc_log_config->log_level);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ SC_LOG_OP_IFACE_CONSOLE == sc_log_config->op_ifaces->iface);
+ FAIL_IF_NOT(sc_log_config->log_format != NULL &&
+ !strcmp("%n- %l", sc_log_config->log_format));
+
+ unsetenv(SC_LOG_ENV_LOG_LEVEL);
+ unsetenv(SC_LOG_ENV_LOG_OP_IFACE);
+ unsetenv(SC_LOG_ENV_LOG_FORMAT);
+
+ SCLogDeInitLogModule();
+#endif
+ PASS;
+}
+
+static int SCLogTestInit02(void)
+{
+#ifndef OS_WIN32
+ SCLogInitData *sc_lid = NULL;
+ SCLogOPIfaceCtx *sc_iface_ctx = NULL;
+ char *logfile = SCLogGetLogFilename("boo.txt");
+ sc_lid = SCLogAllocLogInitData();
+ FAIL_IF_NULL(sc_lid);
+ sc_lid->startup_message = "Test02";
+ sc_lid->global_log_level = SC_LOG_DEBUG;
+ sc_lid->op_filter = "boo";
+ sc_iface_ctx = SCLogInitOPIfaceCtx("file", "%m - %d", SC_LOG_WARNING, logfile);
+ SCLogAppendOPIfaceCtx(sc_iface_ctx, sc_lid);
+ sc_iface_ctx = SCLogInitOPIfaceCtx("console", NULL, SC_LOG_ERROR,
+ NULL);
+ SCLogAppendOPIfaceCtx(sc_iface_ctx, sc_lid);
+
+ SCLogInitLogModule(sc_lid);
+
+ FAIL_IF_NULL(sc_log_config);
+
+ FAIL_IF_NOT(SC_LOG_DEBUG == sc_log_config->log_level);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ SC_LOG_OP_IFACE_FILE == sc_log_config->op_ifaces->iface);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ sc_log_config->op_ifaces->next != NULL &&
+ SC_LOG_OP_IFACE_CONSOLE == sc_log_config->op_ifaces->next->iface);
+ FAIL_IF_NOT(sc_log_config->log_format != NULL &&
+ strcmp(SCLogGetDefaultLogFormat(sc_log_config->log_level),
+ sc_log_config->log_format) == 0);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ sc_log_config->op_ifaces->log_format != NULL &&
+ strcmp("%m - %d", sc_log_config->op_ifaces->log_format) == 0);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ sc_log_config->op_ifaces->next != NULL &&
+ sc_log_config->op_ifaces->next->log_format == NULL);
+
+ SCLogFreeLogInitData(sc_lid);
+ SCLogDeInitLogModule();
+
+ sc_lid = SCLogAllocLogInitData();
+ FAIL_IF_NULL(sc_lid);
+ sc_lid->startup_message = "Test02";
+ sc_lid->global_log_level = SC_LOG_DEBUG;
+ sc_lid->op_filter = "boo";
+ sc_lid->global_log_format = "kaboo";
+
+ SCLogInitLogModule(sc_lid);
+
+ FAIL_IF_NULL(sc_log_config);
+
+ FAIL_IF_NOT(SC_LOG_DEBUG == sc_log_config->log_level);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ SC_LOG_OP_IFACE_CONSOLE == sc_log_config->op_ifaces->iface);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ sc_log_config->op_ifaces->next == NULL);
+ FAIL_IF_NOT(sc_log_config->log_format != NULL &&
+ strcmp("kaboo", sc_log_config->log_format) == 0);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ sc_log_config->op_ifaces->log_format == NULL);
+ FAIL_IF_NOT(sc_log_config->op_ifaces != NULL &&
+ sc_log_config->op_ifaces->next == NULL);
+
+ SCLogFreeLogInitData(sc_lid);
+ SCLogDeInitLogModule();
+ SCFree(logfile);
+#endif
+ PASS;
+}
+
+static int SCLogTestInit03(void)
+{
+ SCLogInitLogModule(NULL);
+
+ SCLogAddFGFilterBL(NULL, "bamboo", -1);
+ SCLogAddFGFilterBL(NULL, "soo", -1);
+ SCLogAddFGFilterBL(NULL, "dummy", -1);
+
+ FAIL_IF_NOT(SCLogPrintFGFilters() == 3);
+
+ SCLogAddFGFilterBL(NULL, "dummy1", -1);
+ SCLogAddFGFilterBL(NULL, "dummy2", -1);
+
+ FAIL_IF_NOT(SCLogPrintFGFilters() == 5);
+
+ SCLogDeInitLogModule();
+
+ PASS;
+}
+
+static int SCLogTestInit04(void)
+{
+ SCLogInitLogModule(NULL);
+
+ SCLogAddFDFilter("bamboo");
+ SCLogAddFDFilter("soo");
+ SCLogAddFDFilter("foo");
+ SCLogAddFDFilter("roo");
+
+ FAIL_IF_NOT(SCLogPrintFDFilters() == 4);
+
+ SCLogAddFDFilter("loo");
+ SCLogAddFDFilter("soo");
+
+ FAIL_IF_NOT(SCLogPrintFDFilters() == 5);
+
+ SCLogRemoveFDFilter("bamboo");
+ SCLogRemoveFDFilter("soo");
+ SCLogRemoveFDFilter("foo");
+ SCLogRemoveFDFilter("noo");
+
+ FAIL_IF_NOT(SCLogPrintFDFilters() == 2);
+
+ SCLogDeInitLogModule();
+
+ PASS;
+}
+
+static int SCLogTestInit05(void)
+{
+ char str[4096];
+ memset(str, 'A', sizeof(str));
+ SCLogInfo("%s", str);
+
+ PASS;
+}
+
+#endif /* UNITTESTS */
+
+void SCLogRegisterTests(void)
+{
+
+#ifdef UNITTESTS
+
+ UtRegisterTest("SCLogTestInit01", SCLogTestInit01);
+ UtRegisterTest("SCLogTestInit02", SCLogTestInit02);
+ UtRegisterTest("SCLogTestInit03", SCLogTestInit03);
+ UtRegisterTest("SCLogTestInit04", SCLogTestInit04);
+ UtRegisterTest("SCLogTestInit05", SCLogTestInit05);
+
+#endif /* UNITTESTS */
+
+ return;
+}