2700 lines
86 KiB
C
2700 lines
86 KiB
C
/*
|
|
* Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
|
* Copyright (C) 2007-2013 Sourcefire, Inc.
|
|
* Copyright (C) 2002-2007 Tomasz Kojm <tkojm@clamav.net>
|
|
*
|
|
* HTTP/1.1 compliance by Arkadiusz Miskiewicz <misiek@pld.org.pl>
|
|
* Proxy support by Nigel Horne <njh@bandsman.co.uk>
|
|
* Proxy authorization support by Gernot Tenchio <g.tenchio@telco-tech.de>
|
|
* (uses fmt_base64() from libowfat (http://www.fefe.de))
|
|
*
|
|
* CDIFF code (C) 2006 Sensory Networks, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "clamav-config.h"
|
|
#endif
|
|
|
|
/* for strptime, it is POSIX, but defining _XOPEN_SOURCE to 600
|
|
* fails on Solaris because it would require a c99 compiler,
|
|
* 500 fails completely on Solaris, and FreeBSD, and w/o _XOPEN_SOURCE
|
|
* strptime is not defined on Linux */
|
|
#define __EXTENSIONS
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <string.h>
|
|
#ifdef HAVE_STRINGS_H
|
|
#include <strings.h>
|
|
#endif
|
|
#include <ctype.h>
|
|
#ifndef _WIN32
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#ifndef _WIN32
|
|
#include <sys/wait.h>
|
|
#endif
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <zlib.h>
|
|
#include <math.h>
|
|
|
|
#include <curl/curl.h>
|
|
#include <openssl/rand.h>
|
|
|
|
#include "target.h"
|
|
|
|
// libclamav
|
|
#include "clamav.h"
|
|
#include "others.h"
|
|
#include "str.h"
|
|
#include "cvd.h"
|
|
#include "regex_list.h"
|
|
|
|
// shared
|
|
#include "optparser.h"
|
|
#include "output.h"
|
|
#include "cdiff.h"
|
|
#include "tar.h"
|
|
#include "clamdcom.h"
|
|
#include "cert_util.h"
|
|
|
|
#include "libfreshclam.h"
|
|
#include "libfreshclam_internal.h"
|
|
#include "dns.h"
|
|
|
|
#define DB_FILENAME_MAX 60
|
|
#define CVD_HEADER_SIZE 512
|
|
|
|
/*
|
|
* Globals
|
|
*/
|
|
/* Callback function pointers */
|
|
fccb_download_complete g_cb_download_complete = NULL;
|
|
|
|
/* Configuration options */
|
|
char *g_localIP = NULL;
|
|
char *g_userAgent = NULL;
|
|
|
|
char *g_proxyServer = NULL;
|
|
uint16_t g_proxyPort = 0;
|
|
char *g_proxyUsername = NULL;
|
|
char *g_proxyPassword = NULL;
|
|
|
|
char *g_tempDirectory = NULL;
|
|
char *g_databaseDirectory = NULL;
|
|
|
|
uint32_t g_maxAttempts = 0;
|
|
uint32_t g_connectTimeout = 0;
|
|
uint32_t g_requestTimeout = 0;
|
|
|
|
uint32_t g_bCompressLocalDatabase = 0;
|
|
|
|
freshclam_dat_v1_t *g_freshclamDat = NULL;
|
|
|
|
/** @brief Generate a Version 4 UUID according to RFC-4122
|
|
*
|
|
* Uses the openssl RAND_bytes function to generate a Version 4 UUID.
|
|
*
|
|
* Copyright 2021 Karthik Velakur with some modifications by the ClamAV team.
|
|
* License: MIT
|
|
* From: https://gist.github.com/kvelakur/9069c9896577c3040030
|
|
*
|
|
* @param buffer A buffer that is SIZEOF_UUID_V4
|
|
*/
|
|
static void uuid_v4_gen(char *buffer)
|
|
{
|
|
union {
|
|
struct
|
|
{
|
|
uint32_t time_low;
|
|
uint16_t time_mid;
|
|
uint16_t time_hi_and_version;
|
|
uint8_t clk_seq_hi_res;
|
|
uint8_t clk_seq_low;
|
|
uint8_t node[6];
|
|
};
|
|
uint8_t __rnd[16];
|
|
} uuid;
|
|
|
|
if (0 >= RAND_bytes(uuid.__rnd, sizeof(uuid.__rnd))) {
|
|
/* Failed to generate random bytes for new UUID */
|
|
memset(uuid.__rnd, 0, sizeof(uuid.__rnd));
|
|
uuid.time_low = (uint32_t)time(NULL);
|
|
}
|
|
|
|
// Refer Section 4.2 of RFC-4122
|
|
// https://tools.ietf.org/html/rfc4122#section-4.2
|
|
uuid.clk_seq_hi_res = (uint8_t)((uuid.clk_seq_hi_res & 0x3F) | 0x80);
|
|
uuid.time_hi_and_version = (uint16_t)((uuid.time_hi_and_version & 0x0FFF) | 0x4000);
|
|
|
|
snprintf(buffer, SIZEOF_UUID_V4, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
uuid.time_low, uuid.time_mid, uuid.time_hi_and_version,
|
|
uuid.clk_seq_hi_res, uuid.clk_seq_low,
|
|
uuid.node[0], uuid.node[1], uuid.node[2],
|
|
uuid.node[3], uuid.node[4], uuid.node[5]);
|
|
buffer[SIZEOF_UUID_V4 - 1] = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
fc_error_t load_freshclam_dat(void)
|
|
{
|
|
fc_error_t status = FC_EINIT;
|
|
int handle = -1;
|
|
ssize_t bread = 0;
|
|
freshclam_dat_v1_t *mdat = NULL;
|
|
uint32_t version = 0;
|
|
char magic[13] = {0};
|
|
|
|
/* Change directory to database directory */
|
|
if (chdir(g_databaseDirectory)) {
|
|
logg("!Can't change dir to %s\n", g_databaseDirectory);
|
|
status = FC_EDIRECTORY;
|
|
goto done;
|
|
}
|
|
logg("*Current working dir is %s\n", g_databaseDirectory);
|
|
|
|
if (-1 == (handle = open("freshclam.dat", O_RDONLY | O_BINARY))) {
|
|
char currdir[PATH_MAX];
|
|
|
|
if (getcwd(currdir, sizeof(currdir)))
|
|
logg("*Can't open freshclam.dat in %s\n", currdir);
|
|
else
|
|
logg("*Can't open freshclam.dat in the current directory\n");
|
|
|
|
logg("*It probably doesn't exist yet. That's ok.\n");
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
|
|
if (strlen(MIRRORS_DAT_MAGIC) != (bread = read(handle, &magic, strlen(MIRRORS_DAT_MAGIC)))) {
|
|
char error_message[260];
|
|
cli_strerror(errno, error_message, 260);
|
|
logg("!Can't read magic from freshclam.dat. Bytes read: %zi, error: %s\n", bread, error_message);
|
|
goto done;
|
|
}
|
|
if (0 != strncmp(magic, MIRRORS_DAT_MAGIC, strlen(MIRRORS_DAT_MAGIC))) {
|
|
logg("*Magic bytes for freshclam.dat did not match expectations.\n");
|
|
goto done;
|
|
}
|
|
|
|
if (sizeof(uint32_t) != (bread = read(handle, &version, sizeof(uint32_t)))) {
|
|
char error_message[260];
|
|
cli_strerror(errno, error_message, 260);
|
|
logg("!Can't read version from freshclam.dat. Bytes read: %zi, error: %s\n", bread, error_message);
|
|
goto done;
|
|
}
|
|
|
|
switch (version) {
|
|
case 1: {
|
|
/* Verify that file size is as expected. */
|
|
off_t file_size = lseek(handle, 0L, SEEK_END);
|
|
|
|
if (strlen(MIRRORS_DAT_MAGIC) + sizeof(freshclam_dat_v1_t) != (size_t)file_size) {
|
|
logg("*freshclam.dat is bigger than expected: %zu != %ld\n", sizeof(freshclam_dat_v1_t), file_size);
|
|
goto done;
|
|
}
|
|
|
|
/* Rewind to just after the magic bytes and read data struct */
|
|
lseek(handle, strlen(MIRRORS_DAT_MAGIC), SEEK_SET);
|
|
|
|
mdat = malloc(sizeof(freshclam_dat_v1_t));
|
|
if (NULL == mdat) {
|
|
logg("!Failed to allocate memory for freshclam.dat\n");
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
|
|
if (sizeof(freshclam_dat_v1_t) != (bread = read(handle, mdat, sizeof(freshclam_dat_v1_t)))) {
|
|
char error_message[260];
|
|
cli_strerror(errno, error_message, 260);
|
|
logg("!Can't read from freshclam.dat. Bytes read: %zi, error: %s\n", bread, error_message);
|
|
goto done;
|
|
}
|
|
|
|
/* Got it. */
|
|
close(handle);
|
|
handle = -1;
|
|
|
|
/* This is the latest version.
|
|
If we change the format in the future, we may wish to create a new
|
|
freshclam.dat struct, import the relevant bits to the new format,
|
|
and then save (overwrite) freshclam.dat with the new data. */
|
|
if (NULL != g_freshclamDat) {
|
|
free(g_freshclamDat);
|
|
}
|
|
g_freshclamDat = mdat;
|
|
mdat = NULL;
|
|
break;
|
|
}
|
|
default: {
|
|
logg("*freshclam.dat version is different than expected: %u != %u\n", 1, version);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
logg("*Loaded freshclam.dat:\n");
|
|
logg("* version: %d\n", g_freshclamDat->version);
|
|
logg("* uuid: %s\n", g_freshclamDat->uuid);
|
|
if (g_freshclamDat->retry_after > 0) {
|
|
char retry_after_string[26];
|
|
struct tm *tm_info = localtime(&g_freshclamDat->retry_after);
|
|
if (NULL == tm_info) {
|
|
logg("!Failed to query the local time for the retry-after date!\n");
|
|
goto done;
|
|
}
|
|
strftime(retry_after_string, 26, "%Y-%m-%d %H:%M:%S", tm_info);
|
|
logg("* retry-after: %s\n", retry_after_string);
|
|
}
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
if (-1 != handle) {
|
|
close(handle);
|
|
}
|
|
if (FC_SUCCESS != status) {
|
|
if (NULL != mdat) {
|
|
free(mdat);
|
|
}
|
|
if (NULL != g_freshclamDat) {
|
|
free(g_freshclamDat);
|
|
g_freshclamDat = NULL;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
fc_error_t save_freshclam_dat(void)
|
|
{
|
|
fc_error_t status = FC_EINIT;
|
|
int handle = -1;
|
|
|
|
if (NULL == g_freshclamDat) {
|
|
logg("!Attempted to save freshclam.dat before initializing data struct!\n");
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == (handle = open("freshclam.dat", O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644))) {
|
|
char currdir[PATH_MAX];
|
|
|
|
if (getcwd(currdir, sizeof(currdir)))
|
|
logg("!Can't create freshclam.dat in %s\n", currdir);
|
|
else
|
|
logg("!Can't create freshclam.dat in the current directory\n");
|
|
|
|
logg("Hint: The database directory must be writable for UID %d or GID %d\n", getuid(), getgid());
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
if (-1 == write(handle, MIRRORS_DAT_MAGIC, strlen(MIRRORS_DAT_MAGIC))) {
|
|
logg("!Can't write to freshclam.dat\n");
|
|
}
|
|
if (-1 == write(handle, g_freshclamDat, sizeof(freshclam_dat_v1_t))) {
|
|
logg("!Can't write to freshclam.dat\n");
|
|
}
|
|
|
|
logg("*Saved freshclam.dat\n");
|
|
|
|
status = FC_SUCCESS;
|
|
done:
|
|
if (-1 != handle) {
|
|
close(handle);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
fc_error_t new_freshclam_dat(void)
|
|
{
|
|
fc_error_t status = FC_EINIT;
|
|
|
|
freshclam_dat_v1_t *mdat = calloc(1, sizeof(freshclam_dat_v1_t));
|
|
if (NULL == mdat) {
|
|
logg("!Failed to allocate memory for freshclam.dat\n");
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
|
|
mdat->version = 1;
|
|
mdat->retry_after = 0;
|
|
uuid_v4_gen(mdat->uuid);
|
|
|
|
if (NULL != g_freshclamDat) {
|
|
free(g_freshclamDat);
|
|
}
|
|
g_freshclamDat = mdat;
|
|
|
|
logg("*Creating new freshclam.dat\n");
|
|
|
|
if (FC_SUCCESS != save_freshclam_dat()) {
|
|
logg("!Failed to save freshclam.dat!\n");
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
if (FC_SUCCESS != status) {
|
|
if (NULL != mdat) {
|
|
free(mdat);
|
|
}
|
|
g_freshclamDat = NULL;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Get DNS text record field # for official databases.
|
|
*
|
|
* @param database Official database name.
|
|
* @return int DNS text record field #
|
|
*/
|
|
static int textrecordfield(const char *database)
|
|
{
|
|
if (!strcmp(database, "main")) {
|
|
return 1;
|
|
} else if (!strcmp(database, "daily")) {
|
|
return 2;
|
|
} else if (!strcmp(database, "bytecode")) {
|
|
return 7;
|
|
} else if (!strcmp(database, "safebrowsing")) {
|
|
return 6;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 61))
|
|
/* In libcurl 7.61.0, support was added for extracting the time in plain
|
|
microseconds. Older libcurl versions are stuck in using 'double' for this
|
|
information so we complicate this example a bit by supporting either
|
|
approach. */
|
|
#define TIME_IN_US 1
|
|
#define TIMETYPE curl_off_t
|
|
#define TIMEOPT CURLINFO_TOTAL_TIME_T
|
|
#define MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL 3000000
|
|
#else
|
|
#define TIMETYPE double
|
|
#define TIMEOPT CURLINFO_TOTAL_TIME
|
|
#define MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL 3
|
|
#endif
|
|
|
|
#define STOP_DOWNLOAD_AFTER_THIS_MANY_BYTES 6000
|
|
|
|
struct xfer_progress {
|
|
TIMETYPE lastRunTime; /* type depends on version, see above */
|
|
uint8_t bComplete;
|
|
CURL *curl;
|
|
};
|
|
|
|
static void printTime(double seconds)
|
|
{
|
|
if (seconds >= 3600) {
|
|
fprintf(stdout, "%2.0fh %02.0fm", trunc(seconds / 3600), trunc(fmod(seconds, 3600.0) / 60));
|
|
} else if (seconds >= 60) {
|
|
fprintf(stdout, "%2.0fm %02.0fs", trunc(seconds / 60), trunc(fmod(seconds, 60.0)));
|
|
} else {
|
|
fprintf(stdout, "%6.1fs", seconds);
|
|
}
|
|
}
|
|
|
|
static void printBytes(curl_off_t bytes, int bPad)
|
|
{
|
|
if (bytes >= (1024 * 1024)) {
|
|
const char *format = bPad ? "%7.02fMiB" : "%.02fMiB";
|
|
double megabytes = bytes / (double)(1024 * 1024);
|
|
fprintf(stdout, format, megabytes);
|
|
} else if (bytes >= 1024) {
|
|
const char *format = bPad ? "%7.02fKiB" : "%.02fKiB";
|
|
double kilobytes = bytes / (double)(1024);
|
|
fprintf(stdout, format, kilobytes);
|
|
} else {
|
|
const char *format = bPad ? "%9" CURL_FORMAT_CURL_OFF_T "B" : "%" CURL_FORMAT_CURL_OFF_T "B";
|
|
fprintf(stdout, format, bytes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function from curl example code, Copyright (C) 1998 - 2018, Daniel Stenberg, see COPYING.curl for license details
|
|
* Progress bar callback function ( CURLOPT_XFERINFOFUNCTION ).
|
|
*/
|
|
static int xferinfo(void *prog,
|
|
curl_off_t TotalToDownload, curl_off_t NowDownloaded,
|
|
curl_off_t TotalToUpload, curl_off_t NowUploaded)
|
|
{
|
|
struct xfer_progress *xferProg = (struct xfer_progress *)prog;
|
|
CURL *curl = xferProg->curl;
|
|
TIMETYPE curtime = 0;
|
|
TIMETYPE remtime = 0;
|
|
|
|
uint32_t i = 0;
|
|
uint32_t totalNumDots = 25;
|
|
uint32_t numDots = 0;
|
|
double fractiondownloaded = 0.0;
|
|
|
|
UNUSEDPARAM(TotalToUpload);
|
|
UNUSEDPARAM(NowUploaded);
|
|
|
|
if ((TotalToDownload <= 0.0) || (xferProg->bComplete)) {
|
|
return 0;
|
|
}
|
|
|
|
fractiondownloaded = (double)NowDownloaded / (double)TotalToDownload;
|
|
numDots = round(fractiondownloaded * totalNumDots);
|
|
|
|
curl_easy_getinfo(curl, TIMEOPT, &curtime);
|
|
|
|
xferProg->lastRunTime = curtime;
|
|
|
|
#ifndef _WIN32
|
|
fprintf(stdout, "\e[?7l");
|
|
#endif
|
|
#ifdef TIME_IN_US
|
|
if (fractiondownloaded <= 0.0) {
|
|
fprintf(stdout, "Time: ");
|
|
printTime(curtime / 1000000.0);
|
|
fprintf(stdout, " ");
|
|
} else {
|
|
remtime = (curtime / fractiondownloaded) - curtime;
|
|
fprintf(stdout, "Time: ");
|
|
printTime(curtime / 1000000.0);
|
|
fprintf(stdout, ", ETA: ");
|
|
printTime(remtime / 1000000.0);
|
|
fprintf(stdout, " ");
|
|
}
|
|
#else
|
|
if (fractiondownloaded <= 0.0) {
|
|
fprintf(stdout, "Time: ");
|
|
printTime(curtime);
|
|
fprintf(stdout, " ");
|
|
} else {
|
|
remtime = (curtime / fractiondownloaded) - curtime;
|
|
fprintf(stdout, "Time: ");
|
|
printTime(curtime);
|
|
fprintf(stdout, ", ETA: ");
|
|
printTime(remtime);
|
|
fprintf(stdout, " ");
|
|
}
|
|
#endif
|
|
|
|
fprintf(stdout, "[");
|
|
if (numDots > 0) {
|
|
if (numDots > 1) {
|
|
for (i = 0; i < numDots - 1; i++) {
|
|
fprintf(stdout, "=");
|
|
}
|
|
}
|
|
fprintf(stdout, ">");
|
|
i++;
|
|
}
|
|
for (; i < totalNumDots; i++) {
|
|
fprintf(stdout, " ");
|
|
}
|
|
|
|
fprintf(stdout, "] ");
|
|
|
|
printBytes(NowDownloaded, 1);
|
|
fprintf(stdout, "/");
|
|
printBytes(TotalToDownload, 0);
|
|
|
|
if (NowDownloaded < TotalToDownload) {
|
|
fprintf(stdout, "\r");
|
|
} else {
|
|
fprintf(stdout, "\n");
|
|
xferProg->bComplete = 1;
|
|
}
|
|
#ifndef _WIN32
|
|
fprintf(stdout, "\e[?7h");
|
|
#endif
|
|
fflush(stdout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if (LIBCURL_VERSION_MAJOR < 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR < 32))
|
|
/**
|
|
* Function from curl example code, Copyright (C) 1998 - 2018, Daniel Stenberg, see COPYING.curl for license details
|
|
* Older style progress bar callback shim; for libcurl older than 7.32.0 ( CURLOPT_PROGRESSFUNCTION ).
|
|
*/
|
|
static int older_progress(void *prog,
|
|
double TotalToDownload, double NowDownloaded,
|
|
double TotalToUpload, double NowUploaded)
|
|
{
|
|
return xferinfo(prog,
|
|
(curl_off_t)TotalToDownload,
|
|
(curl_off_t)NowDownloaded,
|
|
(curl_off_t)TotalToUpload,
|
|
(curl_off_t)NowUploaded);
|
|
}
|
|
#endif
|
|
|
|
static fc_error_t create_curl_handle(
|
|
int bHttp,
|
|
int bAllowRedirect,
|
|
CURL **curlHandle)
|
|
{
|
|
fc_error_t status = FC_EARG;
|
|
|
|
CURL *curl = NULL;
|
|
|
|
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 33))
|
|
CURLcode curl_ret = CURLE_OK;
|
|
#endif
|
|
|
|
char userAgent[128];
|
|
|
|
if (NULL == curlHandle) {
|
|
logg("!create_curl_handle: Invalid arguments!\n");
|
|
goto done;
|
|
}
|
|
|
|
*curlHandle = NULL;
|
|
|
|
curl = curl_easy_init();
|
|
if (NULL == curl) {
|
|
logg("!create_curl_handle: curl_easy_init failed!\n");
|
|
status = FC_EINIT;
|
|
goto done;
|
|
}
|
|
|
|
if (g_userAgent) {
|
|
strncpy(userAgent, g_userAgent, sizeof(userAgent));
|
|
} else {
|
|
/*
|
|
* Use a randomly generated UUID in the User-Agent
|
|
* We'll try to load it from a file in the database directory.
|
|
* If none exists, we'll create a new one and save it to said file.
|
|
*/
|
|
snprintf(userAgent, sizeof(userAgent),
|
|
PACKAGE "/%s (OS: " TARGET_OS_TYPE ", ARCH: " TARGET_ARCH_TYPE ", CPU: " TARGET_CPU_TYPE ", UUID: %s)",
|
|
get_version(),
|
|
g_freshclamDat->uuid);
|
|
}
|
|
userAgent[sizeof(userAgent) - 1] = 0;
|
|
|
|
if (mprintf_verbose) {
|
|
/* ask libcurl to show us the verbose output */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_VERBOSE!\n");
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_STDERR, stdout)) {
|
|
logg("!create_curl_handle: Failed to direct curl debug output to stdout!\n");
|
|
}
|
|
}
|
|
|
|
if (bHttp) {
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_USERAGENT (%s)!\n", userAgent);
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, g_connectTimeout)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_CONNECTTIMEOUT (%u)!\n", g_connectTimeout);
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMEOUT, g_requestTimeout)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_TIMEOUT (%u)!\n", g_requestTimeout);
|
|
}
|
|
|
|
if (bAllowRedirect) {
|
|
/* allow three redirects */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_FOLLOWLOCATION!\n");
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_MAXREDIRS!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 33))
|
|
if (g_localIP) {
|
|
if (NULL == strchr(g_localIP, ':')) {
|
|
logg("*Local IPv4 address requested: %s\n", g_localIP);
|
|
curl_ret = curl_easy_setopt(curl, CURLOPT_DNS_LOCAL_IP4, g_localIP); // Option requires libcurl built with c-ares
|
|
switch (curl_ret) {
|
|
case CURLE_BAD_FUNCTION_ARGUMENT:
|
|
logg("!create_curl_handle: Unable to bind DNS resolves to %s. Invalid IPv4 address.\n", g_localIP);
|
|
status = FC_ECONFIG;
|
|
goto done;
|
|
break;
|
|
case CURLE_UNKNOWN_OPTION:
|
|
case CURLE_NOT_BUILT_IN:
|
|
logg("!create_curl_handle: Unable to bind DNS resolves to %s. Option requires that libcurl was built with c-ares.\n", g_localIP);
|
|
status = FC_ECONFIG;
|
|
goto done;
|
|
default:
|
|
break;
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_IPRESOLVE (IPv4)!\n");
|
|
}
|
|
} else {
|
|
logg("*Local IPv6 address requested: %s\n", g_localIP);
|
|
curl_ret = curl_easy_setopt(curl, CURLOPT_DNS_LOCAL_IP6, g_localIP); // Option requires libcurl built with c-ares
|
|
switch (curl_ret) {
|
|
case CURLE_BAD_FUNCTION_ARGUMENT:
|
|
logg("^create_curl_handle: Unable to bind DNS resolves to %s. Invalid IPv4 address.\n", g_localIP);
|
|
status = FC_ECONFIG;
|
|
goto done;
|
|
break;
|
|
case CURLE_UNKNOWN_OPTION:
|
|
case CURLE_NOT_BUILT_IN:
|
|
logg("^create_curl_handle: Unable to bind DNS resolves to %s. Option requires that libcurl was built with c-ares.\n", g_localIP);
|
|
status = FC_ECONFIG;
|
|
goto done;
|
|
default:
|
|
break;
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_IPRESOLVE (IPv6)!\n");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (g_proxyServer) {
|
|
/*
|
|
* Proxy requested.
|
|
*/
|
|
logg("*Using proxy: %s:%u\n", g_proxyServer, g_proxyPort);
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROXY, g_proxyServer)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_PROXY (%s)!\n", g_proxyServer);
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROXYPORT, g_proxyPort)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_PROXYPORT (%u)!\n", g_proxyPort);
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L)) { // Necessary?
|
|
logg("!create_curl_handle: Failed to set CURLOPT_HTTPPROXYTUNNEL (1)!\n");
|
|
}
|
|
#ifdef CURLOPT_SUPPRESS_CONNECT_HEADERS
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L)) { // Necessary?
|
|
logg("!create_curl_handle: Failed to set CURLOPT_SUPPRESS_CONNECT_HEADERS (1)!\n");
|
|
}
|
|
#endif
|
|
|
|
if (g_proxyUsername) {
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, g_proxyUsername)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_PROXYUSERNAME (%s)!\n", g_proxyUsername);
|
|
}
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, g_proxyPassword)) {
|
|
logg("!create_curl_handle: Failed to set CURLOPT_PROXYPASSWORD (%s)!\n", g_proxyPassword);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(C_DARWIN) || defined(_WIN32)
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, *sslctx_function)) {
|
|
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");
|
|
}
|
|
#else
|
|
set_tls_ca_bundle(curl);
|
|
#endif
|
|
|
|
*curlHandle = curl;
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
|
|
if (FC_SUCCESS != status) {
|
|
if (NULL != curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
struct MemoryStruct {
|
|
char *buffer;
|
|
size_t size;
|
|
};
|
|
|
|
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
|
|
{
|
|
size_t real_size = size * nmemb;
|
|
struct MemoryStruct *receivedData = (struct MemoryStruct *)userp;
|
|
|
|
if ((NULL == contents) || (NULL == userp)) {
|
|
return 0;
|
|
}
|
|
|
|
char *newBuffer = realloc(receivedData->buffer, receivedData->size + real_size + 1);
|
|
if (NULL == newBuffer) {
|
|
logg("!remote_cvdhead - recv callback: Failed to allocate memory CVD header data.\n");
|
|
return 0;
|
|
}
|
|
|
|
receivedData->buffer = newBuffer;
|
|
memcpy(&(receivedData->buffer[receivedData->size]), contents, real_size);
|
|
receivedData->size += real_size;
|
|
receivedData->buffer[receivedData->size] = 0;
|
|
|
|
return real_size;
|
|
}
|
|
|
|
struct FileStruct {
|
|
int handle;
|
|
size_t size;
|
|
};
|
|
|
|
static size_t WriteFileCallback(void *contents, size_t size, size_t nmemb, void *userp)
|
|
{
|
|
size_t real_size = size * nmemb;
|
|
struct FileStruct *receivedFile = (struct FileStruct *)userp;
|
|
size_t bytes_written = 0;
|
|
|
|
if ((NULL == contents) || (NULL == userp)) {
|
|
return 0;
|
|
}
|
|
|
|
bytes_written = write(receivedFile->handle, contents, real_size);
|
|
|
|
receivedFile->size += bytes_written;
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the cvd header info struct for the newest available database.
|
|
*
|
|
* The last-modified datetime will be used to set the If-Modified-Since header.
|
|
* If the remote CVD isn't newer, we should get an HTTP 304 and return
|
|
* FC_UPTODATE instead of FC_SUCCESS, and cvd will be NULL.
|
|
*
|
|
* @param cvdfile database name including extension.
|
|
* @param ifModifiedSince modified time of local database. May be 0 to always get the CVD header.
|
|
* @param server server to use to retrieve for database header.
|
|
* @param logerr non-zero to upgrade warnings to errors.
|
|
* @param cvd [out] CVD header of newest available CVD, if FC_SUCCESS
|
|
* @return fc_error_t FC_SUCCESS if CVD header obtained.
|
|
* @return fc_error_t FC_UPTODATE if received 304 in response to ifModifiedSince date.
|
|
* @return fc_error_t Another error code if failure occured.
|
|
*/
|
|
static fc_error_t remote_cvdhead(
|
|
const char *cvdfile,
|
|
uint32_t ifModifiedSince,
|
|
char *server,
|
|
int logerr,
|
|
struct cl_cvd **cvd)
|
|
{
|
|
fc_error_t ret;
|
|
fc_error_t status = FC_EARG;
|
|
|
|
int bHttpServer = 0;
|
|
char *url = NULL;
|
|
size_t urlLen = 0;
|
|
|
|
char head[CVD_HEADER_SIZE + 1];
|
|
|
|
struct MemoryStruct receivedData = {0};
|
|
|
|
unsigned int i;
|
|
struct cl_cvd *cvdhead;
|
|
|
|
CURL *curl = NULL;
|
|
CURLcode curl_ret;
|
|
char errbuf[CURL_ERROR_SIZE];
|
|
struct curl_slist *slist = NULL;
|
|
struct xfer_progress prog;
|
|
|
|
long http_code = 0;
|
|
|
|
if (NULL == cvd) {
|
|
logg("!remote_cvdhead: Invalid arguments.\n");
|
|
goto done;
|
|
}
|
|
|
|
*cvd = NULL;
|
|
|
|
if (0 == strncasecmp(server, "http", strlen("http"))) {
|
|
bHttpServer = 1;
|
|
}
|
|
|
|
/*
|
|
* Request CVD header.
|
|
*/
|
|
urlLen = strlen(server) + strlen("/") + strlen(cvdfile);
|
|
url = malloc(urlLen + 1);
|
|
snprintf(url, urlLen + 1, "%s/%s", server, cvdfile);
|
|
|
|
logg("Trying to retrieve CVD header from %s\n", url);
|
|
|
|
if (FC_SUCCESS != (ret = create_curl_handle(
|
|
bHttpServer, // Set extra HTTP-specific headers.
|
|
1, // Allow redirects.
|
|
&curl))) { // [out] curl session handle.
|
|
logg("!remote_cvdhead: Failed to create curl handle.\n");
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
|
|
#else
|
|
if (!mprintf_quiet)
|
|
#endif
|
|
{
|
|
prog.lastRunTime = 0;
|
|
prog.curl = curl;
|
|
prog.bComplete = 0;
|
|
|
|
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 32))
|
|
/* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
|
|
compile as they won't have the symbols around.
|
|
|
|
If built with a newer libcurl, but running with an older libcurl:
|
|
curl_easy_setopt() will fail in run-time trying to set the new
|
|
callback, making the older callback get used.
|
|
|
|
New libcurls will prefer the new callback and instead use that one even
|
|
if both callbacks are set. */
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo)) {
|
|
logg("!create_curl_handle: Failed to set transfer info function!\n");
|
|
}
|
|
/* pass the struct pointer into the xferinfo function, note that this is
|
|
an alias to CURLOPT_PROGRESSDATA */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &prog)) {
|
|
logg("!create_curl_handle: Failed to set transfer info data structure!\n");
|
|
}
|
|
#else
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, older_progress)) {
|
|
logg("!create_curl_handle: Failed to set progress function!\n");
|
|
}
|
|
/* pass the struct pointer into the progress function */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &prog)) {
|
|
logg("!create_curl_handle: Failed to set progress data structure!\n");
|
|
}
|
|
#endif
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L)) {
|
|
logg("!create_curl_handle: Failed to disable progress function!\n");
|
|
}
|
|
}
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_URL, url)) {
|
|
logg("!remote_cvdhead: Failed to set CURLOPT_URL for curl session (%s).\n", url);
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
}
|
|
|
|
if (bHttpServer) {
|
|
/*
|
|
* For HTTP, set some extra headers.
|
|
*/
|
|
struct curl_slist *temp = NULL;
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L)) {
|
|
logg("!remote_cvdhead: Failed to set CURLOPT_HTTPGET for curl session.\n");
|
|
}
|
|
|
|
#ifdef FRESHCLAM_NO_CACHE
|
|
if (NULL == (temp = curl_slist_append(slist, "Cache-Control: no-cache"))) { // Necessary?
|
|
logg("!remote_cvdhead: Failed to append \"Cache-Control: no-cache\" header to custom curl header list.\n");
|
|
} else {
|
|
slist = temp;
|
|
}
|
|
#endif
|
|
if (NULL == (temp = curl_slist_append(slist, "Connection: close"))) {
|
|
logg("!remote_cvdhead: Failed to append \"Connection: close\" header to custom curl header list.\n");
|
|
} else {
|
|
slist = temp;
|
|
}
|
|
if (NULL != slist) {
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) {
|
|
logg("!remote_cvdhead: Failed to add custom header list to curl session.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 != ifModifiedSince) {
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMEVALUE, ifModifiedSince)) {
|
|
logg("!remote_cvdhead: Failed to set if-Modified-Since time value for curl session.\n");
|
|
}
|
|
/* If-Modified-Since the above time stamp */
|
|
else if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE)) {
|
|
logg("!remote_cvdhead: Failed to set if-Modified-Since time condition for curl session.\n");
|
|
}
|
|
}
|
|
|
|
/* Request only the first 512 bytes (CVD_HEADER_SIZE) */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_RANGE, "0-511")) {
|
|
logg("!remote_cvdhead: Failed to set CURLOPT_RANGE CVD_HEADER_SIZE for curl session.\n");
|
|
}
|
|
|
|
receivedData.buffer = cli_malloc(1); /* will be grown as needed by the realloc above */
|
|
receivedData.size = 0; /* no data at this point */
|
|
|
|
/* Send all data to this function */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback)) {
|
|
logg("!remote_cvdhead: Failed to set write-data memory callback function for curl session.\n");
|
|
}
|
|
|
|
/* Pass our 'receivedData' struct to the callback function */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&receivedData)) {
|
|
logg("!remote_cvdhead: Failed to set receivedData struct for write-data callback function for curl session.\n");
|
|
}
|
|
|
|
/*
|
|
* Perform download.
|
|
*/
|
|
memset(errbuf, 0, sizeof(errbuf));
|
|
curl_ret = curl_easy_perform(curl);
|
|
if (curl_ret != CURLE_OK) {
|
|
/*
|
|
* Show the error information.
|
|
* If no detailed error information was written to errbuf
|
|
* show the more generic information from curl_easy_strerror instead.
|
|
*/
|
|
size_t len = strlen(errbuf);
|
|
logg("%cremote_cvdhead: Download failed (%d) ", logerr ? '!' : '^', curl_ret);
|
|
if (len)
|
|
logg("%c Message: %s%s", logerr ? '!' : '^', errbuf, ((errbuf[len - 1] != '\n') ? "\n" : ""));
|
|
else
|
|
logg("%c Message: %s\n", logerr ? '!' : '^', curl_easy_strerror(curl_ret));
|
|
status = FC_ECONNECTION;
|
|
goto done;
|
|
}
|
|
|
|
/* Check HTTP code */
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
switch (http_code) {
|
|
case 200:
|
|
case 206: {
|
|
status = FC_SUCCESS;
|
|
break;
|
|
}
|
|
case 304: {
|
|
status = FC_UPTODATE;
|
|
goto done;
|
|
}
|
|
case 403: {
|
|
status = FC_EFORBIDDEN;
|
|
|
|
/* Try again in no less than 24 hours if freshclam received a 403 FORBIDDEN. */
|
|
g_freshclamDat->retry_after = time(NULL) + 60 * 60 * 24;
|
|
|
|
(void)save_freshclam_dat();
|
|
|
|
break;
|
|
}
|
|
case 429: {
|
|
status = FC_ERETRYLATER;
|
|
|
|
curl_off_t retry_after = 0;
|
|
|
|
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 66))
|
|
/* CURLINFO_RETRY_AFTER was introduced in libcurl 7.66 */
|
|
|
|
/* Find out how long we should wait before allowing a retry. */
|
|
curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
|
|
#endif
|
|
|
|
if (retry_after > 0) {
|
|
/* The response gave us a Retry-After date. Use that. */
|
|
g_freshclamDat->retry_after = time(NULL) + (time_t)retry_after;
|
|
} else {
|
|
/* Try again in no less than 4 hours if the response didn't specify
|
|
or if CURLINFO_RETRY_AFTER is not supported. */
|
|
g_freshclamDat->retry_after = time(NULL) + 60 * 60 * 4;
|
|
}
|
|
(void)save_freshclam_dat();
|
|
|
|
break;
|
|
}
|
|
case 404: {
|
|
if (g_proxyServer)
|
|
logg("^remote_cvdhead: file not found: %s (Proxy: %s:%u)\n", url, g_proxyServer, g_proxyPort);
|
|
else
|
|
logg("^remote_cvdhead: file not found: %s\n", url);
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
}
|
|
case 522: {
|
|
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);
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
}
|
|
default: {
|
|
if (g_proxyServer)
|
|
logg("%cremote_cvdhead: Unexpected response (%li) from %s (Proxy: %s:%u)\n",
|
|
logerr ? '!' : '^', http_code, server, g_proxyServer, g_proxyPort);
|
|
else
|
|
logg("%cremote_cvdhead: Unexpected response (%li) from %s\n",
|
|
logerr ? '!' : '^', http_code, server);
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Identify start of CVD header in response body.
|
|
*/
|
|
if (receivedData.size < CVD_HEADER_SIZE) {
|
|
logg("%cremote_cvdhead: Malformed CVD header (too short)\n", logerr ? '!' : '^');
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Copy CVD header byte-by-byte from response body to CVD header buffer.
|
|
* Validate that data contains only printable characters and no NULL terminators.
|
|
*/
|
|
memset(head, 0, sizeof(head));
|
|
|
|
for (i = 0; i < CVD_HEADER_SIZE; i++) {
|
|
if (!receivedData.buffer ||
|
|
(receivedData.buffer && !*receivedData.buffer) ||
|
|
(receivedData.buffer && !isprint(receivedData.buffer[i]))) {
|
|
|
|
logg("%cremote_cvdhead: Malformed CVD header (bad chars)\n", logerr ? '!' : '^');
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
}
|
|
head[i] = receivedData.buffer[i];
|
|
}
|
|
|
|
/*
|
|
* Parse CVD info into CVD info struct.
|
|
*/
|
|
if (!(cvdhead = cl_cvdparse(head))) {
|
|
logg("%cremote_cvdhead: Malformed CVD header (can't parse)\n", logerr ? '!' : '^');
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
} else {
|
|
logg("OK\n");
|
|
}
|
|
|
|
*cvd = cvdhead;
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
|
|
if (NULL != receivedData.buffer) {
|
|
free(receivedData.buffer);
|
|
}
|
|
if (NULL != slist) {
|
|
curl_slist_free_all(slist);
|
|
}
|
|
if (NULL != curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
if (NULL != url) {
|
|
free(url);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static fc_error_t downloadFile(
|
|
const char *url,
|
|
const char *destfile,
|
|
int bAllowRedirect,
|
|
int logerr,
|
|
time_t ifModifiedSince)
|
|
{
|
|
fc_error_t ret;
|
|
fc_error_t status = FC_EARG;
|
|
|
|
int bHttpServer = 0;
|
|
|
|
CURL *curl = NULL;
|
|
CURLcode curl_ret;
|
|
char errbuf[CURL_ERROR_SIZE];
|
|
struct curl_slist *slist = NULL;
|
|
struct xfer_progress prog;
|
|
|
|
long http_code = 0;
|
|
|
|
struct FileStruct receivedFile = {-1, 0};
|
|
|
|
if ((NULL == url) || (NULL == destfile)) {
|
|
logg("!downloadFile: Invalid arguments.\n");
|
|
goto done;
|
|
}
|
|
|
|
logg("*Retrieving %s\n", url);
|
|
|
|
if (0 == strncasecmp(url, "http", strlen("http"))) {
|
|
bHttpServer = 1;
|
|
}
|
|
|
|
if (FC_SUCCESS != (ret = create_curl_handle(bHttpServer, bAllowRedirect, &curl))) {
|
|
logg("!downloadFile: Failed to create curl handle.\n");
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
|
|
#else
|
|
if (!mprintf_quiet)
|
|
#endif
|
|
{
|
|
prog.lastRunTime = 0;
|
|
prog.curl = curl;
|
|
prog.bComplete = 0;
|
|
|
|
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 32))
|
|
/* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
|
|
compile as they won't have the symbols around.
|
|
|
|
If built with a newer libcurl, but running with an older libcurl:
|
|
curl_easy_setopt() will fail in run-time trying to set the new
|
|
callback, making the older callback get used.
|
|
|
|
New libcurls will prefer the new callback and instead use that one even
|
|
if both callbacks are set. */
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo)) {
|
|
logg("!downloadFile: Failed to set transfer info function!\n");
|
|
}
|
|
/* pass the struct pointer into the xferinfo function, note that this is
|
|
an alias to CURLOPT_PROGRESSDATA */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &prog)) {
|
|
logg("!downloadFile: Failed to set transfer info data structure!\n");
|
|
}
|
|
#else
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, older_progress)) {
|
|
logg("!downloadFile: Failed to set progress function!\n");
|
|
}
|
|
/* pass the struct pointer into the progress function */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &prog)) {
|
|
logg("!downloadFile: Failed to set progress data structure!\n");
|
|
}
|
|
#endif
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L)) {
|
|
logg("!downloadFile: Failed to disable progress function!\n");
|
|
}
|
|
}
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_URL, url)) {
|
|
logg("!downloadFile: Failed to set CURLOPT_URL for curl session (%s).\n", url);
|
|
}
|
|
if (0 != ifModifiedSince) {
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMEVALUE, ifModifiedSince)) {
|
|
logg("!downloadFile: Failed to set if-Modified-Since time value for curl session.\n");
|
|
}
|
|
/* If-Modified-Since the above time stamp */
|
|
else if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE)) {
|
|
logg("!downloadFile: Failed to set if-Modified-Since time condition for curl session.\n");
|
|
}
|
|
}
|
|
|
|
if (bHttpServer) {
|
|
/*
|
|
* For HTTP, set some extra headers.
|
|
*/
|
|
struct curl_slist *temp = NULL;
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L)) {
|
|
logg("!downloadFile: Failed to set CURLOPT_HTTPGET for curl session.\n");
|
|
}
|
|
|
|
#ifdef FRESHCLAM_NO_CACHE
|
|
if (NULL == (temp = curl_slist_append(slist, "Cache-Control: no-cache"))) { // Necessary?
|
|
logg("!downloadFile: Failed to append \"Cache-Control: no-cache\" header to custom curl header list.\n");
|
|
} else {
|
|
slist = temp;
|
|
}
|
|
#endif
|
|
if (NULL == (temp = curl_slist_append(slist, "Connection: close"))) { // Necessary?
|
|
logg("!downloadFile: Failed to append \"Connection: close\" header to custom curl header list.\n");
|
|
} else {
|
|
slist = temp;
|
|
}
|
|
if (NULL != slist) {
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) {
|
|
logg("!downloadFile: Failed to add custom header list to curl session.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Write the response body to the destination file handle */
|
|
|
|
if (-1 == (receivedFile.handle = open(destfile, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0644))) {
|
|
char currdir[PATH_MAX];
|
|
|
|
if (getcwd(currdir, sizeof(currdir)))
|
|
logg("!downloadFile: Can't create new file %s in %s\n", destfile, currdir);
|
|
else
|
|
logg("!downloadFile: Can't create new file %s in the current directory\n", destfile);
|
|
|
|
logg("Hint: The database directory must be writable for UID %d or GID %d\n", getuid(), getgid());
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
receivedFile.size = 0;
|
|
|
|
/* Send all data to this function */
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteFileCallback)) {
|
|
logg("!downloadFile: Failed to set write-data fwrite callback function for curl session.\n");
|
|
}
|
|
|
|
if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&receivedFile)) {
|
|
logg("!downloadFile: Failed to set write-data file handle for curl session.\n");
|
|
}
|
|
|
|
logg("*downloadFile: Download source: %s\n", url);
|
|
logg("*downloadFile: Download destination: %s\n", destfile);
|
|
|
|
/* Perform download */
|
|
memset(errbuf, 0, sizeof(errbuf));
|
|
curl_ret = curl_easy_perform(curl);
|
|
if (curl_ret != CURLE_OK) {
|
|
/*
|
|
* Show the error information.
|
|
* If no detailed error information was written to errbuf
|
|
* show the more generic information from curl_easy_strerror instead.
|
|
*/
|
|
size_t len = strlen(errbuf);
|
|
logg("%cDownload failed (%d) ", logerr ? '!' : '^', curl_ret);
|
|
if (len)
|
|
logg("%c Message: %s%s", logerr ? '!' : '^', errbuf, ((errbuf[len - 1] != '\n') ? "\n" : ""));
|
|
else
|
|
logg("%c Message: %s\n", logerr ? '!' : '^', curl_easy_strerror(curl_ret));
|
|
status = FC_ECONNECTION;
|
|
goto done;
|
|
}
|
|
|
|
/* Check HTTP code */
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
switch (http_code) {
|
|
case 200:
|
|
case 206: {
|
|
if (0 == receivedFile.size) {
|
|
status = FC_EEMPTYFILE;
|
|
} else {
|
|
status = FC_SUCCESS;
|
|
}
|
|
break;
|
|
}
|
|
case 304: {
|
|
status = FC_UPTODATE;
|
|
break;
|
|
}
|
|
case 403: {
|
|
status = FC_EFORBIDDEN;
|
|
|
|
/* Try again in no less than 24 hours if freshclam received a 403 FORBIDDEN. */
|
|
g_freshclamDat->retry_after = time(NULL) + 60 * 60 * 24;
|
|
|
|
(void)save_freshclam_dat();
|
|
|
|
break;
|
|
}
|
|
case 429: {
|
|
status = FC_ERETRYLATER;
|
|
|
|
curl_off_t retry_after = 0;
|
|
|
|
#if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 66))
|
|
/* CURLINFO_RETRY_AFTER was introduced in libcurl 7.66 */
|
|
|
|
/* Find out how long we should wait before allowing a retry. */
|
|
curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
|
|
#endif
|
|
|
|
if (retry_after > 0) {
|
|
/* The response gave us a Retry-After date. Use that. */
|
|
g_freshclamDat->retry_after = time(NULL) + (time_t)retry_after;
|
|
} else {
|
|
/* Try again in no less than 4 hours if the response didn't specify
|
|
or if CURLINFO_RETRY_AFTER is not supported. */
|
|
g_freshclamDat->retry_after = time(NULL) + 60 * 60 * 4;
|
|
}
|
|
(void)save_freshclam_dat();
|
|
|
|
break;
|
|
}
|
|
case 404: {
|
|
if (g_proxyServer)
|
|
logg("^downloadFile: file not found: %s (Proxy: %s:%u)\n", url, g_proxyServer, g_proxyPort);
|
|
else
|
|
logg("^downloadFile: file not found: %s\n", url);
|
|
status = FC_EFAILEDGET;
|
|
break;
|
|
}
|
|
case 522: {
|
|
logg("^downloadFile: Origin Connection Time-out. Cloudflare was unable to reach the origin web server and the request timed out. URL: %s\n", url);
|
|
status = FC_EFAILEDGET;
|
|
break;
|
|
}
|
|
default: {
|
|
if (g_proxyServer)
|
|
logg("%cdownloadFile: Unexpected response (%li) from %s (Proxy: %s:%u)\n",
|
|
logerr ? '!' : '^', http_code, url, g_proxyServer, g_proxyPort);
|
|
else
|
|
logg("%cdownloadFile: Unexpected response (%li) from %s\n",
|
|
logerr ? '!' : '^', http_code, url);
|
|
status = FC_EFAILEDGET;
|
|
}
|
|
}
|
|
|
|
done:
|
|
|
|
if (NULL != slist) {
|
|
curl_slist_free_all(slist);
|
|
}
|
|
if (NULL != curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
if (-1 != receivedFile.handle) {
|
|
close(receivedFile.handle);
|
|
}
|
|
|
|
if (FC_UPTODATE < status) {
|
|
if (NULL != destfile) {
|
|
unlink(destfile);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static fc_error_t getcvd(
|
|
const char *cvdfile,
|
|
const char *tmpfile,
|
|
char *server,
|
|
uint32_t ifModifiedSince,
|
|
unsigned int remoteVersion,
|
|
int logerr)
|
|
{
|
|
fc_error_t ret;
|
|
cl_error_t cl_ret;
|
|
fc_error_t status = FC_EARG;
|
|
|
|
struct cl_cvd *cvd = NULL;
|
|
char *tmpfile_with_extension = NULL;
|
|
char *url = NULL;
|
|
size_t urlLen = 0;
|
|
|
|
if ((NULL == cvdfile) || (NULL == tmpfile) || (NULL == server)) {
|
|
logg("!getcvd: Invalid arguments.\n");
|
|
goto done;
|
|
}
|
|
|
|
urlLen = strlen(server) + strlen("/") + strlen(cvdfile);
|
|
url = malloc(urlLen + 1);
|
|
snprintf(url, urlLen + 1, "%s/%s", server, cvdfile);
|
|
|
|
ret = downloadFile(url, tmpfile, 1, logerr, ifModifiedSince);
|
|
if (ret == FC_UPTODATE) {
|
|
logg("%s is up-to-date.\n", cvdfile);
|
|
status = ret;
|
|
goto done;
|
|
} else if (ret > FC_UPTODATE) {
|
|
logg("%cCan't download %s from %s\n", logerr ? '!' : '^', cvdfile, url);
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
|
|
/* Temporarily rename file to correct extension for verification. */
|
|
tmpfile_with_extension = strdup(tmpfile);
|
|
if (!tmpfile_with_extension) {
|
|
logg("!Can't allocate memory for temp file with extension!\n");
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
strncpy(tmpfile_with_extension + strlen(tmpfile_with_extension) - 4, cvdfile + strlen(cvdfile) - 4, 4);
|
|
if (rename(tmpfile, tmpfile_with_extension) == -1) {
|
|
logg("!Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno));
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
|
|
if (CL_SUCCESS != (cl_ret = cl_cvdverify(tmpfile_with_extension))) {
|
|
logg("!Verification: %s\n", cl_strerror(cl_ret));
|
|
status = FC_EBADCVD;
|
|
goto done;
|
|
}
|
|
|
|
if (NULL == (cvd = cl_cvdhead(tmpfile_with_extension))) {
|
|
logg("!Can't read CVD header of new %s database.\n", cvdfile);
|
|
status = FC_EBADCVD;
|
|
goto done;
|
|
}
|
|
|
|
/* Rename the file back to the original, since verification passed. */
|
|
if (rename(tmpfile_with_extension, tmpfile) == -1) {
|
|
logg("!Can't rename %s to %s: %s\n", tmpfile_with_extension, tmpfile, strerror(errno));
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
|
|
if (cvd->version < remoteVersion) {
|
|
logg("*The %s database downloaded from %s is older than the version advertised in the DNS TXT record.\n",
|
|
cvdfile,
|
|
server);
|
|
status = FC_EMIRRORNOTSYNC;
|
|
goto done;
|
|
}
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
if (NULL != cvd) {
|
|
cl_cvdfree(cvd);
|
|
}
|
|
if (NULL != tmpfile_with_extension) {
|
|
unlink(tmpfile_with_extension);
|
|
free(tmpfile_with_extension);
|
|
}
|
|
if (NULL != url) {
|
|
free(url);
|
|
}
|
|
if (
|
|
(FC_SUCCESS != status) &&
|
|
(FC_EMIRRORNOTSYNC != status) /* Keep older version, it's better than nothing. */
|
|
) {
|
|
if (NULL != tmpfile) {
|
|
unlink(tmpfile);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Change to the temp dir for storing CDIFFs for incremental database update.
|
|
*
|
|
* Will create the temp dir if it does not already exist.
|
|
*
|
|
* @param database The database we're updating.
|
|
* @param tmpdir [out] The name of the temp dir to use.
|
|
* @return fc_error_t
|
|
*/
|
|
static fc_error_t mkdir_and_chdir_for_cdiff_tmp(const char *database, const char *tmpdir)
|
|
{
|
|
fc_error_t status = FC_EDIRECTORY;
|
|
|
|
char cvdfile[DB_FILENAME_MAX];
|
|
|
|
if ((NULL == database) || (NULL == tmpdir)) {
|
|
logg("!mkdir_and_chdir_for_cdiff_tmp: Invalid arguments.\n");
|
|
status = FC_EARG;
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == access(tmpdir, R_OK | W_OK)) {
|
|
/*
|
|
* Temp directory for incremental update (cdiff download) does not
|
|
* yet exist.
|
|
*/
|
|
int ret;
|
|
|
|
/*
|
|
* 1) Double-check that we have a CVD or CLD. Without either one, incremental update won't work.
|
|
*/
|
|
ret = snprintf(cvdfile, sizeof(cvdfile), "%s.cvd", database);
|
|
if (((int)sizeof(cvdfile) <= ret) || (-1 == ret)) {
|
|
logg("!mkdir_and_chdir_for_cdiff_tmp: database parameter value too long to create cvd file name: %s\n", database);
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == access(cvdfile, R_OK)) {
|
|
ret = snprintf(cvdfile, sizeof(cvdfile), "%s.cld", database);
|
|
if (((int)sizeof(cvdfile) <= ret) || (-1 == ret)) {
|
|
logg("!mkdir_and_chdir_for_cdiff_tmp: database parameter value too long to create cld file name: %s\n", database);
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == access(cvdfile, R_OK)) {
|
|
logg("!mkdir_and_chdir_for_cdiff_tmp: Can't find (or access) local CVD or CLD for %s database\n", database);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 2) Create the incremental update temp directory.
|
|
*/
|
|
if (-1 == mkdir(tmpdir, 0755)) {
|
|
logg("!mkdir_and_chdir_for_cdiff_tmp: Can't create directory %s\n", tmpdir);
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == cli_cvdunpack(cvdfile, tmpdir)) {
|
|
logg("!mkdir_and_chdir_for_cdiff_tmp: Can't unpack %s into %s\n", cvdfile, tmpdir);
|
|
cli_rmdirs(tmpdir);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (-1 == chdir(tmpdir)) {
|
|
logg("!mkdir_and_chdir_for_cdiff_tmp: Can't change directory to %s\n", tmpdir);
|
|
goto done;
|
|
}
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
|
|
return status;
|
|
}
|
|
|
|
static fc_error_t downloadPatch(
|
|
const char *database,
|
|
const char *tmpdir,
|
|
int version,
|
|
char *server,
|
|
int logerr)
|
|
{
|
|
fc_error_t ret;
|
|
fc_error_t status = FC_EARG;
|
|
|
|
char *tempname = NULL;
|
|
char patch[DB_FILENAME_MAX];
|
|
char olddir[PATH_MAX];
|
|
|
|
char *url = NULL;
|
|
size_t urlLen = 0;
|
|
|
|
int fd = -1;
|
|
|
|
olddir[0] = '\0';
|
|
|
|
if ((NULL == database) || (NULL == tmpdir) || (NULL == server) || (0 == version)) {
|
|
logg("!downloadPatch: Invalid arguments.\n");
|
|
goto done;
|
|
}
|
|
|
|
if (NULL == getcwd(olddir, sizeof(olddir))) {
|
|
logg("!downloadPatch: Can't get path of current working directory\n");
|
|
status = FC_EDIRECTORY;
|
|
goto done;
|
|
}
|
|
|
|
if (FC_SUCCESS != mkdir_and_chdir_for_cdiff_tmp(database, tmpdir)) {
|
|
status = FC_EDIRECTORY;
|
|
goto done;
|
|
}
|
|
|
|
if (NULL == (tempname = cli_gentemp("."))) {
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
|
|
snprintf(patch, sizeof(patch), "%s-%d.cdiff", database, version);
|
|
urlLen = strlen(server) + strlen("/") + strlen(patch);
|
|
url = malloc(urlLen + 1);
|
|
snprintf(url, urlLen + 1, "%s/%s", server, patch);
|
|
|
|
if (FC_SUCCESS != (ret = downloadFile(url, tempname, 1, logerr, 0))) {
|
|
if (ret == FC_EEMPTYFILE) {
|
|
logg("Empty script %s, need to download entire database\n", patch);
|
|
} else {
|
|
logg("%cdownloadPatch: Can't download %s from %s\n", logerr ? '!' : '^', patch, url);
|
|
}
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == (fd = open(tempname, O_RDONLY | O_BINARY))) {
|
|
logg("!downloadPatch: Can't open %s for reading\n", tempname);
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == cdiff_apply(fd, 1)) {
|
|
logg("!downloadPatch: Can't apply patch\n");
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
|
|
if (NULL != url) {
|
|
free(url);
|
|
}
|
|
|
|
if (-1 != fd) {
|
|
close(fd);
|
|
}
|
|
|
|
if (NULL != tempname) {
|
|
unlink(tempname);
|
|
free(tempname);
|
|
}
|
|
|
|
if ('\0' != olddir[0]) {
|
|
if (-1 == chdir(olddir)) {
|
|
logg("!downloadPatch: Can't chdir to %s\n", olddir);
|
|
status = FC_EDIRECTORY;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Get CVD header info for local CVD/CLD database.
|
|
*
|
|
* @param database Database name
|
|
* @param localname [out] (optional) filename of local database.
|
|
* @return struct cl_cvd* CVD info struct of local database, if found. NULL if not found.
|
|
*/
|
|
static struct cl_cvd *currentdb(const char *database, char **localname)
|
|
{
|
|
char filename[DB_FILENAME_MAX];
|
|
struct cl_cvd *cvd = NULL;
|
|
|
|
if (NULL == database) {
|
|
logg("!currentdb: Invalid args!\n");
|
|
goto done;
|
|
}
|
|
|
|
snprintf(filename, sizeof(filename), "%s.cvd", database);
|
|
filename[sizeof(filename) - 1] = 0;
|
|
|
|
if (-1 == access(filename, R_OK)) {
|
|
/* CVD not found. */
|
|
snprintf(filename, sizeof(filename), "%s.cld", database);
|
|
filename[sizeof(filename) - 1] = 0;
|
|
|
|
if (-1 == access(filename, R_OK)) {
|
|
/* CLD also not found. Fail out. */
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (NULL == (cvd = cl_cvdhead(filename))) {
|
|
goto done;
|
|
}
|
|
|
|
if (localname) {
|
|
*localname = cli_strdup(filename);
|
|
}
|
|
|
|
done:
|
|
|
|
return cvd;
|
|
}
|
|
|
|
static fc_error_t buildcld(
|
|
const char *tmpdir,
|
|
const char *database,
|
|
const char *newfile,
|
|
int bCompress)
|
|
{
|
|
fc_error_t status = FC_EARG;
|
|
|
|
char olddir[PATH_MAX] = {0};
|
|
char info[DB_FILENAME_MAX];
|
|
char buff[CVD_HEADER_SIZE + 1];
|
|
char *pt;
|
|
|
|
struct dirent *dent = NULL;
|
|
DIR *dir = NULL;
|
|
gzFile gzs = NULL;
|
|
int fd = -1;
|
|
|
|
if ((NULL == tmpdir) || (NULL == database) || (NULL == newfile)) {
|
|
logg("!buildcld: Invalid arguments.\n");
|
|
goto done;
|
|
}
|
|
|
|
if (!getcwd(olddir, sizeof(olddir))) {
|
|
logg("!buildcld: Can't get path of current working directory\n");
|
|
status = FC_EDIRECTORY;
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == chdir(tmpdir)) {
|
|
logg("!buildcld: Can't access directory %s\n", tmpdir);
|
|
status = FC_EDIRECTORY;
|
|
goto done;
|
|
}
|
|
|
|
snprintf(info, sizeof(info), "%s.info", database);
|
|
info[sizeof(info) - 1] = 0;
|
|
if (-1 == (fd = open(info, O_RDONLY | O_BINARY))) {
|
|
logg("!buildcld: Can't open %s\n", info);
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == read(fd, buff, CVD_HEADER_SIZE)) {
|
|
logg("!buildcld: Can't read %s\n", info);
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
buff[CVD_HEADER_SIZE] = 0;
|
|
|
|
close(fd);
|
|
fd = -1;
|
|
|
|
if (NULL == (pt = strchr(buff, '\n'))) {
|
|
logg("!buildcld: Bad format of %s\n", info);
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
memset(pt, ' ', CVD_HEADER_SIZE + buff - pt);
|
|
|
|
if (-1 == (fd = open(newfile, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0644))) {
|
|
logg("!buildcld: Can't open %s for writing\n", newfile);
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
if (CVD_HEADER_SIZE != write(fd, buff, CVD_HEADER_SIZE)) {
|
|
logg("!buildcld: Can't write to %s\n", newfile);
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
|
|
if (bCompress) {
|
|
close(fd);
|
|
fd = -1;
|
|
if (NULL == (gzs = gzopen(newfile, "ab9f"))) {
|
|
logg("!buildcld: gzopen() failed for %s\n", newfile);
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (-1 == access("COPYING", R_OK)) {
|
|
logg("!buildcld: COPYING file not found\n");
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
|
|
if (-1 == tar_addfile(fd, gzs, "COPYING")) {
|
|
logg("!buildcld: Can't add COPYING to new %s.cld - please check if there is enough disk space available\n", database);
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
|
|
if (-1 != access(info, R_OK)) {
|
|
if (-1 == tar_addfile(fd, gzs, info)) {
|
|
logg("!buildcld: Can't add %s to new %s.cld - please check if there is enough disk space available\n", info, database);
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (-1 != access("daily.cfg", R_OK)) {
|
|
if (-1 == tar_addfile(fd, gzs, "daily.cfg")) {
|
|
logg("!buildcld: Can't add daily.cfg to new %s.cld - please check if there is enough disk space available\n", database);
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (NULL == (dir = opendir("."))) {
|
|
logg("!buildcld: Can't open directory %s\n", tmpdir);
|
|
status = FC_EDIRECTORY;
|
|
goto done;
|
|
}
|
|
|
|
while (NULL != (dent = readdir(dir))) {
|
|
if (dent->d_ino) {
|
|
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))
|
|
continue;
|
|
|
|
if (tar_addfile(fd, gzs, dent->d_name) == -1) {
|
|
logg("!buildcld: Can't add %s to new %s.cld - please check if there is enough disk space available\n", dent->d_name, database);
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
|
|
if (-1 != fd) {
|
|
if (-1 == close(fd)) {
|
|
logg("!buildcld: close() failed for %s\n", newfile);
|
|
}
|
|
}
|
|
if (NULL != gzs) {
|
|
if (gzclose(gzs)) {
|
|
logg("!buildcld: gzclose() failed for %s\n", newfile);
|
|
}
|
|
}
|
|
if (NULL != dir) {
|
|
closedir(dir);
|
|
}
|
|
|
|
if (FC_SUCCESS != status) {
|
|
if (NULL != newfile) {
|
|
unlink(newfile);
|
|
}
|
|
}
|
|
|
|
if ('\0' != olddir[0]) {
|
|
if (-1 == chdir(olddir)) {
|
|
logg("!buildcld: Can't return to previous directory %s\n", olddir);
|
|
status = FC_EDIRECTORY;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static fc_error_t query_remote_database_version(
|
|
const char *database,
|
|
uint32_t ifModifiedSince,
|
|
const char *dnsUpdateInfo,
|
|
char *server,
|
|
int bPrivateMirror,
|
|
int logerr,
|
|
uint32_t *remoteVersion,
|
|
char **remoteFilename)
|
|
{
|
|
fc_error_t ret;
|
|
fc_error_t status = FC_EARG;
|
|
|
|
uint32_t newVersion = 0;
|
|
char cvdfile[DB_FILENAME_MAX];
|
|
char cldfile[DB_FILENAME_MAX];
|
|
|
|
#ifdef HAVE_RESOLV_H
|
|
char *dnqueryDomain = NULL;
|
|
char *extradnsreply = NULL;
|
|
#endif
|
|
|
|
struct cl_cvd *remote = NULL;
|
|
int remote_is_cld = 0;
|
|
|
|
if ((NULL == database) || (NULL == server) || (NULL == remoteVersion) || (NULL == remoteFilename)) {
|
|
logg("!query_remote_database_version: Invalid args!\n");
|
|
goto done;
|
|
}
|
|
|
|
*remoteVersion = 0;
|
|
*remoteFilename = NULL;
|
|
|
|
snprintf(cvdfile, sizeof(cvdfile), "%s.cvd", database);
|
|
cvdfile[sizeof(cvdfile) - 1] = 0;
|
|
snprintf(cldfile, sizeof(cldfile), "%s.cld", database);
|
|
cldfile[sizeof(cldfile) - 1] = 0;
|
|
|
|
if ((!bPrivateMirror) && (NULL != dnsUpdateInfo)) {
|
|
/*
|
|
* Use Primary DNS Update Info record to find the version.
|
|
*/
|
|
int field = 0;
|
|
char *verStrDnsPrimary = NULL;
|
|
|
|
if (0 == (field = textrecordfield(database))) {
|
|
logg("*query_remote_database_version: Database name \"%s\" isn't listed in DNS update info.\n", database);
|
|
} else if (NULL == (verStrDnsPrimary = cli_strtok(dnsUpdateInfo, field, ":"))) {
|
|
logg("^Invalid DNS update info. Falling back to HTTP mode.\n");
|
|
} else if (!cli_isnumber(verStrDnsPrimary)) {
|
|
logg("^Broken database version in TXT record. Falling back to HTTP mode.\n");
|
|
} else {
|
|
newVersion = atoi(verStrDnsPrimary);
|
|
logg("*query_remote_database_version: %s version from DNS: %d\n", cvdfile, newVersion);
|
|
}
|
|
free(verStrDnsPrimary);
|
|
|
|
#ifdef HAVE_RESOLV_H
|
|
if (newVersion == 0) {
|
|
/*
|
|
* Primary DNS Update Info record didn't have the version # for this database.
|
|
* Try to use a <database>.cvd.clamav.net DNS query to find the version #.
|
|
*/
|
|
size_t dnqueryDomainLen = strlen(database) + strlen(".cvd.clamav.net");
|
|
|
|
dnqueryDomain = malloc(dnqueryDomainLen + 1);
|
|
snprintf(dnqueryDomain, dnqueryDomainLen + 1, "%s.cvd.clamav.net", database);
|
|
if (NULL == (extradnsreply = dnsquery(dnqueryDomain, T_TXT, NULL))) {
|
|
logg("^No timestamp in TXT record for %s\n", cvdfile);
|
|
} else {
|
|
char *recordTimeStr = NULL;
|
|
char *verStrDnsExtra = NULL;
|
|
|
|
if (NULL == (recordTimeStr = cli_strtok(extradnsreply, DNS_EXTRADBINFO_RECORDTIME, ":"))) {
|
|
logg("^No recordtime field in TXT record for %s\n", cvdfile);
|
|
} else {
|
|
int recordTime;
|
|
time_t currentTime;
|
|
|
|
recordTime = atoi(recordTimeStr);
|
|
free(recordTimeStr);
|
|
time(¤tTime);
|
|
if ((int)currentTime - recordTime > 10800) {
|
|
logg("^DNS record is older than 3 hours.\n");
|
|
} else if (NULL != (verStrDnsExtra = cli_strtok(extradnsreply, 0, ":"))) {
|
|
if (!cli_isnumber(verStrDnsExtra)) {
|
|
logg("^Broken database version in TXT record for %s\n", cvdfile);
|
|
} else {
|
|
newVersion = atoi(verStrDnsExtra);
|
|
logg("*%s version from DNS: %d\n", cvdfile, newVersion);
|
|
}
|
|
free(verStrDnsExtra);
|
|
} else {
|
|
logg("^Invalid DNS reply. Falling back to HTTP mode.\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (newVersion == 0) {
|
|
/*
|
|
* Was unable to use DNS info records to determine database version.
|
|
* Use HTTP GET to get version info from CVD/CLD header.
|
|
*/
|
|
if (bPrivateMirror) {
|
|
/*
|
|
* For a private mirror, get the CLD instead of the CVD.
|
|
*
|
|
* On the mirror, they should have CDIFFs/scripted/incremental
|
|
* updates enabled, so they should have CLD's to distribute.
|
|
*/
|
|
ret = remote_cvdhead(cldfile, ifModifiedSince, server, logerr, &remote);
|
|
if ((FC_SUCCESS == ret) || (FC_UPTODATE == ret)) {
|
|
remote_is_cld = 1;
|
|
} else {
|
|
/*
|
|
* Failed to get CLD update, and it's unknown if the status is up-to-date.
|
|
*
|
|
* If it's a relatively new mirror, the CLD won't have been replaced with a CVD yet.
|
|
* Attempt to get the CVD instead.
|
|
*/
|
|
ret = remote_cvdhead(cvdfile, ifModifiedSince, server, logerr, &remote);
|
|
}
|
|
} else {
|
|
/*
|
|
* Official update servers will only have the CVD.
|
|
*/
|
|
ret = remote_cvdhead(cvdfile, ifModifiedSince, server, logerr, &remote);
|
|
}
|
|
|
|
switch (ret) {
|
|
case FC_SUCCESS: {
|
|
logg("*%s database version obtained using HTTP GET: %u\n", database, remote->version);
|
|
break;
|
|
}
|
|
case FC_UPTODATE: {
|
|
logg("*%s database version up-to-date, according to HTTP response code from server.\n", database);
|
|
status = FC_UPTODATE;
|
|
goto done;
|
|
}
|
|
default: {
|
|
logg("^Failed to get %s database version information from server: %s\n", database, server);
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
newVersion = remote->version;
|
|
}
|
|
|
|
if (remote_is_cld) {
|
|
*remoteFilename = cli_strdup(cldfile);
|
|
} else {
|
|
*remoteFilename = cli_strdup(cvdfile);
|
|
}
|
|
*remoteVersion = newVersion;
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
|
|
if (NULL != remote) {
|
|
cl_cvdfree(remote);
|
|
}
|
|
#ifdef HAVE_RESOLV_H
|
|
if (NULL != dnqueryDomain) {
|
|
free(dnqueryDomain);
|
|
}
|
|
if (NULL != extradnsreply) {
|
|
free(extradnsreply);
|
|
}
|
|
#endif
|
|
|
|
return status;
|
|
}
|
|
|
|
static fc_error_t 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)
|
|
{
|
|
fc_error_t ret;
|
|
fc_error_t status = FC_EARG;
|
|
|
|
char *localname = NULL;
|
|
struct cl_cvd *local_database = NULL;
|
|
char *remotename = NULL;
|
|
|
|
uint32_t localver = 0;
|
|
uint32_t remotever = 0;
|
|
|
|
if ((NULL == database) || (NULL == server) ||
|
|
(NULL == localVersion) || (NULL == remoteVersion) ||
|
|
(NULL == localFilename) || (NULL == remoteFilename) ||
|
|
(NULL == localTimestamp)) {
|
|
logg("!check_for_new_database_version: Invalid args!\n");
|
|
goto done;
|
|
}
|
|
|
|
*localVersion = 0;
|
|
*remoteVersion = 0;
|
|
*localFilename = NULL;
|
|
*remoteFilename = NULL;
|
|
*localTimestamp = 0;
|
|
|
|
/*
|
|
* Check local database version (if exists)
|
|
*/
|
|
if (NULL == (local_database = currentdb(database, &localname))) {
|
|
logg("*check_for_new_database_version: No local copy of \"%s\" database.\n", database);
|
|
} else {
|
|
logg("*check_for_new_database_version: Local copy of %s found: %s.\n", database, localname);
|
|
*localTimestamp = local_database->stime;
|
|
localver = local_database->version;
|
|
}
|
|
|
|
/*
|
|
* Look up the latest available database version.
|
|
*/
|
|
ret = query_remote_database_version(
|
|
database,
|
|
*localTimestamp,
|
|
dnsUpdateInfo,
|
|
server,
|
|
bPrivateMirror,
|
|
logerr,
|
|
&remotever,
|
|
&remotename);
|
|
switch (ret) {
|
|
case FC_SUCCESS: {
|
|
if (0 == localver) {
|
|
logg("%s database available for download (remote version: %d)\n",
|
|
database, remotever);
|
|
break;
|
|
} else if (localver < remotever) {
|
|
logg("%s database available for update (local version: %d, remote version: %d)\n",
|
|
database, localver, remotever);
|
|
break;
|
|
}
|
|
/* fall-through */
|
|
}
|
|
case FC_UPTODATE: {
|
|
if (NULL == local_database) {
|
|
logg("!check_for_new_database_version: server claims we're up-to-date, but we don't have a local database!\n");
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
}
|
|
logg("%s database is up-to-date (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
|
|
localname,
|
|
local_database->version,
|
|
local_database->sigs,
|
|
local_database->fl,
|
|
local_database->builder);
|
|
|
|
/* The remote version wouldn't be set if the server returned "Not-Modified".
|
|
We know it will be the same as the local version though. */
|
|
remotever = localver;
|
|
break;
|
|
}
|
|
case FC_EFORBIDDEN: {
|
|
/* We tried to look up the version using HTTP and were actively blocked. */
|
|
logg("!check_for_new_database_version: Blocked from using server %s.\n", server);
|
|
status = FC_EFORBIDDEN;
|
|
goto done;
|
|
}
|
|
default: {
|
|
logg("!check_for_new_database_version: Failed to find %s database using server %s.\n", database, server);
|
|
status = FC_EFAILEDGET;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
*remoteVersion = remotever;
|
|
if (NULL != remotename) {
|
|
*remoteFilename = cli_strdup(remotename);
|
|
if (NULL == *remoteFilename) {
|
|
logg("!check_for_new_database_version: Failed to allocate memory for remote filename.\n");
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
}
|
|
if (NULL != localname) {
|
|
*localVersion = localver;
|
|
*localFilename = cli_strdup(localname);
|
|
if (NULL == *localFilename) {
|
|
logg("!check_for_new_database_version: Failed to allocate memory for local filename.\n");
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
|
|
if (NULL != localname) {
|
|
free(localname);
|
|
}
|
|
if (NULL != remotename) {
|
|
free(remotename);
|
|
}
|
|
if (NULL != local_database) {
|
|
cl_cvdfree(local_database);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
fc_error_t updatedb(
|
|
const char *database,
|
|
const char *dnsUpdateInfo,
|
|
char *server,
|
|
int bPrivateMirror,
|
|
void *context,
|
|
int bScriptedUpdates,
|
|
int logerr,
|
|
int *signo,
|
|
char **dbFilename,
|
|
int *bUpdated)
|
|
{
|
|
fc_error_t ret;
|
|
fc_error_t status = FC_EARG;
|
|
|
|
struct cl_cvd *cvd = NULL;
|
|
|
|
uint32_t localTimestamp = 0;
|
|
uint32_t localVersion = 0;
|
|
uint32_t remoteVersion = 0;
|
|
char *localFilename = NULL;
|
|
char *remoteFilename = NULL;
|
|
char *newLocalFilename = NULL;
|
|
|
|
char *tmpdir = NULL;
|
|
char *tmpfile = NULL;
|
|
|
|
unsigned int flevel;
|
|
|
|
unsigned int i, j;
|
|
|
|
if ((NULL == database) || (NULL == server) || (NULL == signo) || (NULL == dbFilename) || (NULL == bUpdated)) {
|
|
logg("!updatedb: Invalid args!\n");
|
|
goto done;
|
|
}
|
|
|
|
*signo = 0;
|
|
*dbFilename = NULL;
|
|
*bUpdated = 0;
|
|
|
|
/*
|
|
* Check if new version exists.
|
|
*/
|
|
if (FC_SUCCESS != (ret = check_for_new_database_version(
|
|
database,
|
|
dnsUpdateInfo,
|
|
server,
|
|
bPrivateMirror,
|
|
logerr,
|
|
&localVersion,
|
|
&remoteVersion,
|
|
&localFilename,
|
|
&remoteFilename,
|
|
&localTimestamp))) {
|
|
logg("*updatedb: %s database update failed.\n", database);
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
|
|
if ((localVersion >= remoteVersion) && (NULL != localFilename)) {
|
|
*dbFilename = cli_strdup(localFilename);
|
|
goto up_to_date;
|
|
}
|
|
|
|
/* Download CVD or CLD to temp file */
|
|
tmpfile = cli_gentemp(g_tempDirectory);
|
|
if (!tmpfile) {
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
|
|
if ((localVersion == 0) || (!bScriptedUpdates)) {
|
|
/*
|
|
* Download entire file.
|
|
*/
|
|
ret = getcvd(remoteFilename, tmpfile, server, localTimestamp, remoteVersion, logerr);
|
|
if (FC_UPTODATE == ret) {
|
|
logg("^Expected newer version of %s database but the server's copy is not newer than our local file (version %d).\n", database, localVersion);
|
|
if (NULL != localFilename) {
|
|
/* Received a 304 (not modified), must be up-to-date after all */
|
|
*dbFilename = cli_strdup(localFilename);
|
|
}
|
|
goto up_to_date;
|
|
} else if (FC_EMIRRORNOTSYNC == ret) {
|
|
/* Let's accept this older version, but keep the error code.
|
|
* We'll have fc_update_database() retry using CDIFFs.
|
|
*/
|
|
logg("*Received an older %s CVD than was advertised. We'll keep it and try updating to the latest version with CDIFFs.\n", database);
|
|
status = ret;
|
|
} else if (FC_SUCCESS != ret) {
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
|
|
newLocalFilename = cli_strdup(remoteFilename);
|
|
} else {
|
|
/*
|
|
* Attempt scripted/CDIFF incremental update.
|
|
*/
|
|
ret = FC_SUCCESS;
|
|
uint32_t numPatchesReceived = 0;
|
|
|
|
tmpdir = cli_gentemp(g_tempDirectory);
|
|
if (!tmpdir) {
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
|
|
#else
|
|
if (!mprintf_quiet)
|
|
#endif
|
|
{
|
|
if (remoteVersion - localVersion == 1) {
|
|
mprintf("Current database is 1 version behind.\n");
|
|
} else {
|
|
mprintf("Current database is %u versions behind.\n", remoteVersion - localVersion);
|
|
}
|
|
}
|
|
for (i = localVersion + 1; i <= remoteVersion; i++) {
|
|
for (j = 1; j <= g_maxAttempts; j++) {
|
|
int llogerr = logerr;
|
|
if (logerr)
|
|
llogerr = (j == g_maxAttempts);
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
|
|
#else
|
|
if (!mprintf_quiet)
|
|
#endif
|
|
{
|
|
mprintf("Downloading database patch # %u...\n", i);
|
|
}
|
|
ret = downloadPatch(database, tmpdir, i, server, llogerr);
|
|
if (ret == FC_ECONNECTION || ret == FC_EFAILEDGET) {
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (FC_SUCCESS == ret) {
|
|
numPatchesReceived += 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (
|
|
(FC_EEMPTYFILE == ret) || /* Request a new CVD if we got an empty CDIFF. */
|
|
(FC_SUCCESS != ret && ( /* Or if the incremental update failed: */
|
|
(0 == numPatchesReceived) && /* 1. Ask for the CVD if we didn't get any patches, */
|
|
(localVersion < remoteVersion - 1) /* 2. AND if we're more than 1 version out of date. */
|
|
))) {
|
|
/*
|
|
* Incremental update failed or intentionally disabled.
|
|
*/
|
|
if (ret == FC_EEMPTYFILE) {
|
|
logg("*Empty CDIFF found. Skip incremental updates for this version and download %s\n", remoteFilename);
|
|
} else {
|
|
logg("^Incremental update failed, trying to download %s\n", remoteFilename);
|
|
}
|
|
|
|
ret = getcvd(remoteFilename, tmpfile, server, localTimestamp, remoteVersion, logerr);
|
|
if (FC_SUCCESS != ret) {
|
|
if (FC_EMIRRORNOTSYNC == ret) {
|
|
/* Note: We can't retry with CDIFF's if FC_EMIRRORNOTSYNC happened here.
|
|
* If we did there could be an infinite loop.
|
|
* Best option is to accept the older CVD.
|
|
*/
|
|
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);
|
|
status = FC_SUCCESS;
|
|
} else {
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
newLocalFilename = cli_strdup(remoteFilename);
|
|
} else if (0 == numPatchesReceived) {
|
|
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);
|
|
goto up_to_date;
|
|
} else {
|
|
/*
|
|
* CDIFFs downloaded; Use CDIFFs to turn old CVD/CLD into new updated CLD.
|
|
*/
|
|
if (numPatchesReceived < remoteVersion - localVersion) {
|
|
logg("Downloaded %u patches for %s, which is fewer than the %u expected patches.\n", numPatchesReceived, database, remoteVersion - localVersion);
|
|
logg("We'll settle for this partial-update, at least for now.\n");
|
|
}
|
|
|
|
size_t newLocalFilenameLen = 0;
|
|
if (FC_SUCCESS != buildcld(tmpdir, database, tmpfile, g_bCompressLocalDatabase)) {
|
|
logg("!updatedb: Incremental update failed. Failed to build CLD.\n");
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
|
|
newLocalFilenameLen = strlen(database) + strlen(".cld");
|
|
newLocalFilename = malloc(newLocalFilenameLen + 1);
|
|
snprintf(newLocalFilename, newLocalFilenameLen + 1, "%s.cld", database);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Update downloaded.
|
|
* Test database before replacing original database with new database.
|
|
*/
|
|
if (NULL != g_cb_download_complete) {
|
|
char *tmpfile_with_extension = NULL;
|
|
size_t tmpfile_with_extension_len = strlen(tmpfile) + 1 + strlen(newLocalFilename);
|
|
|
|
/* Suffix tmpfile with real database name & extension so it can be loaded. */
|
|
tmpfile_with_extension = malloc(tmpfile_with_extension_len + 1);
|
|
if (!tmpfile_with_extension) {
|
|
status = FC_ETESTFAIL;
|
|
goto done;
|
|
}
|
|
snprintf(tmpfile_with_extension, tmpfile_with_extension_len + 1, "%s-%s", tmpfile, newLocalFilename);
|
|
if (rename(tmpfile, tmpfile_with_extension) == -1) {
|
|
logg("!updatedb: Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno));
|
|
free(tmpfile_with_extension);
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
free(tmpfile);
|
|
tmpfile = tmpfile_with_extension;
|
|
tmpfile_with_extension = NULL;
|
|
|
|
/* Run callback to test it. */
|
|
logg("*updatedb: Running g_cb_download_complete callback...\n");
|
|
if (FC_SUCCESS != (ret = g_cb_download_complete(tmpfile, context))) {
|
|
logg("*updatedb: callback failed: %s (%d)\n", fc_strerror(ret), ret);
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Replace original database with new database.
|
|
*/
|
|
#ifdef _WIN32
|
|
if (!access(newLocalFilename, R_OK) && unlink(newLocalFilename)) {
|
|
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);
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
#endif
|
|
if (rename(tmpfile, newLocalFilename) == -1) {
|
|
logg("!updatedb: Can't rename %s to %s: %s\n", tmpfile, newLocalFilename, strerror(errno));
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
|
|
/* If we just updated from a CVD to a CLD, delete the old CVD */
|
|
if ((NULL != localFilename) && !access(localFilename, R_OK) && strcmp(newLocalFilename, localFilename))
|
|
if (unlink(localFilename))
|
|
logg("^updatedb: Can't delete the old database file %s. Please remove it manually.\n", localFilename);
|
|
|
|
/* Parse header to record number of sigs. */
|
|
if (NULL == (cvd = cl_cvdhead(newLocalFilename))) {
|
|
logg("!updatedb: Can't parse new database %s\n", newLocalFilename);
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
|
|
logg("%s updated (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
|
|
newLocalFilename, cvd->version, cvd->sigs, cvd->fl, cvd->builder);
|
|
|
|
flevel = cl_retflevel();
|
|
if (flevel < cvd->fl) {
|
|
logg("^Your ClamAV installation is OUTDATED!\n");
|
|
logg("^Current functionality level = %d, recommended = %d\n", flevel, cvd->fl);
|
|
logg("DON'T PANIC! Read https://docs.clamav.net/manual/Installing.html\n");
|
|
}
|
|
|
|
*signo = cvd->sigs;
|
|
*bUpdated = 1;
|
|
*dbFilename = cli_strdup(newLocalFilename);
|
|
if (NULL == *dbFilename) {
|
|
logg("!updatedb: Failed to allocate memory for database filename.\n");
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
|
|
up_to_date:
|
|
|
|
if (status != FC_EMIRRORNOTSYNC) {
|
|
status = FC_SUCCESS;
|
|
}
|
|
|
|
done:
|
|
|
|
if (NULL != cvd) {
|
|
cl_cvdfree(cvd);
|
|
}
|
|
|
|
if (NULL != localFilename) {
|
|
free(localFilename);
|
|
}
|
|
if (NULL != remoteFilename) {
|
|
free(remoteFilename);
|
|
}
|
|
if (NULL != newLocalFilename) {
|
|
free(newLocalFilename);
|
|
}
|
|
|
|
if (NULL != tmpfile) {
|
|
unlink(tmpfile);
|
|
free(tmpfile);
|
|
}
|
|
if (NULL != tmpdir) {
|
|
cli_rmdirs(tmpdir);
|
|
free(tmpdir);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
fc_error_t updatecustomdb(
|
|
const char *url,
|
|
void *context,
|
|
int logerr,
|
|
int *signo,
|
|
char **dbFilename,
|
|
int *bUpdated)
|
|
{
|
|
fc_error_t ret;
|
|
fc_error_t status = FC_EARG;
|
|
|
|
unsigned int sigs = 0;
|
|
char *tmpfile = NULL;
|
|
const char *databaseName;
|
|
STATBUF statbuf;
|
|
time_t dbtime = 0;
|
|
|
|
if ((NULL == url) || (NULL == signo) || (NULL == dbFilename) || (NULL == bUpdated)) {
|
|
logg("!updatecustomdb: Invalid args!\n");
|
|
goto done;
|
|
}
|
|
|
|
*signo = 0;
|
|
*dbFilename = NULL;
|
|
*bUpdated = 0;
|
|
|
|
tmpfile = cli_gentemp(g_tempDirectory);
|
|
if (!tmpfile) {
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
|
|
if (!strncasecmp(url, "file://", strlen("file://"))) {
|
|
/*
|
|
* Copy from local file.
|
|
*/
|
|
time_t remote_dbtime;
|
|
const char *rpath;
|
|
|
|
rpath = &url[strlen("file://")];
|
|
#ifdef _WIN32
|
|
databaseName = strrchr(rpath, '\\');
|
|
#else
|
|
databaseName = strrchr(rpath, '/');
|
|
#endif
|
|
if ((NULL == databaseName) || strlen(databaseName++) < strlen(".ext") + 1) {
|
|
logg("DatabaseCustomURL: Incorrect URL\n");
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
|
|
if (CLAMSTAT(rpath, &statbuf) == -1) {
|
|
logg("DatabaseCustomURL: file %s missing\n", rpath);
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
remote_dbtime = statbuf.st_mtime;
|
|
dbtime = (CLAMSTAT(databaseName, &statbuf) != -1) ? statbuf.st_mtime : 0;
|
|
if (dbtime > remote_dbtime) {
|
|
logg("%s is up-to-date (version: custom database)\n", databaseName);
|
|
goto up_to_date;
|
|
}
|
|
|
|
/* FIXME: preserve file permissions, calculate % */
|
|
if (-1 == cli_filecopy(rpath, tmpfile)) {
|
|
logg("DatabaseCustomURL: Can't copy file %s into database directory\n", rpath);
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
|
|
logg("Downloading %s [100%%]\n", databaseName);
|
|
} else {
|
|
/*
|
|
* Download from URL. http(s) or ftp(s)
|
|
*/
|
|
databaseName = strrchr(url, '/');
|
|
if ((NULL == databaseName) || (strlen(databaseName++) < 5)) {
|
|
logg("DatabaseCustomURL: Incorrect URL\n");
|
|
status = FC_EFAILEDUPDATE;
|
|
goto done;
|
|
}
|
|
|
|
dbtime = (CLAMSTAT(databaseName, &statbuf) != -1) ? statbuf.st_mtime : 0;
|
|
|
|
ret = downloadFile(url, tmpfile, 1, logerr, dbtime);
|
|
if (ret == FC_UPTODATE) {
|
|
logg("%s is up-to-date (version: custom database)\n", databaseName);
|
|
goto up_to_date;
|
|
} else if (ret > FC_UPTODATE) {
|
|
logg("%cCan't download %s from %s\n", logerr ? '!' : '^', databaseName, url);
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Update downloaded.
|
|
* Test database before replacing original database with new database.
|
|
*/
|
|
if (NULL != g_cb_download_complete) {
|
|
char *tmpfile_with_extension = NULL;
|
|
size_t tmpfile_with_extension_len = strlen(tmpfile) + 1 + strlen(databaseName);
|
|
|
|
/* Suffix tmpfile with real database name & extension so it can be loaded. */
|
|
tmpfile_with_extension = malloc(tmpfile_with_extension_len + 1);
|
|
if (!tmpfile_with_extension) {
|
|
status = FC_ETESTFAIL;
|
|
goto done;
|
|
}
|
|
snprintf(tmpfile_with_extension, tmpfile_with_extension_len + 1, "%s-%s", tmpfile, databaseName);
|
|
if (rename(tmpfile, tmpfile_with_extension) == -1) {
|
|
logg("!Custom database update failed: Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno));
|
|
free(tmpfile_with_extension);
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
free(tmpfile);
|
|
tmpfile = tmpfile_with_extension;
|
|
tmpfile_with_extension = NULL;
|
|
|
|
/* Run callback to test it. */
|
|
logg("*updatecustomdb: Running g_cb_download_complete callback...\n");
|
|
if (FC_SUCCESS != (ret = g_cb_download_complete(tmpfile, context))) {
|
|
logg("*updatecustomdb: callback failed: %s (%d)\n", fc_strerror(ret), ret);
|
|
status = ret;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Replace original database with new database.
|
|
*/
|
|
#ifdef _WIN32
|
|
if (!access(databaseName, R_OK) && unlink(databaseName)) {
|
|
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);
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
#endif
|
|
if (rename(tmpfile, databaseName) == -1) {
|
|
logg("!updatecustomdb: Can't rename %s to %s: %s\n", tmpfile, databaseName, strerror(errno));
|
|
status = FC_EDBDIRACCESS;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Record # of signatures in updated database.
|
|
*/
|
|
if (cli_strbcasestr(databaseName, ".cld") || cli_strbcasestr(databaseName, ".cvd")) {
|
|
struct cl_cvd *cvd = NULL;
|
|
unsigned int flevel;
|
|
|
|
if (NULL == (cvd = cl_cvdhead(databaseName))) {
|
|
logg("!updatecustomdb: Can't parse new database %s\n", databaseName);
|
|
status = FC_EFILE;
|
|
goto done;
|
|
}
|
|
|
|
sigs = cvd->sigs;
|
|
|
|
flevel = cl_retflevel();
|
|
if (flevel < cvd->fl) {
|
|
logg("^Your ClamAV installation is OUTDATED!\n");
|
|
logg("^Current functionality level = %d, recommended = %d\n", flevel, cvd->fl);
|
|
logg("DON'T PANIC! Read https://docs.clamav.net/manual/Installing.html\n");
|
|
}
|
|
|
|
cl_cvdfree(cvd);
|
|
} else if (cli_strbcasestr(databaseName, ".cbc")) {
|
|
sigs = 1;
|
|
} else {
|
|
sigs = countlines(databaseName);
|
|
}
|
|
|
|
logg("%s updated (version: custom database, sigs: %u)\n", databaseName, sigs);
|
|
*signo = sigs;
|
|
*bUpdated = 1;
|
|
|
|
up_to_date:
|
|
|
|
*dbFilename = cli_strdup(databaseName);
|
|
if (NULL == *dbFilename) {
|
|
logg("!Failed to allocate memory for database filename.\n");
|
|
status = FC_EMEM;
|
|
goto done;
|
|
}
|
|
|
|
status = FC_SUCCESS;
|
|
|
|
done:
|
|
|
|
if (NULL != tmpfile) {
|
|
unlink(tmpfile);
|
|
free(tmpfile);
|
|
}
|
|
|
|
return status;
|
|
}
|