libclamav/stats.c
1bdaf813
 /*
  *  Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
  *
  *  Author: Shawn Webb
  *
  *  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.
  */
 
f2571e34
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
4a4b84da
 #if HAVE_UNISTD_H
f2571e34
 #include <unistd.h>
4a4b84da
 #endif
f2571e34
 
 #include <sys/types.h>
c8bf9b6c
 #if !defined(_WIN32)
70ab826f
 #if defined(C_SOLARIS)
 #include <sys/utsname.h>
 #else
424fb3b5
 #if HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
58f224b8
 #if HAVE_SYSCTLBYNAME
c8bf9b6c
 #include <sys/sysctl.h>
70ab826f
 #endif
58f224b8
 #endif
53242b97
 #else
 #include <Windows.h>
 #include <tchar.h>
c8bf9b6c
 #endif
f2571e34
 
 #ifdef CL_THREAD_SAFE
 #include <pthread.h>
 #endif
 
68e60b12
 #include <errno.h>
 
f2571e34
 #include "libclamav/others.h"
 #include "libclamav/clamav.h"
d4f90ad4
 #include "libclamav/dconf.h"
e182c02c
 #include "libclamav/stats_json.h"
4473a0a9
 #include "libclamav/stats.h"
9e0f01d9
 #include "libclamav/hostid.h"
2d6361a9
 #include "libclamav/www.h"
f2571e34
 
b86e3908
 #define DEBUG_STATS 0
3c29ca0b
 
 static cli_flagged_sample_t *find_sample(cli_intel_t *intel, const char *virname, const unsigned char *md5, size_t size, stats_section_t *sections);
f2571e34
 void free_sample(cli_flagged_sample_t *sample);
 
3c29ca0b
 #if DEBUG_STATS
 char *get_hash(unsigned char *md5)
 {
     char *hash;
     int i;
 
     hash = calloc(1, 33);
     if (!(hash))
         return NULL;
 
288057e9
     for (i = 0; i < 16; i++)
         sprintf(hash + (i * 2), "%02x", md5[i]);
3c29ca0b
 
     return hash;
 }
 
 char *get_sample_names(char **names)
 {
     char *ret;
     size_t n, i, sz;
 
     sz = 0;
288057e9
     for (n = 0; names[n] != NULL; n++)
3c29ca0b
         sz += strlen(names[n]);
 
     ret = calloc(1, sz + n + 1);
     if (!(ret))
         return NULL;
 
288057e9
     for (i = 0; names[i] != NULL; i++)
         sprintf(ret + strlen(ret), "%s%s", (i == 0) ? "" : " ", names[i]);
3c29ca0b
 
     return ret;
 }
 
 void print_sample(cli_flagged_sample_t *sample)
 {
     char *hash, *names;
     size_t i;
 
     if (!(sample))
         return;
 
     hash = get_hash(sample->md5);
     if (!(hash))
         return;
 
     cli_warnmsg("Sample[%s]:\n", hash);
     cli_warnmsg("    * Size: %zu\n", sample->size);
     cli_warnmsg("    * Hits: %u\n", sample->hits);
 
     free(hash);
 
     names = get_sample_names(sample->virus_name);
     if ((names))
         cli_warnmsg("    * Names: %s\n", names);
 
     if (sample->sections && sample->sections->nsections) {
288057e9
         for (i = 0; i < sample->sections->nsections; i++) {
3c29ca0b
             hash = get_hash(sample->sections->sections[i].md5);
             if ((hash)) {
                 cli_warnmsg("    * Section[%zu] (%zu): %s\n", i, sample->sections->sections[i].len, hash);
                 free(hash);
             }
         }
     }
 
     if ((names))
         free(names);
 }
 #endif
 
 void clamav_stats_add_sample(const char *virname, const unsigned char *md5, size_t size, stats_section_t *sections, void *cbdata)
