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(¤tTime);
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