FreshClam: Error handling fixes per code review

pull/155/head
Micah Snyder 4 years ago committed by Micah Snyder (micasnyd)
parent 24d83f43b0
commit 46fd3f631a
  1. 11
      etc/freshclam.conf.sample
  2. 2
      freshclam/freshclam.c
  3. 41
      libfreshclam/libfreshclam.c
  4. 3
      libfreshclam/libfreshclam.h
  5. 99
      libfreshclam/libfreshclam_internal.c
  6. 2
      shared/optparser.c
  7. 1
      unit_tests/freshclam_test.py
  8. 11
      win32/conf_examples/freshclam.conf.sample

@ -177,11 +177,14 @@ DatabaseMirror database.clamav.net
# Default: 0
#ReceiveTimeout 1800
# With this option enabled, freshclam will attempt to load new
# databases into memory to make sure they are properly handled
# by libclamav before replacing the old ones.
# With this option enabled, freshclam will attempt to load new databases into
# memory to make sure they are properly handled by libclamav before replacing
# the old ones.
# Tip: This feature uses a lot of RAM. If your system has limited RAM and you
# are actively running ClamD or ClamScan during the update, then you may need
# to set `TestDatabases no`.
# Default: yes
#TestDatabases yes
#TestDatabases no
# This option enables downloading of bytecode.cvd, which includes additional
# detection mechanisms and improvements to the ClamAV engine.

