shared/output.c
afb48b28
 /*
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2007-2013 Sourcefire, Inc.
086eab5c
  *
  *  Authors: Tomasz Kojm
afb48b28
  *
  *  This program is free software; you can redistribute it and/or modify
bb34cb31
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
afb48b28
  *
  *  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
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
afb48b28
  */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
ace24e1f
 #ifdef HAVE_UNISTD_H
afb48b28
 #include <unistd.h>
ace24e1f
 #endif
afb48b28
 #include <fcntl.h>
 #include <time.h>
 #include <sys/stat.h>
 #include <errno.h>
4cd80898
 #ifndef _WIN32
afb48b28
 #include <sys/time.h>
 #include <sys/socket.h>
ace24e1f
 #endif
afb48b28
 #if HAVE_SYS_TYPES_H
 #include <sys/types.h>
 #endif
 
0378a9ab
 #ifdef HAVE_SYS_SELECT_H
 #include <sys/select.h>
 #endif
 
afb48b28
 #if defined(USE_SYSLOG) && !defined(C_AIX)
 #include <syslog.h>
 #endif
 
 #include "output.h"
944d7c82
 #include "libclamav/clamav.h"
9e751804
 #include "libclamav/others.h"
13d52e4f
 #include "libclamav/str.h"
afb48b28
 
2ed08e8a
 #ifdef CL_NOTHREADS
 #undef CL_THREAD_SAFE
 #endif
 
afb48b28
 #ifdef CL_THREAD_SAFE
 #include <pthread.h>
 pthread_mutex_t logg_mutex = PTHREAD_MUTEX_INITIALIZER;
3d8a490c
 pthread_mutex_t mdprintf_mutex = PTHREAD_MUTEX_INITIALIZER;
afb48b28
 #endif
 
57c410e9
 #if defined(C_LINUX) && defined(HAVE_LIBINTL_H)
0ae41a2d
 #include <libintl.h>
 #include <locale.h>
 
 #define gettext_noop(s) s
dc7f716a
 #define _(s) gettext(s)
 #define N_(s) gettext_noop(s)
0ae41a2d
 
 #else
 
dc7f716a
 #define _(s) s
 #define N_(s) s
0ae41a2d
 
 #endif
 
afff80ef
 FILE *logg_fp = NULL;
afb48b28
 
42ccf9c2
 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;
58bcf502
 const char *logg_file = NULL;
 #if defined(USE_SYSLOG) && !defined(C_AIX)
0ae41a2d
 short logg_syslog;
58bcf502
 #endif
 
 short int mprintf_disabled = 0, mprintf_verbose = 0, mprintf_quiet = 0,
dc7f716a
           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;                         \
     }
afff80ef
 
afb48b28
 int mdprintf(int desc, const char *str, ...)
 {
dc7f716a
     va_list args;
     char buffer[512], *abuffer = NULL, *buff;
     int bytes, todo, ret = 0;
     size_t len;
afff80ef
 
     ARGLEN(args, str, len);
dc7f716a
     if (len <= sizeof(buffer))
     {
         len = sizeof(buffer);
         buff = buffer;
     }
     else
     {
         abuffer = malloc(len);
         if (!abuffer)
         {
             len = sizeof(buffer);
             buff = buffer;
         }
         else
         {
             buff = abuffer;
         }
afff80ef
     }
afb48b28
     va_start(args, str);
afff80ef
     bytes = vsnprintf(buff, len, str, args);
afb48b28
     va_end(args);
afff80ef
     buff[len - 1] = 0;
2e81b220
 
dc7f716a
     if (bytes < 0)
     {
         if (len > sizeof(buffer))
             free(abuffer);
         return bytes;
afff80ef
     }
dc7f716a
     if ((size_t)bytes >= len)
         bytes = len - 1;
afff80ef
 
0378a9ab
     todo = bytes;
3d8a490c
 #ifdef CL_THREAD_SAFE
     /* make sure we don't mix sends from multiple threads,
      * important for IDSESSION */
     pthread_mutex_lock(&mdprintf_mutex);
 #endif
dc7f716a
     while (todo > 0)
     {
         ret = send(desc, buff, bytes, 0);
         if (ret < 0)
         {
             struct timeval tv;
             if (errno != EWOULDBLOCK)
                 break;
                 /* didn't send anything yet */
3d8a490c
 #ifdef CL_THREAD_SAFE
dc7f716a
             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);
3d8a490c
 #ifdef CL_THREAD_SAFE
dc7f716a
             pthread_mutex_lock(&mdprintf_mutex);
 #endif
             if (!ret)
             {
                 /* timed out */
                 ret = -1;
                 break;
             }
         }
         else
         {
             todo -= ret;
             buff += ret;
         }
0378a9ab
     }
