Browse code

FreshClam: Error handling fixes per code review

Micah Snyder authored on 2021/03/26 05:31:46
Showing 7 changed files
... ...
@@ -177,11 +177,14 @@ DatabaseMirror database.clamav.net
177 177
 # Default: 0
178 178
 #ReceiveTimeout 1800
179 179
 
180
-# With this option enabled, freshclam will attempt to load new
181
-# databases into memory to make sure they are properly handled
182
-# by libclamav before replacing the old ones.
180
+# With this option enabled, freshclam will attempt to load new databases into
181
+# memory to make sure they are properly handled by libclamav before replacing
182
+# the old ones.
183
+# Tip: This feature uses a lot of RAM. If your system has limited RAM and you
184
+# are actively running ClamD or ClamScan during the update, then you may need
185
+# to set `TestDatabases no`.
183 186
 # Default: yes
184
-#TestDatabases yes
187
+#TestDatabases no
185 188
 
186 189
 # This option enables downloading of bytecode.cvd, which includes additional
187 190
 # detection mechanisms and improvements to the ClamAV engine.
... ...
@@ -1943,7 +1943,7 @@ int main(int argc, char **argv)
1943 1943
 
1944 1944
                 if (FC_EFORBIDDEN == ret) {
1945 1945
                     /* We're being actively blocked, which is a fatal error. Exit. */
1946
-                    logg("^Freshclam was forbidden from downloading a database.\n");
1946
+                    logg("^FreshClam was forbidden from downloading a database.\n");
1947 1947
                     logg("^This is fatal. Retrying later won't help. Exiting now.\n");
1948 1948
                     status = ret;
1949 1949
                     goto done;
... ...
@@ -114,9 +114,9 @@ const char *fc_strerror(fc_error_t fcerror)
114 114
         case FC_EARG:
115 115
             return "Invalid argument(s)";
116 116
         case FC_EFORBIDDEN:
117
-            return "Forbidden, Blocked by CDN";
117
+            return "Forbidden; Blocked by CDN";
118 118
         case FC_ERETRYLATER:
119
-            return "Too-many-requests, Retry later";
119
+            return "Too many requests; Retry later";
120 120
         default:
121 121
             return "Unknown libfreshclam error code!";
122 122
     }
... ...
@@ -308,6 +308,10 @@ void fc_cleanup(void)
308 308
         free(g_tempDirectory);
309 309
         g_tempDirectory = NULL;
310 310
     }
311
+    if (NULL != g_mirrorsDat) {
312
+        free(g_mirrorsDat);
313
+        g_mirrorsDat = NULL;
314
+    }
311 315
 }
312 316
 
313 317
 fc_error_t fc_prune_database_directory(char **databaseList, uint32_t nDatabases)
