/* * Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * Copyright (C) 2007-2013 Sourcefire, Inc. * * Authors: Tomasz Kojm * * This program is free software; you can redistribute it and/or modify * it 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #if HAVE_CONFIG_H #include "clamav-config.h" #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #ifndef _WIN32 #include #include #endif #if HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_SELECT_H #include #endif #if defined(USE_SYSLOG) && !defined(C_AIX) #include #endif #include "output.h" #include "libclamav/clamav.h" #include "libclamav/others.h" #include "libclamav/str.h" #ifdef CL_NOTHREADS #undef CL_THREAD_SAFE #endif #ifdef CL_THREAD_SAFE #include pthread_mutex_t logg_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mdprintf_mutex = PTHREAD_MUTEX_INITIALIZER; #endif #if defined(C_LINUX) && defined(HAVE_LIBINTL_H) #include #include #define gettext_noop(s) s #define _(s) gettext(s) #define N_(s) gettext_noop(s) #else #define _(s) s #define N_(s) s #endif FILE *logg_fp = NULL; short int logg_verbose = 0, logg_nowarn = 0, logg_lock = 1, logg_time = 0, logg_foreground = 1, logg_noflush = 0, logg_rotate = 0; off_t logg_size = 0; const char *logg_file = NULL; #if defined(USE_SYSLOG) && !defined(C_AIX) short logg_syslog; #endif short int mprintf_disabled = 0, mprintf_verbose = 0, mprintf_quiet = 0, mprintf_stdout = 0, mprintf_nowarn = 0, mprintf_send_timeout = 100, mprintf_progress = 0; #define ARGLEN(args, str, len) \ { \ size_t arglen = 1, i; \ char *pt; \ va_start(args, str); \ len = strlen(str); \ for (i = 0; i < len - 1; i++) \ { \ if (str[i] == '%') \ { \ switch (str[++i]) \ { \ case 's': \ pt = va_arg(args, char *); \ if (pt) \ arglen += strlen(pt); \ break; \ case 'f': \ va_arg(args, double); \ arglen += 25; \ break; \ case 'l': \ va_arg(args, long); \ arglen += 20; \ break; \ default: \ va_arg(args, int); \ arglen += 10; \ break; \ } \ } \ } \ va_end(args); \ len += arglen; \ } int mdprintf(int desc, const char *str, ...) { va_list args; char buffer[512], *abuffer = NULL, *buff; int bytes, todo, ret = 0; size_t len; ARGLEN(args, str, len); if (len <= sizeof(buffer)) { len = sizeof(buffer); buff = buffer; } else { abuffer = malloc(len); if (!abuffer) { len = sizeof(buffer); buff = buffer; } else { buff = abuffer; } } va_start(args, str); bytes = vsnprintf(buff, len, str, args); va_end(args); buff[len - 1] = 0; if (bytes < 0) { if (len > sizeof(buffer)) free(abuffer); return bytes; } if ((size_t)bytes >= len) bytes = len - 1; todo = bytes; #ifdef CL_THREAD_SAFE /* make sure we don't mix sends from multiple threads, * important for IDSESSION */ pthread_mutex_lock(&mdprintf_mutex); #endif while (todo > 0) { ret = send(desc, buff, bytes, 0); if (ret < 0) { struct timeval tv; if (errno != EWOULDBLOCK) break; /* didn't send anything yet */ #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&mdprintf_mutex); #endif tv.tv_sec = 0; tv.tv_usec = mprintf_send_timeout * 1000; do { fd_set wfds; FD_ZERO(&wfds); FD_SET(desc, &wfds); ret = select(desc + 1, NULL, &wfds, NULL, &tv); } while (ret < 0 && errno == EINTR); #ifdef CL_THREAD_SAFE pthread_mutex_lock(&mdprintf_mutex); #endif if (!ret) { /* timed out */ ret = -1; break; } } else { todo -= ret; buff += ret; } } #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&mdprintf_mutex); #endif if (len > sizeof(buffer)) free(abuffer); return ret < 0 ? -1 : bytes; } static int rename_logg(STATBUF *sb) { char *rotate_file; size_t rotate_file_len; time_t t; struct tm tmp; if (!logg_rotate) { if (logg_fp) { fprintf(logg_fp, "Log size = %lld, max = %lld\n", (long long int)sb->st_size, (long long int)logg_size); fprintf(logg_fp, "WARNING: Log size limit met but log file rotation turned off. Forcing log file rotation anyways.\n"); } logg_rotate = 1; } rotate_file_len = strlen(logg_file) + sizeof("-YYYY-MM-DD_HH:MM:SS"); rotate_file = calloc(1, rotate_file_len + 1); if (!rotate_file) { if (logg_fp) fprintf(logg_fp, "Need to rotate log file due to size but ran out of memory.\n"); return -1; } t = time(NULL); if (!localtime_r(&t, &tmp)) { if (logg_fp) fprintf(logg_fp, "Need to rotate log file due to size but could not get local time.\n"); free(rotate_file); return -1; } strcpy(rotate_file, logg_file); strftime(rotate_file + strlen(rotate_file), rotate_file_len - strlen(rotate_file), "-%Y%m%d_%H%M%S", &tmp); if (logg_fp) { fclose(logg_fp); logg_fp = NULL; } if (rename(logg_file, rotate_file)) { free(rotate_file); return -1; } free(rotate_file); return 0; } static int logg_open(void) { STATBUF sb; if (logg_file) if (logg_size > 0) if (CLAMSTAT(logg_file, &sb) != -1) if (sb.st_size > logg_size) if (rename_logg(&sb)) return -1; return 0; } void logg_close(void) { #if defined(USE_SYSLOG) && !defined(C_AIX) if (logg_syslog) closelog(); #endif #ifdef CL_THREAD_SAFE pthread_mutex_lock(&logg_mutex); #endif if (logg_fp) { fclose(logg_fp); logg_fp = NULL; } #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&logg_mutex); #endif } /* * legend: * ! - ERROR: * ^ - WARNING: * ~ - normal * # - normal, not foreground (logfile and syslog only) * * - verbose * $ - debug * none - normal * * Default Foreground LogVerbose Debug Syslog * ! yes mprintf yes yes LOG_ERR * ^ yes mprintf yes yes LOG_WARNING * ~ yes mprintf yes yes LOG_INFO * # yes no yes yes LOG_INFO * * no mprintf yes yes LOG_DEBUG * $ no mprintf no yes LOG_DEBUG * none yes mprintf yes yes LOG_INFO */ int logg(const char *str, ...) { va_list args; char buffer[1025], *abuffer = NULL, *buff; time_t currtime; size_t len; mode_t old_umask; #ifdef F_WRLCK struct flock fl; #endif if ((*str == '$' && logg_verbose < 2) || (*str == '*' && !logg_verbose)) return 0; ARGLEN(args, str, len); if (len <= sizeof(buffer)) { len = sizeof(buffer); buff = buffer; } else { abuffer = malloc(len); if (!abuffer) { len = sizeof(buffer); buff = buffer; } else { buff = abuffer; } } va_start(args, str); vsnprintf(buff, len, str, args); va_end(args); buff[len - 1] = 0; #ifdef CL_THREAD_SAFE pthread_mutex_lock(&logg_mutex); #endif logg_open(); if (!logg_fp && logg_file) { old_umask = umask(0037); if ((logg_fp = fopen(logg_file, "at")) == NULL) { umask(old_umask); #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&logg_mutex); #endif printf("ERROR: Can't open %s in append mode (check permissions!).\n", logg_file); if (len > sizeof(buffer)) free(abuffer); return -1; } else umask(old_umask); #ifdef F_WRLCK if (logg_lock) { memset(&fl, 0, sizeof(fl)); fl.l_type = F_WRLCK; if (fcntl(fileno(logg_fp), F_SETLK, &fl) == -1) { #ifdef EOPNOTSUPP if (errno == EOPNOTSUPP) printf("WARNING: File locking not supported (NFS?)\n"); else #endif { #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&logg_mutex); #endif printf("ERROR: %s is locked by another process\n", logg_file); if (len > sizeof(buffer)) free(abuffer); return -1; } } } #endif } if (logg_fp) { char flush = !logg_noflush; /* Need to avoid logging time for verbose messages when logverbose is not set or we get a bunch of timestamps in the log without newlines... */ if (logg_time && ((*buff != '*') || logg_verbose)) { char timestr[32]; time(&currtime); cli_ctime(&currtime, timestr, sizeof(timestr)); /* cut trailing \n */ timestr[strlen(timestr) - 1] = '\0'; fprintf(logg_fp, "%s -> ", timestr); } if (*buff == '!') { fprintf(logg_fp, "ERROR: %s", buff + 1); flush = 1; } else if (*buff == '^') { if (!logg_nowarn) fprintf(logg_fp, "WARNING: %s", buff + 1); flush = 1; } else if (*buff == '*' || *buff == '$') { fprintf(logg_fp, "%s", buff + 1); } else if (*buff == '#' || *buff == '~') { fprintf(logg_fp, "%s", buff + 1); } else fprintf(logg_fp, "%s", buff); if (flush) fflush(logg_fp); } if (logg_foreground) { if (buff[0] != '#') { if (logg_time) { char timestr[32]; time(&currtime); cli_ctime(&currtime, timestr, sizeof(timestr)); /* cut trailing \n */ timestr[strlen(timestr) - 1] = '\0'; mprintf("%s -> %s", timestr, buff); } else { mprintf("%s", buff); } } } #if defined(USE_SYSLOG) && !defined(C_AIX) if (logg_syslog) { cli_chomp(buff); if (buff[0] == '!') { syslog(LOG_ERR, "%s", buff + 1); } else if (buff[0] == '^') { if (!logg_nowarn) syslog(LOG_WARNING, "%s", buff + 1); } else if (buff[0] == '*' || buff[0] == '$') { syslog(LOG_DEBUG, "%s", buff + 1); } else if (buff[0] == '#' || buff[0] == '~') { syslog(LOG_INFO, "%s", buff + 1); } else syslog(LOG_INFO, "%s", buff); } #endif #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&logg_mutex); #endif if (len > sizeof(buffer)) free(abuffer); return 0; } void mprintf(const char *str, ...) { va_list args; FILE *fd; char buffer[512], *abuffer = NULL, *buff; size_t len; if (mprintf_disabled) return; fd = stdout; /* legend: * ! - error * @ - error with logging * ... */ /* * ERROR WARNING STANDARD * normal stderr stderr stdout * * verbose stderr stderr stdout * * quiet stderr no no */ ARGLEN(args, str, len); if (len <= sizeof(buffer)) { len = sizeof(buffer); buff = buffer; } else { abuffer = malloc(len); if (!abuffer) { len = sizeof(buffer); buff = buffer; } else { buff = abuffer; } } va_start(args, str); vsnprintf(buff, len, str, args); va_end(args); buff[len - 1] = 0; #ifdef _WIN32 do { int tmplen = len + 1; wchar_t *tmpw = malloc(tmplen * sizeof(wchar_t)); char *nubuff; if (!tmpw) break; if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buff, -1, tmpw, tmplen)) { free(tmpw); break; } /* FIXME CHECK IT'S REALLY UTF8 */ nubuff = (char *)malloc(tmplen); if (!nubuff) { free(tmpw); break; } if (!WideCharToMultiByte(CP_OEMCP, 0, tmpw, -1, nubuff, tmplen, NULL, NULL)) { free(nubuff); free(tmpw); break; } free(tmpw); if (len > sizeof(buffer)) free(abuffer); abuffer = buff = nubuff; len = sizeof(buffer) + 1; } while (0); #endif if (buff[0] == '!') { if (!mprintf_stdout) fd = stderr; fprintf(fd, "ERROR: %s", &buff[1]); } else if (buff[0] == '@') { if (!mprintf_stdout) fd = stderr; fprintf(fd, "ERROR: %s", &buff[1]); } else if (!mprintf_quiet) { if (buff[0] == '^') { if (!mprintf_nowarn) { if (!mprintf_stdout) fd = stderr; fprintf(fd, "WARNING: %s", &buff[1]); } } else if (buff[0] == '*') { if (mprintf_verbose) fprintf(fd, "%s", &buff[1]); } else if (buff[0] == '~') { fprintf(fd, "%s", &buff[1]); } else fprintf(fd, "%s", buff); } if (fd == stdout) fflush(stdout); if (len > sizeof(buffer)) free(abuffer); } struct facstruct { const char *name; int code; }; #if defined(USE_SYSLOG) && !defined(C_AIX) static const struct facstruct facilitymap[] = { #ifdef LOG_AUTH {"LOG_AUTH", LOG_AUTH}, #endif #ifdef LOG_AUTHPRIV {"LOG_AUTHPRIV", LOG_AUTHPRIV}, #endif #ifdef LOG_CRON {"LOG_CRON", LOG_CRON}, #endif #ifdef LOG_DAEMON {"LOG_DAEMON", LOG_DAEMON}, #endif #ifdef LOG_FTP {"LOG_FTP", LOG_FTP}, #endif #ifdef LOG_KERN {"LOG_KERN", LOG_KERN}, #endif #ifdef LOG_LPR {"LOG_LPR", LOG_LPR}, #endif #ifdef LOG_MAIL {"LOG_MAIL", LOG_MAIL}, #endif #ifdef LOG_NEWS {"LOG_NEWS", LOG_NEWS}, #endif #ifdef LOG_AUTH {"LOG_AUTH", LOG_AUTH}, #endif #ifdef LOG_SYSLOG {"LOG_SYSLOG", LOG_SYSLOG}, #endif #ifdef LOG_USER {"LOG_USER", LOG_USER}, #endif #ifdef LOG_UUCP {"LOG_UUCP", LOG_UUCP}, #endif #ifdef LOG_LOCAL0 {"LOG_LOCAL0", LOG_LOCAL0}, #endif #ifdef LOG_LOCAL1 {"LOG_LOCAL1", LOG_LOCAL1}, #endif #ifdef LOG_LOCAL2 {"LOG_LOCAL2", LOG_LOCAL2}, #endif #ifdef LOG_LOCAL3 {"LOG_LOCAL3", LOG_LOCAL3}, #endif #ifdef LOG_LOCAL4 {"LOG_LOCAL4", LOG_LOCAL4}, #endif #ifdef LOG_LOCAL5 {"LOG_LOCAL5", LOG_LOCAL5}, #endif #ifdef LOG_LOCAL6 {"LOG_LOCAL6", LOG_LOCAL6}, #endif #ifdef LOG_LOCAL7 {"LOG_LOCAL7", LOG_LOCAL7}, #endif {NULL, -1}}; int logg_facility(const char *name) { int i; for (i = 0; facilitymap[i].name; i++) if (!strcmp(facilitymap[i].name, name)) return facilitymap[i].code; return -1; } #endif