f2571e34
 {
     cli_intel_t *intel;
     cli_flagged_sample_t *sample;
     size_t i;
     char **p;
288057e9
     int err, submit = 0;
f2571e34
 
     if (!(cbdata))
         return;
 
     intel = (cli_intel_t *)cbdata;
4473a0a9
     if (!(intel->engine))
         return;
f2571e34
 
d4f90ad4
     if (intel->engine->dconf->stats & DCONF_STATS_DISABLED)
         return;
 
4473a0a9
     /* First check if we need to submit stats based on memory/number limits */
     if ((intel->engine->cb_stats_get_size))
         submit = (intel->engine->cb_stats_get_size(cbdata) >= intel->maxmem);
     else
         submit = (clamav_stats_get_size(cbdata) >= intel->maxmem);
 
     if (submit == 0) {
         if ((intel->engine->cb_stats_get_num))
             submit = (intel->engine->cb_stats_get_num(cbdata) >= intel->maxsamples);
         else
             submit = (clamav_stats_get_num(cbdata) >= intel->maxsamples);
     }
f2571e34
 
4473a0a9
     if (submit) {
         if ((intel->engine->cb_stats_submit)) {
             intel->engine->cb_stats_submit(intel->engine, cbdata);
         } else {
f2571e34
             if ((intel->engine->cb_stats_flush))
                 intel->engine->cb_stats_flush(intel->engine, intel);
 
             return;
         }
     }
 
 #ifdef CL_THREAD_SAFE
68e60b12
     err = pthread_mutex_lock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_add_sample: locking mutex failed (err: %d): %s\n", err, strerror(err));
f2571e34
         return;
     }
 #endif
 
3c29ca0b
     sample = find_sample(intel, virname, md5, size, sections);
f2571e34
     if (!(sample)) {
         if (!(intel->samples)) {
             sample = intel->samples = calloc(1, sizeof(cli_flagged_sample_t));
             if (!(sample))
                 goto end;
         } else {
             sample = calloc(1, sizeof(cli_flagged_sample_t));
             if (!(sample))
                 goto end;
 
288057e9
             sample->next         = intel->samples;
f2571e34
             intel->samples->prev = sample;
288057e9
             intel->samples       = sample;
f2571e34
         }
 
         if ((sample->virus_name)) {
288057e9
             for (i = 0; sample->virus_name[i] != NULL; i++)
f2571e34
                 ;
3fc1fc69
             p = realloc(sample->virus_name, sizeof(char **) * (i + 1));
             if (!(p)) {
                 free(sample->virus_name);
                 free(sample);
                 if (sample == intel->samples)
                     intel->samples = NULL;
 
                 goto end;
             }
 
             sample->virus_name = p;
f2571e34
         } else {
288057e9
             i                  = 0;
f2571e34
             sample->virus_name = calloc(1, sizeof(char **));
             if (!(sample->virus_name)) {
                 free(sample);
                 if (sample == intel->samples)
                     intel->samples = NULL;
 
                 goto end;
             }
         }
 
         sample->virus_name[i] = strdup((virname != NULL) ? virname : "[unknown]");
         if (!(sample->virus_name[i])) {
             free(sample->virus_name);
0c58ddd2
             free(sample);
f2571e34
             if (sample == intel->samples)
                 intel->samples = NULL;
 
             goto end;
         }
 
288057e9
         p = realloc(sample->virus_name, sizeof(char **) * (i + 2));
f2571e34
         if (!(p)) {
             free(sample->virus_name);
             free(sample);
             if (sample == intel->samples)
                 intel->samples = NULL;
 
             goto end;
         }
 
288057e9
         sample->virus_name        = p;
         sample->virus_name[i + 1] = NULL;
f2571e34
 
         memcpy(sample->md5, md5, sizeof(sample->md5));
b3c40dc6
         sample->size = (uint32_t)size;
f2571e34
         intel->nsamples++;
3c29ca0b
 
         if (sections && sections->nsections && !(sample->sections)) {
             /* Copy the section data that has already been allocated. We don't care if calloc fails; just skip copying if it does. */
             sample->sections = calloc(1, sizeof(stats_section_t));
e6bcbd5a
             if ((sample->sections)) {
                 sample->sections->sections = calloc(sections->nsections, sizeof(struct cli_section_hash));
                 if ((sample->sections->sections)) {
                     memcpy(sample->sections->sections, sections->sections, sections->nsections * sizeof(struct cli_section_hash));
                     sample->sections->nsections = sections->nsections;
                 } else {
                     free(sample->sections);
                     sample->sections = NULL;
                 }
             }
3c29ca0b
         }
f2571e34
     }
 
     sample->hits++;
 
 end:
 #ifdef CL_THREAD_SAFE
68e60b12
     err = pthread_mutex_unlock(&(intel->mutex));
     if (err) {
7cd9337a
         cli_warnmsg("clamav_stats_add_sample: unlocking mutex failed (err: %d): %s\n", err, strerror(err));
f2571e34
     }
 #endif
f092d0a1
     return;
f2571e34
 }
 
 void clamav_stats_flush(struct cl_engine *engine, void *cbdata)
 {
     cli_intel_t *intel;
     cli_flagged_sample_t *sample, *next;
68e60b12
     int err;
 
     if (!(cbdata) || !(engine))
         return;
 
     intel = (cli_intel_t *)cbdata;
f2571e34
 
 #ifdef CL_THREAD_SAFE
68e60b12
     err = pthread_mutex_lock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_flush: locking mutex failed (err: %d): %s\n", err, strerror(err));
f2571e34
         return;
     }
 #endif
 