3d8a490c
 #ifdef CL_THREAD_SAFE
     pthread_mutex_unlock(&mdprintf_mutex);
 #endif
2e81b220
 
dc7f716a
     if (len > sizeof(buffer))
         free(abuffer);
2e81b220
 
0378a9ab
     return ret < 0 ? -1 : bytes;
afb48b28
 }
 
42ccf9c2
 static int rename_logg(STATBUF *sb)
 {
     char *rotate_file;
     size_t rotate_file_len;
     time_t t;
     struct tm tmp;
 
dc7f716a
     if (!logg_rotate)
     {
         if (logg_fp)
         {
059ca614
             fprintf(logg_fp, "Log size = %lld, max = %lld\n", (long long int)sb->st_size, (long long int)logg_size);
e1c3ee76
             fprintf(logg_fp, "WARNING: Log size limit met but log file rotation turned off. Forcing log file rotation anyways.\n");
ec039b6a
         }
42ccf9c2
 
e1c3ee76
         logg_rotate = 1;
42ccf9c2
     }
 
     rotate_file_len = strlen(logg_file) + sizeof("-YYYY-MM-DD_HH:MM:SS");
     rotate_file = calloc(1, rotate_file_len + 1);
dc7f716a
     if (!rotate_file)
     {
e1c3ee76
         if (logg_fp)
42ccf9c2
             fprintf(logg_fp, "Need to rotate log file due to size but ran out of memory.\n");
 
         return -1;
     }
 
     t = time(NULL);
dc7f716a
     if (!localtime_r(&t, &tmp))
     {
e1c3ee76
         if (logg_fp)
42ccf9c2
             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);
dc7f716a
     strftime(rotate_file + strlen(rotate_file), rotate_file_len - strlen(rotate_file), "-%Y%m%d_%H%M%S", &tmp);
42ccf9c2
 
dc7f716a
     if (logg_fp)
     {
42ccf9c2
         fclose(logg_fp);
         logg_fp = NULL;
     }
 
dc7f716a
     if (rename(logg_file, rotate_file))
     {
42ccf9c2
         free(rotate_file);
         return -1;
     }
 
     free(rotate_file);
     return 0;
 }
 
 static int logg_open(void)
 {
     STATBUF sb;
 
dc7f716a
     if (logg_file)
         if (logg_size > 0)
             if (CLAMSTAT(logg_file, &sb) != -1)
                 if (sb.st_size > logg_size)
                     if (rename_logg(&sb))
42ccf9c2
                         return -1;
 
     return 0;
 }
 
afff80ef
 void logg_close(void)
 {
 #if defined(USE_SYSLOG) && !defined(C_AIX)
dc7f716a
     if (logg_syslog)
         closelog();
afff80ef
 #endif
afb48b28
 
 #ifdef CL_THREAD_SAFE
     pthread_mutex_lock(&logg_mutex);
 #endif
dc7f716a
     if (logg_fp)
     {
         fclose(logg_fp);
         logg_fp = NULL;
afb48b28
     }
 #ifdef CL_THREAD_SAFE
     pthread_mutex_unlock(&logg_mutex);
 #endif
 }
 
2cf89f22
 /*
  * 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
  */
