/* 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 * * 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; }