288057e9
     for (sample = intel->samples; sample != NULL; sample = next) {
f2571e34
         next = sample->next;
 
         free_sample(sample);
     }
 
288057e9
     intel->samples  = NULL;
f2571e34
     intel->nsamples = 0;
e6bcbd5a
     if (intel->hostid) {
         free(intel->hostid);
         intel->hostid = NULL;
     }
f2571e34
 
 #ifdef CL_THREAD_SAFE
68e60b12
     err = pthread_mutex_unlock(&(intel->mutex));
     if (err)
         cli_warnmsg("clamav_stats_flush: unlocking mutex failed (err: %d): %s\n", err, strerror(err));
f2571e34
 #endif
 }
 
 void free_sample(cli_flagged_sample_t *sample)
 {
     size_t i;
 
     if ((sample->virus_name)) {
288057e9
         for (i = 0; sample->virus_name[i] != NULL; i++)
f2571e34
             free(sample->virus_name[i]);
 
         free(sample->virus_name);
     }
 
186712e0
     if ((sample->sections) && (sample->sections->nsections)) {
         free(sample->sections->sections);
         free(sample->sections);
     }
 
f2571e34
     free(sample);
 }
 
 void clamav_stats_submit(struct cl_engine *engine, void *cbdata)
 {
     char *json;
2d6361a9
     cli_intel_t *intel, myintel;
     cli_flagged_sample_t *sample, *next;
68e60b12
     int err;
f2571e34
 
     intel = (cli_intel_t *)cbdata;
2d6361a9
     if (!(intel) || !(engine))
         return;
f2571e34
 
d4f90ad4
     if (engine->dconf->stats & DCONF_STATS_DISABLED)
         return;
 
b7485a22
     if (!(engine->cb_stats_get_hostid)) {
         /* Submitting stats is disabled due to HostID being turned off */
         if ((engine->cb_stats_flush))
             engine->cb_stats_flush(engine, cbdata);
 
         return;
     }
 
690a27b8
     cli_dbgmsg("stats - start\n");
 
f2571e34
 #ifdef CL_THREAD_SAFE
68e60b12
     err = pthread_mutex_lock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_submit: locking mutex failed (err: %d): %s\n", err, strerror(err));
f2571e34
 
         if ((intel->engine) && (intel->engine->cb_stats_flush))
             intel->engine->cb_stats_flush(intel->engine, cbdata);
 
         return;
     }
 #endif
 
2d6361a9
     /* Empty out the cached intelligence data so that other threads don't sit waiting to add data to the cache */
     memcpy(&myintel, intel, sizeof(cli_intel_t));
288057e9
     intel->samples  = NULL;
2d6361a9
     intel->nsamples = 0;
 
     json = export_stats_to_json(engine, &myintel);
f2571e34
 
 #ifdef CL_THREAD_SAFE
68e60b12
     err = pthread_mutex_unlock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_submit: unlocking mutex failed (err: %d): %s\n", err, strerror(err));
f2571e34
     }
 #endif
 
288057e9
     for (sample = myintel.samples; sample != NULL; sample = next) {
3c29ca0b
 #if DEBUG_STATS
         print_sample(sample);
 #endif
2d6361a9
         next = sample->next;
f2571e34
 
2d6361a9
         free_sample(sample);
     }
f2571e34
 
2d6361a9
     if (json) {
4e1236c8
         submit_post(STATS_HOST, STATS_PORT, "PUT", "/clamav/1/submit/stats", json, myintel.timeout);
2d6361a9
         free(json);
     }
e6bcbd5a
 
     if (myintel.hostid && !(intel->hostid)) {
         free(myintel.hostid);
         myintel.hostid = NULL;
     }
690a27b8
 
     cli_dbgmsg("stats - end\n");
f2571e34
 }
 
3c29ca0b
 void clamav_stats_remove_sample(const char *virname, const unsigned char *md5, size_t size, void *cbdata)
4473a0a9
 {
     cli_intel_t *intel;
     cli_flagged_sample_t *sample;
     int err;
 
     intel = (cli_intel_t *)cbdata;
     if (!(intel))
         return;
 
 #ifdef CL_THREAD_SAFE
     err = pthread_mutex_lock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_remove_sample: locking mutex failed (err: %d): %s\n", err, strerror(err));
         return;
     }
 #endif
 
