/* Copyright (C) 2007-2010 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 "threads.h" #include "util-debug.h" #include "util-error.h" #include "util-enum.h" #include "util-debug-filters.h" #include "decode.h" #include "detect.h" #include "packet-queue.h" #include "threadvars.h" #include "tm-queuehandlers.h" #include "tm-queues.h" #include "tm-threads.h" #include "util-unittest.h" #include "util-syslog.h" #include "conf.h" /* 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 }, { "Emergency", SC_LOG_EMERGENCY }, { "Alert", SC_LOG_ALERT }, { "Critical", SC_LOG_CRITICAL }, { "Error", SC_LOG_ERROR }, { "Warning", SC_LOG_WARNING }, { "Notice", SC_LOG_NOTICE }, { "Info", SC_LOG_INFO }, { "Debug", 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 } }; #if defined (OS_WIN32) /** * \brief Used for synchronous output on WIN32 */ static SCMutex sc_log_stream_lock = NULL; #endif /* OS_WIN32 */ /** * \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(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_EMERGENCY: syslog_log_level = LOG_EMERG; break; case SC_LOG_ALERT: syslog_log_level = LOG_ALERT; break; case SC_LOG_CRITICAL: syslog_log_level = LOG_CRIT; break; 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_DEBUG: 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) { #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 throught 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; } #ifdef HAVE_LIBJANSSON /** */ int SCLogMessageJSON(struct timeval *tval, char *buffer, size_t buffer_size, SCLogLevel log_level, const char *file, unsigned line, const char *function, SCError error_code, const char *message) { json_t *js = json_object(); if (unlikely(js == NULL)) goto error; json_t *ejs = json_object(); if (unlikely(ejs == NULL)) goto error; char timebuf[64]; CreateIsoTimeString(tval, timebuf, sizeof(timebuf)); json_object_set_new(js, "timestamp", json_string(timebuf)); json_object_set_new(js, "event_type", json_string("engine")); if (error_code > 0) { json_object_set_new(ejs, "error_code", json_integer(error_code)); json_object_set_new(ejs, "error", json_string(SCErrorToString(error_code))); } if (message) json_object_set_new(ejs, "message", json_string(message)); if (log_level >= SC_LOG_DEBUG) { if (function) json_object_set_new(ejs, "function", json_string(function)); if (file) json_object_set_new(ejs, "file", json_string(file)); if (line > 0) json_object_set_new(ejs, "line", json_integer(line)); } json_object_set_new(js, "engine", ejs); char *js_s = json_dumps(js, JSON_PRESERVE_ORDER|JSON_COMPACT|JSON_ENSURE_ASCII| #ifdef JSON_ESCAPE_SLASH JSON_ESCAPE_SLASH #else 0 #endif ); snprintf(buffer, buffer_size, "%s", js_s); free(js_s); json_object_del(js, "engine"); json_object_clear(js); json_decref(js); return 0; error: return -1; } #endif /* HAVE_LIBJANSSON */ /** * \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 */ static SCError SCLogMessageGetBuffer( struct timeval *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 SCError error_code, const char *message) { #ifdef HAVE_LIBJANSSON if (type == SC_LOG_OP_TYPE_JSON) return SCLogMessageJSON(tval, buffer, buffer_size, log_level, file, line, function, error_code, message); #endif char *temp = buffer; const char *s = NULL; struct tm *tms = NULL; char *redb = ""; char *red = ""; char *yellowb = ""; char *yellow = ""; char *green = ""; char *blue = ""; 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; if (sc_log_module_initialized != 1) { #ifdef DEBUG printf("Logging module not initialized. Call SCLogInitLogModule(), " "before using the logging API\n"); #endif return SC_ERR_LOG_MODULE_NOT_INIT; } char *temp_fmt = strdup(log_format); if (unlikely(temp_fmt == NULL)) { return SC_ERR_MEM_ALLOC; } char *temp_fmt_h = temp_fmt; char *substr = temp_fmt; while ( (temp_fmt = index(temp_fmt, SC_LOG_FMT_PREFIX)) ) { if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) { if (temp_fmt_h != NULL) SCFree(temp_fmt_h); return SC_OK; } switch(temp_fmt[1]) { case SC_LOG_FMT_TIME: temp_fmt[0] = '\0'; struct tm local_tm; tms = SCLocalTime(tval->tv_sec, &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) goto error; 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) goto error; 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) goto error; temp += cw; temp_fmt++; substr = temp_fmt; substr++; break; case SC_LOG_FMT_TM: temp_fmt[0] = '\0'; /* disabled to prevent dead lock: * log or alloc (which calls log on error) can call TmThreadsGetCallingThread * which will lock tv_root_lock. This can happen while we already hold this * lock. */ #if 0 ThreadVars *tv = TmThreadsGetCallingThread(); cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - *msg), "%s%s", substr, ((tv != NULL)? tv->name: "UNKNOWN TM")); #endif cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s", substr, "N/A"); if (cw < 0) goto error; 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) goto error; 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) goto error; 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) goto error; 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) goto error; temp += cw; temp_fmt++; substr = temp_fmt; substr++; break; } temp_fmt++; } if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) { if (temp_fmt_h != NULL) SCFree(temp_fmt_h); return SC_OK; } cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s", substr); if (cw < 0) goto error; temp += cw; if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) { if (temp_fmt_h != NULL) SCFree(temp_fmt_h); return SC_OK; } if (error_code != SC_OK) { cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "[%sERRCODE%s: %s%s%s(%s%d%s)] - ", yellow, reset, red, SCErrorToString(error_code), reset, yellow, error_code, reset); if (cw < 0) goto error; temp += cw; if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) { if (temp_fmt_h != NULL) SCFree(temp_fmt_h); return SC_OK; } } char *xyellow = error_code > SC_OK ? yellow : ""; cw = snprintf(temp, SC_LOG_MAX_LOG_MSG_LEN - (temp - buffer), "%s%s%s", xyellow, message, reset); if (cw < 0) goto error; temp += cw; if ((temp - buffer) > SC_LOG_MAX_LOG_MSG_LEN) { if (temp_fmt_h != NULL) SCFree(temp_fmt_h); return SC_OK; } SCFree(temp_fmt_h); if (sc_log_config->op_filter_regex != NULL) { #define MAX_SUBSTRINGS 30 int ov[MAX_SUBSTRINGS]; if (pcre_exec(sc_log_config->op_filter_regex, sc_log_config->op_filter_regex_study, buffer, strlen(buffer), 0, 0, ov, MAX_SUBSTRINGS) < 0) { return SC_ERR_LOG_FG_FILTER_MATCH; // bit hacky, but just return !0 } #undef MAX_SUBSTRINGS } return SC_OK; error: if (temp_fmt_h != NULL) SCFree(temp_fmt_h); return SC_ERR_SPRINTF; } /** * \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 SCError error_code, 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); 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(&tval, 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, error_code, message) == 0) { SCLogPrintToStream((log_level == SC_LOG_ERROR)? stderr: stdout, buffer); } break; case SC_LOG_OP_IFACE_FILE: if (SCLogMessageGetBuffer(&tval, 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, error_code, message) == 0) { SCLogPrintToStream(op_iface_ctx->file_d, buffer); } break; case SC_LOG_OP_IFACE_SYSLOG: if (SCLogMessageGetBuffer(&tval, 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, error_code, message) == 0) { SCLogPrintToSyslog(SCLogMapLogLevelToSyslogLevel(log_level), buffer); } break; default: break; } op_iface_ctx = op_iface_ctx->next; } return SC_OK; } /** * \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; SCLogOPIfaceCtx *op_iface_ctx = NULL; int i = 0; if ( (buffer = SCMalloc(sc_log_config->op_ifaces_cnt * sizeof(SCLogOPBuffer))) == NULL) { SCLogError(SC_ERR_FATAL, "Fatal error encountered in SCLogAllocLogOPBuffer. Exiting..."); exit(EXIT_FAILURE); } op_iface_ctx = sc_log_config->op_ifaces; for (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() { SCLogOPIfaceCtx *iface_ctx = NULL; if ( (iface_ctx = SCMalloc(sizeof(SCLogOPIfaceCtx))) == NULL) { SCLogError(SC_ERR_FATAL, "Fatal error encountered in SCLogallocLogOPIfaceCtx. Exiting..."); exit(EXIT_FAILURE); } memset(iface_ctx, 0, sizeof(SCLogOPIfaceCtx)); 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, const char *log_format, int log_level, SCLogOPType type) { SCLogOPIfaceCtx *iface_ctx = SCLogAllocLogOPIfaceCtx(); if (iface_ctx == NULL) { SCLogError(SC_ERR_FATAL, "Fatal error encountered in SCLogInitFileOPIface. Exiting..."); exit(EXIT_FAILURE); } if (file == NULL || log_format == NULL) { goto error; } iface_ctx->iface = SC_LOG_OP_IFACE_FILE; iface_ctx->type = type; if ( (iface_ctx->file_d = fopen(file, "w+")) == NULL) { printf("Error opening file %s\n", file); goto error; } if ((iface_ctx->file = SCStrdup(file)) == NULL) { goto error; } if ((iface_ctx->log_format = SCStrdup(log_format)) == NULL) { goto error; } 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; } 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) { SCLogError(SC_ERR_FATAL, "Fatal error encountered in SCLogInitConsoleOPIface. Exiting..."); exit(EXIT_FAILURE); } 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; if (isatty(fileno(stdout)) && isatty(fileno(stderr))) { iface_ctx->use_color = TRUE; } 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) { SCLogError(SC_ERR_FATAL, "Fatal error encountered in SCLogInitSyslogOPIface. Exiting..."); exit(EXIT_FAILURE); } 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); 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; } /** * \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) { 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 = SC_LOG_DEF_LOG_FORMAT; #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, NULL, SC_LOG_LEVEL_MAX,0); SCFree(str); } } else { op_ifaces_ctx = SCLogInitFileOPIface(s, 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; const char *ep; int 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 = pcre_compile(filter, opts, &ep, &eo, NULL); if (sc_lc->op_filter_regex == NULL) { SCFree(sc_lc->op_filter); printf("pcre compile of \"%s\" failed at offset %d : %s\n", filter, eo, ep); return; } sc_lc->op_filter_regex_study = pcre_study(sc_lc->op_filter_regex, 0, &ep); if (ep != NULL) { printf("pcre study failed: %s\n", ep); return; } } return; } /** * \brief Returns a pointer to a new SCLogInitData. This is a public interface * intended to be used after the logging paramters are read from the * conf file * * \retval sc_lid Pointer to the newly created SCLogInitData * \initonly */ SCLogInitData *SCLogAllocLogInitData(void) { SCLogInitData *sc_lid = NULL; /* not using SCMalloc here because if it fails we can't log */ if ( (sc_lid = SCMalloc(sizeof(SCLogInitData))) == NULL) return NULL; memset(sc_lid, 0, sizeof(SCLogInitData)); return sc_lid; } /** * \brief Frees a SCLogInitData * * \param sc_lid Pointer to the SCLogInitData to be freed */ void SCLogFreeLogInitData(SCLogInitData *sc_lid) { if (sc_lid != NULL) { if (sc_lid->startup_message != NULL) SCFree(sc_lid->startup_message); if (sc_lid->global_log_format != NULL) SCFree(sc_lid->global_log_format); if (sc_lid->op_filter != NULL) SCFree(sc_lid->op_filter); SCLogFreeLogOPIfaceCtx(sc_lid->op_ifaces); } return; } /** * \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); 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; } /** * \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 */ 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) { #ifndef UNITTESTS printf("Warning: Supplied log_level_override for op_interface \"%s\" " "is invalid. Defaulting to not specifying an override\n", iface_name); #endif 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, 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; } } /** * \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) { SCLogError(SC_ERR_MUTEX, "Failed to initialize log mutex."); exit(EXIT_FAILURE); } #endif /* OS_WIN32 */ /* sc_log_config is a global variable */ if ( (sc_log_config = SCMalloc(sizeof(SCLogConfig))) == NULL) { SCLogError(SC_ERR_FATAL, "Fatal error encountered in SCLogInitLogModule. Exiting..."); exit(EXIT_FAILURE); } memset(sc_log_config, 0, sizeof(SCLogConfig)); 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); return; } void SCLogLoadConfig(int daemon, int verbose) { ConfNode *outputs; SCLogInitData *sc_lid; int have_logging = 0; 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. */ char *default_log_level_s = NULL; if (ConfGet("logging.default-log-level", &default_log_level_s) == 1) { sc_lid->global_log_level = SCMapEnumNameToValue(default_log_level_s, sc_log_level_map); if (sc_lid->global_log_level == -1) { SCLogError(SC_ERR_INVALID_ARGUMENT, "Invalid default log level: %s", default_log_level_s); exit(EXIT_FAILURE); } } else { SCLogWarning(SC_ERR_MISSING_CONFIG_PARAM, "No default log level set, will use notice."); sc_lid->global_log_level = SC_LOG_NOTICE; } if (verbose) { sc_lid->global_log_level += verbose; if (sc_lid->global_log_level > SC_LOG_LEVEL_MAX) sc_lid->global_log_level = SC_LOG_LEVEL_MAX; } if (ConfGet("logging.default-log-format", &sc_lid->global_log_format) != 1) sc_lid->global_log_format = SC_LOG_DEF_LOG_FORMAT; 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) { #ifdef HAVE_LIBJANSSON type = SC_LOG_OP_TYPE_JSON; #else SCLogError(SC_ERR_INVALID_ARGUMENT, "libjansson support not " "compiled in, can't use 'json' logging"); exit(EXIT_FAILURE); #endif /* HAVE_LIBJANSSON */ } } /* if available use the log format setting for this output, * otherwise fall back to the global setting. */ format = ConfNodeLookupChildValue(output, "format"); if (format == NULL) format = sc_lid->global_log_format; level_s = ConfNodeLookupChildValue(output, "level"); if (level_s != NULL) { level = SCMapEnumNameToValue(level_s, sc_log_level_map); if (level == -1) { SCLogError(SC_ERR_INVALID_ARGUMENT, "Invalid log level: %s", level_s); exit(EXIT_FAILURE); } } if (strcmp(output->name, "console") == 0) { op_iface_ctx = SCLogInitConsoleOPIface(format, level, type); } else if (strcmp(output->name, "file") == 0) { const char *filename = ConfNodeLookupChildValue(output, "filename"); if (filename == NULL) { SCLogError(SC_ERR_MISSING_CONFIG_PARAM, "Logging to file requires a filename"); exit(EXIT_FAILURE); } have_logging = 1; op_iface_ctx = SCLogInitFileOPIface(filename, format, level, type); } 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(SC_ERR_INVALID_ARGUMENT, "Invalid syslog " "facility: \"%s\", now using \"%s\" as syslog " "facility", facility_s, SC_LOG_DEF_SYSLOG_FACILITY_STR); facility = SC_LOG_DEF_SYSLOG_FACILITY; } } printf("Initialization syslog logging with format \"%s\".\n", format); have_logging = 1; op_iface_ctx = SCLogInitSyslogOPIface(facility, format, level, type); } else { SCLogWarning(SC_ERR_INVALID_ARGUMENT, "Invalid logging method: %s, " "ignoring", output->name); } if (op_iface_ctx != NULL) { SCLogAppendOPIfaceCtx(op_iface_ctx, sc_lid); } } if (daemon && (have_logging == 0)) { SCLogError(SC_ERR_MISSING_CONFIG_PARAM, "NO logging compatible with daemon mode selected," " suricata won't be able to log. Please update " " 'logging.outputs' in the YAML."); } 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(char *filearg) { char *log_dir; char *log_filename; log_dir = ConfigGetLogDirectory(); 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) if (sc_log_stream_lock != NULL) { SCMutexDestroy(&sc_log_stream_lock); sc_log_stream_lock = NULL; } #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 int SCLogTestInit01() { int result = 1; /* 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); if (sc_log_config == NULL) return 0; result &= (SC_LOG_DEF_LOG_LEVEL == sc_log_config->log_level); result &= (sc_log_config->op_ifaces != NULL && SC_LOG_DEF_LOG_OP_IFACE == sc_log_config->op_ifaces->iface); result &= (sc_log_config->log_format != NULL && strcmp(SC_LOG_DEF_LOG_FORMAT, 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); result &= (SC_LOG_DEBUG == sc_log_config->log_level); result &= (sc_log_config->op_ifaces != NULL && SC_LOG_OP_IFACE_CONSOLE == sc_log_config->op_ifaces->iface); result &= (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(); return result; } int SCLogTestInit02() { SCLogInitData *sc_lid = NULL; SCLogOPIfaceCtx *sc_iface_ctx = NULL; int result = 1; char *logfile = SCLogGetLogFilename("boo.txt"); sc_lid = SCLogAllocLogInitData(); if (sc_lid == NULL) return 0; 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_ALERT, 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); if (sc_log_config == NULL) return 0; result &= (SC_LOG_DEBUG == sc_log_config->log_level); result &= (sc_log_config->op_ifaces != NULL && SC_LOG_OP_IFACE_FILE == sc_log_config->op_ifaces->iface); result &= (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); result &= (sc_log_config->log_format != NULL && strcmp(SC_LOG_DEF_LOG_FORMAT, sc_log_config->log_format) == 0); result &= (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); result &= (sc_log_config->op_ifaces != NULL && sc_log_config->op_ifaces->next != NULL && sc_log_config->op_ifaces->next->log_format == NULL); SCLogDeInitLogModule(); sc_lid = SCLogAllocLogInitData(); if (sc_lid == NULL) return 0; 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); if (sc_log_config == NULL) return 0; result &= (SC_LOG_DEBUG == sc_log_config->log_level); result &= (sc_log_config->op_ifaces != NULL && SC_LOG_OP_IFACE_CONSOLE == sc_log_config->op_ifaces->iface); result &= (sc_log_config->op_ifaces != NULL && sc_log_config->op_ifaces->next == NULL); result &= (sc_log_config->log_format != NULL && strcmp("kaboo", sc_log_config->log_format) == 0); result &= (sc_log_config->op_ifaces != NULL && sc_log_config->op_ifaces->log_format == NULL); result &= (sc_log_config->op_ifaces != NULL && sc_log_config->op_ifaces->next == NULL); SCLogDeInitLogModule(); return result; } int SCLogTestInit03() { int result = 1; SCLogInitLogModule(NULL); SCLogAddFGFilterBL(NULL, "bamboo", -1); SCLogAddFGFilterBL(NULL, "soo", -1); SCLogAddFGFilterBL(NULL, "dummy", -1); result &= (SCLogPrintFGFilters() == 3); SCLogAddFGFilterBL(NULL, "dummy1", -1); SCLogAddFGFilterBL(NULL, "dummy2", -1); result &= (SCLogPrintFGFilters() == 5); SCLogDeInitLogModule(); return result; } int SCLogTestInit04() { int result = 1; SCLogInitLogModule(NULL); SCLogAddFDFilter("bamboo"); SCLogAddFDFilter("soo"); SCLogAddFDFilter("foo"); SCLogAddFDFilter("roo"); result &= (SCLogPrintFDFilters() == 4); SCLogAddFDFilter("loo"); SCLogAddFDFilter("soo"); result &= (SCLogPrintFDFilters() == 5); SCLogRemoveFDFilter("bamboo"); SCLogRemoveFDFilter("soo"); SCLogRemoveFDFilter("foo"); SCLogRemoveFDFilter("noo"); result &= (SCLogPrintFDFilters() == 2); SCLogDeInitLogModule(); return result; } int SCLogTestInit05() { int result = 1; SCLogInfo("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); return result; } #endif /* UNITTESTS */ void SCLogRegisterTests() { #ifdef UNITTESTS UtRegisterTest("SCLogTestInit01", SCLogTestInit01, 1); UtRegisterTest("SCLogTestInit02", SCLogTestInit02, 1); UtRegisterTest("SCLogTestInit03", SCLogTestInit03, 1); UtRegisterTest("SCLogTestInit04", SCLogTestInit04, 1); UtRegisterTest("SCLogTestInit05", SCLogTestInit05, 1); #endif /* UNITTESTS */ return; }