@ -1943,7 +1943,7 @@ int main(int argc, char **argv)
if (FC_EFORBIDDEN == ret) {
/* We're being actively blocked, which is a fatal error. Exit. */
logg("^Freshclam was forbidden from downloading a database.\n");
logg("^FreshClam was forbidden from downloading a database.\n");
logg("^This is fatal. Retrying later won't help. Exiting now.\n");
status = ret;
goto done;

@ -114,9 +114,9 @@ const char *fc_strerror(fc_error_t fcerror)
case FC_EARG:
return "Invalid argument(s)";
case FC_EFORBIDDEN:
return "Forbidden, Blocked by CDN";
return "Forbidden; Blocked by CDN";
case FC_ERETRYLATER:
return "Too-many-requests, Retry later";
return "Too many requests; Retry later";
default:
return "Unknown libfreshclam error code!";
}
@ -308,6 +308,10 @@ void fc_cleanup(void)
free(g_tempDirectory);
g_tempDirectory = NULL;
}
if (NULL != g_mirrorsDat) {
free(g_mirrorsDat);
g_mirrorsDat = NULL;
}
}
fc_error_t fc_prune_database_directory(char **databaseList, uint32_t nDatabases)
@ -663,14 +667,14 @@ fc_error_t fc_update_database(
case FC_EFORBIDDEN: {
logg("^FreshClam received error code 403 from the ClamAV Content Delivery Network (CDN).\n");
logg("This could mean several things:\n");
logg(" 1. You are running an out of date version of ClamAV / FreshClam.\n");
logg(" 1. You are running an out-of-date version of ClamAV / FreshClam.\n");
logg(" Ensure you are the most updated version by visiting https://www.clamav.net/downloads\n");
logg(" 2. Your network is explicitly denied by the FreshClam CDN.\n");
logg(" In order to rectify this please check that you are:\n");
logg(" a. Running an up to date version of FreshClam\n");
logg(" a. Running an up-to-date version of FreshClam\n");
logg(" b. Running FreshClam no more than once an hour\n");
logg(" c. If you have checked (a) and (b), please open a ticket at\n");
logg(" https://bugzilla.clamav.net under the “Mirrors” component\n");
logg(" https://bugzilla.clamav.net under the 'Mirrors' component\n");
logg(" and we will investigate why your network is blocked.\n");
status = ret;
goto done;
@ -680,11 +684,16 @@ fc_error_t fc_update_database(
char retry_after_string[26];
struct tm *tm_info;
tm_info = localtime(&g_mirrorsDat->retry_after);
if (NULL == tm_info) {
logg("!Failed to query the local time for the retry-after date!\n");
status = FC_ERROR;
goto done;
}
strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
logg("^FreshClam received error code 429 from the ClamAV Content Delivery Network (CDN).\n");
logg("This means that you have been rate limited by the CDN.\n");
logg(" 1. Run FreshClam no more than once an hour to check for updates.\n");
logg(" Freshclam should check DNS first to see if an update is needed.\n");
logg(" FreshClam should check DNS first to see if an update is needed.\n");
logg(" 2. If you have more than 10 hosts on your network attempting to download,\n");
logg(" it is recommended that you set up a private mirror on your network using\n");
logg(" cvdupdate (https://pypi.org/project/cvdupdate/) to save bandwidth on the\n");
@ -747,11 +756,16 @@ fc_error_t fc_update_databases(
char retry_after_string[26];
struct tm *tm_info;
tm_info = localtime(&g_mirrorsDat->retry_after);
if (NULL == tm_info) {
logg("!Failed to query the local time for the retry-after date!\n");
status = FC_ERROR;
goto done;
}
strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
logg("^FreshClam previously received error code 429 from the ClamAV Content Delivery Network (CDN).\n");
logg("This means that you have been rate limited by the CDN.\n");
logg(" 1. Run FreshClam no more than once an hour to check for updates.\n");
logg(" Freshclam should check DNS first to see if an update is needed.\n");
logg(" FreshClam should check DNS first to see if an update is needed.\n");
logg(" 2. If you have more than 10 hosts on your network attempting to download,\n");
logg(" it is recommended that you set up a private mirror on your network using\n");
logg(" cvdupdate (https://pypi.org/project/cvdupdate/) to save bandwidth on the\n");
@ -859,14 +873,14 @@ fc_error_t fc_download_url_database(
case FC_EFORBIDDEN: {
logg("^FreshClam received error code 403 from the ClamAV Content Delivery Network (CDN).\n");
logg("This could mean several things:\n");
logg(" 1. You are running an out of date version of ClamAV / FreshClam.\n");
logg(" 1. You are running an out-of-date version of ClamAV / FreshClam.\n");
logg(" Ensure you are the most updated version by visiting https://www.clamav.net/downloads\n");
logg(" 2. Your network is explicitly denied by the FreshClam CDN.\n");
logg(" In order to rectify this please check that you are:\n");
logg(" a. Running an up to date version of FreshClam\n");
logg(" a. Running an up-to-date version of FreshClam\n");
logg(" b. Running FreshClam no more than once an hour\n");
logg(" c. If you have checked (a) and (b), please open a ticket at\n");
logg(" https://bugzilla.clamav.net under the “Mirrors” component\n");
logg(" https://bugzilla.clamav.net under the 'Mirrors' component\n");
logg(" and we will investigate why your network is blocked.\n");
status = ret;
goto done;
@ -876,11 +890,16 @@ fc_error_t fc_download_url_database(
char retry_after_string[26];
struct tm *tm_info;
tm_info = localtime(&g_mirrorsDat->retry_after);
if (NULL == tm_info) {
logg("!Failed to query the local time for the retry-after date!\n");
status = FC_ERROR;
goto done;
}
strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
logg("^FreshClam received error code 429 from the ClamAV Content Delivery Network (CDN).\n");
logg("This means that you have been rate limited by the CDN.\n");
logg(" 1. Run FreshClam no more than once an hour to check for updates.\n");
logg(" Freshclam should check DNS first to see if an update is needed.\n");
logg(" FreshClam should check DNS first to see if an update is needed.\n");
logg(" 2. If you have more than 10 hosts on your network attempting to download,\n");
logg(" it is recommended that you set up a private mirror on your network using\n");
logg(" cvdupdate (https://pypi.org/project/cvdupdate/) to save bandwidth on the\n");

@ -81,7 +81,8 @@ typedef enum fc_error_tag {
FC_EMEM,
FC_EARG,
FC_EFORBIDDEN,
FC_ERETRYLATER
FC_ERETRYLATER,
FC_ERROR
} fc_error_t;
/**

@ -122,14 +122,13 @@ mirrors_dat_v1_t *g_mirrorsDat = NULL;
*
* Uses the openssl RAND_bytes function to generate a Version 4 UUID.
*
* Copyright 2021 Karthik Velakur
* Copyright 2021 Karthik Velakur with some modifications by the ClamAV team.
* License: MIT
* From: https://gist.github.com/kvelakur/9069c9896577c3040030
*
* @param buffer A buffer that is SIZEOF_UUID_V4
* @retval 1 on success, 0 otherwise.
*/
static int uuid_v4_gen(char *buffer)
static void uuid_v4_gen(char *buffer)
{
union {
struct
@ -144,7 +143,11 @@ static int uuid_v4_gen(char *buffer)
uint8_t __rnd[16];
} uuid;
int rc = RAND_bytes(uuid.__rnd, sizeof(uuid));
if (0 >= RAND_bytes(uuid.__rnd, sizeof(uuid.__rnd))) {
/* Failed to generate random bytes for new UUID */
memset(uuid.__rnd, 0, sizeof(uuid.__rnd));
uuid.time_low = (uint32_t)time(NULL);
}
// Refer Section 4.2 of RFC-4122
// https://tools.ietf.org/html/rfc4122#section-4.2
@ -158,7 +161,7 @@ static int uuid_v4_gen(char *buffer)
uuid.node[3], uuid.node[4], uuid.node[5]);
buffer[SIZEOF_UUID_V4 - 1] = 0;
return rc;
return;
}
fc_error_t load_mirrors_dat(void)
@ -178,7 +181,7 @@ fc_error_t load_mirrors_dat(void)
}
logg("*Current working dir is %s\n", g_databaseDirectory);
if (-1 == (handle = safe_open("mirrors.dat", O_RDONLY | O_BINARY))) {
if (-1 == (handle = open("mirrors.dat", O_RDONLY | O_BINARY))) {
char currdir[PATH_MAX];
if (getcwd(currdir, sizeof(currdir)))
@ -194,7 +197,7 @@ fc_error_t load_mirrors_dat(void)
if (strlen(MIRRORS_DAT_MAGIC) != (bread = read(handle, &magic, strlen(MIRRORS_DAT_MAGIC)))) {
char error_message[260];
cli_strerror(errno, error_message, 260);
logg("!Can't read version from mirrors.dat. Bytes read: %zi, error: %s\n", bread, error_message);
logg("!Can't read magic from mirrors.dat. Bytes read: %zi, error: %s\n", bread, error_message);
goto done;
}
if (0 != strncmp(magic, MIRRORS_DAT_MAGIC, strlen(MIRRORS_DAT_MAGIC))) {
@ -236,18 +239,23 @@ fc_error_t load_mirrors_dat(void)
goto done;
}
/* Got it.*/
/* Got it. */
close(handle);
handle = -1;
/* This is the latest version.
If we change the format in the future, we may wish to create a new
mirrors dat struct, import the relevant bits to the new format,
and then save (overwrite) mirrors.dat with the new data. */
if (NULL != g_mirrorsDat) {
free(g_mirrorsDat);
}
g_mirrorsDat = mdat;
mdat = NULL;
break;
}
default: {
logg("*mirrors.dat version is different than expected: %u != %u\n", 1, g_mirrorsDat->version);
logg("*mirrors.dat version is different than expected: %u != %u\n", 1, version);
goto done;
}
}
@ -258,6 +266,10 @@ fc_error_t load_mirrors_dat(void)
if (g_mirrorsDat->retry_after > 0) {
char retry_after_string[26];
struct tm *tm_info = localtime(&g_mirrorsDat->retry_after);
if (NULL == tm_info) {
logg("!Failed to query the local time for the retry-after date!\n");
goto done;
}
strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
logg("* retry-after: %s\n", retry_after_string);
}
@ -269,7 +281,13 @@ done:
close(handle);
}
if (FC_SUCCESS != status) {
free(mdat);
if (NULL != mdat) {
free(mdat);
}
if (NULL != g_mirrorsDat) {
free(g_mirrorsDat);
g_mirrorsDat = NULL;
}
}
return status;
@ -280,7 +298,12 @@ fc_error_t save_mirrors_dat(void)
fc_error_t status = FC_EINIT;
int handle = -1;
if (-1 == (handle = safe_open("mirrors.dat", O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644))) {
if (NULL == g_mirrorsDat) {
logg("!Attempted to save mirrors data to mirrors.dat before initializing it!\n");
goto done;
}
if (-1 == (handle = open("mirrors.dat", O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644))) {
char currdir[PATH_MAX];
if (getcwd(currdir, sizeof(currdir)))
@ -314,7 +337,7 @@ fc_error_t new_mirrors_dat(void)
{
fc_error_t status = FC_EINIT;
mirrors_dat_v1_t *mdat = malloc(sizeof(mirrors_dat_v1_t));
mirrors_dat_v1_t *mdat = calloc(1, sizeof(mirrors_dat_v1_t));
if (NULL == mdat) {
logg("!Failed to allocate memory for mirrors.dat\n");
status = FC_EMEM;
@ -323,13 +346,11 @@ fc_error_t new_mirrors_dat(void)
mdat->version = 1;
mdat->retry_after = 0;
if (0 == uuid_v4_gen(mdat->uuid)) {
/* Failed to create UUID */
status = FC_EINIT;
logg("!Failed to create random UUID!\n");
goto done;
}
uuid_v4_gen(mdat->uuid);
if (NULL != g_mirrorsDat) {
free(g_mirrorsDat);
}
g_mirrorsDat = mdat;
logg("*Creating new mirrors.dat\n");
@ -372,7 +393,7 @@ static int textrecordfield(const char *database)
return 0;
}
#if LIBCURL_VERSION_NUM >= 0x073d00
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 61))
/* In libcurl 7.61.0, support was added for extracting the time in plain
microseconds. Older libcurl versions are stuck in using 'double' for this
information so we complicate this example a bit by supporting either
@ -519,7 +540,7 @@ static int xferinfo(void *prog,
return 0;
}
#if LIBCURL_VERSION_NUM < 0x072000
#if (LIBCURL_VERSION_MAJOR < 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR < 32))
/**
* Function from curl example code, Copyright (C) 1998 - 2018, Daniel Stenberg, see COPYING.curl for license details
* Older style progress bar callback shim; for libcurl older than 7.32.0 ( CURLOPT_PROGRESSFUNCTION ).
@ -844,7 +865,7 @@ static fc_error_t remote_cvdhead(
prog.curl = curl;
prog.bComplete = 0;
#if LIBCURL_VERSION_NUM >= 0x072000
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 32))
/* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
compile as they won't have the symbols around.
@ -980,15 +1001,22 @@ static fc_error_t remote_cvdhead(
}
case 429: {
status = FC_ERETRYLATER;
curl_off_t retry_after;
curl_off_t retry_after = 0;
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 66))
/* CURLINFO_RETRY_AFTER was introduced in libcurl 7.66 */
/* Find out how long we should wait before allowing a retry. */
curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
#endif
if (retry_after > 0) {
/* The response gave us a Retry-After date. Use that. */
g_mirrorsDat->retry_after = time(NULL) + (time_t)retry_after;
} else {
/* Try again in no less than 4 hours if the response didn't specify. */
/* Try again in no less than 4 hours if the response didn't specify
or if CURLINFO_RETRY_AFTER is not supported. */
g_mirrorsDat->retry_after = time(NULL) + 60 * 60 * 4;
}
(void)save_mirrors_dat();
@ -1128,7 +1156,7 @@ static fc_error_t downloadFile(
prog.curl = curl;
prog.bComplete = 0;
#if LIBCURL_VERSION_NUM >= 0x072000
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 32))
/* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
compile as they won't have the symbols around.
@ -1269,15 +1297,22 @@ static fc_error_t downloadFile(
}
case 429: {
status = FC_ERETRYLATER;
curl_off_t retry_after;
curl_off_t retry_after = 0;
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 66))
/* CURLINFO_RETRY_AFTER was introduced in libcurl 7.66 */
/* Find out how long we should wait before allowing a retry. */
curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
#endif
if (retry_after > 0) {
/* The response gave us a Retry-After date. Use that. */
g_mirrorsDat->retry_after = time(NULL) + (time_t)retry_after;
} else {
/* Try again in no less than 4 hours if the response didn't specify. */
/* Try again in no less than 4 hours if the response didn't specify
or if CURLINFO_RETRY_AFTER is not supported. */
g_mirrorsDat->retry_after = time(NULL) + 60 * 60 * 4;
}
(void)save_mirrors_dat();
@ -1358,7 +1393,7 @@ static fc_error_t getcvd(
ret = downloadFile(url, tmpfile, 1, logerr, ifModifiedSince);
if (ret == FC_UPTODATE) {
logg("%s is up to date.\n", cvdfile);
logg("%s is up-to-date.\n", cvdfile);
status = ret;
goto done;
} else if (ret > FC_UPTODATE) {
@ -2064,11 +2099,11 @@ static fc_error_t check_for_new_database_version(
}
case FC_UPTODATE: {
if (NULL == local_database) {
logg("!check_for_new_database_version: server claims we're up to date, but we don't have a local database!\n");
logg("!check_for_new_database_version: server claims we're up-to-date, but we don't have a local database!\n");
status = FC_EFAILEDGET;
goto done;
}
logg("%s database is up to date (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
logg("%s database is up-to-date (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
localname,
local_database->version,
local_database->sigs,
@ -2208,7 +2243,7 @@ fc_error_t updatedb(
if (FC_UPTODATE == ret) {
logg("^Expected newer version of %s database but the server's copy is not newer than our local file (version %d).\n", database, localVersion);
if (NULL != localFilename) {
/* Received a 304 (not modified), must be up to date after all */
/* Received a 304 (not modified), must be up-to-date after all */
*dbFilename = cli_strdup(localFilename);
}
goto up_to_date;
@ -2490,7 +2525,7 @@ fc_error_t updatecustomdb(
remote_dbtime = statbuf.st_mtime;
dbtime = (CLAMSTAT(databaseName, &statbuf) != -1) ? statbuf.st_mtime : 0;
if (dbtime > remote_dbtime) {
logg("%s is up to date (version: custom database)\n", databaseName);
logg("%s is up-to-date (version: custom database)\n", databaseName);
goto up_to_date;
}
@ -2517,7 +2552,7 @@ fc_error_t updatecustomdb(
ret = downloadFile(url, tmpfile, 1, logerr, dbtime);
if (ret == FC_UPTODATE) {
logg("%s is up to date (version: custom database)\n", databaseName);
logg("%s is up-to-date (version: custom database)\n", databaseName);
goto up_to_date;
} else if (ret > FC_UPTODATE) {
logg("%cCan't download %s from %s\n", logerr ? '!' : '^', databaseName, url);

@ -515,7 +515,7 @@ const struct clam_option __clam_options[] = {
{"ScriptedUpdates", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_FRESHCLAM, "With this option you can control scripted updates. It's highly recommended to keep them enabled.", "yes"},
{"TestDatabases", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_FRESHCLAM, "With this option enabled, freshclam will attempt to load new\ndatabases into memory to make sure they are properly handled\nby libclamav before replacing the old ones.", "yes"},
{"TestDatabases", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_FRESHCLAM, "With this option enabled, freshclam will attempt to load new\ndatabases into memory to make sure they are properly handled\nby libclamav before replacing the old ones. Tip: This feature uses a lot of RAM. If your system has limited RAM and you are actively running ClamD or ClamScan during the update, then you may need to set `TestDatabases no`.", "yes"},
{"CompressLocalDatabase", NULL, 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_FRESHCLAM, "By default freshclam will keep the local databases (.cld) uncompressed to\nmake their handling faster. With this option you can enable the compression.\nThe change will take effect with the next database update.", ""},

@ -53,7 +53,6 @@ class TC(testcase.TestCase):
def tearDown(self):
if TC.mock_mirror != None:
TC.mock_mirror.terminate()
TC.mock_mirror.kill()
TC.mock_mirror = None
if (TC.path_db / 'mirrors.dat').exists():

@ -174,11 +174,14 @@ DatabaseMirror database.clamav.net
# Default: 0
#ReceiveTimeout 1800
# With this option enabled, freshclam will attempt to load new
# databases into memory to make sure they are properly handled
# by libclamav before replacing the old ones.
# With this option enabled, freshclam will attempt to load new databases into
# memory to make sure they are properly handled by libclamav before replacing
# the old ones.
# Tip: This feature uses a lot of RAM. If your system has limited RAM and you
# are actively running ClamD or ClamScan during the update, then you may need
# to set `TestDatabases no`.
# Default: yes
#TestDatabases yes
#TestDatabases no
# This option enables downloading of bytecode.cvd, which includes additional
# detection mechanisms and improvements to the ClamAV engine.

Loading…
Cancel
Save