3c29ca0b
     while ((sample = find_sample(intel, virname, md5, size, NULL))) {
         if (sample->prev)
             sample->prev->next = sample->next;
         if (sample->next)
             sample->next->prev = sample->prev;
         if (sample == intel->samples)
             intel->samples = sample->next;
4473a0a9
 
3c29ca0b
         free_sample(sample);
         intel->nsamples--;
     }
4473a0a9
 
 #ifdef CL_THREAD_SAFE
     err = pthread_mutex_unlock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_remove_sample: unlocking mutex failed (err: %d): %s\n", err, strerror(err));
     }
 #endif
 }
 
3c29ca0b
 void clamav_stats_decrement_count(const char *virname, const unsigned char *md5, size_t size, void *cbdata)
4473a0a9
 {
     cli_intel_t *intel;
     cli_flagged_sample_t *sample;
     int err;
 
     intel = (cli_intel_t *)cbdata;
     if (!(intel))
         return;
 
 #ifdef CL_THREAD_SAFE
     err = pthread_mutex_lock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_decrement_count: locking mutex failed (err: %d): %s\n", err, strerror(err));
         return;
     }
 #endif
 
3c29ca0b
     sample = find_sample(intel, virname, md5, size, NULL);
4473a0a9
     if (!(sample))
e198df77
         goto clamav_stats_decrement_end;
4473a0a9
 
     if (sample->hits == 1) {
         if ((intel->engine->cb_stats_remove_sample))
3c29ca0b
             intel->engine->cb_stats_remove_sample(virname, md5, size, intel);
4473a0a9
         else
3c29ca0b
             clamav_stats_remove_sample(virname, md5, size, intel);
4473a0a9
 
e198df77
         goto clamav_stats_decrement_end;
4473a0a9
     }
 
     sample->hits--;
 
288057e9
 clamav_stats_decrement_end:
4473a0a9
 #ifdef CL_THREAD_SAFE
     err = pthread_mutex_unlock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_decrement_count: unlocking mutex failed (err: %d): %s\n", err, strerror(err));
     }
 #endif
f092d0a1
     return;
4473a0a9
 }
 
 size_t clamav_stats_get_num(void *cbdata)
 {
     cli_intel_t *intel;
 
     intel = (cli_intel_t *)cbdata;
 
     if (!(intel))
         return 0;
 
     return intel->nsamples;
 }
 
 size_t clamav_stats_get_size(void *cbdata)
 {
     cli_intel_t *intel;
     cli_flagged_sample_t *sample;
     size_t sz, i;
     int err;
 
     intel = (cli_intel_t *)cbdata;
     if (!(intel))
         return 0;
 
c8bf9b6c
     sz = sizeof(cli_intel_t);
 
4473a0a9
 #ifdef CL_THREAD_SAFE
     err = pthread_mutex_lock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_get_size: locking mutex failed (err: %d): %s\n", err, strerror(err));
c8bf9b6c
         return sz;
4473a0a9
     }
 #endif
 
     for (sample = intel->samples; sample != NULL; sample = sample->next) {
         sz += sizeof(cli_flagged_sample_t);
         if ((sample->virus_name)) {
288057e9
             for (i = 0; sample->virus_name[i] != NULL; i++)
4473a0a9
                 sz += strlen(sample->virus_name[i]);
             sz += sizeof(char **) * i;
         }
     }
 
 #ifdef CL_THREAD_SAFE
     err = pthread_mutex_unlock(&(intel->mutex));
     if (err) {
         cli_warnmsg("clamav_stats_get_size: unlocking mutex failed (err: %d): %s\n", err, strerror(err));
     }
 #endif
 
     return sz;
 }
 
c8bf9b6c
 #if defined(_WIN32)
 char *clamav_stats_get_hostid(void *cbdata)
 {
cfeb7723
     HW_PROFILE_INFO HwProfInfo;
53242b97
 
cfeb7723
     if (!GetCurrentHwProfile(&HwProfInfo))
         return strdup(STATS_ANON_UUID);
53242b97
 
     return strdup(HwProfInfo.szHwProfileGuid);
c8bf9b6c
 }
70ab826f
 #elif defined(C_SOLARIS)
 char *clamav_stats_get_hostid(void *cbdata)
 {
     struct utsname utsnm;
     int ret;
 
     ret = uname(&utsnm);
     if (ret != -1)
         return strdup(utsnm.nodename);
 
     return strdup(STATS_ANON_UUID);
 }