... ...
@@ -663,14 +667,14 @@ fc_error_t fc_update_database(
663 663
                 case FC_EFORBIDDEN: {
664 664
                     logg("^FreshClam received error code 403 from the ClamAV Content Delivery Network (CDN).\n");
665 665
                     logg("This could mean several things:\n");
666
-                    logg(" 1. You are running an out of date version of ClamAV / FreshClam.\n");
666
+                    logg(" 1. You are running an out-of-date version of ClamAV / FreshClam.\n");
667 667
                     logg("    Ensure you are the most updated version by visiting https://www.clamav.net/downloads\n");
668 668
                     logg(" 2. Your network is explicitly denied by the FreshClam CDN.\n");
669 669
                     logg("    In order to rectify this please check that you are:\n");
670
-                    logg("   a. Running an up to date version of FreshClam\n");
670
+                    logg("   a. Running an up-to-date version of FreshClam\n");
671 671
                     logg("   b. Running FreshClam no more than once an hour\n");
672 672
                     logg("   c. If you have checked (a) and (b), please open a ticket at\n");
673
-                    logg("      https://bugzilla.clamav.net under the “Mirrors” component\n");
673
+                    logg("      https://bugzilla.clamav.net under the 'Mirrors' component\n");
674 674
                     logg("      and we will investigate why your network is blocked.\n");
675 675
                     status = ret;
676 676
                     goto done;
... ...
@@ -680,11 +684,16 @@ fc_error_t fc_update_database(
680 680
                     char retry_after_string[26];
681 681
                     struct tm *tm_info;
682 682
                     tm_info = localtime(&g_mirrorsDat->retry_after);
683
+                    if (NULL == tm_info) {
684
+                        logg("!Failed to query the local time for the retry-after date!\n");
685
+                        status = FC_ERROR;
686
+                        goto done;
687
+                    }
683 688
                     strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
684 689
                     logg("^FreshClam received error code 429 from the ClamAV Content Delivery Network (CDN).\n");
685 690
                     logg("This means that you have been rate limited by the CDN.\n");
686 691
                     logg(" 1. Run FreshClam no more than once an hour to check for updates.\n");
687
-                    logg("    Freshclam should check DNS first to see if an update is needed.\n");
692
+                    logg("    FreshClam should check DNS first to see if an update is needed.\n");
688 693
                     logg(" 2. If you have more than 10 hosts on your network attempting to download,\n");
689 694
                     logg("    it is recommended that you set up a private mirror on your network using\n");
690 695
                     logg("    cvdupdate (https://pypi.org/project/cvdupdate/) to save bandwidth on the\n");
... ...
@@ -747,11 +756,16 @@ fc_error_t fc_update_databases(
747 747
             char retry_after_string[26];
748 748
             struct tm *tm_info;
749 749
             tm_info = localtime(&g_mirrorsDat->retry_after);
750
+            if (NULL == tm_info) {
751
+                logg("!Failed to query the local time for the retry-after date!\n");
752
+                status = FC_ERROR;
753
+                goto done;
754
+            }
750 755
             strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
751 756
             logg("^FreshClam previously received error code 429 from the ClamAV Content Delivery Network (CDN).\n");
752 757
             logg("This means that you have been rate limited by the CDN.\n");
753 758
             logg(" 1. Run FreshClam no more than once an hour to check for updates.\n");
754
-            logg("    Freshclam should check DNS first to see if an update is needed.\n");
759
+            logg("    FreshClam should check DNS first to see if an update is needed.\n");
755 760
             logg(" 2. If you have more than 10 hosts on your network attempting to download,\n");
756 761
             logg("    it is recommended that you set up a private mirror on your network using\n");
757 762
             logg("    cvdupdate (https://pypi.org/project/cvdupdate/) to save bandwidth on the\n");
... ...
@@ -859,14 +873,14 @@ fc_error_t fc_download_url_database(
859 859
             case FC_EFORBIDDEN: {
860 860
                 logg("^FreshClam received error code 403 from the ClamAV Content Delivery Network (CDN).\n");
861 861
                 logg("This could mean several things:\n");
862
-                logg(" 1. You are running an out of date version of ClamAV / FreshClam.\n");
862
+                logg(" 1. You are running an out-of-date version of ClamAV / FreshClam.\n");
863 863
                 logg("    Ensure you are the most updated version by visiting https://www.clamav.net/downloads\n");
864 864
                 logg(" 2. Your network is explicitly denied by the FreshClam CDN.\n");
865 865
                 logg("    In order to rectify this please check that you are:\n");
866
-                logg("   a. Running an up to date version of FreshClam\n");
866
+                logg("   a. Running an up-to-date version of FreshClam\n");
867 867
                 logg("   b. Running FreshClam no more than once an hour\n");
868 868
                 logg("   c. If you have checked (a) and (b), please open a ticket at\n");
869
-                logg("      https://bugzilla.clamav.net under the “Mirrors” component\n");
869
+                logg("      https://bugzilla.clamav.net under the 'Mirrors' component\n");
870 870
                 logg("      and we will investigate why your network is blocked.\n");
871 871
                 status = ret;
872 872
                 goto done;
... ...
@@ -876,11 +890,16 @@ fc_error_t fc_download_url_database(
876 876
                 char retry_after_string[26];
877 877
                 struct tm *tm_info;
878 878
                 tm_info = localtime(&g_mirrorsDat->retry_after);
879
+                if (NULL == tm_info) {
880
+                    logg("!Failed to query the local time for the retry-after date!\n");
881
+                    status = FC_ERROR;
882
+                    goto done;
883
+                }
879 884
                 strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
880 885
                 logg("^FreshClam received error code 429 from the ClamAV Content Delivery Network (CDN).\n");
881 886
                 logg("This means that you have been rate limited by the CDN.\n");
882 887
                 logg(" 1. Run FreshClam no more than once an hour to check for updates.\n");
883
-                logg("    Freshclam should check DNS first to see if an update is needed.\n");
888
+                logg("    FreshClam should check DNS first to see if an update is needed.\n");
884 889
                 logg(" 2. If you have more than 10 hosts on your network attempting to download,\n");
885 890
                 logg("    it is recommended that you set up a private mirror on your network using\n");
886 891
                 logg("    cvdupdate (https://pypi.org/project/cvdupdate/) to save bandwidth on the\n");
... ...
@@ -81,7 +81,8 @@ typedef enum fc_error_tag {
81 81
     FC_EMEM,
82 82
     FC_EARG,
83 83
     FC_EFORBIDDEN,
84
-    FC_ERETRYLATER
84
+    FC_ERETRYLATER,
85
+    FC_ERROR
85 86
 } fc_error_t;
86 87
 
87 88
 /**
... ...
@@ -122,14 +122,13 @@ mirrors_dat_v1_t *g_mirrorsDat = NULL;
122 122
  *
123 123
  * Uses the openssl RAND_bytes function to generate a Version 4 UUID.
124 124
  *
125
- * Copyright 2021 Karthik Velakur
125
+ * Copyright 2021 Karthik Velakur with some modifications by the ClamAV team.
126 126
  * License: MIT
127 127
  * From: https://gist.github.com/kvelakur/9069c9896577c3040030
128 128
  *
129 129
  * @param buffer A buffer that is SIZEOF_UUID_V4
130
- * @retval 1 on success, 0 otherwise.
131 130
  */
132
-static int uuid_v4_gen(char *buffer)
131
+static void uuid_v4_gen(char *buffer)
133 132
 {
134 133
     union {
135 134
         struct
... ...
@@ -144,7 +143,11 @@ static int uuid_v4_gen(char *buffer)
144 144
         uint8_t __rnd[16];
145 145
     } uuid;
146 146
 
147
-    int rc = RAND_bytes(uuid.__rnd, sizeof(uuid));
147
+    if (0 >= RAND_bytes(uuid.__rnd, sizeof(uuid.__rnd))) {
148
+        /* Failed to generate random bytes for new UUID */
149
+        memset(uuid.__rnd, 0, sizeof(uuid.__rnd));
150
+        uuid.time_low = (uint32_t)time(NULL);
151
+    }
148 152
 
149 153
     // Refer Section 4.2 of RFC-4122
150 154
     // https://tools.ietf.org/html/rfc4122#section-4.2
... ...
@@ -158,7 +161,7 @@ static int uuid_v4_gen(char *buffer)
158 158
              uuid.node[3], uuid.node[4], uuid.node[5]);
159 159
     buffer[SIZEOF_UUID_V4 - 1] = 0;
160 160
 
161
-    return rc;
161
+    return;
162 162
 }
163 163
 
164 164
 fc_error_t load_mirrors_dat(void)
... ...
@@ -178,7 +181,7 @@ fc_error_t load_mirrors_dat(void)
178 178
     }
179 179
     logg("*Current working dir is %s\n", g_databaseDirectory);
180 180
 
181
-    if (-1 == (handle = safe_open("mirrors.dat", O_RDONLY | O_BINARY))) {
181
+    if (-1 == (handle = open("mirrors.dat", O_RDONLY | O_BINARY))) {
182 182
         char currdir[PATH_MAX];
183 183
 
184 184
         if (getcwd(currdir, sizeof(currdir)))
... ...
@@ -194,7 +197,7 @@ fc_error_t load_mirrors_dat(void)
194 194
     if (strlen(MIRRORS_DAT_MAGIC) != (bread = read(handle, &magic, strlen(MIRRORS_DAT_MAGIC)))) {
195 195
         char error_message[260];
196 196
         cli_strerror(errno, error_message, 260);
197
-        logg("!Can't read version from mirrors.dat. Bytes read: %zi, error: %s\n", bread, error_message);
197
+        logg("!Can't read magic from mirrors.dat. Bytes read: %zi, error: %s\n", bread, error_message);
198 198
         goto done;
199 199
     }
200 200
     if (0 != strncmp(magic, MIRRORS_DAT_MAGIC, strlen(MIRRORS_DAT_MAGIC))) {
... ...
@@ -236,18 +239,23 @@ fc_error_t load_mirrors_dat(void)
236 236
                 goto done;
237 237
             }
238 238
 
239
-            /* Got it.*/
239
+            /* Got it. */
240 240
             close(handle);
241
+            handle = -1;
241 242
 
242 243
             /* This is the latest version.
243 244
                If we change the format in the future, we may wish to create a new
244 245
                mirrors dat struct, import the relevant bits to the new format,
245 246
                and then save (overwrite) mirrors.dat with the new data. */
247
+            if (NULL != g_mirrorsDat) {
248
+                free(g_mirrorsDat);
249
+            }
246 250
             g_mirrorsDat = mdat;
251
+            mdat         = NULL;
247 252
             break;
248 253
         }
249 254
         default: {
250
-            logg("*mirrors.dat version is different than expected: %u != %u\n", 1, g_mirrorsDat->version);
255
+            logg("*mirrors.dat version is different than expected: %u != %u\n", 1, version);
251 256
             goto done;
252 257
         }
253 258
     }
... ...
@@ -258,6 +266,10 @@ fc_error_t load_mirrors_dat(void)
258 258
     if (g_mirrorsDat->retry_after > 0) {
259 259
         char retry_after_string[26];
260 260
         struct tm *tm_info = localtime(&g_mirrorsDat->retry_after);
261
+        if (NULL == tm_info) {
262
+            logg("!Failed to query the local time for the retry-after date!\n");
263
+            goto done;
264
+        }
261 265
         strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
262 266
         logg("*  retry-after: %s\n", retry_after_string);
263 267
     }
... ...
@@ -269,7 +281,13 @@ done:
269 269
         close(handle);
270 270
     }
271 271
     if (FC_SUCCESS != status) {
272
-        free(mdat);
272
+        if (NULL != mdat) {
273
+            free(mdat);
274
+        }
275
+        if (NULL != g_mirrorsDat) {
276
+            free(g_mirrorsDat);
277
+            g_mirrorsDat = NULL;
278
+        }
273 279
     }
274 280
 
275 281
     return status;
... ...
@@ -280,7 +298,12 @@ fc_error_t save_mirrors_dat(void)
280 280
     fc_error_t status = FC_EINIT;
281 281
     int handle        = -1;
282 282
 
283
-    if (-1 == (handle = safe_open("mirrors.dat", O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644))) {
283
+    if (NULL == g_mirrorsDat) {
284
+        logg("!Attempted to save mirrors data to mirrors.dat before initializing it!\n");
285
+        goto done;
286
+    }
287
+
288
+    if (-1 == (handle = open("mirrors.dat", O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644))) {
284 289
         char currdir[PATH_MAX];
285 290
 
286 291
         if (getcwd(currdir, sizeof(currdir)))
... ...
@@ -314,7 +337,7 @@ fc_error_t new_mirrors_dat(void)
314 314
 {
315 315
     fc_error_t status = FC_EINIT;
316 316
 
317
-    mirrors_dat_v1_t *mdat = malloc(sizeof(mirrors_dat_v1_t));
317
+    mirrors_dat_v1_t *mdat = calloc(1, sizeof(mirrors_dat_v1_t));
318 318
     if (NULL == mdat) {
319 319
         logg("!Failed to allocate memory for mirrors.dat\n");
320 320
         status = FC_EMEM;
... ...
@@ -323,13 +346,11 @@ fc_error_t new_mirrors_dat(void)
323 323
 
324 324
     mdat->version     = 1;
325 325
     mdat->retry_after = 0;
326
-    if (0 == uuid_v4_gen(mdat->uuid)) {
327
-        /* Failed to create UUID */
328
-        status = FC_EINIT;
329
-        logg("!Failed to create random UUID!\n");
330
-        goto done;
331
-    }
326
+    uuid_v4_gen(mdat->uuid);
332 327
 
328
+    if (NULL != g_mirrorsDat) {
329
+        free(g_mirrorsDat);
330
+    }
333 331
     g_mirrorsDat = mdat;
334 332
 
335 333
     logg("*Creating new mirrors.dat\n");
... ...
@@ -372,7 +393,7 @@ static int textrecordfield(const char *database)
372 372
     return 0;
373 373
 }
374 374
 
375
-#if LIBCURL_VERSION_NUM >= 0x073d00
375
+#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 61))
376 376
 /* In libcurl 7.61.0, support was added for extracting the time in plain
377 377
    microseconds. Older libcurl versions are stuck in using 'double' for this
378 378
    information so we complicate this example a bit by supporting either
... ...
@@ -519,7 +540,7 @@ static int xferinfo(void *prog,
519 519
     return 0;
520 520
 }
521 521
 
522
-#if LIBCURL_VERSION_NUM < 0x072000
522
+#if (LIBCURL_VERSION_MAJOR < 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR < 32))
523 523
 /**
524 524
  * Function from curl example code, Copyright (C) 1998 - 2018, Daniel Stenberg, see COPYING.curl for license details
525 525
  * 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(
844 844
         prog.curl        = curl;
845 845
         prog.bComplete   = 0;
846 846
 
847
-#if LIBCURL_VERSION_NUM >= 0x072000
847
+#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 32))
848 848
         /* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
849 849
        compile as they won't have the symbols around.
850 850
 
... ...
@@ -980,15 +1001,22 @@ static fc_error_t remote_cvdhead(
980 980
         }
981 981
         case 429: {
982 982
             status = FC_ERETRYLATER;
983
-            curl_off_t retry_after;
983
+
984
+            curl_off_t retry_after = 0;
985
+
986
+#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 66))
987
+            /* CURLINFO_RETRY_AFTER was introduced in libcurl 7.66 */
984 988
 
985 989
             /* Find out how long we should wait before allowing a retry. */
986 990
             curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
991
+#endif
992
+
987 993
             if (retry_after > 0) {
988 994
                 /* The response gave us a Retry-After date. Use that. */
989 995
                 g_mirrorsDat->retry_after = time(NULL) + (time_t)retry_after;
990 996
             } else {
991
-                /* Try again in no less than 4 hours if the response didn't specify. */
997
+                /* Try again in no less than 4 hours if the response didn't specify
998
+                   or if CURLINFO_RETRY_AFTER is not supported. */
992 999
                 g_mirrorsDat->retry_after = time(NULL) + 60 * 60 * 4;
993 1000
             }
994 1001
             (void)save_mirrors_dat();
... ...
@@ -1128,7 +1156,7 @@ static fc_error_t downloadFile(
1128 1128
         prog.curl        = curl;
1129 1129
         prog.bComplete   = 0;
1130 1130
 
1131
-#if LIBCURL_VERSION_NUM >= 0x072000
1131
+#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 32))
1132 1132
         /* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
1133 1133
        compile as they won't have the symbols around.
1134 1134
 
... ...
@@ -1269,15 +1297,22 @@ static fc_error_t downloadFile(
1269 1269
         }
1270 1270
         case 429: {
1271 1271
             status = FC_ERETRYLATER;
1272
-            curl_off_t retry_after;
1272
+
1273
+            curl_off_t retry_after = 0;
1274
+
1275
+#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 66))
1276
+            /* CURLINFO_RETRY_AFTER was introduced in libcurl 7.66 */
1273 1277
 
1274 1278
             /* Find out how long we should wait before allowing a retry. */
1275 1279
             curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
1280
+#endif
1281
+
1276 1282
             if (retry_after > 0) {
1277 1283
                 /* The response gave us a Retry-After date. Use that. */
1278 1284
                 g_mirrorsDat->retry_after = time(NULL) + (time_t)retry_after;
1279 1285
             } else {
1280
-                /* Try again in no less than 4 hours if the response didn't specify. */
1286
+                /* Try again in no less than 4 hours if the response didn't specify
1287
+                   or if CURLINFO_RETRY_AFTER is not supported. */
1281 1288
                 g_mirrorsDat->retry_after = time(NULL) + 60 * 60 * 4;
1282 1289
             }
1283 1290
             (void)save_mirrors_dat();
... ...
@@ -1358,7 +1393,7 @@ static fc_error_t getcvd(
1358 1358
 
1359 1359
     ret = downloadFile(url, tmpfile, 1, logerr, ifModifiedSince);
1360 1360
     if (ret == FC_UPTODATE) {
1361
-        logg("%s is up to date.\n", cvdfile);
1361
+        logg("%s is up-to-date.\n", cvdfile);
1362 1362
         status = ret;
1363 1363
         goto done;
1364 1364
     } else if (ret > FC_UPTODATE) {
... ...
@@ -2064,11 +2099,11 @@ static fc_error_t check_for_new_database_version(
2064 2064
         }
2065 2065
         case FC_UPTODATE: {
2066 2066
             if (NULL == local_database) {
2067
-                logg("!check_for_new_database_version: server claims we're up to date, but we don't have a local database!\n");
2067
+                logg("!check_for_new_database_version: server claims we're up-to-date, but we don't have a local database!\n");
2068 2068
                 status = FC_EFAILEDGET;
2069 2069
                 goto done;
2070 2070
             }
2071
-            logg("%s database is up to date (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
2071
+            logg("%s database is up-to-date (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
2072 2072
                  localname,
2073 2073
                  local_database->version,
2074 2074
                  local_database->sigs,
... ...
@@ -2208,7 +2243,7 @@ fc_error_t updatedb(
2208 2208
         if (FC_UPTODATE == ret) {
2209 2209
             logg("^Expected newer version of %s database but the server's copy is not newer than our local file (version %d).\n", database, localVersion);
2210 2210
             if (NULL != localFilename) {
2211
-                /* Received a 304 (not modified), must be up to date after all */
2211
+                /* Received a 304 (not modified), must be up-to-date after all */
2212 2212
                 *dbFilename = cli_strdup(localFilename);
2213 2213
             }
2214 2214
             goto up_to_date;
... ...
@@ -2490,7 +2525,7 @@ fc_error_t updatecustomdb(
2490 2490
         remote_dbtime = statbuf.st_mtime;
2491 2491
         dbtime        = (CLAMSTAT(databaseName, &statbuf) != -1) ? statbuf.st_mtime : 0;
2492 2492
         if (dbtime > remote_dbtime) {
2493
-            logg("%s is up to date (version: custom database)\n", databaseName);
2493
+            logg("%s is up-to-date (version: custom database)\n", databaseName);
2494 2494
             goto up_to_date;
2495 2495
         }
2496 2496
 
... ...
@@ -2517,7 +2552,7 @@ fc_error_t updatecustomdb(
2517 2517
 
2518 2518
         ret = downloadFile(url, tmpfile, 1, logerr, dbtime);
2519 2519
         if (ret == FC_UPTODATE) {
2520
-            logg("%s is up to date (version: custom database)\n", databaseName);
2520
+            logg("%s is up-to-date (version: custom database)\n", databaseName);
2521 2521
             goto up_to_date;
2522 2522
         } else if (ret > FC_UPTODATE) {
2523 2523
             logg("%cCan't download %s from %s\n", logerr ? '!' : '^', databaseName, url);
... ...
@@ -515,7 +515,7 @@ const struct clam_option __clam_options[] = {
515 515
 
516 516
     {"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"},
517 517
 
518
-    {"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"},
518
+    {"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"},
519 519
 
520 520
     {"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.", ""},
521 521
 
... ...
@@ -174,11 +174,14 @@ DatabaseMirror database.clamav.net
174 174
 # Default: 0
175 175
 #ReceiveTimeout 1800
176 176
 
177
-# With this option enabled, freshclam will attempt to load new
178
-# databases into memory to make sure they are properly handled
179
-# by libclamav before replacing the old ones.
177
+# With this option enabled, freshclam will attempt to load new databases into
178
+# memory to make sure they are properly handled by libclamav before replacing
179
+# the old ones.
180
+# Tip: This feature uses a lot of RAM. If your system has limited RAM and you
181
+# are actively running ClamD or ClamScan during the update, then you may need
182
+# to set `TestDatabases no`.
180 183
 # Default: yes
181
-#TestDatabases yes
184
+#TestDatabases no
182 185
 
183 186
 # This option enables downloading of bytecode.cvd, which includes additional
184 187
 # detection mechanisms and improvements to the ClamAV engine.