afb48b28
 int logg(const char *str, ...)
 {
dc7f716a
     va_list args;
     char buffer[1025], *abuffer = NULL, *buff;
     time_t currtime;
     size_t len;
     mode_t old_umask;
42ccf9c2
 #ifdef F_WRLCK
dc7f716a
     struct flock fl;
42ccf9c2
 #endif
afff80ef
 
fb6fe4f5
     if ((*str == '$' && logg_verbose < 2) ||
dc7f716a
         (*str == '*' && !logg_verbose))
         return 0;
afff80ef
 
     ARGLEN(args, str, len);
dc7f716a
     if (len <= sizeof(buffer))
     {
         len = sizeof(buffer);
         buff = buffer;
     }
     else
     {
         abuffer = malloc(len);
         if (!abuffer)
         {
             len = sizeof(buffer);
             buff = buffer;
         }
         else
         {
             buff = abuffer;
         }
afff80ef
     }
fe7ee98f
     va_start(args, str);
afff80ef
     vsnprintf(buff, len, str, args);
5a3aeff4
     va_end(args);
afff80ef
     buff[len - 1] = 0;
fe7ee98f
 
afb48b28
 #ifdef CL_THREAD_SAFE
234582ae
     pthread_mutex_lock(&logg_mutex);
afb48b28
 #endif
42ccf9c2
 
     logg_open();
 
dc7f716a
     if (!logg_fp && logg_file)
     {
42ccf9c2
         old_umask = umask(0037);
dc7f716a
         if ((logg_fp = fopen(logg_file, "at")) == NULL)
         {
42ccf9c2
             umask(old_umask);
afb48b28
 #ifdef CL_THREAD_SAFE
42ccf9c2
             pthread_mutex_unlock(&logg_mutex);
afb48b28
 #endif
42ccf9c2
             printf("ERROR: Can't open %s in append mode (check permissions!).\n", logg_file);
dc7f716a
             if (len > sizeof(buffer))
42ccf9c2
                 free(abuffer);
             return -1;
dc7f716a
         }
         else
             umask(old_umask);
afb48b28
 
ace24e1f
 #ifdef F_WRLCK
dc7f716a
         if (logg_lock)
         {
42ccf9c2
             memset(&fl, 0, sizeof(fl));
             fl.l_type = F_WRLCK;
dc7f716a
             if (fcntl(fileno(logg_fp), F_SETLK, &fl) == -1)
             {
74f0e79e
 #ifdef EOPNOTSUPP
dc7f716a
                 if (errno == EOPNOTSUPP)
42ccf9c2
                     printf("WARNING: File locking not supported (NFS?)\n");
                 else
74f0e79e
 #endif
42ccf9c2
                 {
afb48b28
 #ifdef CL_THREAD_SAFE
42ccf9c2
                     pthread_mutex_unlock(&logg_mutex);
afb48b28
 #endif
42ccf9c2
                     printf("ERROR: %s is locked by another process\n", logg_file);
dc7f716a
                     if (len > sizeof(buffer))
                         free(abuffer);
42ccf9c2
                     return -1;
                 }
             }
         }
ace24e1f
 #endif
42ccf9c2
     }
afb48b28
 
dc7f716a
     if (logg_fp)
     {
         char flush = !logg_noflush;
         /* Need to avoid logging time for verbose messages when logverbose
eff613a9
                is not set or we get a bunch of timestamps in the log without
                newlines... */
dc7f716a
         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);
         }
13d52e4f
 
dc7f716a
         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);
afb48b28
 
dc7f716a
         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);
afb48b28
     }
 #endif
 
234582ae
 #ifdef CL_THREAD_SAFE
     pthread_mutex_unlock(&logg_mutex);
 #endif
 
dc7f716a
     if (len > sizeof(buffer))
         free(abuffer);
afb48b28
     return 0;
 }
 
 void mprintf(const char *str, ...)
 {
dc7f716a
     va_list args;
     FILE *fd;
     char buffer[512], *abuffer = NULL, *buff;
     size_t len;
afb48b28
 
dc7f716a
     if (mprintf_disabled)
         return;
afb48b28
 
e1187134
     fd = stdout;
afb48b28
 
dc7f716a
     /* legend:
afb48b28
  * ! - error
  * @ - error with logging
  * ...
  */
 
dc7f716a
     /*
afb48b28
  *             ERROR    WARNING    STANDARD
e1187134
  * normal      stderr   stderr     stdout
afb48b28
  * 
e1187134
  * verbose     stderr   stderr     stdout
afb48b28
  * 
e1187134
  * quiet       stderr     no         no
afb48b28
  */
 
afff80ef
     ARGLEN(args, str, len);
dc7f716a
     if (len <= sizeof(buffer))
     {
         len = sizeof(buffer);
         buff = buffer;
     }
     else
     {
         abuffer = malloc(len);
         if (!abuffer)
         {
             len = sizeof(buffer);
             buff = buffer;
         }
         else
         {
             buff = abuffer;
         }
afff80ef
     }
afb48b28
     va_start(args, str);
afff80ef
     vsnprintf(buff, len, str, args);
118920f8
     va_end(args);
afff80ef
     buff[len - 1] = 0;
afb48b28
 
254cecef
 #ifdef _WIN32
dc7f716a
     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);