c8bf9b6c
 #else
 char *clamav_stats_get_hostid(void *cbdata)
 {
     char *sysctls[] = {
         "kern.hostuuid",
288057e9
         NULL};
c8bf9b6c
     size_t bufsz, i;
     char *buf;
 
cd94be7a
     UNUSEDPARAM(cbdata);
 
02c1afff
 #if HAVE_SYSCTLBYNAME
cfeb7723
     /*
      * FreeBSD provides a handy-dandy sysctl for grabbing the system's HostID. In a jail that
      * hasn't run the hostid rc.d script, the hostid defaults to all zeros.
      */
288057e9
     for (i = 0; sysctls[i] != NULL; i++) {
02c1afff
         if (sysctlbyname(sysctls[i], NULL, &bufsz, NULL, 0))
             continue;
c8bf9b6c
 
02c1afff
         break; /* Got one */
     }
 
     if (sysctls[i] != NULL) {
288057e9
         buf = calloc(1, bufsz + 1);
02c1afff
         if (sysctlbyname(sysctls[i], buf, &bufsz, NULL, 0))
             return strdup(STATS_ANON_UUID); /* Not sure why this would happen, but we'll just default to the anon uuid on error */
 
         return buf;
c8bf9b6c
     }
9e47301b
 
     return strdup(STATS_ANON_UUID);
02c1afff
 #else
9e0f01d9
     buf = internal_get_host_id();
     if (!(buf))
02c1afff
         return strdup(STATS_ANON_UUID);
9e0f01d9
     return buf;
02c1afff
 #endif
c8bf9b6c
 }
 #endif
 
3c29ca0b
 static cli_flagged_sample_t *find_sample(cli_intel_t *intel, const char *virname, const unsigned char *md5, size_t size, stats_section_t *sections)
f2571e34
 {
     cli_flagged_sample_t *sample;
     size_t i;
 
     for (sample = intel->samples; sample != NULL; sample = sample->next) {
3c29ca0b
         int foundSections = 0;
f2571e34
 
         if (sample->size != size)
             continue;
 
         if (memcmp(sample->md5, md5, sizeof(sample->md5)))
             continue;
 
         if (!(virname))
             return sample;
 
3c29ca0b
         if ((sections) && (sample->sections)) {
             if (sections->nsections == sample->sections->nsections) {
288057e9
                 for (i = 0; i < sections->nsections; i++)
3c29ca0b
                     if (sections->sections[i].len == sample->sections->sections[i].len)
                         if (memcmp(sections->sections[i].md5, sample->sections->sections[i].md5, sizeof(stats_section_t)))
                             break;
 
                 if (i == sections->nsections)
                     foundSections = 1;
             }
         } else {
             foundSections = 1;
         }
 
         if (foundSections)
288057e9
             for (i = 0; sample->virus_name[i] != NULL; i++)
3c29ca0b
                 if (!strcmp(sample->virus_name[i], virname))
                     return sample;
f2571e34
     }
 
     return NULL;
 }
7c92a662
 
 void cl_engine_set_clcb_stats_submit(struct cl_engine *engine, clcb_stats_submit callback)
 {
     engine->cb_stats_submit = callback;
 }
 
e6786fef
 void cl_engine_set_stats_set_cbdata(struct cl_engine *engine, void *cbdata)
7c92a662
 {
     engine->stats_data = cbdata;
 }
 
 void cl_engine_set_clcb_stats_add_sample(struct cl_engine *engine, clcb_stats_add_sample callback)
 {
     engine->cb_stats_add_sample = callback;
 }
 
 void cl_engine_set_clcb_stats_remove_sample(struct cl_engine *engine, clcb_stats_remove_sample callback)
 {
     engine->cb_stats_remove_sample = callback;
 }
 
 void cl_engine_set_clcb_stats_decrement_count(struct cl_engine *engine, clcb_stats_decrement_count callback)
 {
     engine->cb_stats_decrement_count = callback;
 }
 
 void cl_engine_set_clcb_stats_flush(struct cl_engine *engine, clcb_stats_flush callback)
 {
     engine->cb_stats_flush = callback;
 }
 
 void cl_engine_set_clcb_stats_get_num(struct cl_engine *engine, clcb_stats_get_num callback)
 {
     engine->cb_stats_get_num = callback;
 }
 
 void cl_engine_set_clcb_stats_get_size(struct cl_engine *engine, clcb_stats_get_size callback)
 {
     engine->cb_stats_get_size = callback;
 }
 
 void cl_engine_set_clcb_stats_get_hostid(struct cl_engine *engine, clcb_stats_get_hostid callback)
 {
     engine->cb_stats_get_hostid = callback;
 }
38702161
 
 void cl_engine_stats_enable(struct cl_engine *engine)
 {
     engine->cb_stats_add_sample = clamav_stats_add_sample;
288057e9
     engine->cb_stats_submit     = clamav_stats_submit;
38702161
 }