1 /*
2  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *  Copyright (C) 2007-2013 Sourcefire, Inc.
4  *  Copyright (C) 2002-2007 Tomasz Kojm <tkojm@clamav.net>
5  *
6  *  HTTP/1.1 compliance by Arkadiusz Miskiewicz <misiek@pld.org.pl>
7  *  Proxy support by Nigel Horne <njh@bandsman.co.uk>
8  *  Proxy authorization support by Gernot Tenchio <g.tenchio@telco-tech.de>
9  *		     (uses fmt_base64() from libowfat (http://www.fefe.de))
10  *
11  *  CDIFF code (C) 2006 Sensory Networks, Inc.
12  *
13  *  This program is free software; you can redistribute it and/or modify
14  *  it under the terms of the GNU General Public License as published by
15  *  the Free Software Foundation; either version 2 of the License, or
16  *  (at your option) any later version.
17  *
18  *  This program is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU General Public License for more details.
22  *
23  *  You should have received a copy of the GNU General Public License
24  *  along with this program; if not, write to the Free Software
25  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26  *  MA 02110-1301, USA.
27  */
28 
29 #if HAVE_CONFIG_H
30 #include "clamav-config.h"
31 #endif
32 
33 /* for strptime, it is POSIX, but defining _XOPEN_SOURCE to 600
34  * fails on Solaris because it would require a c99 compiler,
35  * 500 fails completely on Solaris, and FreeBSD, and w/o _XOPEN_SOURCE
36  * strptime is not defined on Linux */
37 #define __EXTENSIONS
38 
39 #include <stdio.h>
40 #include <stdlib.h>
41 #ifdef HAVE_UNISTD_H
42 #include <unistd.h>
43 #endif
44 #include <string.h>
45 #ifdef HAVE_STRINGS_H
46 #include <strings.h>
47 #endif
48 #include <ctype.h>
49 #ifndef _WIN32
50 #include <netinet/in.h>
51 #include <netdb.h>
52 #include <arpa/inet.h>
53 #include <sys/socket.h>
54 #include <sys/time.h>
55 #endif
56 #include <sys/types.h>
57 #include <time.h>
58 #include <fcntl.h>
59 #ifndef _WIN32
60 #include <sys/wait.h>
61 #endif
62 #include <sys/stat.h>
63 #include <dirent.h>
64 #include <errno.h>
65 #include <zlib.h>
66 #include <math.h>
67 
68 #include <curl/curl.h>
69 #include <openssl/rand.h>
70 
71 #include "target.h"
72 
73 // libclamav
74 #include "clamav.h"
75 #include "others.h"
76 #include "str.h"
77 #include "cvd.h"
78 #include "regex_list.h"
79 
80 // shared
81 #include "optparser.h"
82 #include "output.h"
83 #include "cdiff.h"
84 #include "tar.h"
85 #include "clamdcom.h"
86 #include "cert_util.h"
87 
88 #include "libfreshclam.h"
89 #include "libfreshclam_internal.h"
90 #include "dns.h"
91 
92 #define DB_FILENAME_MAX 60
93 #define CVD_HEADER_SIZE 512
94 
95 /*
96  * Globals
97  */
98 /* Callback function pointers */
99 fccb_download_complete g_cb_download_complete = NULL;
100 
101 /* Configuration options */
102 char *g_localIP   = NULL;
103 char *g_userAgent = NULL;
104 
105 char *g_proxyServer   = NULL;
106 uint16_t g_proxyPort  = 0;
107 char *g_proxyUsername = NULL;
108 char *g_proxyPassword = NULL;
109 
110 char *g_tempDirectory     = NULL;
111 char *g_databaseDirectory = NULL;
112 
113 uint32_t g_maxAttempts    = 0;
114 uint32_t g_connectTimeout = 0;
115 uint32_t g_requestTimeout = 0;
116 
117 uint32_t g_bCompressLocalDatabase = 0;
118 
119 freshclam_dat_v1_t *g_freshclamDat = NULL;
120 
121 /** @brief Generate a Version 4 UUID according to RFC-4122
122  *
123  * Uses the openssl RAND_bytes function to generate a Version 4 UUID.
124  *
125  * Copyright 2021 Karthik Velakur with some modifications by the ClamAV team.
126  * License: MIT
127  * From: https://gist.github.com/kvelakur/9069c9896577c3040030
128  *
129  * @param buffer A buffer that is SIZEOF_UUID_V4
130  */
uuid_v4_gen(char * buffer)131 static void uuid_v4_gen(char *buffer)
132 {
133     union {
134         struct
135         {
136             uint32_t time_low;
137             uint16_t time_mid;
138             uint16_t time_hi_and_version;
139             uint8_t clk_seq_hi_res;
140             uint8_t clk_seq_low;
141             uint8_t node[6];
142         };
143         uint8_t __rnd[16];
144     } uuid;
145 
146     if (0 >= RAND_bytes(uuid.__rnd, sizeof(uuid.__rnd))) {
147         /* Failed to generate random bytes for new UUID */
148         memset(uuid.__rnd, 0, sizeof(uuid.__rnd));
149         uuid.time_low = (uint32_t)time(NULL);
150     }
151 
152     // Refer Section 4.2 of RFC-4122
153     // https://tools.ietf.org/html/rfc4122#section-4.2
154     uuid.clk_seq_hi_res      = (uint8_t)((uuid.clk_seq_hi_res & 0x3F) | 0x80);
155     uuid.time_hi_and_version = (uint16_t)((uuid.time_hi_and_version & 0x0FFF) | 0x4000);
156 
157     snprintf(buffer, SIZEOF_UUID_V4, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
158              uuid.time_low, uuid.time_mid, uuid.time_hi_and_version,
159              uuid.clk_seq_hi_res, uuid.clk_seq_low,
160              uuid.node[0], uuid.node[1], uuid.node[2],
161              uuid.node[3], uuid.node[4], uuid.node[5]);
162     buffer[SIZEOF_UUID_V4 - 1] = 0;
163 
164     return;
165 }
166 
load_freshclam_dat(void)167 fc_error_t load_freshclam_dat(void)
168 {
169     fc_error_t status        = FC_EINIT;
170     int handle               = -1;
171     ssize_t bread            = 0;
172     freshclam_dat_v1_t *mdat = NULL;
173     uint32_t version         = 0;
174     char magic[13]           = {0};
175 
176     /* Change directory to database directory */
177     if (chdir(g_databaseDirectory)) {
178         logg("!Can't change dir to %s\n", g_databaseDirectory);
179         status = FC_EDIRECTORY;
180         goto done;
181     }
182     logg("*Current working dir is %s\n", g_databaseDirectory);
183 
184     if (-1 == (handle = open("freshclam.dat", O_RDONLY | O_BINARY))) {
185         char currdir[PATH_MAX];
186 
187         if (getcwd(currdir, sizeof(currdir)))
188             logg("*Can't open freshclam.dat in %s\n", currdir);
189         else
190             logg("*Can't open freshclam.dat in the current directory\n");
191 
192         logg("*It probably doesn't exist yet. That's ok.\n");
193         status = FC_EFILE;
194         goto done;
195     }
196 
197     if (strlen(MIRRORS_DAT_MAGIC) != (bread = read(handle, &magic, strlen(MIRRORS_DAT_MAGIC)))) {
198         char error_message[260];
199         cli_strerror(errno, error_message, 260);
200         logg("!Can't read magic from freshclam.dat. Bytes read: %zi, error: %s\n", bread, error_message);
201         goto done;
202     }
203     if (0 != strncmp(magic, MIRRORS_DAT_MAGIC, strlen(MIRRORS_DAT_MAGIC))) {
204         logg("*Magic bytes for freshclam.dat did not match expectations.\n");
205         goto done;
206     }
207 
208     if (sizeof(uint32_t) != (bread = read(handle, &version, sizeof(uint32_t)))) {
209         char error_message[260];
210         cli_strerror(errno, error_message, 260);
211         logg("!Can't read version from freshclam.dat. Bytes read: %zi, error: %s\n", bread, error_message);
212         goto done;
213     }
214 
215     switch (version) {
216         case 1: {
217             /* Verify that file size is as expected. */
218             off_t file_size = lseek(handle, 0L, SEEK_END);
219 
220             if (strlen(MIRRORS_DAT_MAGIC) + sizeof(freshclam_dat_v1_t) != (size_t)file_size) {
221                 logg("*freshclam.dat is bigger than expected: %zu != %ld\n", sizeof(freshclam_dat_v1_t), file_size);
222                 goto done;
223             }
224 
225             /* Rewind to just after the magic bytes and read data struct */
226             lseek(handle, strlen(MIRRORS_DAT_MAGIC), SEEK_SET);
227 
228             mdat = malloc(sizeof(freshclam_dat_v1_t));
229             if (NULL == mdat) {
230                 logg("!Failed to allocate memory for freshclam.dat\n");
231                 status = FC_EMEM;
232                 goto done;
233             }
234 
235             if (sizeof(freshclam_dat_v1_t) != (bread = read(handle, mdat, sizeof(freshclam_dat_v1_t)))) {
236                 char error_message[260];
237                 cli_strerror(errno, error_message, 260);
238                 logg("!Can't read from freshclam.dat. Bytes read: %zi, error: %s\n", bread, error_message);
239                 goto done;
240             }
241 
242             /* Got it. */
243             close(handle);
244             handle = -1;
245 
246             /* This is the latest version.
247                If we change the format in the future, we may wish to create a new
248                freshclam.dat struct, import the relevant bits to the new format,
249                and then save (overwrite) freshclam.dat with the new data. */
250             if (NULL != g_freshclamDat) {
251                 free(g_freshclamDat);
252             }
253             g_freshclamDat = mdat;
254             mdat           = NULL;
255             break;
256         }
257         default: {
258             logg("*freshclam.dat version is different than expected: %u != %u\n", 1, version);
259             goto done;
260         }
261     }
262 
263     logg("*Loaded freshclam.dat:\n");
264     logg("*  version:    %d\n", g_freshclamDat->version);
265     logg("*  uuid:       %s\n", g_freshclamDat->uuid);
266     if (g_freshclamDat->retry_after > 0) {
267         char retry_after_string[26];
268         struct tm *tm_info = localtime(&g_freshclamDat->retry_after);
269         if (NULL == tm_info) {
270             logg("!Failed to query the local time for the retry-after date!\n");
271             goto done;
272         }
273         strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
274         logg("*  retry-after: %s\n", retry_after_string);
275     }
276 
277     status = FC_SUCCESS;
278 
279 done:
280     if (-1 != handle) {
281         close(handle);
282     }
283     if (FC_SUCCESS != status) {
284         if (NULL != mdat) {
285             free(mdat);
286         }
287         if (NULL != g_freshclamDat) {
288             free(g_freshclamDat);
289             g_freshclamDat = NULL;
290         }
291     }
292 
293     return status;
294 }
295 
save_freshclam_dat(void)296 fc_error_t save_freshclam_dat(void)
297 {
298     fc_error_t status = FC_EINIT;
299     int handle        = -1;
300 
301     if (NULL == g_freshclamDat) {
302         logg("!Attempted to save freshclam.dat before initializing data struct!\n");
303         goto done;
304     }
305 
306     if (-1 == (handle = open("freshclam.dat", O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644))) {
307         char currdir[PATH_MAX];
308 
309         if (getcwd(currdir, sizeof(currdir)))
310             logg("!Can't create freshclam.dat in %s\n", currdir);
311         else
312             logg("!Can't create freshclam.dat in the current directory\n");
313 
314         logg("Hint: The database directory must be writable for UID %d or GID %d\n", getuid(), getgid());
315         status = FC_EDBDIRACCESS;
316         goto done;
317     }
318     if (-1 == write(handle, MIRRORS_DAT_MAGIC, strlen(MIRRORS_DAT_MAGIC))) {
319         logg("!Can't write to freshclam.dat\n");
320     }
321     if (-1 == write(handle, g_freshclamDat, sizeof(freshclam_dat_v1_t))) {
322         logg("!Can't write to freshclam.dat\n");
323     }
324 
325     logg("*Saved freshclam.dat\n");
326 
327     status = FC_SUCCESS;
328 done:
329     if (-1 != handle) {
330         close(handle);
331     }
332 
333     return status;
334 }
335 
new_freshclam_dat(void)336 fc_error_t new_freshclam_dat(void)
337 {
338     fc_error_t status = FC_EINIT;
339 
340     freshclam_dat_v1_t *mdat = calloc(1, sizeof(freshclam_dat_v1_t));
341     if (NULL == mdat) {
342         logg("!Failed to allocate memory for freshclam.dat\n");
343         status = FC_EMEM;
344         goto done;
345     }
346 
347     mdat->version     = 1;
348     mdat->retry_after = 0;
349     uuid_v4_gen(mdat->uuid);
350 
351     if (NULL != g_freshclamDat) {
352         free(g_freshclamDat);
353     }
354     g_freshclamDat = mdat;
355 
356     logg("*Creating new freshclam.dat\n");
357 
358     if (FC_SUCCESS != save_freshclam_dat()) {
359         logg("!Failed to save freshclam.dat!\n");
360         status = FC_EFILE;
361         goto done;
362     }
363 
364     status = FC_SUCCESS;
365 
366 done:
367     if (FC_SUCCESS != status) {
368         if (NULL != mdat) {
369             free(mdat);
370         }
371         g_freshclamDat = NULL;
372     }
373     return status;
374 }
375 
376 /**
377  * @brief Get DNS text record field # for official databases.
378  *
379  * @param database  Official database name.
380  * @return int      DNS text record field #
381  */
textrecordfield(const char * database)382 static int textrecordfield(const char *database)
383 {
384     if (!strcmp(database, "main")) {
385         return 1;
386     } else if (!strcmp(database, "daily")) {
387         return 2;
388     } else if (!strcmp(database, "bytecode")) {
389         return 7;
390     } else if (!strcmp(database, "safebrowsing")) {
391         return 6;
392     }
393     return 0;
394 }
395 
396 #if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 61))
397 /* In libcurl 7.61.0, support was added for extracting the time in plain
398    microseconds. Older libcurl versions are stuck in using 'double' for this
399    information so we complicate this example a bit by supporting either
400    approach. */
401 #define TIME_IN_US 1
402 #define TIMETYPE curl_off_t
403 #define TIMEOPT CURLINFO_TOTAL_TIME_T
404 #define MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL 3000000
405 #else
406 #define TIMETYPE double
407 #define TIMEOPT CURLINFO_TOTAL_TIME
408 #define MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL 3
409 #endif
410 
411 #define STOP_DOWNLOAD_AFTER_THIS_MANY_BYTES 6000
412 
413 struct xfer_progress {
414     TIMETYPE lastRunTime; /* type depends on version, see above */
415     uint8_t bComplete;
416     CURL *curl;
417 };
418 
printTime(double seconds)419 static void printTime(double seconds)
420 {
421     if (seconds >= 3600) {
422         fprintf(stdout, "%2.0fh %02.0fm", trunc(seconds / 3600), trunc(fmod(seconds, 3600.0) / 60));
423     } else if (seconds >= 60) {
424         fprintf(stdout, "%2.0fm %02.0fs", trunc(seconds / 60), trunc(fmod(seconds, 60.0)));
425     } else {
426         fprintf(stdout, "%6.1fs", seconds);
427     }
428 }
429 
printBytes(curl_off_t bytes,int bPad)430 static void printBytes(curl_off_t bytes, int bPad)
431 {
432     if (bytes >= (1024 * 1024)) {
433         const char *format = bPad ? "%7.02fMiB" : "%.02fMiB";
434         double megabytes   = bytes / (double)(1024 * 1024);
435         fprintf(stdout, format, megabytes);
436     } else if (bytes >= 1024) {
437         const char *format = bPad ? "%7.02fKiB" : "%.02fKiB";
438         double kilobytes   = bytes / (double)(1024);
439         fprintf(stdout, format, kilobytes);
440     } else {
441         const char *format = bPad ? "%9" CURL_FORMAT_CURL_OFF_T "B" : "%" CURL_FORMAT_CURL_OFF_T "B";
442         fprintf(stdout, format, bytes);
443     }
444 }
445 
446 /**
447  * Function from curl example code, Copyright (C) 1998 - 2018, Daniel Stenberg, see COPYING.curl for license details
448  * Progress bar callback function ( CURLOPT_XFERINFOFUNCTION ).
449  */
xferinfo(void * prog,curl_off_t TotalToDownload,curl_off_t NowDownloaded,curl_off_t TotalToUpload,curl_off_t NowUploaded)450 static int xferinfo(void *prog,
451                     curl_off_t TotalToDownload, curl_off_t NowDownloaded,
452                     curl_off_t TotalToUpload, curl_off_t NowUploaded)
453 {
454     struct xfer_progress *xferProg = (struct xfer_progress *)prog;
455     CURL *curl                     = xferProg->curl;
456     TIMETYPE curtime               = 0;
457     TIMETYPE remtime               = 0;
458 
459     uint32_t i                = 0;
460     uint32_t totalNumDots     = 25;
461     uint32_t numDots          = 0;
462     double fractiondownloaded = 0.0;
463 
464     UNUSEDPARAM(TotalToUpload);
465     UNUSEDPARAM(NowUploaded);
466 
467     if ((TotalToDownload <= 0.0) || (xferProg->bComplete)) {
468         return 0;
469     }
470 
471     fractiondownloaded = (double)NowDownloaded / (double)TotalToDownload;
472     numDots            = round(fractiondownloaded * totalNumDots);
473 
474     curl_easy_getinfo(curl, TIMEOPT, &curtime);
475 
476     xferProg->lastRunTime = curtime;
477 
478 #ifndef _WIN32
479     fprintf(stdout, "\e[?7l");
480 #endif
481 #ifdef TIME_IN_US
482     if (fractiondownloaded <= 0.0) {
483         fprintf(stdout, "Time: ");
484         printTime(curtime / 1000000.0);
485         fprintf(stdout, "               ");
486     } else {
487         remtime = (curtime / fractiondownloaded) - curtime;
488         fprintf(stdout, "Time: ");
489         printTime(curtime / 1000000.0);
490         fprintf(stdout, ", ETA: ");
491         printTime(remtime / 1000000.0);
492         fprintf(stdout, " ");
493     }
494 #else
495     if (fractiondownloaded <= 0.0) {
496         fprintf(stdout, "Time: ");
497         printTime(curtime);
498         fprintf(stdout, "               ");
499     } else {
500         remtime = (curtime / fractiondownloaded) - curtime;
501         fprintf(stdout, "Time: ");
502         printTime(curtime);
503         fprintf(stdout, ", ETA: ");
504         printTime(remtime);
505         fprintf(stdout, " ");
506     }
507 #endif
508 
509     fprintf(stdout, "[");
510     if (numDots > 0) {
511         if (numDots > 1) {
512             for (i = 0; i < numDots - 1; i++) {
513                 fprintf(stdout, "=");
514             }
515         }
516         fprintf(stdout, ">");
517         i++;
518     }
519     for (; i < totalNumDots; i++) {
520         fprintf(stdout, " ");
521     }
522 
523     fprintf(stdout, "] ");
524 
525     printBytes(NowDownloaded, 1);
526     fprintf(stdout, "/");
527     printBytes(TotalToDownload, 0);
528 
529     if (NowDownloaded < TotalToDownload) {
530         fprintf(stdout, "\r");
531     } else {
532         fprintf(stdout, "\n");
533         xferProg->bComplete = 1;
534     }
535 #ifndef _WIN32
536     fprintf(stdout, "\e[?7h");
537 #endif
538     fflush(stdout);
539 
540     return 0;
541 }
542 
543 #if (LIBCURL_VERSION_MAJOR < 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR < 32))
544 /**
545  * Function from curl example code, Copyright (C) 1998 - 2018, Daniel Stenberg, see COPYING.curl for license details
546  * Older style progress bar callback shim; for libcurl older than 7.32.0 ( CURLOPT_PROGRESSFUNCTION ).
547  */
older_progress(void * prog,double TotalToDownload,double NowDownloaded,double TotalToUpload,double NowUploaded)548 static int older_progress(void *prog,
549                           double TotalToDownload, double NowDownloaded,
550                           double TotalToUpload, double NowUploaded)
551 {
552     return xferinfo(prog,
553                     (curl_off_t)TotalToDownload,
554                     (curl_off_t)NowDownloaded,
555                     (curl_off_t)TotalToUpload,
556                     (curl_off_t)NowUploaded);
557 }
558 #endif
559 
create_curl_handle(int bHttp,int bAllowRedirect,CURL ** curlHandle)560 static fc_error_t create_curl_handle(
561     int bHttp,
562     int bAllowRedirect,
563     CURL **curlHandle)
564 {
565     fc_error_t status = FC_EARG;
566 
567     CURL *curl = NULL;
568 
569 #if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 33))
570     CURLcode curl_ret = CURLE_OK;
571 #endif
572 
573     char userAgent[128];
574 
575     if (NULL == curlHandle) {
576         logg("!create_curl_handle: Invalid arguments!\n");
577         goto done;
578     }
579 
580     *curlHandle = NULL;
581 
582     curl = curl_easy_init();
583     if (NULL == curl) {
584         logg("!create_curl_handle: curl_easy_init failed!\n");
585         status = FC_EINIT;
586         goto done;
587     }
588 
589     if (g_userAgent) {
590         strncpy(userAgent, g_userAgent, sizeof(userAgent));
591     } else {
592         /*
593          * Use a randomly generated UUID in the User-Agent
594          * We'll try to load it from a file in the database directory.
595          * If none exists, we'll create a new one and save it to said file.
596          */
597         snprintf(userAgent, sizeof(userAgent),
598                  PACKAGE "/%s (OS: " TARGET_OS_TYPE ", ARCH: " TARGET_ARCH_TYPE ", CPU: " TARGET_CPU_TYPE ", UUID: %s)",
599                  get_version(),
600                  g_freshclamDat->uuid);
601     }
602     userAgent[sizeof(userAgent) - 1] = 0;
603 
604     if (mprintf_verbose) {
605         /* ask libcurl to show us the verbose output */
606         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L)) {
607             logg("!create_curl_handle: Failed to set CURLOPT_VERBOSE!\n");
608         }
609         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_STDERR, stdout)) {
610             logg("!create_curl_handle: Failed to direct curl debug output to stdout!\n");
611         }
612     }
613 
614     if (bHttp) {
615         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent)) {
616             logg("!create_curl_handle: Failed to set CURLOPT_USERAGENT (%s)!\n", userAgent);
617         }
618         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, g_connectTimeout)) {
619             logg("!create_curl_handle: Failed to set CURLOPT_CONNECTTIMEOUT (%u)!\n", g_connectTimeout);
620         }
621         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMEOUT, g_requestTimeout)) {
622             logg("!create_curl_handle: Failed to set CURLOPT_TIMEOUT (%u)!\n", g_requestTimeout);
623         }
624 
625         if (bAllowRedirect) {
626             /* allow three redirects */
627             if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)) {
628                 logg("!create_curl_handle: Failed to set CURLOPT_FOLLOWLOCATION!\n");
629             }
630             if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L)) {
631                 logg("!create_curl_handle: Failed to set CURLOPT_MAXREDIRS!\n");
632             }
633         }
634     }
635 
636 #if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 33))
637     if (g_localIP) {
638         if (NULL == strchr(g_localIP, ':')) {
639             logg("*Local IPv4 address requested: %s\n", g_localIP);
640             curl_ret = curl_easy_setopt(curl, CURLOPT_DNS_LOCAL_IP4, g_localIP); // Option requires libcurl built with c-ares
641             switch (curl_ret) {
642                 case CURLE_BAD_FUNCTION_ARGUMENT:
643                     logg("!create_curl_handle: Unable to bind DNS resolves to %s. Invalid IPv4 address.\n", g_localIP);
644                     status = FC_ECONFIG;
645                     goto done;
646                     break;
647                 case CURLE_UNKNOWN_OPTION:
648                 case CURLE_NOT_BUILT_IN:
649                     logg("!create_curl_handle: Unable to bind DNS resolves to %s. Option requires that libcurl was built with c-ares.\n", g_localIP);
650                     status = FC_ECONFIG;
651                     goto done;
652                 default:
653                     break;
654             }
655             if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4)) {
656                 logg("!create_curl_handle: Failed to set CURLOPT_IPRESOLVE (IPv4)!\n");
657             }
658         } else {
659             logg("*Local IPv6 address requested: %s\n", g_localIP);
660             curl_ret = curl_easy_setopt(curl, CURLOPT_DNS_LOCAL_IP6, g_localIP); // Option requires libcurl built with c-ares
661             switch (curl_ret) {
662                 case CURLE_BAD_FUNCTION_ARGUMENT:
663                     logg("^create_curl_handle: Unable to bind DNS resolves to %s. Invalid IPv4 address.\n", g_localIP);
664                     status = FC_ECONFIG;
665                     goto done;
666                     break;
667                 case CURLE_UNKNOWN_OPTION:
668                 case CURLE_NOT_BUILT_IN:
669                     logg("^create_curl_handle: Unable to bind DNS resolves to %s. Option requires that libcurl was built with c-ares.\n", g_localIP);
670                     status = FC_ECONFIG;
671                     goto done;
672                 default:
673                     break;
674             }
675             if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6)) {
676                 logg("!create_curl_handle: Failed to set CURLOPT_IPRESOLVE (IPv6)!\n");
677             }
678         }
679     }
680 #endif
681     if (g_proxyServer) {
682         /*
683          * Proxy requested.
684          */
685         logg("*Using proxy: %s:%u\n", g_proxyServer, g_proxyPort);
686 
687         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROXY, g_proxyServer)) {
688             logg("!create_curl_handle: Failed to set CURLOPT_PROXY (%s)!\n", g_proxyServer);
689         }
690         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROXYPORT, g_proxyPort)) {
691             logg("!create_curl_handle: Failed to set CURLOPT_PROXYPORT (%u)!\n", g_proxyPort);
692         }
693         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L)) { // Necessary?
694             logg("!create_curl_handle: Failed to set CURLOPT_HTTPPROXYTUNNEL (1)!\n");
695         }
696 #ifdef CURLOPT_SUPPRESS_CONNECT_HEADERS
697         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L)) { // Necessary?
698             logg("!create_curl_handle: Failed to set CURLOPT_SUPPRESS_CONNECT_HEADERS (1)!\n");
699         }
700 #endif
701 
702         if (g_proxyUsername) {
703             if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, g_proxyUsername)) {
704                 logg("!create_curl_handle: Failed to set CURLOPT_PROXYUSERNAME (%s)!\n", g_proxyUsername);
705             }
706             if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, g_proxyPassword)) {
707                 logg("!create_curl_handle: Failed to set CURLOPT_PROXYPASSWORD (%s)!\n", g_proxyPassword);
708             }
709         }
710     }
711 
712 #if defined(C_DARWIN) || defined(_WIN32)
713     if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, *sslctx_function)) {
714         logg("*create_curl_handle: Failed to set SSL CTX function. Your libcurl may use an SSL backend that does not support CURLOPT_SSL_CTX_FUNCTION.\n");
715     }
716 #else
717     set_tls_ca_bundle(curl);
718 #endif
719 
720     *curlHandle = curl;
721     status      = FC_SUCCESS;
722 
723 done:
724 
725     if (FC_SUCCESS != status) {
726         if (NULL != curl) {
727             curl_easy_cleanup(curl);
728         }
729     }
730 
731     return status;
732 }
733 
734 struct MemoryStruct {
735     char *buffer;
736     size_t size;
737 };
738 
WriteMemoryCallback(void * contents,size_t size,size_t nmemb,void * userp)739 static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
740 {
741     size_t real_size                  = size * nmemb;
742     struct MemoryStruct *receivedData = (struct MemoryStruct *)userp;
743 
744     if ((NULL == contents) || (NULL == userp)) {
745         return 0;
746     }
747 
748     char *newBuffer = realloc(receivedData->buffer, receivedData->size + real_size + 1);
749     if (NULL == newBuffer) {
750         logg("!remote_cvdhead - recv callback: Failed to allocate memory CVD header data.\n");
751         return 0;
752     }
753 
754     receivedData->buffer = newBuffer;
755     memcpy(&(receivedData->buffer[receivedData->size]), contents, real_size);
756     receivedData->size += real_size;
757     receivedData->buffer[receivedData->size] = 0;
758 
759     return real_size;
760 }
761 
762 struct FileStruct {
763     int handle;
764     size_t size;
765 };
766 
WriteFileCallback(void * contents,size_t size,size_t nmemb,void * userp)767 static size_t WriteFileCallback(void *contents, size_t size, size_t nmemb, void *userp)
768 {
769     size_t real_size                = size * nmemb;
770     struct FileStruct *receivedFile = (struct FileStruct *)userp;
771     size_t bytes_written            = 0;
772 
773     if ((NULL == contents) || (NULL == userp)) {
774         return 0;
775     }
776 
777     bytes_written = write(receivedFile->handle, contents, real_size);
778 
779     receivedFile->size += bytes_written;
780 
781     return bytes_written;
782 }
783 
784 /**
785  * @brief Get the cvd header info struct for the newest available database.
786  *
787  * The last-modified datetime will be used to set the If-Modified-Since header.
788  * If the remote CVD isn't newer, we should get an HTTP 304 and return
789  * FC_UPTODATE instead of FC_SUCCESS, and cvd will be NULL.
790  *
791  * @param cvdfile           database name including extension.
792  * @param ifModifiedSince   modified time of local database. May be 0 to always get the CVD header.
793  * @param server            server to use to retrieve for database header.
794  * @param logerr            non-zero to upgrade warnings to errors.
795  * @param cvd               [out] CVD header of newest available CVD, if FC_SUCCESS
796  * @return fc_error_t       FC_SUCCESS if CVD header obtained.
797  * @return fc_error_t       FC_UPTODATE if received 304 in response to ifModifiedSince date.
798  * @return fc_error_t       Another error code if failure occured.
799  */
remote_cvdhead(const char * cvdfile,uint32_t ifModifiedSince,char * server,int logerr,struct cl_cvd ** cvd)800 static fc_error_t remote_cvdhead(
801     const char *cvdfile,
802     uint32_t ifModifiedSince,
803     char *server,
804     int logerr,
805     struct cl_cvd **cvd)
806 {
807     fc_error_t ret;
808     fc_error_t status = FC_EARG;
809 
810     int bHttpServer = 0;
811     char *url       = NULL;
812     size_t urlLen   = 0;
813 
814     char head[CVD_HEADER_SIZE + 1];
815 
816     struct MemoryStruct receivedData = {0};
817 
818     unsigned int i;
819     struct cl_cvd *cvdhead;
820 
821     CURL *curl = NULL;
822     CURLcode curl_ret;
823     char errbuf[CURL_ERROR_SIZE];
824     struct curl_slist *slist = NULL;
825     struct xfer_progress prog;
826 
827     long http_code = 0;
828 
829     if (NULL == cvd) {
830         logg("!remote_cvdhead: Invalid arguments.\n");
831         goto done;
832     }
833 
834     *cvd = NULL;
835 
836     if (0 == strncasecmp(server, "http", strlen("http"))) {
837         bHttpServer = 1;
838     }
839 
840     /*
841      * Request CVD header.
842      */
843     urlLen = strlen(server) + strlen("/") + strlen(cvdfile);
844     url    = malloc(urlLen + 1);
845     snprintf(url, urlLen + 1, "%s/%s", server, cvdfile);
846 
847     logg("Trying to retrieve CVD header from %s\n", url);
848 
849     if (FC_SUCCESS != (ret = create_curl_handle(
850                            bHttpServer, // Set extra HTTP-specific headers.
851                            1,           // Allow redirects.
852                            &curl))) {   // [out] curl session handle.
853         logg("!remote_cvdhead: Failed to create curl handle.\n");
854         status = ret;
855         goto done;
856     }
857 
858 #ifdef HAVE_UNISTD_H
859     if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
860 #else
861     if (!mprintf_quiet)
862 #endif
863     {
864         prog.lastRunTime = 0;
865         prog.curl        = curl;
866         prog.bComplete   = 0;
867 
868 #if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 32))
869         /* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
870        compile as they won't have the symbols around.
871 
872        If built with a newer libcurl, but running with an older libcurl:
873        curl_easy_setopt() will fail in run-time trying to set the new
874        callback, making the older callback get used.
875 
876        New libcurls will prefer the new callback and instead use that one even
877        if both callbacks are set. */
878 
879         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo)) {
880             logg("!create_curl_handle: Failed to set transfer info function!\n");
881         }
882         /* pass the struct pointer into the xferinfo function, note that this is
883            an alias to CURLOPT_PROGRESSDATA */
884         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &prog)) {
885             logg("!create_curl_handle: Failed to set transfer info data structure!\n");
886         }
887 #else
888         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, older_progress)) {
889             logg("!create_curl_handle: Failed to set progress function!\n");
890         }
891         /* pass the struct pointer into the progress function */
892         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &prog)) {
893             logg("!create_curl_handle: Failed to set progress data structure!\n");
894         }
895 #endif
896 
897         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L)) {
898             logg("!create_curl_handle: Failed to disable progress function!\n");
899         }
900     }
901 
902     if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_URL, url)) {
903         logg("!remote_cvdhead: Failed to set CURLOPT_URL for curl session (%s).\n", url);
904         status = FC_EFAILEDGET;
905         goto done;
906     }
907 
908     if (bHttpServer) {
909         /*
910          * For HTTP, set some extra headers.
911          */
912         struct curl_slist *temp = NULL;
913 
914         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L)) {
915             logg("!remote_cvdhead: Failed to set CURLOPT_HTTPGET for curl session.\n");
916         }
917 
918 #ifdef FRESHCLAM_NO_CACHE
919         if (NULL == (temp = curl_slist_append(slist, "Cache-Control: no-cache"))) { // Necessary?
920             logg("!remote_cvdhead: Failed to append \"Cache-Control: no-cache\" header to custom curl header list.\n");
921         } else {
922             slist = temp;
923         }
924 #endif
925         if (NULL == (temp = curl_slist_append(slist, "Connection: close"))) {
926             logg("!remote_cvdhead: Failed to append \"Connection: close\" header to custom curl header list.\n");
927         } else {
928             slist = temp;
929         }
930         if (NULL != slist) {
931             if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) {
932                 logg("!remote_cvdhead: Failed to add custom header list to curl session.\n");
933             }
934         }
935     }
936 
937     if (0 != ifModifiedSince) {
938         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMEVALUE, ifModifiedSince)) {
939             logg("!remote_cvdhead: Failed to set if-Modified-Since time value for curl session.\n");
940         }
941         /* If-Modified-Since the above time stamp */
942         else if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE)) {
943             logg("!remote_cvdhead: Failed to set if-Modified-Since time condition for curl session.\n");
944         }
945     }
946 
947     /* Request only the first 512 bytes (CVD_HEADER_SIZE) */
948     if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_RANGE, "0-511")) {
949         logg("!remote_cvdhead: Failed to set CURLOPT_RANGE CVD_HEADER_SIZE for curl session.\n");
950     }
951 
952     receivedData.buffer = cli_malloc(1); /* will be grown as needed by the realloc above */
953     receivedData.size   = 0;             /* no data at this point */
954 
955     /* Send all data to this function  */
956     if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback)) {
957         logg("!remote_cvdhead: Failed to set write-data memory callback function for curl session.\n");
958     }
959 
960     /* Pass our 'receivedData' struct to the callback function */
961     if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&receivedData)) {
962         logg("!remote_cvdhead: Failed to set receivedData struct for write-data callback function for curl session.\n");
963     }
964 
965     /*
966      * Perform download.
967      */
968     memset(errbuf, 0, sizeof(errbuf));
969     curl_ret = curl_easy_perform(curl);
970     if (curl_ret != CURLE_OK) {
971         /*
972          * Show the error information.
973          * If no detailed error information was written to errbuf
974          * show the more generic information from curl_easy_strerror instead.
975          */
976         size_t len = strlen(errbuf);
977         logg("%cremote_cvdhead: Download failed (%d) ", logerr ? '!' : '^', curl_ret);
978         if (len)
979             logg("%c Message: %s%s", logerr ? '!' : '^', errbuf, ((errbuf[len - 1] != '\n') ? "\n" : ""));
980         else
981             logg("%c Message: %s\n", logerr ? '!' : '^', curl_easy_strerror(curl_ret));
982         status = FC_ECONNECTION;
983         goto done;
984     }
985 
986     /* Check HTTP code */
987     curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
988     switch (http_code) {
989         case 200:
990         case 206: {
991             status = FC_SUCCESS;
992             break;
993         }
994         case 304: {
995             status = FC_UPTODATE;
996             goto done;
997         }
998         case 403: {
999             status = FC_EFORBIDDEN;
1000 
1001             /* Try again in no less than 24 hours if freshclam received a 403 FORBIDDEN. */
1002             g_freshclamDat->retry_after = time(NULL) + 60 * 60 * 24;
1003 
1004             (void)save_freshclam_dat();
1005 
1006             break;
1007         }
1008         case 429: {
1009             status = FC_ERETRYLATER;
1010 
1011             curl_off_t retry_after = 0;
1012 
1013 #if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 66))
1014             /* CURLINFO_RETRY_AFTER was introduced in libcurl 7.66 */
1015 
1016             /* Find out how long we should wait before allowing a retry. */
1017             curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
1018 #endif
1019 
1020             if (retry_after > 0) {
1021                 /* The response gave us a Retry-After date. Use that. */
1022                 g_freshclamDat->retry_after = time(NULL) + (time_t)retry_after;
1023             } else {
1024                 /* Try again in no less than 4 hours if the response didn't specify
1025                    or if CURLINFO_RETRY_AFTER is not supported. */
1026                 g_freshclamDat->retry_after = time(NULL) + 60 * 60 * 4;
1027             }
1028             (void)save_freshclam_dat();
1029 
1030             break;
1031         }
1032         case 404: {
1033             if (g_proxyServer)
1034                 logg("^remote_cvdhead: file not found: %s (Proxy: %s:%u)\n", url, g_proxyServer, g_proxyPort);
1035             else
1036                 logg("^remote_cvdhead: file not found: %s\n", url);
1037             status = FC_EFAILEDGET;
1038             goto done;
1039         }
1040         case 522: {
1041             logg("^remote_cvdhead: Origin Connection Time-out. Cloudflare was unable to reach the origin web server and the request timed out. URL: %s\n", url);
1042             status = FC_EFAILEDGET;
1043             goto done;
1044         }
1045         default: {
1046             if (g_proxyServer)
1047                 logg("%cremote_cvdhead: Unexpected response (%li) from %s (Proxy: %s:%u)\n",
1048                      logerr ? '!' : '^', http_code, server, g_proxyServer, g_proxyPort);
1049             else
1050                 logg("%cremote_cvdhead: Unexpected response (%li) from %s\n",
1051                      logerr ? '!' : '^', http_code, server);
1052             status = FC_EFAILEDGET;
1053             goto done;
1054         }
1055     }
1056 
1057     /*
1058      * Identify start of CVD header in response body.
1059      */
1060     if (receivedData.size < CVD_HEADER_SIZE) {
1061         logg("%cremote_cvdhead: Malformed CVD header (too short)\n", logerr ? '!' : '^');
1062         status = FC_EFAILEDGET;
1063         goto done;
1064     }
1065 
1066     /*
1067      * Copy CVD header byte-by-byte from response body to CVD header buffer.
1068      * Validate that data contains only printable characters and no NULL terminators.
1069      */
1070     memset(head, 0, sizeof(head));
1071 
1072     for (i = 0; i < CVD_HEADER_SIZE; i++) {
1073         if (!receivedData.buffer ||
1074             (receivedData.buffer && !*receivedData.buffer) ||
1075             (receivedData.buffer && !isprint(receivedData.buffer[i]))) {
1076 
1077             logg("%cremote_cvdhead: Malformed CVD header (bad chars)\n", logerr ? '!' : '^');
1078             status = FC_EFAILEDGET;
1079             goto done;
1080         }
1081         head[i] = receivedData.buffer[i];
1082     }
1083 
1084     /*
1085      * Parse CVD info into CVD info struct.
1086      */
1087     if (!(cvdhead = cl_cvdparse(head))) {
1088         logg("%cremote_cvdhead: Malformed CVD header (can't parse)\n", logerr ? '!' : '^');
1089         status = FC_EFAILEDGET;
1090         goto done;
1091     } else {
1092         logg("OK\n");
1093     }
1094 
1095     *cvd   = cvdhead;
1096     status = FC_SUCCESS;
1097 
1098 done:
1099 
1100     if (NULL != receivedData.buffer) {
1101         free(receivedData.buffer);
1102     }
1103     if (NULL != slist) {
1104         curl_slist_free_all(slist);
1105     }
1106     if (NULL != curl) {
1107         curl_easy_cleanup(curl);
1108     }
1109     if (NULL != url) {
1110         free(url);
1111     }
1112 
1113     return status;
1114 }
1115 
downloadFile(const char * url,const char * destfile,int bAllowRedirect,int logerr,time_t ifModifiedSince)1116 static fc_error_t downloadFile(
1117     const char *url,
1118     const char *destfile,
1119     int bAllowRedirect,
1120     int logerr,
1121     time_t ifModifiedSince)
1122 {
1123     fc_error_t ret;
1124     fc_error_t status = FC_EARG;
1125 
1126     int bHttpServer = 0;
1127 
1128     CURL *curl = NULL;
1129     CURLcode curl_ret;
1130     char errbuf[CURL_ERROR_SIZE];
1131     struct curl_slist *slist = NULL;
1132     struct xfer_progress prog;
1133 
1134     long http_code = 0;
1135 
1136     struct FileStruct receivedFile = {-1, 0};
1137 
1138     if ((NULL == url) || (NULL == destfile)) {
1139         logg("!downloadFile: Invalid arguments.\n");
1140         goto done;
1141     }
1142 
1143     logg("*Retrieving %s\n", url);
1144 
1145     if (0 == strncasecmp(url, "http", strlen("http"))) {
1146         bHttpServer = 1;
1147     }
1148 
1149     if (FC_SUCCESS != (ret = create_curl_handle(bHttpServer, bAllowRedirect, &curl))) {
1150         logg("!downloadFile: Failed to create curl handle.\n");
1151         status = ret;
1152         goto done;
1153     }
1154 
1155 #ifdef HAVE_UNISTD_H
1156     if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
1157 #else
1158     if (!mprintf_quiet)
1159 #endif
1160     {
1161         prog.lastRunTime = 0;
1162         prog.curl        = curl;
1163         prog.bComplete   = 0;
1164 
1165 #if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 32))
1166         /* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
1167        compile as they won't have the symbols around.
1168 
1169        If built with a newer libcurl, but running with an older libcurl:
1170        curl_easy_setopt() will fail in run-time trying to set the new
1171        callback, making the older callback get used.
1172 
1173        New libcurls will prefer the new callback and instead use that one even
1174        if both callbacks are set. */
1175 
1176         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo)) {
1177             logg("!downloadFile: Failed to set transfer info function!\n");
1178         }
1179         /* pass the struct pointer into the xferinfo function, note that this is
1180        an alias to CURLOPT_PROGRESSDATA */
1181         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &prog)) {
1182             logg("!downloadFile: Failed to set transfer info data structure!\n");
1183         }
1184 #else
1185         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, older_progress)) {
1186             logg("!downloadFile: Failed to set progress function!\n");
1187         }
1188         /* pass the struct pointer into the progress function */
1189         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &prog)) {
1190             logg("!downloadFile: Failed to set progress data structure!\n");
1191         }
1192 #endif
1193 
1194         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L)) {
1195             logg("!downloadFile: Failed to disable progress function!\n");
1196         }
1197     }
1198 
1199     if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_URL, url)) {
1200         logg("!downloadFile: Failed to set CURLOPT_URL for curl session (%s).\n", url);
1201     }
1202     if (0 != ifModifiedSince) {
1203         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMEVALUE, ifModifiedSince)) {
1204             logg("!downloadFile: Failed to set if-Modified-Since time value for curl session.\n");
1205         }
1206         /* If-Modified-Since the above time stamp */
1207         else if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE)) {
1208             logg("!downloadFile: Failed to set if-Modified-Since time condition for curl session.\n");
1209         }
1210     }
1211 
1212     if (bHttpServer) {
1213         /*
1214          * For HTTP, set some extra headers.
1215          */
1216         struct curl_slist *temp = NULL;
1217 
1218         if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L)) {
1219             logg("!downloadFile: Failed to set CURLOPT_HTTPGET for curl session.\n");
1220         }
1221 
1222 #ifdef FRESHCLAM_NO_CACHE
1223         if (NULL == (temp = curl_slist_append(slist, "Cache-Control: no-cache"))) { // Necessary?
1224             logg("!downloadFile: Failed to append \"Cache-Control: no-cache\" header to custom curl header list.\n");
1225         } else {
1226             slist = temp;
1227         }
1228 #endif
1229         if (NULL == (temp = curl_slist_append(slist, "Connection: close"))) { // Necessary?
1230             logg("!downloadFile: Failed to append \"Connection: close\" header to custom curl header list.\n");
1231         } else {
1232             slist = temp;
1233         }
1234         if (NULL != slist) {
1235             if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) {
1236                 logg("!downloadFile: Failed to add custom header list to curl session.\n");
1237             }
1238         }
1239     }
1240 
1241     /* Write the response body to the destination file handle */
1242 
1243     if (-1 == (receivedFile.handle = open(destfile, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0644))) {
1244         char currdir[PATH_MAX];
1245 
1246         if (getcwd(currdir, sizeof(currdir)))
1247             logg("!downloadFile: Can't create new file %s in %s\n", destfile, currdir);
1248         else
1249             logg("!downloadFile: Can't create new file %s in the current directory\n", destfile);
1250 
1251         logg("Hint: The database directory must be writable for UID %d or GID %d\n", getuid(), getgid());
1252         status = FC_EDBDIRACCESS;
1253         goto done;
1254     }
1255     receivedFile.size = 0;
1256 
1257     /* Send all data to this function  */
1258     if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteFileCallback)) {
1259         logg("!downloadFile: Failed to set write-data fwrite callback function for curl session.\n");
1260     }
1261 
1262     if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&receivedFile)) {
1263         logg("!downloadFile: Failed to set write-data file handle for curl session.\n");
1264     }
1265 
1266     logg("*downloadFile: Download source:      %s\n", url);
1267     logg("*downloadFile: Download destination: %s\n", destfile);
1268 
1269     /* Perform download */
1270     memset(errbuf, 0, sizeof(errbuf));
1271     curl_ret = curl_easy_perform(curl);
1272     if (curl_ret != CURLE_OK) {
1273         /*
1274          * Show the error information.
1275          * If no detailed error information was written to errbuf
1276          * show the more generic information from curl_easy_strerror instead.
1277          */
1278         size_t len = strlen(errbuf);
1279         logg("%cDownload failed (%d) ", logerr ? '!' : '^', curl_ret);
1280         if (len)
1281             logg("%c Message: %s%s", logerr ? '!' : '^', errbuf, ((errbuf[len - 1] != '\n') ? "\n" : ""));
1282         else
1283             logg("%c Message: %s\n", logerr ? '!' : '^', curl_easy_strerror(curl_ret));
1284         status = FC_ECONNECTION;
1285         goto done;
1286     }
1287 
1288     /* Check HTTP code */
1289     curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
1290     switch (http_code) {
1291         case 200:
1292         case 206: {
1293             if (0 == receivedFile.size) {
1294                 status = FC_EEMPTYFILE;
1295             } else {
1296                 status = FC_SUCCESS;
1297             }
1298             break;
1299         }
1300         case 304: {
1301             status = FC_UPTODATE;
1302             break;
1303         }
1304         case 403: {
1305             status = FC_EFORBIDDEN;
1306 
1307             /* Try again in no less than 24 hours if freshclam received a 403 FORBIDDEN. */
1308             g_freshclamDat->retry_after = time(NULL) + 60 * 60 * 24;
1309 
1310             (void)save_freshclam_dat();
1311 
1312             break;
1313         }
1314         case 429: {
1315             status = FC_ERETRYLATER;
1316 
1317             curl_off_t retry_after = 0;
1318 
1319 #if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 66))
1320             /* CURLINFO_RETRY_AFTER was introduced in libcurl 7.66 */
1321 
1322             /* Find out how long we should wait before allowing a retry. */
1323             curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
1324 #endif
1325 
1326             if (retry_after > 0) {
1327                 /* The response gave us a Retry-After date. Use that. */
1328                 g_freshclamDat->retry_after = time(NULL) + (time_t)retry_after;
1329             } else {
1330                 /* Try again in no less than 4 hours if the response didn't specify
1331                    or if CURLINFO_RETRY_AFTER is not supported. */
1332                 g_freshclamDat->retry_after = time(NULL) + 60 * 60 * 4;
1333             }
1334             (void)save_freshclam_dat();
1335 
1336             break;
1337         }
1338         case 404: {
1339             if (g_proxyServer)
1340                 logg("^downloadFile: file not found: %s (Proxy: %s:%u)\n", url, g_proxyServer, g_proxyPort);
1341             else
1342                 logg("^downloadFile: file not found: %s\n", url);
1343             status = FC_EFAILEDGET;
1344             break;
1345         }
1346         case 522: {
1347             logg("^downloadFile: Origin Connection Time-out. Cloudflare was unable to reach the origin web server and the request timed out. URL: %s\n", url);
1348             status = FC_EFAILEDGET;
1349             break;
1350         }
1351         default: {
1352             if (g_proxyServer)
1353                 logg("%cdownloadFile: Unexpected response (%li) from %s (Proxy: %s:%u)\n",
1354                      logerr ? '!' : '^', http_code, url, g_proxyServer, g_proxyPort);
1355             else
1356                 logg("%cdownloadFile: Unexpected response (%li) from %s\n",
1357                      logerr ? '!' : '^', http_code, url);
1358             status = FC_EFAILEDGET;
1359         }
1360     }
1361 
1362 done:
1363 
1364     if (NULL != slist) {
1365         curl_slist_free_all(slist);
1366     }
1367     if (NULL != curl) {
1368         curl_easy_cleanup(curl);
1369     }
1370 
1371     if (-1 != receivedFile.handle) {
1372         close(receivedFile.handle);
1373     }
1374 
1375     if (FC_UPTODATE < status) {
1376         if (NULL != destfile) {
1377             unlink(destfile);
1378         }
1379     }
1380 
1381     return status;
1382 }
1383 
getcvd(const char * cvdfile,const char * tmpfile,char * server,uint32_t ifModifiedSince,unsigned int remoteVersion,int logerr)1384 static fc_error_t getcvd(
1385     const char *cvdfile,
1386     const char *tmpfile,
1387     char *server,
1388     uint32_t ifModifiedSince,
1389     unsigned int remoteVersion,
1390     int logerr)
1391 {
1392     fc_error_t ret;
1393     cl_error_t cl_ret;
1394     fc_error_t status = FC_EARG;
1395 
1396     struct cl_cvd *cvd           = NULL;
1397     char *tmpfile_with_extension = NULL;
1398     char *url                    = NULL;
1399     size_t urlLen                = 0;
1400 
1401     if ((NULL == cvdfile) || (NULL == tmpfile) || (NULL == server)) {
1402         logg("!getcvd: Invalid arguments.\n");
1403         goto done;
1404     }
1405 
1406     urlLen = strlen(server) + strlen("/") + strlen(cvdfile);
1407     url    = malloc(urlLen + 1);
1408     snprintf(url, urlLen + 1, "%s/%s", server, cvdfile);
1409 
1410     ret = downloadFile(url, tmpfile, 1, logerr, ifModifiedSince);
1411     if (ret == FC_UPTODATE) {
1412         logg("%s is up-to-date.\n", cvdfile);
1413         status = ret;
1414         goto done;
1415     } else if (ret > FC_UPTODATE) {
1416         logg("%cCan't download %s from %s\n", logerr ? '!' : '^', cvdfile, url);
1417         status = ret;
1418         goto done;
1419     }
1420 
1421     /* Temporarily rename file to correct extension for verification. */
1422     tmpfile_with_extension = strdup(tmpfile);
1423     if (!tmpfile_with_extension) {
1424         logg("!Can't allocate memory for temp file with extension!\n");
1425         status = FC_EMEM;
1426         goto done;
1427     }
1428     strncpy(tmpfile_with_extension + strlen(tmpfile_with_extension) - 4, cvdfile + strlen(cvdfile) - 4, 4);
1429     if (rename(tmpfile, tmpfile_with_extension) == -1) {
1430         logg("!Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno));
1431         status = FC_EDBDIRACCESS;
1432         goto done;
1433     }
1434 
1435     if (CL_SUCCESS != (cl_ret = cl_cvdverify(tmpfile_with_extension))) {
1436         logg("!Verification: %s\n", cl_strerror(cl_ret));
1437         status = FC_EBADCVD;
1438         goto done;
1439     }
1440 
1441     if (NULL == (cvd = cl_cvdhead(tmpfile_with_extension))) {
1442         logg("!Can't read CVD header of new %s database.\n", cvdfile);
1443         status = FC_EBADCVD;
1444         goto done;
1445     }
1446 
1447     /* Rename the file back to the original, since verification passed. */
1448     if (rename(tmpfile_with_extension, tmpfile) == -1) {
1449         logg("!Can't rename %s to %s: %s\n", tmpfile_with_extension, tmpfile, strerror(errno));
1450         status = FC_EDBDIRACCESS;
1451         goto done;
1452     }
1453 
1454     if (cvd->version < remoteVersion) {
1455         logg("*The %s database downloaded from %s is older than the version advertised in the DNS TXT record.\n",
1456              cvdfile,
1457              server);
1458         status = FC_EMIRRORNOTSYNC;
1459         goto done;
1460     }
1461 
1462     status = FC_SUCCESS;
1463 
1464 done:
1465     if (NULL != cvd) {
1466         cl_cvdfree(cvd);
1467     }
1468     if (NULL != tmpfile_with_extension) {
1469         unlink(tmpfile_with_extension);
1470         free(tmpfile_with_extension);
1471     }
1472     if (NULL != url) {
1473         free(url);
1474     }
1475     if (
1476         (FC_SUCCESS != status) &&
1477         (FC_EMIRRORNOTSYNC != status) /* Keep older version, it's better than nothing. */
1478     ) {
1479         if (NULL != tmpfile) {
1480             unlink(tmpfile);
1481         }
1482     }
1483 
1484     return status;
1485 }
1486 
1487 /**
1488  * @brief Change to the temp dir for storing CDIFFs for incremental database update.
1489  *
1490  * Will create the temp dir if it does not already exist.
1491  *
1492  * @param database      The database we're updating.
1493  * @param tmpdir        [out] The name of the temp dir to use.
1494  * @return fc_error_t
1495  */
mkdir_and_chdir_for_cdiff_tmp(const char * database,const char * tmpdir)1496 static fc_error_t mkdir_and_chdir_for_cdiff_tmp(const char *database, const char *tmpdir)
1497 {
1498     fc_error_t status = FC_EDIRECTORY;
1499 
1500     char cvdfile[DB_FILENAME_MAX];
1501 
1502     if ((NULL == database) || (NULL == tmpdir)) {
1503         logg("!mkdir_and_chdir_for_cdiff_tmp: Invalid arguments.\n");
1504         status = FC_EARG;
1505         goto done;
1506     }
1507 
1508     if (-1 == access(tmpdir, R_OK | W_OK)) {
1509         /*
1510          * Temp directory for incremental update (cdiff download) does not
1511          * yet exist.
1512          */
1513         int ret;
1514 
1515         /*
1516          * 1) Double-check that we have a CVD or CLD. Without either one, incremental update won't work.
1517          */
1518         ret = snprintf(cvdfile, sizeof(cvdfile), "%s.cvd", database);
1519         if (((int)sizeof(cvdfile) <= ret) || (-1 == ret)) {
1520             logg("!mkdir_and_chdir_for_cdiff_tmp: database parameter value too long to create cvd file name: %s\n", database);
1521             goto done;
1522         }
1523 
1524         if (-1 == access(cvdfile, R_OK)) {
1525             ret = snprintf(cvdfile, sizeof(cvdfile), "%s.cld", database);
1526             if (((int)sizeof(cvdfile) <= ret) || (-1 == ret)) {
1527                 logg("!mkdir_and_chdir_for_cdiff_tmp: database parameter value too long to create cld file name: %s\n", database);
1528                 goto done;
1529             }
1530 
1531             if (-1 == access(cvdfile, R_OK)) {
1532                 logg("!mkdir_and_chdir_for_cdiff_tmp: Can't find (or access) local CVD or CLD for %s database\n", database);
1533                 goto done;
1534             }
1535         }
1536 
1537         /*
1538          * 2) Create the incremental update temp directory.
1539          */
1540         if (-1 == mkdir(tmpdir, 0755)) {
1541             logg("!mkdir_and_chdir_for_cdiff_tmp: Can't create directory %s\n", tmpdir);
1542             goto done;
1543         }
1544 
1545         if (-1 == cli_cvdunpack(cvdfile, tmpdir)) {
1546             logg("!mkdir_and_chdir_for_cdiff_tmp: Can't unpack %s into %s\n", cvdfile, tmpdir);
1547             cli_rmdirs(tmpdir);
1548             goto done;
1549         }
1550     }
1551 
1552     if (-1 == chdir(tmpdir)) {
1553         logg("!mkdir_and_chdir_for_cdiff_tmp: Can't change directory to %s\n", tmpdir);
1554         goto done;
1555     }
1556 
1557     status = FC_SUCCESS;
1558 
1559 done:
1560 
1561     return status;
1562 }
1563 
downloadPatch(const char * database,const char * tmpdir,int version,char * server,int logerr)1564 static fc_error_t downloadPatch(
1565     const char *database,
1566     const char *tmpdir,
1567     int version,
1568     char *server,
1569     int logerr)
1570 {
1571     fc_error_t ret;
1572     fc_error_t status = FC_EARG;
1573 
1574     char *tempname = NULL;
1575     char patch[DB_FILENAME_MAX];
1576     char olddir[PATH_MAX];
1577 
1578     char *url     = NULL;
1579     size_t urlLen = 0;
1580 
1581     int fd = -1;
1582 
1583     olddir[0] = '\0';
1584 
1585     if ((NULL == database) || (NULL == tmpdir) || (NULL == server) || (0 == version)) {
1586         logg("!downloadPatch: Invalid arguments.\n");
1587         goto done;
1588     }
1589 
1590     if (NULL == getcwd(olddir, sizeof(olddir))) {
1591         logg("!downloadPatch: Can't get path of current working directory\n");
1592         status = FC_EDIRECTORY;
1593         goto done;
1594     }
1595 
1596     if (FC_SUCCESS != mkdir_and_chdir_for_cdiff_tmp(database, tmpdir)) {
1597         status = FC_EDIRECTORY;
1598         goto done;
1599     }
1600 
1601     if (NULL == (tempname = cli_gentemp("."))) {
1602         status = FC_EMEM;
1603         goto done;
1604     }
1605 
1606     snprintf(patch, sizeof(patch), "%s-%d.cdiff", database, version);
1607     urlLen = strlen(server) + strlen("/") + strlen(patch);
1608     url    = malloc(urlLen + 1);
1609     snprintf(url, urlLen + 1, "%s/%s", server, patch);
1610 
1611     if (FC_SUCCESS != (ret = downloadFile(url, tempname, 1, logerr, 0))) {
1612         if (ret == FC_EEMPTYFILE) {
1613             logg("Empty script %s, need to download entire database\n", patch);
1614         } else {
1615             logg("%cdownloadPatch: Can't download %s from %s\n", logerr ? '!' : '^', patch, url);
1616         }
1617         status = ret;
1618         goto done;
1619     }
1620 
1621     if (-1 == (fd = open(tempname, O_RDONLY | O_BINARY))) {
1622         logg("!downloadPatch: Can't open %s for reading\n", tempname);
1623         status = FC_EFILE;
1624         goto done;
1625     }
1626 
1627     if (-1 == cdiff_apply(fd, 1)) {
1628         logg("!downloadPatch: Can't apply patch\n");
1629         status = FC_EFAILEDUPDATE;
1630         goto done;
1631     }
1632 
1633     status = FC_SUCCESS;
1634 
1635 done:
1636 
1637     if (NULL != url) {
1638         free(url);
1639     }
1640 
1641     if (-1 != fd) {
1642         close(fd);
1643     }
1644 
1645     if (NULL != tempname) {
1646         unlink(tempname);
1647         free(tempname);
1648     }
1649 
1650     if ('\0' != olddir[0]) {
1651         if (-1 == chdir(olddir)) {
1652             logg("!downloadPatch: Can't chdir to %s\n", olddir);
1653             status = FC_EDIRECTORY;
1654         }
1655     }
1656 
1657     return status;
1658 }
1659 
1660 /**
1661  * @brief Get CVD header info for local CVD/CLD database.
1662  *
1663  * @param database          Database name
1664  * @param localname         [out] (optional) filename of local database.
1665  * @return struct cl_cvd*   CVD info struct of local database, if found. NULL if not found.
1666  */
currentdb(const char * database,char ** localname)1667 static struct cl_cvd *currentdb(const char *database, char **localname)
1668 {
1669     char filename[DB_FILENAME_MAX];
1670     struct cl_cvd *cvd = NULL;
1671 
1672     if (NULL == database) {
1673         logg("!currentdb: Invalid args!\n");
1674         goto done;
1675     }
1676 
1677     snprintf(filename, sizeof(filename), "%s.cvd", database);
1678     filename[sizeof(filename) - 1] = 0;
1679 
1680     if (-1 == access(filename, R_OK)) {
1681         /* CVD not found. */
1682         snprintf(filename, sizeof(filename), "%s.cld", database);
1683         filename[sizeof(filename) - 1] = 0;
1684 
1685         if (-1 == access(filename, R_OK)) {
1686             /* CLD also not found. Fail out. */
1687             goto done;
1688         }
1689     }
1690 
1691     if (NULL == (cvd = cl_cvdhead(filename))) {
1692         goto done;
1693     }
1694 
1695     if (localname) {
1696         *localname = cli_strdup(filename);
1697     }
1698 
1699 done:
1700 
1701     return cvd;
1702 }
1703 
buildcld(const char * tmpdir,const char * database,const char * newfile,int bCompress)1704 static fc_error_t buildcld(
1705     const char *tmpdir,
1706     const char *database,
1707     const char *newfile,
1708     int bCompress)
1709 {
1710     fc_error_t status = FC_EARG;
1711 
1712     char olddir[PATH_MAX] = {0};
1713     char info[DB_FILENAME_MAX];
1714     char buff[CVD_HEADER_SIZE + 1];
1715     char *pt;
1716 
1717     struct dirent *dent = NULL;
1718     DIR *dir            = NULL;
1719     gzFile gzs          = NULL;
1720     int fd              = -1;
1721 
1722     if ((NULL == tmpdir) || (NULL == database) || (NULL == newfile)) {
1723         logg("!buildcld: Invalid arguments.\n");
1724         goto done;
1725     }
1726 
1727     if (!getcwd(olddir, sizeof(olddir))) {
1728         logg("!buildcld: Can't get path of current working directory\n");
1729         status = FC_EDIRECTORY;
1730         goto done;
1731     }
1732 
1733     if (-1 == chdir(tmpdir)) {
1734         logg("!buildcld: Can't access directory %s\n", tmpdir);
1735         status = FC_EDIRECTORY;
1736         goto done;
1737     }
1738 
1739     snprintf(info, sizeof(info), "%s.info", database);
1740     info[sizeof(info) - 1] = 0;
1741     if (-1 == (fd = open(info, O_RDONLY | O_BINARY))) {
1742         logg("!buildcld: Can't open %s\n", info);
1743         status = FC_EFILE;
1744         goto done;
1745     }
1746 
1747     if (-1 == read(fd, buff, CVD_HEADER_SIZE)) {
1748         logg("!buildcld: Can't read %s\n", info);
1749         status = FC_EFILE;
1750         goto done;
1751     }
1752     buff[CVD_HEADER_SIZE] = 0;
1753 
1754     close(fd);
1755     fd = -1;
1756 
1757     if (NULL == (pt = strchr(buff, '\n'))) {
1758         logg("!buildcld: Bad format of %s\n", info);
1759         status = FC_EFAILEDUPDATE;
1760         goto done;
1761     }
1762     memset(pt, ' ', CVD_HEADER_SIZE + buff - pt);
1763 
1764     if (-1 == (fd = open(newfile, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0644))) {
1765         logg("!buildcld: Can't open %s for writing\n", newfile);
1766         status = FC_EFILE;
1767         goto done;
1768     }
1769     if (CVD_HEADER_SIZE != write(fd, buff, CVD_HEADER_SIZE)) {
1770         logg("!buildcld: Can't write to %s\n", newfile);
1771         status = FC_EFILE;
1772         goto done;
1773     }
1774 
1775     if (bCompress) {
1776         close(fd);
1777         fd = -1;
1778         if (NULL == (gzs = gzopen(newfile, "ab9f"))) {
1779             logg("!buildcld: gzopen() failed for %s\n", newfile);
1780             status = FC_EFAILEDUPDATE;
1781             goto done;
1782         }
1783     }
1784 
1785     if (-1 == access("COPYING", R_OK)) {
1786         logg("!buildcld: COPYING file not found\n");
1787         status = FC_EFAILEDUPDATE;
1788         goto done;
1789     }
1790 
1791     if (-1 == tar_addfile(fd, gzs, "COPYING")) {
1792         logg("!buildcld: Can't add COPYING to new %s.cld - please check if there is enough disk space available\n", database);
1793         status = FC_EFAILEDUPDATE;
1794         goto done;
1795     }
1796 
1797     if (-1 != access(info, R_OK)) {
1798         if (-1 == tar_addfile(fd, gzs, info)) {
1799             logg("!buildcld: Can't add %s to new %s.cld - please check if there is enough disk space available\n", info, database);
1800             status = FC_EFAILEDUPDATE;
1801             goto done;
1802         }
1803     }
1804 
1805     if (-1 != access("daily.cfg", R_OK)) {
1806         if (-1 == tar_addfile(fd, gzs, "daily.cfg")) {
1807             logg("!buildcld: Can't add daily.cfg to new %s.cld - please check if there is enough disk space available\n", database);
1808             status = FC_EFAILEDUPDATE;
1809             goto done;
1810         }
1811     }
1812 
1813     if (NULL == (dir = opendir("."))) {
1814         logg("!buildcld: Can't open directory %s\n", tmpdir);
1815         status = FC_EDIRECTORY;
1816         goto done;
1817     }
1818 
1819     while (NULL != (dent = readdir(dir))) {
1820         if (dent->d_ino) {
1821             if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..") || !strcmp(dent->d_name, "COPYING") || !strcmp(dent->d_name, "daily.cfg") || !strcmp(dent->d_name, info))
1822                 continue;
1823 
1824             if (tar_addfile(fd, gzs, dent->d_name) == -1) {
1825                 logg("!buildcld: Can't add %s to new %s.cld - please check if there is enough disk space available\n", dent->d_name, database);
1826                 status = FC_EFAILEDUPDATE;
1827                 goto done;
1828             }
1829         }
1830     }
1831 
1832     status = FC_SUCCESS;
1833 
1834 done:
1835 
1836     if (-1 != fd) {
1837         if (-1 == close(fd)) {
1838             logg("!buildcld: close() failed for %s\n", newfile);
1839         }
1840     }
1841     if (NULL != gzs) {
1842         if (gzclose(gzs)) {
1843             logg("!buildcld: gzclose() failed for %s\n", newfile);
1844         }
1845     }
1846     if (NULL != dir) {
1847         closedir(dir);
1848     }
1849 
1850     if (FC_SUCCESS != status) {
1851         if (NULL != newfile) {
1852             unlink(newfile);
1853         }
1854     }
1855 
1856     if ('\0' != olddir[0]) {
1857         if (-1 == chdir(olddir)) {
1858             logg("!buildcld: Can't return to previous directory %s\n", olddir);
1859             status = FC_EDIRECTORY;
1860         }
1861     }
1862 
1863     return status;
1864 }
1865 
query_remote_database_version(const char * database,uint32_t ifModifiedSince,const char * dnsUpdateInfo,char * server,int bPrivateMirror,int logerr,uint32_t * remoteVersion,char ** remoteFilename)1866 static fc_error_t query_remote_database_version(
1867     const char *database,
1868     uint32_t ifModifiedSince,
1869     const char *dnsUpdateInfo,
1870     char *server,
1871     int bPrivateMirror,
1872     int logerr,
1873     uint32_t *remoteVersion,
1874     char **remoteFilename)
1875 {
1876     fc_error_t ret;
1877     fc_error_t status = FC_EARG;
1878 
1879     uint32_t newVersion = 0;
1880     char cvdfile[DB_FILENAME_MAX];
1881     char cldfile[DB_FILENAME_MAX];
1882 
1883 #ifdef HAVE_RESOLV_H
1884     char *dnqueryDomain = NULL;
1885     char *extradnsreply = NULL;
1886 #endif
1887 
1888     struct cl_cvd *remote = NULL;
1889     int remote_is_cld     = 0;
1890 
1891     if ((NULL == database) || (NULL == server) || (NULL == remoteVersion) || (NULL == remoteFilename)) {
1892         logg("!query_remote_database_version: Invalid args!\n");
1893         goto done;
1894     }
1895 
1896     *remoteVersion  = 0;
1897     *remoteFilename = NULL;
1898 
1899     snprintf(cvdfile, sizeof(cvdfile), "%s.cvd", database);
1900     cvdfile[sizeof(cvdfile) - 1] = 0;
1901     snprintf(cldfile, sizeof(cldfile), "%s.cld", database);
1902     cldfile[sizeof(cldfile) - 1] = 0;
1903 
1904     if ((!bPrivateMirror) && (NULL != dnsUpdateInfo)) {
1905         /*
1906          * Use Primary DNS Update Info record to find the version.
1907          */
1908         int field              = 0;
1909         char *verStrDnsPrimary = NULL;
1910 
1911         if (0 == (field = textrecordfield(database))) {
1912             logg("*query_remote_database_version: Database name \"%s\" isn't listed in DNS update info.\n", database);
1913         } else if (NULL == (verStrDnsPrimary = cli_strtok(dnsUpdateInfo, field, ":"))) {
1914             logg("^Invalid DNS update info. Falling back to HTTP mode.\n");
1915         } else if (!cli_isnumber(verStrDnsPrimary)) {
1916             logg("^Broken database version in TXT record. Falling back to HTTP mode.\n");
1917         } else {
1918             newVersion = atoi(verStrDnsPrimary);
1919             logg("*query_remote_database_version: %s version from DNS: %d\n", cvdfile, newVersion);
1920         }
1921         free(verStrDnsPrimary);
1922 
1923 #ifdef HAVE_RESOLV_H
1924         if (newVersion == 0) {
1925             /*
1926              * Primary DNS Update Info record didn't have the version # for this database.
1927              * Try to use a <database>.cvd.clamav.net DNS query to find the version #.
1928              */
1929             size_t dnqueryDomainLen = strlen(database) + strlen(".cvd.clamav.net");
1930 
1931             dnqueryDomain = malloc(dnqueryDomainLen + 1);
1932             snprintf(dnqueryDomain, dnqueryDomainLen + 1, "%s.cvd.clamav.net", database);
1933             if (NULL == (extradnsreply = dnsquery(dnqueryDomain, T_TXT, NULL))) {
1934                 logg("^No timestamp in TXT record for %s\n", cvdfile);
1935             } else {
1936                 char *recordTimeStr  = NULL;
1937                 char *verStrDnsExtra = NULL;
1938 
1939                 if (NULL == (recordTimeStr = cli_strtok(extradnsreply, DNS_EXTRADBINFO_RECORDTIME, ":"))) {
1940                     logg("^No recordtime field in TXT record for %s\n", cvdfile);
1941                 } else {
1942                     int recordTime;
1943                     time_t currentTime;
1944 
1945                     recordTime = atoi(recordTimeStr);
1946                     free(recordTimeStr);
1947                     time(&currentTime);
1948                     if ((int)currentTime - recordTime > 10800) {
1949                         logg("^DNS record is older than 3 hours.\n");
1950                     } else if (NULL != (verStrDnsExtra = cli_strtok(extradnsreply, 0, ":"))) {
1951                         if (!cli_isnumber(verStrDnsExtra)) {
1952                             logg("^Broken database version in TXT record for %s\n", cvdfile);
1953                         } else {
1954                             newVersion = atoi(verStrDnsExtra);
1955                             logg("*%s version from DNS: %d\n", cvdfile, newVersion);
1956                         }
1957                         free(verStrDnsExtra);
1958                     } else {
1959                         logg("^Invalid DNS reply. Falling back to HTTP mode.\n");
1960                     }
1961                 }
1962             }
1963         }
1964 #endif
1965     }
1966 
1967     if (newVersion == 0) {
1968         /*
1969          * Was unable to use DNS info records to determine database version.
1970          * Use HTTP GET to get version info from CVD/CLD header.
1971          */
1972         if (bPrivateMirror) {
1973             /*
1974              * For a private mirror, get the CLD instead of the CVD.
1975              *
1976              * On the mirror, they should have CDIFFs/scripted/incremental
1977              * updates enabled, so they should have CLD's to distribute.
1978              */
1979             ret = remote_cvdhead(cldfile, ifModifiedSince, server, logerr, &remote);
1980             if ((FC_SUCCESS == ret) || (FC_UPTODATE == ret)) {
1981                 remote_is_cld = 1;
1982             } else {
1983                 /*
1984                  * Failed to get CLD update, and it's unknown if the status is up-to-date.
1985                  *
1986                  * If it's a relatively new mirror, the CLD won't have been replaced with a CVD yet.
1987                  * Attempt to get the CVD instead.
1988                  */
1989                 ret = remote_cvdhead(cvdfile, ifModifiedSince, server, logerr, &remote);
1990             }
1991         } else {
1992             /*
1993              * Official update servers will only have the CVD.
1994              */
1995             ret = remote_cvdhead(cvdfile, ifModifiedSince, server, logerr, &remote);
1996         }
1997 
1998         switch (ret) {
1999             case FC_SUCCESS: {
2000                 logg("*%s database version obtained using HTTP GET: %u\n", database, remote->version);
2001                 break;
2002             }
2003             case FC_UPTODATE: {
2004                 logg("*%s database version up-to-date, according to HTTP response code from server.\n", database);
2005                 status = FC_UPTODATE;
2006                 goto done;
2007             }
2008             default: {
2009                 logg("^Failed to get %s database version information from server: %s\n", database, server);
2010                 status = ret;
2011                 goto done;
2012             }
2013         }
2014 
2015         newVersion = remote->version;
2016     }
2017 
2018     if (remote_is_cld) {
2019         *remoteFilename = cli_strdup(cldfile);
2020     } else {
2021         *remoteFilename = cli_strdup(cvdfile);
2022     }
2023     *remoteVersion = newVersion;
2024 
2025     status = FC_SUCCESS;
2026 
2027 done:
2028 
2029     if (NULL != remote) {
2030         cl_cvdfree(remote);
2031     }
2032 #ifdef HAVE_RESOLV_H
2033     if (NULL != dnqueryDomain) {
2034         free(dnqueryDomain);
2035     }
2036     if (NULL != extradnsreply) {
2037         free(extradnsreply);
2038     }
2039 #endif
2040 
2041     return status;
2042 }
2043 
check_for_new_database_version(const char * database,const char * dnsUpdateInfo,char * server,int bPrivateMirror,int logerr,uint32_t * localVersion,uint32_t * remoteVersion,char ** localFilename,char ** remoteFilename,uint32_t * localTimestamp)2044 static fc_error_t check_for_new_database_version(
2045     const char *database,
2046     const char *dnsUpdateInfo,
2047     char *server,
2048     int bPrivateMirror,
2049     int logerr,
2050     uint32_t *localVersion,
2051     uint32_t *remoteVersion,
2052     char **localFilename,
2053     char **remoteFilename,
2054     uint32_t *localTimestamp)
2055 {
2056     fc_error_t ret;
2057     fc_error_t status = FC_EARG;
2058 
2059     char *localname               = NULL;
2060     struct cl_cvd *local_database = NULL;
2061     char *remotename              = NULL;
2062 
2063     uint32_t localver  = 0;
2064     uint32_t remotever = 0;
2065 
2066     if ((NULL == database) || (NULL == server) ||
2067         (NULL == localVersion) || (NULL == remoteVersion) ||
2068         (NULL == localFilename) || (NULL == remoteFilename) ||
2069         (NULL == localTimestamp)) {
2070         logg("!check_for_new_database_version: Invalid args!\n");
2071         goto done;
2072     }
2073 
2074     *localVersion   = 0;
2075     *remoteVersion  = 0;
2076     *localFilename  = NULL;
2077     *remoteFilename = NULL;
2078     *localTimestamp = 0;
2079 
2080     /*
2081      * Check local database version (if exists)
2082      */
2083     if (NULL == (local_database = currentdb(database, &localname))) {
2084         logg("*check_for_new_database_version: No local copy of \"%s\" database.\n", database);
2085     } else {
2086         logg("*check_for_new_database_version: Local copy of %s found: %s.\n", database, localname);
2087         *localTimestamp = local_database->stime;
2088         localver        = local_database->version;
2089     }
2090 
2091     /*
2092      * Look up the latest available database version.
2093      */
2094     ret = query_remote_database_version(
2095         database,
2096         *localTimestamp,
2097         dnsUpdateInfo,
2098         server,
2099         bPrivateMirror,
2100         logerr,
2101         &remotever,
2102         &remotename);
2103     switch (ret) {
2104         case FC_SUCCESS: {
2105             if (0 == localver) {
2106                 logg("%s database available for download (remote version: %d)\n",
2107                      database, remotever);
2108                 break;
2109             } else if (localver < remotever) {
2110                 logg("%s database available for update (local version: %d, remote version: %d)\n",
2111                      database, localver, remotever);
2112                 break;
2113             }
2114             /* fall-through */
2115         }
2116         case FC_UPTODATE: {
2117             if (NULL == local_database) {
2118                 logg("!check_for_new_database_version: server claims we're up-to-date, but we don't have a local database!\n");
2119                 status = FC_EFAILEDGET;
2120                 goto done;
2121             }
2122             logg("%s database is up-to-date (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
2123                  localname,
2124                  local_database->version,
2125                  local_database->sigs,
2126                  local_database->fl,
2127                  local_database->builder);
2128 
2129             /* The remote version wouldn't be set if the server returned "Not-Modified".
2130                We know it will be the same as the local version though. */
2131             remotever = localver;
2132             break;
2133         }
2134         case FC_EFORBIDDEN: {
2135             /* We tried to look up the version using HTTP and were actively blocked. */
2136             logg("!check_for_new_database_version: Blocked from using server %s.\n", server);
2137             status = FC_EFORBIDDEN;
2138             goto done;
2139         }
2140         default: {
2141             logg("!check_for_new_database_version: Failed to find %s database using server %s.\n", database, server);
2142             status = FC_EFAILEDGET;
2143             goto done;
2144         }
2145     }
2146 
2147     *remoteVersion = remotever;
2148     if (NULL != remotename) {
2149         *remoteFilename = cli_strdup(remotename);
2150         if (NULL == *remoteFilename) {
2151             logg("!check_for_new_database_version: Failed to allocate memory for remote filename.\n");
2152             status = FC_EMEM;
2153             goto done;
2154         }
2155     }
2156     if (NULL != localname) {
2157         *localVersion  = localver;
2158         *localFilename = cli_strdup(localname);
2159         if (NULL == *localFilename) {
2160             logg("!check_for_new_database_version: Failed to allocate memory for local filename.\n");
2161             status = FC_EMEM;
2162             goto done;
2163         }
2164     }
2165 
2166     status = FC_SUCCESS;
2167 
2168 done:
2169 
2170     if (NULL != localname) {
2171         free(localname);
2172     }
2173     if (NULL != remotename) {
2174         free(remotename);
2175     }
2176     if (NULL != local_database) {
2177         cl_cvdfree(local_database);
2178     }
2179 
2180     return status;
2181 }
2182 
updatedb(const char * database,const char * dnsUpdateInfo,char * server,int bPrivateMirror,void * context,int bScriptedUpdates,int logerr,int * signo,char ** dbFilename,int * bUpdated)2183 fc_error_t updatedb(
2184     const char *database,
2185     const char *dnsUpdateInfo,
2186     char *server,
2187     int bPrivateMirror,
2188     void *context,
2189     int bScriptedUpdates,
2190     int logerr,
2191     int *signo,
2192     char **dbFilename,
2193     int *bUpdated)
2194 {
2195     fc_error_t ret;
2196     fc_error_t status = FC_EARG;
2197 
2198     struct cl_cvd *cvd = NULL;
2199 
2200     uint32_t localTimestamp = 0;
2201     uint32_t localVersion   = 0;
2202     uint32_t remoteVersion  = 0;
2203     char *localFilename     = NULL;
2204     char *remoteFilename    = NULL;
2205     char *newLocalFilename  = NULL;
2206 
2207     char *tmpdir  = NULL;
2208     char *tmpfile = NULL;
2209 
2210     unsigned int flevel;
2211 
2212     unsigned int i, j;
2213 
2214     if ((NULL == database) || (NULL == server) || (NULL == signo) || (NULL == dbFilename) || (NULL == bUpdated)) {
2215         logg("!updatedb: Invalid args!\n");
2216         goto done;
2217     }
2218 
2219     *signo      = 0;
2220     *dbFilename = NULL;
2221     *bUpdated   = 0;
2222 
2223     /*
2224      * Check if new version exists.
2225      */
2226     if (FC_SUCCESS != (ret = check_for_new_database_version(
2227                            database,
2228                            dnsUpdateInfo,
2229                            server,
2230                            bPrivateMirror,
2231                            logerr,
2232                            &localVersion,
2233                            &remoteVersion,
2234                            &localFilename,
2235                            &remoteFilename,
2236                            &localTimestamp))) {
2237         logg("*updatedb: %s database update failed.\n", database);
2238         status = ret;
2239         goto done;
2240     }
2241 
2242     if ((localVersion >= remoteVersion) && (NULL != localFilename)) {
2243         *dbFilename = cli_strdup(localFilename);
2244         goto up_to_date;
2245     }
2246 
2247     /* Download CVD or CLD to temp file */
2248     tmpfile = cli_gentemp(g_tempDirectory);
2249     if (!tmpfile) {
2250         status = FC_EMEM;
2251         goto done;
2252     }
2253 
2254     if ((localVersion == 0) || (!bScriptedUpdates)) {
2255         /*
2256          * Download entire file.
2257          */
2258         ret = getcvd(remoteFilename, tmpfile, server, localTimestamp, remoteVersion, logerr);
2259         if (FC_UPTODATE == ret) {
2260             logg("^Expected newer version of %s database but the server's copy is not newer than our local file (version %d).\n", database, localVersion);
2261             if (NULL != localFilename) {
2262                 /* Received a 304 (not modified), must be up-to-date after all */
2263                 *dbFilename = cli_strdup(localFilename);
2264             }
2265             goto up_to_date;
2266         } else if (FC_EMIRRORNOTSYNC == ret) {
2267             /* Let's accept this older version, but keep the error code.
2268              * We'll have fc_update_database() retry using CDIFFs.
2269              */
2270             logg("*Received an older %s CVD than was advertised. We'll keep it and try updating to the latest version with CDIFFs.\n", database);
2271             status = ret;
2272         } else if (FC_SUCCESS != ret) {
2273             status = ret;
2274             goto done;
2275         }
2276 
2277         newLocalFilename = cli_strdup(remoteFilename);
2278     } else {
2279         /*
2280          * Attempt scripted/CDIFF incremental update.
2281          */
2282         ret                         = FC_SUCCESS;
2283         uint32_t numPatchesReceived = 0;
2284 
2285         tmpdir = cli_gentemp(g_tempDirectory);
2286         if (!tmpdir) {
2287             status = FC_EMEM;
2288             goto done;
2289         }
2290 
2291 #ifdef HAVE_UNISTD_H
2292         if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
2293 #else
2294         if (!mprintf_quiet)
2295 #endif
2296         {
2297             if (remoteVersion - localVersion == 1) {
2298                 mprintf("Current database is 1 version behind.\n");
2299             } else {
2300                 mprintf("Current database is %u versions behind.\n", remoteVersion - localVersion);
2301             }
2302         }
2303         for (i = localVersion + 1; i <= remoteVersion; i++) {
2304             for (j = 1; j <= g_maxAttempts; j++) {
2305                 int llogerr = logerr;
2306                 if (logerr)
2307                     llogerr = (j == g_maxAttempts);
2308 
2309 #ifdef HAVE_UNISTD_H
2310                 if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
2311 #else
2312                 if (!mprintf_quiet)
2313 #endif
2314                 {
2315                     mprintf("Downloading database patch # %u...\n", i);
2316                 }
2317                 ret = downloadPatch(database, tmpdir, i, server, llogerr);
2318                 if (ret == FC_ECONNECTION || ret == FC_EFAILEDGET) {
2319                     continue;
2320                 } else {
2321                     break;
2322                 }
2323             }
2324             if (FC_SUCCESS == ret) {
2325                 numPatchesReceived += 1;
2326             } else {
2327                 break;
2328             }
2329         }
2330 
2331         if (
2332             (FC_EEMPTYFILE == ret) ||                                 /* Request a new CVD if we got an empty CDIFF.      */
2333             (FC_SUCCESS != ret && (                                   /* Or if the incremental update failed:             */
2334                                    (0 == numPatchesReceived) &&       /* 1. Ask for the CVD if we didn't get any patches, */
2335                                    (localVersion < remoteVersion - 1) /* 2. AND if we're more than 1 version out of date. */
2336                                    ))) {
2337             /*
2338              * Incremental update failed or intentionally disabled.
2339              */
2340             if (ret == FC_EEMPTYFILE) {
2341                 logg("*Empty CDIFF found. Skip incremental updates for this version and download %s\n", remoteFilename);
2342             } else {
2343                 logg("^Incremental update failed, trying to download %s\n", remoteFilename);
2344             }
2345 
2346             ret = getcvd(remoteFilename, tmpfile, server, localTimestamp, remoteVersion, logerr);
2347             if (FC_SUCCESS != ret) {
2348                 if (FC_EMIRRORNOTSYNC == ret) {
2349                     /* Note: We can't retry with CDIFF's if FC_EMIRRORNOTSYNC happened here.
2350                      * If we did there could be an infinite loop.
2351                      * Best option is to accept the older CVD.
2352                      */
2353                     logg("^Received an older %s CVD than was advertised. Incremental updates either failed or are disabled, so we'll have to settle for a slightly out-of-date database.\n", database);
2354                     status = FC_SUCCESS;
2355                 } else {
2356                     status = ret;
2357                     goto done;
2358                 }
2359             }
2360 
2361             newLocalFilename = cli_strdup(remoteFilename);
2362         } else if (0 == numPatchesReceived) {
2363             logg("The database server doesn't have the latest patch for the %s database (version %u). The server will likely have updated if you check again in a few hours.\n", database, remoteVersion);
2364             goto up_to_date;
2365         } else {
2366             /*
2367              * CDIFFs downloaded; Use CDIFFs to turn old CVD/CLD into new updated CLD.
2368              */
2369             if (numPatchesReceived < remoteVersion - localVersion) {
2370                 logg("Downloaded %u patches for %s, which is fewer than the %u expected patches.\n", numPatchesReceived, database, remoteVersion - localVersion);
2371                 logg("We'll settle for this partial-update, at least for now.\n");
2372             }
2373 
2374             size_t newLocalFilenameLen = 0;
2375             if (FC_SUCCESS != buildcld(tmpdir, database, tmpfile, g_bCompressLocalDatabase)) {
2376                 logg("!updatedb: Incremental update failed. Failed to build CLD.\n");
2377                 status = FC_EFAILEDUPDATE;
2378                 goto done;
2379             }
2380 
2381             newLocalFilenameLen = strlen(database) + strlen(".cld");
2382             newLocalFilename    = malloc(newLocalFilenameLen + 1);
2383             snprintf(newLocalFilename, newLocalFilenameLen + 1, "%s.cld", database);
2384         }
2385     }
2386 
2387     /*
2388      * Update downloaded.
2389      * Test database before replacing original database with new database.
2390      */
2391     if (NULL != g_cb_download_complete) {
2392         char *tmpfile_with_extension      = NULL;
2393         size_t tmpfile_with_extension_len = strlen(tmpfile) + 1 + strlen(newLocalFilename);
2394 
2395         /* Suffix tmpfile with real database name & extension so it can be loaded. */
2396         tmpfile_with_extension = malloc(tmpfile_with_extension_len + 1);
2397         if (!tmpfile_with_extension) {
2398             status = FC_ETESTFAIL;
2399             goto done;
2400         }
2401         snprintf(tmpfile_with_extension, tmpfile_with_extension_len + 1, "%s-%s", tmpfile, newLocalFilename);
2402         if (rename(tmpfile, tmpfile_with_extension) == -1) {
2403             logg("!updatedb: Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno));
2404             free(tmpfile_with_extension);
2405             status = FC_EDBDIRACCESS;
2406             goto done;
2407         }
2408         free(tmpfile);
2409         tmpfile                = tmpfile_with_extension;
2410         tmpfile_with_extension = NULL;
2411 
2412         /* Run callback to test it. */
2413         logg("*updatedb: Running g_cb_download_complete callback...\n");
2414         if (FC_SUCCESS != (ret = g_cb_download_complete(tmpfile, context))) {
2415             logg("*updatedb: callback failed: %s (%d)\n", fc_strerror(ret), ret);
2416             status = ret;
2417             goto done;
2418         }
2419     }
2420 
2421     /*
2422      * Replace original database with new database.
2423      */
2424 #ifdef _WIN32
2425     if (!access(newLocalFilename, R_OK) && unlink(newLocalFilename)) {
2426         logg("!Update failed. Can't delete the old database %s to replace it with a new database. Please fix the problem manually and try again.\n", newLocalFilename);
2427         status = FC_EDBDIRACCESS;
2428         goto done;
2429     }
2430 #endif
2431     if (rename(tmpfile, newLocalFilename) == -1) {
2432         logg("!updatedb: Can't rename %s to %s: %s\n", tmpfile, newLocalFilename, strerror(errno));
2433         status = FC_EDBDIRACCESS;
2434         goto done;
2435     }
2436 
2437     /* If we just updated from a CVD to a CLD, delete the old CVD */
2438     if ((NULL != localFilename) && !access(localFilename, R_OK) && strcmp(newLocalFilename, localFilename))
2439         if (unlink(localFilename))
2440             logg("^updatedb: Can't delete the old database file %s. Please remove it manually.\n", localFilename);
2441 
2442     /* Parse header to record number of sigs. */
2443     if (NULL == (cvd = cl_cvdhead(newLocalFilename))) {
2444         logg("!updatedb: Can't parse new database %s\n", newLocalFilename);
2445         status = FC_EFILE;
2446         goto done;
2447     }
2448 
2449     logg("%s updated (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
2450          newLocalFilename, cvd->version, cvd->sigs, cvd->fl, cvd->builder);
2451 
2452     flevel = cl_retflevel();
2453     if (flevel < cvd->fl) {
2454         logg("^Your ClamAV installation is OUTDATED!\n");
2455         logg("^Current functionality level = %d, recommended = %d\n", flevel, cvd->fl);
2456         logg("DON'T PANIC! Read https://docs.clamav.net/manual/Installing.html\n");
2457     }
2458 
2459     *signo      = cvd->sigs;
2460     *bUpdated   = 1;
2461     *dbFilename = cli_strdup(newLocalFilename);
2462     if (NULL == *dbFilename) {
2463         logg("!updatedb: Failed to allocate memory for database filename.\n");
2464         status = FC_EMEM;
2465         goto done;
2466     }
2467 
2468 up_to_date:
2469 
2470     if (status != FC_EMIRRORNOTSYNC) {
2471         status = FC_SUCCESS;
2472     }
2473 
2474 done:
2475 
2476     if (NULL != cvd) {
2477         cl_cvdfree(cvd);
2478     }
2479 
2480     if (NULL != localFilename) {
2481         free(localFilename);
2482     }
2483     if (NULL != remoteFilename) {
2484         free(remoteFilename);
2485     }
2486     if (NULL != newLocalFilename) {
2487         free(newLocalFilename);
2488     }
2489 
2490     if (NULL != tmpfile) {
2491         unlink(tmpfile);
2492         free(tmpfile);
2493     }
2494     if (NULL != tmpdir) {
2495         cli_rmdirs(tmpdir);
2496         free(tmpdir);
2497     }
2498 
2499     return status;
2500 }
2501 
updatecustomdb(const char * url,void * context,int logerr,int * signo,char ** dbFilename,int * bUpdated)2502 fc_error_t updatecustomdb(
2503     const char *url,
2504     void *context,
2505     int logerr,
2506     int *signo,
2507     char **dbFilename,
2508     int *bUpdated)
2509 {
2510     fc_error_t ret;
2511     fc_error_t status = FC_EARG;
2512 
2513     unsigned int sigs = 0;
2514     char *tmpfile     = NULL;
2515     const char *databaseName;
2516     STATBUF statbuf;
2517     time_t dbtime = 0;
2518 
2519     if ((NULL == url) || (NULL == signo) || (NULL == dbFilename) || (NULL == bUpdated)) {
2520         logg("!updatecustomdb: Invalid args!\n");
2521         goto done;
2522     }
2523 
2524     *signo      = 0;
2525     *dbFilename = NULL;
2526     *bUpdated   = 0;
2527 
2528     tmpfile = cli_gentemp(g_tempDirectory);
2529     if (!tmpfile) {
2530         status = FC_EFAILEDUPDATE;
2531         goto done;
2532     }
2533 
2534     if (!strncasecmp(url, "file://", strlen("file://"))) {
2535         /*
2536          * Copy from local file.
2537          */
2538         time_t remote_dbtime;
2539         const char *rpath;
2540 
2541         rpath = &url[strlen("file://")];
2542 #ifdef _WIN32
2543         databaseName = strrchr(rpath, '\\');
2544 #else
2545         databaseName = strrchr(rpath, '/');
2546 #endif
2547         if ((NULL == databaseName) || strlen(databaseName++) < strlen(".ext") + 1) {
2548             logg("DatabaseCustomURL: Incorrect URL\n");
2549             status = FC_EFAILEDUPDATE;
2550             goto done;
2551         }
2552 
2553         if (CLAMSTAT(rpath, &statbuf) == -1) {
2554             logg("DatabaseCustomURL: file %s missing\n", rpath);
2555             status = FC_EFAILEDUPDATE;
2556             goto done;
2557         }
2558         remote_dbtime = statbuf.st_mtime;
2559         dbtime        = (CLAMSTAT(databaseName, &statbuf) != -1) ? statbuf.st_mtime : 0;
2560         if (dbtime > remote_dbtime) {
2561             logg("%s is up-to-date (version: custom database)\n", databaseName);
2562             goto up_to_date;
2563         }
2564 
2565         /* FIXME: preserve file permissions, calculate % */
2566         if (-1 == cli_filecopy(rpath, tmpfile)) {
2567             logg("DatabaseCustomURL: Can't copy file %s into database directory\n", rpath);
2568             status = FC_EFAILEDUPDATE;
2569             goto done;
2570         }
2571 
2572         logg("Downloading %s [100%%]\n", databaseName);
2573     } else {
2574         /*
2575          * Download from URL.  http(s) or ftp(s)
2576          */
2577         databaseName = strrchr(url, '/');
2578         if ((NULL == databaseName) || (strlen(databaseName++) < 5)) {
2579             logg("DatabaseCustomURL: Incorrect URL\n");
2580             status = FC_EFAILEDUPDATE;
2581             goto done;
2582         }
2583 
2584         dbtime = (CLAMSTAT(databaseName, &statbuf) != -1) ? statbuf.st_mtime : 0;
2585 
2586         ret = downloadFile(url, tmpfile, 1, logerr, dbtime);
2587         if (ret == FC_UPTODATE) {
2588             logg("%s is up-to-date (version: custom database)\n", databaseName);
2589             goto up_to_date;
2590         } else if (ret > FC_UPTODATE) {
2591             logg("%cCan't download %s from %s\n", logerr ? '!' : '^', databaseName, url);
2592             status = ret;
2593             goto done;
2594         }
2595     }
2596 
2597     /*
2598      * Update downloaded.
2599      * Test database before replacing original database with new database.
2600      */
2601     if (NULL != g_cb_download_complete) {
2602         char *tmpfile_with_extension      = NULL;
2603         size_t tmpfile_with_extension_len = strlen(tmpfile) + 1 + strlen(databaseName);
2604 
2605         /* Suffix tmpfile with real database name & extension so it can be loaded. */
2606         tmpfile_with_extension = malloc(tmpfile_with_extension_len + 1);
2607         if (!tmpfile_with_extension) {
2608             status = FC_ETESTFAIL;
2609             goto done;
2610         }
2611         snprintf(tmpfile_with_extension, tmpfile_with_extension_len + 1, "%s-%s", tmpfile, databaseName);
2612         if (rename(tmpfile, tmpfile_with_extension) == -1) {
2613             logg("!Custom database update failed: Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno));
2614             free(tmpfile_with_extension);
2615             status = FC_EDBDIRACCESS;
2616             goto done;
2617         }
2618         free(tmpfile);
2619         tmpfile                = tmpfile_with_extension;
2620         tmpfile_with_extension = NULL;
2621 
2622         /* Run callback to test it. */
2623         logg("*updatecustomdb: Running g_cb_download_complete callback...\n");
2624         if (FC_SUCCESS != (ret = g_cb_download_complete(tmpfile, context))) {
2625             logg("*updatecustomdb: callback failed: %s (%d)\n", fc_strerror(ret), ret);
2626             status = ret;
2627             goto done;
2628         }
2629     }
2630 
2631     /*
2632      * Replace original database with new database.
2633      */
2634 #ifdef _WIN32
2635     if (!access(databaseName, R_OK) && unlink(databaseName)) {
2636         logg("!Custom database update failed. Can't delete the old database %s to replace it with a new database. Please fix the problem manually and try again.\n", databaseName);
2637         status = FC_EDBDIRACCESS;
2638         goto done;
2639     }
2640 #endif
2641     if (rename(tmpfile, databaseName) == -1) {
2642         logg("!updatecustomdb: Can't rename %s to %s: %s\n", tmpfile, databaseName, strerror(errno));
2643         status = FC_EDBDIRACCESS;
2644         goto done;
2645     }
2646 
2647     /*
2648      * Record # of signatures in updated database.
2649      */
2650     if (cli_strbcasestr(databaseName, ".cld") || cli_strbcasestr(databaseName, ".cvd")) {
2651         struct cl_cvd *cvd = NULL;
2652         unsigned int flevel;
2653 
2654         if (NULL == (cvd = cl_cvdhead(databaseName))) {
2655             logg("!updatecustomdb: Can't parse new database %s\n", databaseName);
2656             status = FC_EFILE;
2657             goto done;
2658         }
2659 
2660         sigs = cvd->sigs;
2661 
2662         flevel = cl_retflevel();
2663         if (flevel < cvd->fl) {
2664             logg("^Your ClamAV installation is OUTDATED!\n");
2665             logg("^Current functionality level = %d, recommended = %d\n", flevel, cvd->fl);
2666             logg("DON'T PANIC! Read https://docs.clamav.net/manual/Installing.html\n");
2667         }
2668 
2669         cl_cvdfree(cvd);
2670     } else if (cli_strbcasestr(databaseName, ".cbc")) {
2671         sigs = 1;
2672     } else {
2673         sigs = countlines(databaseName);
2674     }
2675 
2676     logg("%s updated (version: custom database, sigs: %u)\n", databaseName, sigs);
2677     *signo    = sigs;
2678     *bUpdated = 1;
2679 
2680 up_to_date:
2681 
2682     *dbFilename = cli_strdup(databaseName);
2683     if (NULL == *dbFilename) {
2684         logg("!Failed to allocate memory for database filename.\n");
2685         status = FC_EMEM;
2686         goto done;
2687     }
2688 
2689     status = FC_SUCCESS;
2690 
2691 done:
2692 
2693     if (NULL != tmpfile) {
2694         unlink(tmpfile);
2695         free(tmpfile);
2696     }
2697 
2698     return status;
2699 }
2700