afb48b28
     }
 
dc7f716a
     if (fd == stdout)
         fflush(stdout);
afff80ef
 
dc7f716a
     if (len > sizeof(buffer))
         free(abuffer);
afb48b28
 }
c695dab4
 
dc7f716a
 struct facstruct
 {
c695dab4
     const char *name;
     int code;
 };
 
 #if defined(USE_SYSLOG) && !defined(C_AIX)
 static const struct facstruct facilitymap[] = {
45ae238c
 #ifdef LOG_AUTH
dc7f716a
     {"LOG_AUTH", LOG_AUTH},
45ae238c
 #endif
 #ifdef LOG_AUTHPRIV
dc7f716a
     {"LOG_AUTHPRIV", LOG_AUTHPRIV},
45ae238c
 #endif
 #ifdef LOG_CRON
dc7f716a
     {"LOG_CRON", LOG_CRON},
45ae238c
 #endif
 #ifdef LOG_DAEMON
dc7f716a
     {"LOG_DAEMON", LOG_DAEMON},
45ae238c
 #endif
 #ifdef LOG_FTP
dc7f716a
     {"LOG_FTP", LOG_FTP},
45ae238c
 #endif
 #ifdef LOG_KERN
dc7f716a
     {"LOG_KERN", LOG_KERN},
45ae238c
 #endif
 #ifdef LOG_LPR
dc7f716a
     {"LOG_LPR", LOG_LPR},
45ae238c
 #endif
 #ifdef LOG_MAIL
dc7f716a
     {"LOG_MAIL", LOG_MAIL},
45ae238c
 #endif
 #ifdef LOG_NEWS
dc7f716a
     {"LOG_NEWS", LOG_NEWS},
45ae238c
 #endif
 #ifdef LOG_AUTH
dc7f716a
     {"LOG_AUTH", LOG_AUTH},
45ae238c
 #endif
 #ifdef LOG_SYSLOG
dc7f716a
     {"LOG_SYSLOG", LOG_SYSLOG},
45ae238c
 #endif
 #ifdef LOG_USER
dc7f716a
     {"LOG_USER", LOG_USER},
45ae238c
 #endif
 #ifdef LOG_UUCP
dc7f716a
     {"LOG_UUCP", LOG_UUCP},
45ae238c
 #endif
 #ifdef LOG_LOCAL0
dc7f716a
     {"LOG_LOCAL0", LOG_LOCAL0},
45ae238c
 #endif
 #ifdef LOG_LOCAL1
dc7f716a
     {"LOG_LOCAL1", LOG_LOCAL1},
45ae238c
 #endif
 #ifdef LOG_LOCAL2
dc7f716a
     {"LOG_LOCAL2", LOG_LOCAL2},
45ae238c
 #endif
 #ifdef LOG_LOCAL3
dc7f716a
     {"LOG_LOCAL3", LOG_LOCAL3},
45ae238c
 #endif
 #ifdef LOG_LOCAL4
dc7f716a
     {"LOG_LOCAL4", LOG_LOCAL4},
45ae238c
 #endif
 #ifdef LOG_LOCAL5
dc7f716a
     {"LOG_LOCAL5", LOG_LOCAL5},
45ae238c
 #endif
 #ifdef LOG_LOCAL6
dc7f716a
     {"LOG_LOCAL6", LOG_LOCAL6},
45ae238c
 #endif
 #ifdef LOG_LOCAL7
dc7f716a
     {"LOG_LOCAL7", LOG_LOCAL7},
45ae238c
 #endif
dc7f716a
     {NULL, -1}};
c695dab4
 
 int logg_facility(const char *name)
 {
dc7f716a
     int i;
c695dab4
 
dc7f716a
     for (i = 0; facilitymap[i].name; i++)
         if (!strcmp(facilitymap[i].name, name))
             return facilitymap[i].code;
c695dab4
 
     return -1;
 }
 #endif