/* eurephia_log.c -- eurephia logging * * GPLv2 only - Copyright (C) 2008 - 2013 * David Sommerseth * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License. * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /** * @file eurephia_log.c * @author David Sommerseth * @date 2008-08-06 * * @brief Function for unified logging * */ #include #include #include #include #include #include #include #include #include #include /** * Maps eurephia log types (priorities) to string values * * @param prio eurephia logtype, such as LOG_INFO, LOG_DEBUG, LOG_FATAL, etc * * @return String containing the log type/prio */ static inline const char *logprio_str(int prio) { switch( prio ) { case LOG_INFO: return "-- INFO -- \0"; case LOG_DEBUG: return "-- DEBUG -- \0"; case LOG_WARNING: return "** WARNING ** \0"; case LOG_ERROR: return "** ERROR ** \0"; case LOG_CRITICAL: return "** CRITICAL ** \0"; case LOG_FATAL: return "** - FATAL - ** \0"; case LOG_PANIC: return "** * PANIC * ** \0"; default: return "[[ UNKNOWN ]]\0"; } } /** * Mapping table for eurephia log types to syslog log types */ static const int syslog_priority[] = { -1, LOG_INFO, /**< LOG_INFO */ LOG_DEBUG, /**< LOG_DEBUG */ LOG_WARNING, /**< LOG_WARNING */ LOG_ERR, /**< LOG_ERROR */ LOG_CRIT, /**< LOG_CRITICAL */ LOG_ALERT, /**< LOG_FATAL */ LOG_EMERG /**< LOG_PANIC */ }; /** * Mapping table for eurephia log types to OpenVPN's plug-in v3 log API */ static const int openvpnlog_flags[] = { -1, PLOG_NOTE, /**< LOG_INFO */ PLOG_DEBUG, /**< LOG_DEBUG */ PLOG_WARN, /**< LOG_WARNING */ PLOG_ERR, /**< LOG_ERROR */ PLOG_ERR | PLOG_NOMUTE, /**< LOG_CRITICAL */ PLOG_ERR | PLOG_NOMUTE, /**< LOG_FATAL */ PLOG_ERR | PLOG_NOMUTE /**< LOG_PANIC */ }; /** * Converts eurephiaLOGTYPE value to a string * * @param lt eurephiaLOGTYPE, must be either logFILE or logSYSLOG * * @return Returns a static string with log type */ static inline const char *logtype_str(eurephiaLOGTYPE lt) { switch( lt ) { case logFILE: return "file\0"; case logSYSLOG: return "syslog\0"; case logOPENVPN: return "openvpn::plugin_vlog\0"; } return NULL; } /** * Converts a string of log destination/facilities to syslog values * * @param dest String containing the log destination * * @return Returns a syslog compatible value, such as LOG_AUTHPRIV, LOG_LOCAL{0-7}, LOG_USER, etc. * Unknown types will be set to syslog default, LOG_USER. * * @remark The following list will list up facilities which will be ignored and LOG_USER will be used * instead: LOG_CRON, LOG_FTP, LOG_KERN, LOG_LPR, LOG_MAIL, LOG_NEWS, LOG_SYSLOG, LOG_UUCP. * */ static const int syslog_logdest(const char *dest) { if( dest == NULL ) { return LOG_USER; } if( strcasecmp(dest, "auth") == 0 ) { return LOG_AUTHPRIV; // LOG_AUTH is deprecated, and LOG_AUTHPRIV should be used instead } else if( strcasecmp(dest, "authpriv") == 0 ) { return LOG_AUTHPRIV; } else if( strcasecmp(dest, "daemon") == 0 ) { return LOG_DAEMON; } else if( strcasecmp(dest, "local0") == 0 ) { return LOG_LOCAL0; } else if( strcasecmp(dest, "local1") == 0 ) { return LOG_LOCAL1; } else if( strcasecmp(dest, "local2") == 0 ) { return LOG_LOCAL2; } else if( strcasecmp(dest, "local3") == 0 ) { return LOG_LOCAL3; } else if( strcasecmp(dest, "local4") == 0 ) { return LOG_LOCAL4; } else if( strcasecmp(dest, "local5") == 0 ) { return LOG_LOCAL5; } else if( strcasecmp(dest, "local6") == 0 ) { return LOG_LOCAL6; } else if( strcasecmp(dest, "local7") == 0 ) { return LOG_LOCAL7; } else { return LOG_USER; } } /** * POSIX Mutex to avoid simultaneously logging activity from * several threads at the same time */ pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; /** * Simple function for logging entries to a file with timestamp * * @param log FILE pointer to a log file * @param logdst Log destiation/priority * @param loglvl Log level of the message * @param file String containing file name of the place this function was called. Usually the __FILE__ macro. * @param line Line number of the source file this function was called. Usually the __LINE__ macro. * @param fmt stdarg, format string * @param ap stdarg va_list, prepared by va_start() */ static void file_log(FILE *log, int logdst, int loglvl, const char *file, const int line, const char *fmt, va_list ap) { char tstmp_str[200]; time_t tstmp; struct tm *loctstmp; if( log == NULL ) { return; } // Get time stamp memset(&tstmp_str, 0, 200); tstmp = time(NULL); loctstmp = localtime(&tstmp); if( loctstmp != NULL ) { if( strftime(tstmp_str, 198, "%Y-%m-%d %H:%M:%S %Z", loctstmp) == 0 ) { snprintf(tstmp_str, 198, "(error getting time stamp string)"); } } else { snprintf(tstmp_str, 198, "(error getting timestamp)"); } // Do the logging pthread_mutex_lock(&log_mutex); // Block other threads from writing when we write #ifdef ENABLE_DEBUG fprintf(log, "[%s] %s [%i] {%s:%i} ", tstmp_str, logprio_str(logdst), loglvl, file, line); #else fprintf(log, "[%s] %s [%i] ", tstmp_str, logprio_str(logdst), loglvl); #endif vfprintf(log, fmt, ap); fprintf(log, "\n"); fflush(log); pthread_mutex_unlock(&log_mutex); // Unblock other threads } /** * Internal function. This function should normally be called via the veurephia_log() function. * * @param ctx eurephiaCTX * @param logdst Log destination, can be LOG_INFO, LOG_DEBUG, LOG_WARNING, LOG_ERROR, * LOG_CRITICAL, LOG_FATAL or LOG_PANIC * @param loglvl Log level of the message. If the eurephiaCTX has a lower log level setup * than what this parameter is set to, the message will not be logged. * @param file String containing file name of the place this function was called. Usually the __FILE__ macro. * @param line Line number of the source file this function was called. Usually the __LINE__ macro. * @param ap stdarg's va_list data * @param fmt stdarg's char *fmt pointer */ void _veurephia_log_func(eurephiaCTX *ctx, int logdst, int loglvl, const char *file, const int line, va_list ap, const char *fmt) { // Only log if we have an open log file and which has high enough log level if( (ctx != NULL) && (ctx->log != NULL) && (ctx->log->opened == 1) && (ctx->log->loglevel >= loglvl) ) { switch( ctx->log->logtype ) { case logFILE: file_log(ctx->log->logfile, logdst, loglvl, file, line, fmt, ap); break; case logSYSLOG: vsyslog(syslog_priority[logdst], fmt, ap); break; case logOPENVPN: ctx->log->ovpn_log(openvpnlog_flags[logdst], "eurephia", fmt, ap); break; } } } /** * Closes an eurephia log context * * @param ctx eurephiaCTX containing the log context to be closed * */ void eurephia_log_close(eurephiaCTX *ctx) { if( (ctx == NULL) || (ctx->log == NULL) ) { return; } eurephia_log(ctx, LOG_INFO, 2, "Closing %s logging (%s).", logtype_str(ctx->log->logtype), ctx->log->destination); if( ctx->log->opened == 1 ) { switch( ctx->log->logtype ) { case logFILE: if( ctx->log->logfile != NULL ) { fflush(ctx->log->logfile); fclose(ctx->log->logfile); } ctx->log->logfile = NULL; break; case logSYSLOG: closelog(); break; case logOPENVPN: // The OpenVPN plug-in v3 log API does not need to close the log break; } ctx->log->opened = 0; } free_nullsafe(ctx, ctx->log->destination); free_nullsafe(ctx, ctx->log); } /** * Initialises and preapres the log device * * @param ctx eurephiaCTX where the log context will be associated * @param ident Log ident. Used when logging to syslog primarily, to identify log entries in syslog logs * @param dest Log destination. For can be a file name, "syslog:", "stdout:", "stderr:" or "none:". * If it is "syslog:" it must continue with a string describing log facility such as * "syslog:authpriv", "syslog:local0", "syslog:local1", "syslog:user", etc. If the * facility is unknown, it will default to "user" * @param loglevel Sets the verbosity level for the log file. The higher number, the more information * will be logged. * * @return Returns 1 on success, otherwise 0; */ int eurephia_log_init(eurephiaCTX *ctx, const char *ident, const char *dest, int loglevel, plugin_vlog_t plglog_fnc) { assert( (ctx != NULL) && (dest != NULL) ); // Create log context ctx->log = (eurephiaLOG *) malloc_nullsafe(ctx, sizeof(eurephiaLOG)+2); if( ctx->log == NULL ) { return 0; } if( strncmp(dest, "syslog:", 7) == 0 ) { ctx->log->logtype = logSYSLOG; ctx->log->destination = strdup(dest+7); } else if( strcmp(dest, "openvpn:") == 0 ) { ctx->log->logtype = logOPENVPN; ctx->log->ovpn_log = plglog_fnc; } else { ctx->log->logtype = logFILE; ctx->log->destination = strdup(dest); } if( (ctx->log->destination == NULL) && (ctx->log->logtype != logOPENVPN) ) { free_nullsafe(ctx, ctx->log); return 0; } ctx->log->loglevel = loglevel; switch( ctx->log->logtype ) { case logFILE: // Open log file if( strcmp(dest, "stdout:") == 0 ) { ctx->log->logfile = stdout; } else if( strcmp(dest, "stderr:") == 0 ) { ctx->log->logfile = stderr; } else if( strcmp(dest, "none:") == 0 ) { ctx->log->logfile = NULL; ctx->log->loglevel = 0; ctx->log->opened = 0; return 1; } else if( (ctx->log->logfile = fopen(dest, "aw")) == NULL ) { fprintf(stderr, "ERROR: Could not open log file: %s\n", ctx->log->destination); free_nullsafe(ctx, ctx->log->destination); free_nullsafe(ctx, ctx->log); return 0; } break; case logSYSLOG: // Open syslog openlog(ident, LOG_PID, syslog_logdest(ctx->log->destination)); break; case logOPENVPN: // Logging via OpenVPN's log function do not require any init break; } ctx->log->opened = 1; eurephia_log(ctx, LOG_INFO, 1, "Logging to %s (%s) started", logtype_str(ctx->log->logtype), ctx->log->destination); return 1; }