freshclam/manager.c
e3aaff8e
 /*
e542ad45
  *  Copyright (C) 2002 - 2013 Tomasz Kojm <tkojm@clamav.net>
7e5f05e0
  *  HTTP/1.1 compliance by Arkadiusz Miskiewicz <misiek@pld.org.pl>
e3aaff8e
  *  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))
bb34cb31
  *  CDIFF code (C) 2006 Sensory Networks, Inc.
e3aaff8e
  *
  *  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
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
e3aaff8e
  */
6df88f29
 
6d6e8271
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
0a0ae0b4
 /* 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
 
e3aaff8e
 #include <stdio.h>
 #include <stdlib.h>
a9d3aa14
 #ifdef HAVE_UNISTD_H
e3aaff8e
 #include <unistd.h>
17ad1c5b
 #endif
e3aaff8e
 #include <string.h>
048e43bd
 #ifdef HAVE_STRINGS_H
64fd0bfc
 #include <strings.h>
048e43bd
 #endif
e3aaff8e
 #include <ctype.h>
4cd80898
 #ifndef _WIN32
e3aaff8e
 #include <netinet/in.h>
 #include <netdb.h>
b54eb319
 #include <arpa/inet.h>
e3aaff8e
 #include <sys/socket.h>
 #include <sys/time.h>
17ad1c5b
 #endif
4cd80898
 #include <sys/types.h>
e3aaff8e
 #include <time.h>
 #include <fcntl.h>
71a5cb43
 #ifndef	_WIN32
 #include <sys/wait.h>
 #endif
e3aaff8e
 #include <sys/stat.h>
9d193ff2
 #include <dirent.h>
fc918fb5
 #include <errno.h>
d8b95725
 #include <zlib.h>
e3aaff8e
 
c8cdb82f
 #include "target.h"
 
b35baf68
 #include "freshclamcodes.h"
e3aaff8e
 #include "manager.h"
 #include "notify.h"
3e92581e
 #include "dns.h"
7bbd5f7f
 #include "execute.h"
7d1c492d
 #include "nonblock.h"
376307a0
 #include "mirman.h"
7bbd5f7f
 
3f7802c9
 #include "shared/optparser.h"
d7c59961
 #include "shared/output.h"
 #include "shared/misc.h"
 #include "shared/cdiff.h"
9d193ff2
 #include "shared/tar.h"
3507891f
 #include "shared/clamdcom.h"
3e92581e
 
e48dcea9
 #include "libclamav/clamav.h"
d7c59961
 #include "libclamav/others.h"
 #include "libclamav/str.h"
 #include "libclamav/cvd.h"
9309eff0
 #include "libclamav/regex_list.h"
e3aaff8e
 
54bf9b46
 extern char updtmpdir[512], dbdir[512];
14547300
 char g_label[33];
af38c8ae
 
e8b94525
 #define CHDIR_ERR(x)				\
 	if(chdir(x) == -1)			\
 	    logg("!Can't chdir to %s\n", x);
 
b54eb319
 
6df88f29
 static int
b35baf68
 textrecordfield (const char * dbname)
 {
     if (!strcmp (dbname, "main"))
     {
         return 1;
     }
     else if (!strcmp (dbname, "daily"))
     {
         return 2;
     }
     else if (!strcmp (dbname, "bytecode"))
     {
         return 7;
     }
     else if (!strcmp (dbname, "safebrowsing"))
     {
         return 6;
     }
     return 0;
 }
 
 static int
6df88f29
 getclientsock (const char *localip, int prot)
e3aaff8e
 {
6df88f29
     int socketfd = -1;
e3aaff8e
 
d7c9ac1f
 #ifdef SUPPORT_IPv6
6df88f29
     if (prot == AF_INET6)
         socketfd = socket (AF_INET6, SOCK_STREAM, 0);
b54eb319
     else
d7c9ac1f
 #endif
6df88f29
         socketfd = socket (AF_INET, SOCK_STREAM, 0);
     if (socketfd < 0)
     {
4a4a0ed2
         logg ("!Can't create new socket: %s\n", strerror(errno));
6df88f29
         return -1;
c427ea09
     }
 
6df88f29
     if (localip)
     {
         struct addrinfo *res;
         int ret;
 
         ret = getaddrinfo (localip, NULL, NULL, &res);
         if (ret)
         {
             logg ("!Could not resolve local ip address '%s': %s\n", localip,
                   gai_strerror (ret));
             logg ("^Using standard local ip address and port for fetching.\n");
         }
         else
         {
             char ipaddr[46];
 
138b5aba
             if (bind (socketfd, res->ai_addr, (socklen_t)res->ai_addrlen) != 0)
6df88f29
             {
                 logg ("!Could not bind to local ip address '%s': %s\n",
                       localip, strerror (errno));
                 logg ("^Using default client ip.\n");
             }
             else
             {
                 void *addr;
fc918fb5
 
d7c9ac1f
 #ifdef SUPPORT_IPv6
6df88f29
                 if (res->ai_family == AF_INET6)
                     addr = &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
                 else
d7c9ac1f
 #endif
6df88f29
                     addr = &((struct sockaddr_in *) res->ai_addr)->sin_addr;
fc918fb5
 
6df88f29
                 if (inet_ntop (res->ai_family, addr, ipaddr, sizeof (ipaddr)))
                     logg ("*Using ip '%s' for fetching.\n", ipaddr);
             }
             freeaddrinfo (res);
         }
b54eb319
 
fc918fb5
     }
926d80f0
 
     return socketfd;
 }
 
6df88f29
 static int
 qcompare (const void *a, const void *b)
31d80e0e
 {
6df88f29
     return (*(const struct addrinfo **) a)->ai_flags -
         (*(const struct addrinfo **) b)->ai_flags;
31d80e0e
 }
 
6df88f29
 static int
 wwwconnect (const char *server, const char *proxy, int pport, char *ip,
             const char *localip, int ctimeout, struct mirdat *mdat,
             int logerr, unsigned int can_whitelist, unsigned int attempt)
926d80f0
 {
6df88f29
     int socketfd, port, ret;
     unsigned int ips = 0, ignored = 0, i;
     struct addrinfo hints, *res = NULL, *rp, *loadbal_rp = NULL, *addrs[128];
     char port_s[6], loadbal_ipaddr[46];
     uint32_t loadbal = 1, minsucc = 0xffffffff, minfail =
         0xffffffff, addrnum = 0;
     int ipv4start = -1, ipv4end = -1;
     struct mirdat_ip *md;
     char ipaddr[46];
     const char *hostpt;
926d80f0
 
6df88f29
     if (ip)
         strcpy (ip, "UNKNOWN");
926d80f0
 
6df88f29
     if (proxy)
     {
         hostpt = proxy;
95d401c4
 
6df88f29
         if (!(port = pport))
         {
             const struct servent *webcache =
                 getservbyname ("webcache", "TCP");
e3aaff8e
 
6df88f29
             if (webcache)
                 port = ntohs (webcache->s_port);
             else
                 port = 8080;
e3aaff8e
 
6df88f29
             endservent ();
         }
95d401c4
 
6df88f29
     }
     else
     {
         hostpt = server;
         port = 80;
e3aaff8e
     }
 
6df88f29
     memset (&hints, 0, sizeof (hints));
3027857c
 #ifdef SUPPORT_IPv6
b54eb319
     hints.ai_family = AF_UNSPEC;
3027857c
 #else
     hints.ai_family = AF_INET;
 #endif
b54eb319
     hints.ai_socktype = SOCK_STREAM;
6df88f29
     snprintf (port_s, sizeof (port_s), "%d", port);
     port_s[sizeof (port_s) - 1] = 0;
     ret = getaddrinfo (hostpt, port_s, &hints, &res);
     if (ret)
     {
         logg ("%cCan't get information about %s: %s\n", logerr ? '!' : '^',
               hostpt, gai_strerror (ret));
         return -1;
     }
 
     for (rp = res; rp && addrnum < 128; rp = rp->ai_next)
     {
         rp->ai_flags = cli_rndnum (1024);
         addrs[addrnum] = rp;
         if (rp->ai_family == AF_INET)
         {
             if (ipv4start == -1)
                 ipv4start = addrnum;
         }
         else if (ipv4end == -1 && ipv4start != -1)
         {
             ipv4end = addrnum - 1;
         }
         if (!rp->ai_next && ipv4end == -1)
             ipv4end = addrnum;
         addrnum++;
     }
     if (ipv4end != -1 && ipv4start != -1 && ipv4end - ipv4start + 1 > 1)
         qsort (&addrs[ipv4start], ipv4end - ipv4start + 1,
                sizeof (struct addrinfo *), qcompare);
 
     if (attempt > 1)
         loadbal = 0;
 
     for (i = 0; i < addrnum;)
     {
         void *addr;
 
         rp = addrs[i];
         ips++;
d7c9ac1f
 #ifdef SUPPORT_IPv6
6df88f29
         if (rp->ai_family == AF_INET6)
             addr = &((struct sockaddr_in6 *) rp->ai_addr)->sin6_addr;
         else
d7c9ac1f
 #endif
6df88f29
             addr = &((struct sockaddr_in *) rp->ai_addr)->sin_addr;
 
         if (!inet_ntop (rp->ai_family, addr, ipaddr, sizeof (ipaddr)))
         {
             logg ("%cinet_ntop() failed\n", logerr ? '!' : '^');
             freeaddrinfo (res);
             return -1;
         }
 
         if (mdat && (ret = mirman_check (addr, rp->ai_family, mdat, &md)))
         {
             if (ret == 1)
                 logg ("*Ignoring mirror %s (due to previous errors)\n",
                       ipaddr);
             else
                 logg ("*Ignoring mirror %s (has connected too many times with an outdated version)\n", ipaddr);
 
             ignored++;
             if (!loadbal || i + 1 < addrnum)
             {
                 i++;
                 continue;
             }
         }
 
         if (mdat && loadbal)
         {
             if (!ret)
             {
                 if (!md)
                 {
                     loadbal_rp = rp;
                     strncpy (loadbal_ipaddr, ipaddr, sizeof (loadbal_ipaddr));
                 }
                 else
                 {
                     if (md->succ <= minsucc && md->fail <= minfail)
                     {
                         minsucc = md->succ;
                         minfail = md->fail;
                         loadbal_rp = rp;
                         strncpy (loadbal_ipaddr, ipaddr,
                                  sizeof (loadbal_ipaddr));
                     }
                     if (i + 1 < addrnum)
                     {
                         i++;
                         continue;
                     }
                 }
             }
 
             if (!loadbal_rp)
             {
                 if (i + 1 == addrnum)
                 {
                     loadbal = 0;
                     i = 0;
                 }
                 continue;
             }
             rp = loadbal_rp;
             strncpy (ipaddr, loadbal_ipaddr, sizeof (ipaddr));
09005700
 #ifdef SUPPORT_IPv6
6df88f29
             if (rp->ai_family == AF_INET6)
                 addr = &((struct sockaddr_in6 *) rp->ai_addr)->sin6_addr;
             else
09005700
 #endif
6df88f29
                 addr = &((struct sockaddr_in *) rp->ai_addr)->sin_addr;
         }
         else if (loadbal_rp == rp)
         {
             i++;
             continue;
         }
b54eb319
 
6df88f29
         if (ip)
             strcpy (ip, ipaddr);
b54eb319
 
6df88f29
         if (rp != loadbal_rp && rp != addrs[0])
             logg ("Trying host %s (%s)...\n", hostpt, ipaddr);
b54eb319
 
6df88f29
         socketfd = getclientsock (localip, rp->ai_family);
         if (socketfd < 0)
         {
             freeaddrinfo (res);
             return -1;
         }
b54eb319
 
 #ifdef SO_ERROR
6df88f29
         if (wait_connect (socketfd, rp->ai_addr, rp->ai_addrlen, ctimeout) ==
             -1)
         {
b54eb319
 #else
6df88f29
         if (connect (socketfd, rp->ai_addr, rp->ai_addrlen) == -1)
         {
b54eb319
 #endif
6df88f29
             logg ("Can't connect to port %d of host %s (IP: %s)\n", port,
                   hostpt, ipaddr);
             closesocket (socketfd);
             if (loadbal)
             {
                 loadbal = 0;
                 i = 0;
             }
             else
                 i++;
7cbadfa4
             if (mdat)
                 mirman_update (addr, rp->ai_family, mdat, 2);
6df88f29
             continue;
         }
         else
         {
             if (mdat)
             {
                 if (rp->ai_family == AF_INET)
                     mdat->currip[0] = *((uint32_t *) addr);
                 else
                     memcpy (mdat->currip, addr, 4 * sizeof (uint32_t));
                 mdat->af = rp->ai_family;
             }
             freeaddrinfo (res);
             return socketfd;
         }
         i++;
     }
     freeaddrinfo (res);
b54eb319
 
6df88f29
     if (mdat && can_whitelist && ips && (ips == ignored))
         mirman_whitelist (mdat, 1);
a68aa31f
 
5c80179f
     return -2;
e3aaff8e
 }
 
6df88f29
 static unsigned int
 fmt_base64 (char *dest, const char *src, unsigned int len)
fe3f5f8c
 {
6df88f29
     unsigned short bits = 0, temp = 0;
     unsigned long written = 0;
     unsigned int i;
     const char base64[] =
         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
fe3f5f8c
 
 
6df88f29
     for (i = 0; i < len; i++)
     {
         temp <<= 8;
         temp += src[i];
         bits += 8;
         while (bits > 6)
         {
             dest[written] = base64[((temp >> (bits - 6)) & 63)];
             written++;
             bits -= 6;
         }
fe3f5f8c
     }
 
6df88f29
     if (bits)
     {
         temp <<= (6 - bits);
         dest[written] = base64[temp & 63];
         written++;
fe3f5f8c
     }
 
6df88f29
     while (written & 3)
     {
         dest[written] = '=';
         written++;
fe3f5f8c
     }
 
     return written;
 }
 
6df88f29
 static char *
 proxyauth (const char *user, const char *pass)
fe3f5f8c
 {
6df88f29
     int len;
     char *buf, *userpass, *auth;
fe3f5f8c
 
 
6df88f29
     userpass = malloc (strlen (user) + strlen (pass) + 2);
     if (!userpass)
     {
         logg ("!proxyauth: Can't allocate memory for 'userpass'\n");
         return NULL;
fe3f5f8c
     }
6df88f29
     sprintf (userpass, "%s:%s", user, pass);
fe3f5f8c
 
6df88f29
     buf = malloc ((strlen (pass) + strlen (user)) * 2 + 4);
     if (!buf)
     {
         logg ("!proxyauth: Can't allocate memory for 'buf'\n");
         free (userpass);
         return NULL;
fe3f5f8c
     }
 
6df88f29
     len = fmt_base64 (buf, userpass, strlen (userpass));
     free (userpass);
fe3f5f8c
     buf[len] = '\0';
6df88f29
     auth = malloc (strlen (buf) + 30);
     if (!auth)
     {
         free (buf);
         logg ("!proxyauth: Can't allocate memory for 'authorization'\n");
         return NULL;
fe3f5f8c
     }
 
6df88f29
     sprintf (auth, "Proxy-Authorization: Basic %s\r\n", buf);
     free (buf);
fe3f5f8c
 
     return auth;
 }
 
6df88f29
 static int
 Rfc2822DateTime (char *buf, time_t mtime)
e780fcb4
 {
6df88f29
     struct tm *gmt;
e780fcb4
 
6df88f29
     gmt = gmtime (&mtime);
     if (!gmt)
     {
         logg ("gmtime: %s\n", strerror (errno));
         strcpy (buf, "ERROR");
         return -1;
bdaa7959
     }
6df88f29
     return strftime (buf, 36, "%a, %d %b %Y %X GMT", gmt);
e780fcb4
 }
 
6df88f29
 static struct cl_cvd *
 remote_cvdhead (const char *cvdfile, const char *localfile,
                 const char *hostname, char *ip, const char *localip,
                 const char *proxy, int port, const char *user,
                 const char *pass, const char *uas, int *ims, int ctimeout,
                 int rtimeout, struct mirdat *mdat, int logerr,
                 unsigned int can_whitelist, unsigned int attempt)
d7c59961
 {
6df88f29
     char cmd[512], head[513], buffer[FILEBUFF], ipaddr[46], *ch, *tmp;
     int bread, cnt, sd;
     unsigned int i, j;
     char *remotename = NULL, *authorization = NULL;
     struct cl_cvd *cvd;
     char last_modified[36], uastr[128];
 
114cdb6b
     /* Initialize mirror status variable to unknown */
     *ims = -1;
6df88f29
 
     if (proxy)
     {
         remotename = malloc (strlen (hostname) + 8);
         if (!remotename)
         {
             logg ("!remote_cvdhead: Can't allocate memory for 'remotename'\n");
             return NULL;
         }
         sprintf (remotename, "http://%s", hostname);
 
         if (user)
         {
             authorization = proxyauth (user, pass);
             if (!authorization)
             {
                 free (remotename);
                 return NULL;
             }
         }
     }
 
     if (!access (localfile, R_OK))
     {
         cvd = cl_cvdhead (localfile);
         if (!cvd)
         {
             logg ("!remote_cvdhead: Can't parse file %s\n", localfile);
             free (remotename);
             free (authorization);
             return NULL;
         }
         Rfc2822DateTime (last_modified, (time_t) cvd->stime);
         cl_cvdfree (cvd);
     }
c8cdb82f
     else
6df88f29
     {
         time_t mtime = 1104119530;
c8cdb82f
 
6df88f29
         Rfc2822DateTime (last_modified, mtime);
         logg ("*Assuming modification time in the past\n");
     }
e3aaff8e
 
6df88f29
     logg ("*If-Modified-Since: %s\n", last_modified);
e3aaff8e
 
6df88f29
     logg ("Reading CVD header (%s): ", cvdfile);
d7c59961
 
6df88f29
     if (uas)
         strncpy (uastr, uas, sizeof (uastr));
     else
         snprintf (uastr, sizeof (uastr),
                   PACKAGE "/%s (OS: " TARGET_OS_TYPE ", ARCH: "
                   TARGET_ARCH_TYPE ", CPU: " TARGET_CPU_TYPE ")",
                   get_version ());
     uastr[sizeof (uastr) - 1] = 0;
 
     snprintf (cmd, sizeof (cmd),
               "GET %s/%s HTTP/1.0\r\n"
               "Host: %s\r\n%s"
               "User-Agent: %s\r\n"
               "Connection: close\r\n"
               "Range: bytes=0-511\r\n"
               "If-Modified-Since: %s\r\n"
               "\r\n", (remotename != NULL) ? remotename : "", cvdfile,
               hostname, (authorization != NULL) ? authorization : "", uastr,
               last_modified);
 
     free (remotename);
     free (authorization);
 
     memset (ipaddr, 0, sizeof (ipaddr));
 
     if (ip[0])                  /* use ip to connect */
         sd = wwwconnect (ip, proxy, port, ipaddr, localip, ctimeout, mdat,
                          logerr, can_whitelist, attempt);
d7c59961
     else
6df88f29
         sd = wwwconnect (hostname, proxy, port, ipaddr, localip, ctimeout,
                          mdat, logerr, can_whitelist, attempt);
d7c59961
 
6df88f29
     if (sd < 0)
     {
         return NULL;
     }
     else
     {
5f317e7d
         if (proxy)
             logg ("*Connected to %s.\n", hostname);
         else
             logg ("*Connected to %s (IP: %s).\n", hostname, ipaddr);
6df88f29
         logg ("*Trying to retrieve CVD header of http://%s/%s\n", hostname,
               cvdfile);
d7c59961
     }
 
6df88f29
     if (!ip[0])
         strcpy (ip, ipaddr);
d7c59961
 
6df88f29
     if (send (sd, cmd, strlen (cmd), 0) < 0)
     {
         logg ("%cremote_cvdhead: write failed\n", logerr ? '!' : '^');
         closesocket (sd);
         return NULL;
d7c59961
     }
 
e3aaff8e
     tmp = buffer;
f7148839
     cnt = FILEBUFF;
22b802b6
 #ifdef SO_ERROR
6df88f29
     while ((bread = wait_recv (sd, tmp, cnt, 0, rtimeout)) > 0)
     {
22b802b6
 #else
6df88f29
     while ((bread = recv (sd, tmp, cnt, 0)) > 0)
     {
22b802b6
 #endif
6df88f29
         tmp += bread;
         cnt -= bread;
         if (cnt <= 0)
             break;
e3aaff8e
     }
6df88f29
     closesocket (sd);
e3aaff8e
 
6df88f29
     if (bread == -1)
     {
         logg ("%cremote_cvdhead: Error while reading CVD header from %s\n",
               logerr ? '!' : '^', hostname);
         mirman_update (mdat->currip, mdat->af, mdat, 1);
         return NULL;
e3aaff8e
     }
 
6df88f29
     if ((strstr (buffer, "HTTP/1.1 404")) != NULL
         || (strstr (buffer, "HTTP/1.0 404")) != NULL)
     {
         logg ("%c%s not found on remote server\n", logerr ? '!' : '^',
               cvdfile);
         mirman_update (mdat->currip, mdat->af, mdat, 2);
         return NULL;
e780fcb4
     }
 
     /* check whether the resource is up-to-date */
6df88f29
     if ((strstr (buffer, "HTTP/1.1 304")) != NULL
         || (strstr (buffer, "HTTP/1.0 304")) != NULL)
     {
114cdb6b
         /* mirror status: up to date */
6df88f29
         *ims = 0;
         logg ("OK (IMS)\n");
         mirman_update (mdat->currip, mdat->af, mdat, 0);
         return NULL;
     }
     else
     {
114cdb6b
         /* mirror status: newer versin available */
6df88f29
         *ims = 1;
e3aaff8e
     }
 
6df88f29
     if (!strstr (buffer, "HTTP/1.1 200") && !strstr (buffer, "HTTP/1.0 200")
         && !strstr (buffer, "HTTP/1.1 206")
         && !strstr (buffer, "HTTP/1.0 206"))
     {
         logg ("%cUnknown response from remote server\n", logerr ? '!' : '^');
         mirman_update (mdat->currip, mdat->af, mdat, 1);
         return NULL;
59e52b2c
     }
 
fe3c5385
     i = 3;
     ch = buffer + i;
6df88f29
     while (i < sizeof (buffer))
     {
         if (*ch == '\n' && *(ch - 1) == '\r' && *(ch - 2) == '\n'
             && *(ch - 3) == '\r')
         {
             ch++;
             i++;
             break;
         }
         ch++;
         i++;
fe3c5385
     }
 
6df88f29
     if (sizeof (buffer) - i < 512)
     {
         logg ("%cremote_cvdhead: Malformed CVD header (too short)\n",
               logerr ? '!' : '^');
         mirman_update (mdat->currip, mdat->af, mdat, 1);
         return NULL;
fe3c5385
     }
e3aaff8e
 
6df88f29
     memset (head, 0, sizeof (head));
e4ae7726
 
6df88f29
     for (j = 0; j < 512; j++)
     {
         if (!ch || (ch && !*ch) || (ch && !isprint (ch[j])))
         {
             logg ("%cremote_cvdhead: Malformed CVD header (bad chars)\n",
                   logerr ? '!' : '^');
             mirman_update (mdat->currip, mdat->af, mdat, 1);
             return NULL;
         }
         head[j] = ch[j];
e3aaff8e
     }
 
6df88f29
     if (!(cvd = cl_cvdparse (head)))
     {
         logg ("%cremote_cvdhead: Malformed CVD header (can't parse)\n",
               logerr ? '!' : '^');
         mirman_update (mdat->currip, mdat->af, mdat, 1);
     }
     else
     {
         logg ("OK\n");
         mirman_update (mdat->currip, mdat->af, mdat, 0);
376307a0
     }
e4ae7726
 
     return cvd;
e3aaff8e
 }
 
6df88f29
 static int
 getfile_mirman (const char *srcfile, const char *destfile,
                 const char *hostname, char *ip, const char *localip,
                 const char *proxy, int port, const char *user,
                 const char *pass, const char *uas, int ctimeout, int rtimeout,
                 struct mirdat *mdat, int logerr, unsigned int can_whitelist,
                 const char *ims, const char *ipaddr, int sd)
e3aaff8e
 {
6df88f29
     char cmd[512], uastr[128], buffer[FILEBUFF], *ch;
     int bread, fd, totalsize = 0, rot = 0, totaldownloaded = 0,
         percentage = 0;
     unsigned int i;
     char *remotename = NULL, *authorization = NULL, *headerline;
     const char *rotation = "|/-\\", *fname;
c8cdb82f
 
6df13d04
     UNUSEDPARAM(localip);
     UNUSEDPARAM(port);
     UNUSEDPARAM(ctimeout);
     UNUSEDPARAM(can_whitelist);
6df88f29
 
     if (proxy)
     {
         remotename = malloc (strlen (hostname) + 8);
         if (!remotename)
         {
             logg ("!getfile: Can't allocate memory for 'remotename'\n");
b35baf68
             return FCE_MEM;
6df88f29
         }
         sprintf (remotename, "http://%s", hostname);
 
         if (user)
         {
             authorization = proxyauth (user, pass);
             if (!authorization)
             {
                 free (remotename);
b35baf68
                 return FCE_MEM;
6df88f29
             }
         }
     }
 
     if (ims)
         logg ("*If-Modified-Since: %s\n", ims);
 
     if (uas)
         strncpy (uastr, uas, sizeof (uastr));
     else
         snprintf (uastr, sizeof (uastr),
                   PACKAGE "/%s (OS: " TARGET_OS_TYPE ", ARCH: "
                   TARGET_ARCH_TYPE ", CPU: " TARGET_CPU_TYPE ")",
                   get_version ());
     uastr[sizeof (uastr) - 1] = 0;
 
     snprintf (cmd, sizeof (cmd),
               "GET %s/%s HTTP/1.0\r\n" "Host: %s\r\n%s" "User-Agent: %s\r\n"
69dfd3de
 #ifdef FRESHCLAM_NO_CACHE
6df88f29
               "Cache-Control: no-cache\r\n"
69dfd3de
 #endif
6df88f29
               "Connection: close\r\n"
               "%s%s%s"
               "\r\n", (remotename != NULL) ? remotename : "", srcfile,
               hostname, (authorization != NULL) ? authorization : "", uastr,
               ims ? "If-Modified-Since: " : "", ims ? ims : "",
               ims ? "\r\n" : "");
e3aaff8e
 
6df88f29
     if (remotename)
         free (remotename);
12460549
 
6df88f29
     if (authorization)
         free (authorization);
12460549
 
5f317e7d
     if (proxy)
         logg ("*Trying to download http://%s/%s\n", hostname, srcfile);
     else
         logg ("*Trying to download http://%s/%s (IP: %s)\n", hostname, srcfile,
               ipaddr);
d7c59961
 
6df88f29
     if (ip && !ip[0])
         strcpy (ip, ipaddr);
d7c59961
 
6df88f29
     if (send (sd, cmd, strlen (cmd), 0) < 0)
     {
         logg ("%cgetfile: Can't write to socket\n", logerr ? '!' : '^');
b35baf68
         return FCE_CONNECTION;
d7c59961
     }
e3aaff8e
 
d7c59961
     /* read http headers */
e3aaff8e
     ch = buffer;
     i = 0;
6df88f29
     while (1)
     {
         /* recv one byte at a time, until we reach \r\n\r\n */
22b802b6
 #ifdef SO_ERROR
6df88f29
         if ((i >= sizeof (buffer) - 1)
             || wait_recv (sd, buffer + i, 1, 0, rtimeout) == -1)
         {
22b802b6
 #else
6df88f29
         if ((i >= sizeof (buffer) - 1) || recv (sd, buffer + i, 1, 0) == -1)
         {
22b802b6
 #endif
5f317e7d
             if (proxy)
                 logg ("%cgetfile: Error while reading database from %s: %s\n", logerr ? '!' : '^', hostname, strerror (errno));
             else
                 logg ("%cgetfile: Error while reading database from %s (IP: %s): %s\n", logerr ? '!' : '^', hostname, ipaddr, strerror (errno));
6df88f29
             if (mdat)
                 mirman_update (mdat->currip, mdat->af, mdat, 1);
b35baf68
             return FCE_CONNECTION;
6df88f29
         }
f9da20cc
 
6df88f29
         if (i > 2 && *ch == '\n' && *(ch - 1) == '\r' && *(ch - 2) == '\n'
             && *(ch - 3) == '\r')
         {
             i++;
             break;
         }
         ch++;
         i++;
d6a56e70
     }
e3aaff8e
 
3755dd3f
     buffer[i] = 0;
 
f9da20cc
     /* check whether the resource actually existed or not */
6df88f29
     if ((strstr (buffer, "HTTP/1.1 404")) != NULL
         || (strstr (buffer, "HTTP/1.0 404")) != NULL)
     {
5f317e7d
         if (proxy)
             logg ("^getfile: %s not found on %s\n", srcfile, hostname);
         else
             logg ("^getfile: %s not found on %s (IP: %s)\n", srcfile, hostname,
                   ipaddr);
 
6df88f29
         if (mdat)
             mirman_update (mdat->currip, mdat->af, mdat, 2);
b35baf68
         return FCE_FAILEDGET;
f9da20cc
     }
 
b9d1c339
     /* If-Modified-Since */
6df88f29
     if (strstr (buffer, "HTTP/1.1 304") || strstr (buffer, "HTTP/1.0 304"))
     {
         if (mdat)
             mirman_update (mdat->currip, mdat->af, mdat, 0);
b35baf68
         return FC_UPTODATE;
b9d1c339
     }
 
6df88f29
     if (!strstr (buffer, "HTTP/1.1 200") && !strstr (buffer, "HTTP/1.0 200")
         && !strstr (buffer, "HTTP/1.1 206")
         && !strstr (buffer, "HTTP/1.0 206"))
     {
5f317e7d
         if (proxy)
             logg ("%cgetfile: Unknown response from %s\n",
                   logerr ? '!' : '^', hostname);
         else
             logg ("%cgetfile: Unknown response from %s (IP: %s)\n",
                   logerr ? '!' : '^', hostname, ipaddr);
6df88f29
         if (mdat)
             mirman_update (mdat->currip, mdat->af, mdat, 1);
b35baf68
         return FCE_FAILEDGET;
59e52b2c
     }
 
b12491c6
     /* get size of resource */
6df88f29
     for (i = 0; (headerline = cli_strtok (buffer, i, "\n")); i++)
     {
         if (strstr (headerline, "Content-Length:"))
         {
             if ((ch = cli_strtok (headerline, 1, ": ")))
             {
                 totalsize = atoi (ch);
                 free (ch);
             }
             else
             {
                 totalsize = 0;
             }
b12491c6
         }
6df88f29
         free (headerline);
d7c59961
     }
 
6df88f29
     if ((fd =
          open (destfile, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0644)) == -1)
     {
         char currdir[512];
d7c59961
 
6df88f29
         if (getcwd (currdir, sizeof (currdir)))
             logg ("!getfile: Can't create new file %s in %s\n", destfile,
                   currdir);
         else
             logg ("!getfile: Can't create new file %s in the current directory\n", destfile);
e8b94525
 
6df88f29
         logg ("Hint: The database directory must be writable for UID %d or GID %d\n", getuid (), getgid ());
b35baf68
         return FCE_DBDIRACCESS;
b12491c6
     }
 
6df88f29
     if ((fname = strrchr (srcfile, '/')))
         fname++;
b9d1c339
     else
6df88f29
         fname = srcfile;
b9d1c339
 
22b802b6
 #ifdef SO_ERROR
6df88f29
     while ((bread = wait_recv (sd, buffer, FILEBUFF, 0, rtimeout)) > 0)
     {
22b802b6
 #else
6df88f29
     while ((bread = recv (sd, buffer, FILEBUFF, 0)) > 0)
     {
22b802b6
 #endif
6df88f29
         if (write (fd, buffer, bread) != bread)
         {
             logg ("getfile: Can't write %d bytes to %s\n", bread, destfile);
             close (fd);
             unlink (destfile);
b35baf68
             return FCE_DBDIRACCESS;
6df88f29
         }
 
         totaldownloaded += bread;
         if (totalsize > 0)
25a9fc52
             percentage = (int) (100 * (float) totaldownloaded / totalsize);
 
cf5ba118
 #ifdef HAVE_UNISTD_H
92e8a9ed
         if (!mprintf_quiet && (mprintf_progress || isatty(fileno(stdout))))
cf5ba118
 #else
6df88f29
         if (!mprintf_quiet)
cf5ba118
 #endif
6df88f29
         {
             if (totalsize > 0)
             {
                 mprintf ("Downloading %s [%3i%%]\r", fname, percentage);
             }
             else
             {
                 mprintf ("Downloading %s [%c]\r", fname, rotation[rot]);
b12491c6
                 rot++;
                 rot %= 4;
             }
6df88f29
             fflush (stdout);
b12491c6
         }
e3aaff8e
     }
6df88f29
     close (fd);
e3aaff8e
 
6df88f29
     if (bread == -1)
     {
5f317e7d
         if (proxy)
             logg ("%cgetfile: Download interrupted: %s (Host: %s)\n",
                   logerr ? '!' : '^', strerror (errno), hostname);
         else
             logg ("%cgetfile: Download interrupted: %s (IP: %s)\n",
                   logerr ? '!' : '^', strerror (errno), ipaddr);
6df88f29
         if (mdat)
             mirman_update (mdat->currip, mdat->af, mdat, 2);
b35baf68
         return FCE_CONNECTION;
4aa1842a
     }
 
6df88f29
     if (!totaldownloaded)
b35baf68
         return FCE_EMPTYFILE;
6ce045e8
 
6df88f29
     if (totalsize > 0)
         logg ("Downloading %s [100%%]\n", fname);
b12491c6
     else
6df88f29
         logg ("Downloading %s [*]\n", fname);
d7c59961
 
6df88f29
     if (mdat)
         mirman_update (mdat->currip, mdat->af, mdat, 0);
d7c59961
     return 0;
 }
 
6df88f29
 static int
 getfile (const char *srcfile, const char *destfile, const char *hostname,
          char *ip, const char *localip, const char *proxy, int port,
          const char *user, const char *pass, const char *uas, int ctimeout,
          int rtimeout, struct mirdat *mdat, int logerr,
          unsigned int can_whitelist, const char *ims,
          const struct optstruct *opts, unsigned int attempt)
09005700
 {
6df88f29
     int ret, sd;
     char ipaddr[46];
09005700
 
6df13d04
     UNUSEDPARAM(opts);
 
6df88f29
     memset (ipaddr, 0, sizeof (ipaddr));
     if (ip && ip[0])            /* use ip to connect */
         sd = wwwconnect (ip, proxy, port, ipaddr, localip, ctimeout, mdat,
                          logerr, can_whitelist, attempt);
09005700
     else
6df88f29
         sd = wwwconnect (hostname, proxy, port, ipaddr, localip, ctimeout,
                          mdat, logerr, can_whitelist, attempt);
09005700
 
6df88f29
     if (sd < 0)
b35baf68
         return FCE_CONNECTION;
09005700
 
6df88f29
     if (mdat)
     {
         mirman_update_sf (mdat->currip, mdat->af, mdat, 0, 1);
         mirman_write ("mirrors.dat", dbdir, mdat);
09005700
     }
 
6df88f29
     ret =
         getfile_mirman (srcfile, destfile, hostname, ip, localip, proxy, port,
                         user, pass, uas, ctimeout, rtimeout, mdat, logerr,
                         can_whitelist, ims, ipaddr, sd);
     closesocket (sd);
09005700
 
6df88f29
     if (mdat)
     {
         mirman_update_sf (mdat->currip, mdat->af, mdat, 0, -1);
         mirman_write ("mirrors.dat", dbdir, mdat);
09005700
     }
 
     return ret;
 }
 
6df88f29
 static int
 getcvd (const char *cvdfile, const char *newfile, const char *hostname,
         char *ip, const char *localip, const char *proxy, int port,
         const char *user, const char *pass, const char *uas,
         unsigned int newver, int ctimeout, int rtimeout, struct mirdat *mdat,
         int logerr, unsigned int can_whitelist, const struct optstruct *opts,
         unsigned int attempt)
d7c59961
 {
6df88f29
     struct cl_cvd *cvd;
     int ret;
f1d5ea92
     char *newfile2;
d7c59961
 
 
6df88f29
     logg ("*Retrieving http://%s/%s\n", hostname, cvdfile);
09005700
 
6df88f29
     if ((ret =
          getfile (cvdfile, newfile, hostname, ip, localip, proxy, port, user,
                   pass, uas, ctimeout, rtimeout, mdat, logerr, can_whitelist,
                   NULL, opts, attempt)))
     {
         logg ("%cCan't download %s from %s\n", logerr ? '!' : '^', cvdfile,
               hostname);
         unlink (newfile);
d7c59961
         return ret;
     }
 
7cd9337a
     /* bb#10983 - temporarily rename newfile to correct extension for verification */
f1d5ea92
     newfile2 = strdup (newfile);
     if (!newfile2)
6df88f29
     {
f1d5ea92
         logg ("!Can't allocate memory for filename!\n");
         unlink (newfile);
         return FCE_MEM;
     }
     strncpy(newfile2 + strlen (newfile2) - 4, cvdfile + strlen (cvdfile) - 4, 4);
     if (rename (newfile, newfile2) == -1)
     {
         logg ("!Can't rename %s to %s: %s\n", newfile, newfile2,
               strerror (errno));
6df88f29
         unlink (newfile);
f1d5ea92
         free(newfile2);
         return FCE_DBDIRACCESS;
     }
 
     if ((ret = cl_cvdverify (newfile2)))
     {
         logg ("!Verification: %s\n", cl_strerror (ret));
         unlink (newfile2);
         free(newfile2);
b35baf68
         return FCE_BADCVD;
d7c59961
     }
 
f1d5ea92
     if (!(cvd = cl_cvdhead (newfile2)))
6df88f29
     {
         logg ("!Can't read CVD header of new %s database.\n", cvdfile);
f1d5ea92
         unlink (newfile2);
         free(newfile2);
b35baf68
         return FCE_BADCVD;
d7c59961
     }
 
f1d5ea92
     if (rename (newfile2, newfile) == -1)
     {
         logg ("!Can't rename %s to %s: %s\n", newfile2, newfile,
               strerror (errno));
         unlink (newfile2);
         free(newfile2);
         return FCE_DBDIRACCESS;
     }
     free(newfile2);
 
6df88f29
     if (cvd->version < newver)
     {
         logg ("^Mirror %s is not synchronized.\n", ip);
         mirman_update (mdat->currip, mdat->af, mdat, 2);
         cl_cvdfree (cvd);
         unlink (newfile);
b35baf68
         return FCE_MIRRORNOTSYNC;
d7c59961
     }
 
6df88f29
     cl_cvdfree (cvd);
e3aaff8e
     return 0;
 }
 
6df88f29
 static int
 chdir_tmp (const char *dbname, const char *tmpdir)
d7c59961
 {
6df88f29
     char cvdfile[32];
d7c59961
 
6df88f29
     if (access (tmpdir, R_OK | W_OK) == -1)
     {
ffd412cf
         int ret;
         ret = snprintf (cvdfile, sizeof(cvdfile), "%s.cvd", dbname);
         if (ret >= sizeof(cvdfile) || ret == -1) {
             logg ("!chdir_tmp: dbname parameter value too long to create cvd file name: %s\n", dbname);
             return -1;
         }
6df88f29
         if (access (cvdfile, R_OK) == -1)
         {
ffd412cf
             ret = snprintf (cvdfile, sizeof(cvdfile), "%s.cld", dbname);
             if (ret >= sizeof(cvdfile) || ret == -1) {
                 logg ("!chdir_tmp: dbname parameter value too long to create cld file name: %s\n", dbname);
                 return -1;
             }
6df88f29
             if (access (cvdfile, R_OK) == -1)
             {
                 logg ("!chdir_tmp: Can't access local %s database\n", dbname);
                 return -1;
             }
         }
d7c59961
 
6df88f29
         if (mkdir (tmpdir, 0755) == -1)
         {
             logg ("!chdir_tmp: Can't create directory %s\n", tmpdir);
             return -1;
         }
d7c59961
 
6df88f29
         if (cli_cvdunpack (cvdfile, tmpdir) == -1)
         {
             logg ("!chdir_tmp: Can't unpack %s into %s\n", cvdfile, tmpdir);
             cli_rmdirs (tmpdir);
             return -1;
         }
e3aaff8e
     }
d7c59961
 
6df88f29
     if (chdir (tmpdir) == -1)
     {
         logg ("!chdir_tmp: Can't change directory to %s\n", tmpdir);
         return -1;
e3aaff8e
     }
d7c59961
 
     return 0;
 }
 
6df88f29
 static int
 getpatch (const char *dbname, const char *tmpdir, int version,
           const char *hostname, char *ip, const char *localip,
           const char *proxy, int port, const char *user, const char *pass,
           const char *uas, int ctimeout, int rtimeout, struct mirdat *mdat,
           int logerr, unsigned int can_whitelist,
           const struct optstruct *opts, unsigned int attempt)
d7c59961
 {
6df88f29
     char *tempname, patch[32], olddir[512];
     int ret, fd;
d7c59961
 
 
6df88f29
     if (!getcwd (olddir, sizeof (olddir)))
     {
         logg ("!getpatch: Can't get path of current working directory\n");
b35baf68
         return FCE_DIRECTORY;
d7c59961
     }
 
6df88f29
     if (chdir_tmp (dbname, tmpdir) == -1)
b35baf68
         return FCE_DIRECTORY;
d7c59961
 
6df88f29
     tempname = cli_gentemp (".");
feb33f16
     if(!tempname) {
         CHDIR_ERR (olddir);
1e62280a
         return FCE_MEM;
feb33f16
     }
6df88f29
     snprintf (patch, sizeof (patch), "%s-%d.cdiff", dbname, version);
d7c59961
 
6df88f29
     logg ("*Retrieving http://%s/%s\n", hostname, patch);
     if ((ret =
          getfile (patch, tempname, hostname, ip, localip, proxy, port, user,
                   pass, uas, ctimeout, rtimeout, mdat, logerr, can_whitelist,
                   NULL, opts, attempt)))
     {
b35baf68
         if (ret == FCE_EMPTYFILE)
6df88f29
             logg ("Empty script %s, need to download entire database\n",
                   patch);
         else
             logg ("%cgetpatch: Can't download %s from %s\n",
                   logerr ? '!' : '^', patch, hostname);
         unlink (tempname);
         free (tempname);
         CHDIR_ERR (olddir);
d7c59961
         return ret;
     }
 
6df88f29
     if ((fd = open (tempname, O_RDONLY | O_BINARY)) == -1)
     {
         logg ("!getpatch: Can't open %s for reading\n", tempname);
         unlink (tempname);
         free (tempname);
         CHDIR_ERR (olddir);
b35baf68
         return FCE_FILE;
d7c59961
     }
 
6df88f29
     if (cdiff_apply (fd, 1) == -1)
     {
         logg ("!getpatch: Can't apply patch\n");
         close (fd);
         unlink (tempname);
         free (tempname);
         CHDIR_ERR (olddir);
b35baf68
         return FCE_FAILEDUPDATE;
6df88f29
     }
 
     close (fd);
     unlink (tempname);
     free (tempname);
     if (chdir (olddir) == -1)
     {
         logg ("!getpatch: Can't chdir to %s\n", olddir);
b35baf68
         return FCE_DIRECTORY;
e8b94525
     }
d7c59961
     return 0;
 }
 
6df88f29
 static struct cl_cvd *
 currentdb (const char *dbname, char *localname)
d7c59961
 {
6df88f29
     char db[32];
     struct cl_cvd *cvd = NULL;
d7c59961
 
 
6df88f29
     snprintf (db, sizeof (db), "%s.cvd", dbname);
     if (localname)
         strcpy (localname, db);
9d193ff2
 
6df88f29
     if (access (db, R_OK) == -1)
     {
         snprintf (db, sizeof (db), "%s.cld", dbname);
         if (localname)
             strcpy (localname, db);
6ba48775
     }
d7c59961
 
6df88f29
     if (!access (db, R_OK))
         cvd = cl_cvdhead (db);
d7c59961
 
     return cvd;
 }
 
6df88f29
 static int
 buildcld (const char *tmpdir, const char *dbname, const char *newfile,
           unsigned int compr)
9d193ff2
 {
6df88f29
     DIR *dir;
     char cwd[512], info[32], buff[513], *pt;
     struct dirent *dent;
     int fd, err = 0;
     gzFile gzs = NULL;
9d193ff2
 
6df88f29
     if (!getcwd (cwd, sizeof (cwd)))
     {
         logg ("!buildcld: Can't get path of current working directory\n");
         return -1;
e8b94525
     }
9d193ff2
 
6df88f29
     if (chdir (tmpdir) == -1)
     {
         logg ("!buildcld: Can't access directory %s\n", tmpdir);
         return -1;
9d193ff2
     }
 
6df88f29
     snprintf (info, sizeof (info), "%s.info", dbname);
     if ((fd = open (info, O_RDONLY | O_BINARY)) == -1)
     {
         logg ("!buildcld: Can't open %s\n", info);
         CHDIR_ERR (cwd);
         return -1;
9d193ff2
     }
 
6df88f29
     if (read (fd, buff, 512) == -1)
     {
         logg ("!buildcld: Can't read %s\n", info);
         CHDIR_ERR (cwd);
         close (fd);
         return -1;
9d193ff2
     }
45623e7c
     buff[512] = 0;
6df88f29
     close (fd);
 
     if (!(pt = strchr (buff, '\n')))
     {
         logg ("!buildcld: Bad format of %s\n", info);
         CHDIR_ERR (cwd);
         return -1;
     }
     memset (pt, ' ', 512 + buff - pt);
 
     if ((fd =
          open (newfile, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0644)) == -1)
     {
         logg ("!buildcld: Can't open %s for writing\n", newfile);
         CHDIR_ERR (cwd);
         return -1;
     }
     if (write (fd, buff, 512) != 512)
     {
         logg ("!buildcld: Can't write to %s\n", newfile);
         CHDIR_ERR (cwd);
         close (fd);
         unlink (newfile);
         return -1;
     }
 
     if ((dir = opendir (".")) == NULL)
     {
         logg ("!buildcld: Can't open directory %s\n", tmpdir);
         CHDIR_ERR (cwd);
         close (fd);
         unlink (newfile);
         return -1;
     }
 
     if (compr)
     {
         close (fd);
         if (!(gzs = gzopen (newfile, "ab9f")))
         {
             logg ("!buildcld: gzopen() failed for %s\n", newfile);
             CHDIR_ERR (cwd);
             closedir (dir);
             unlink (newfile);
             return -1;
         }
     }
 
     if (access ("COPYING", R_OK))
     {
         logg ("!buildcld: COPYING file not found\n");
         err = 1;
     }
     else
     {
         if (tar_addfile (fd, gzs, "COPYING") == -1)
         {
             logg ("!buildcld: Can't add COPYING to new %s.cld - please check if there is enough disk space available\n", dbname);
             if (!strcmp (dbname, "main") || !strcmp (dbname, "safebrowsing"))
                 logg ("Updates to main.cvd or safebrowsing.cvd may require 200MB of disk space or more\n");
             err = 1;
         }
     }
 
     if (!err && !access (info, R_OK))
     {
         if (tar_addfile (fd, gzs, info) == -1)
         {
             logg ("!buildcld: Can't add %s to new %s.cld - please check if there is enough disk space available\n", info, dbname);
             if (!strcmp (dbname, "main") || !strcmp (dbname, "safebrowsing"))
                 logg ("Updates to main.cvd or safebrowsing.cvd may require 200MB of disk space or more\n");
             err = 1;
         }
     }
 
     if (!err && !access ("daily.cfg", R_OK))
     {
         if (tar_addfile (fd, gzs, "daily.cfg") == -1)
         {
             logg ("!buildcld: Can't add daily.cfg to new %s.cld - please check if there is enough disk space available\n", dbname);
             err = 1;
         }
     }
 
     if (err)
     {
         CHDIR_ERR (cwd);
         if (gzs)
             gzclose (gzs);
         else
             close (fd);
         closedir (dir);
         unlink (newfile);
         return -1;
     }
 
     while ((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, dbname);
                 if (!strcmp (dbname, "main")
                     || !strcmp (dbname, "safebrowsing"))
                     logg ("Updates to main.cvd or safebrowsing.cvd may require 200MB of disk space or more\n");
                 CHDIR_ERR (cwd);
                 if (gzs)
                     gzclose (gzs);
                 else
                     close (fd);
                 closedir (dir);
                 unlink (newfile);
                 return -1;
             }
         }
     }
     closedir (dir);
 
     if (gzs)
     {
         if (gzclose (gzs))
         {
             logg ("!buildcld: gzclose() failed for %s\n", newfile);
             unlink (newfile);
             return -1;
         }
     }
     else
     {
         if (close (fd) == -1)
         {
             logg ("!buildcld: close() failed for %s\n", newfile);
             unlink (newfile);
             return -1;
         }
     }
 
     if (chdir (cwd) == -1)
     {
         logg ("!buildcld: Can't return to previous directory %s\n", cwd);
         return -1;
9d193ff2
     }
 
     return 0;
 }
 
6df88f29
 static int
 test_database (const char *newfile, const char *newdb, int bytecode)
536cf542
 {
     struct cl_engine *engine;
     unsigned newsigs = 0;
     int ret;
 
6df88f29
     logg ("*Loading signatures from %s\n", newdb);
     if (!(engine = cl_engine_new ()))
     {
b35baf68
         return FCE_TESTFAIL;
536cf542
     }
7c92a662
     cl_engine_set_clcb_stats_submit(engine, NULL);
536cf542
 
6df88f29
     if ((ret =
          cl_load (newfile, engine, &newsigs,
                   CL_DB_PHISHING | CL_DB_PHISHING_URLS | CL_DB_BYTECODE |
e542ad45
                   CL_DB_PUA | CL_DB_ENHANCED)) != CL_SUCCESS)
6df88f29
     {
         logg ("!Failed to load new database: %s\n", cl_strerror (ret));
         cl_engine_free (engine);
b35baf68
         return FCE_TESTFAIL;
6df88f29
     }
     if (bytecode
         && (ret =
             cli_bytecode_prepare2 (engine, &engine->bcs,
                                    engine->dconf->
                                    bytecode
                                    /*FIXME: dconf has no sense here */ )))
     {
         logg ("!Failed to compile/load bytecode: %s\n", cl_strerror (ret));
         cl_engine_free (engine);
b35baf68
         return FCE_TESTFAIL;
6df88f29
     }
     logg ("*Properly loaded %u signatures from new %s\n", newsigs, newdb);
     if (engine->domainlist_matcher
         && engine->domainlist_matcher->sha256_pfx_set.keys)
         cli_hashset_destroy (&engine->domainlist_matcher->sha256_pfx_set);
     cl_engine_free (engine);
536cf542
     return 0;
 }
 
 #ifndef WIN32
6df88f29
 static int
 test_database_wrap (const char *file, const char *newdb, int bytecode)
536cf542
 {
     char firstline[256];
     char lastline[256];
     int pipefd[2];
     pid_t pid;
2e136dcd
     int status = 0, ret;
536cf542
     FILE *f;
 
6df88f29
     if (pipe (pipefd) == -1)
     {
         logg ("^pipe() failed: %s\n", strerror (errno));
         return test_database (file, newdb, bytecode);
     }
 
     switch (pid = fork ())
     {
     case 0:
         close (pipefd[0]);
5733158b
         if (dup2 (pipefd[1], 2) == -1)
             logg("^dup2() failed: %s\n", strerror(errno));
6df88f29
         exit (test_database (file, newdb, bytecode));
     case -1:
         close (pipefd[0]);
         close (pipefd[1]);
         logg ("^fork() failed: %s\n", strerror (errno));
         return test_database (file, newdb, bytecode);
     default:
         /* read first / last line printed by child */
         close (pipefd[1]);
         f = fdopen (pipefd[0], "r");
         firstline[0] = 0;
         lastline[0] = 0;
         do
         {
             if (!fgets (firstline, sizeof (firstline), f))
                 break;
             /* ignore warning messages, otherwise the outdated warning will
              * make us miss the important part of the error message */
         }
         while (!strncmp (firstline, "LibClamAV Warning:", 18));
         /* must read entire output, child doesn't like EPIPE */
         while (fgets (lastline, sizeof (firstline), f))
         {
             /* print the full output only when LogVerbose or -v is given */
             logg ("*%s", lastline);
         }
         fclose (f);
 
         while ((ret = waitpid (pid, &status, 0)) == -1 && errno == EINTR);
         if (ret == -1 && errno != ECHILD)
             logg ("^waitpid() failed: %s\n", strerror (errno));
         cli_chomp (firstline);
         cli_chomp (lastline);
         if (firstline[0])
         {
             logg ("!During database load : %s%s%s\n",
                   firstline, lastline[0] ? " [...] " : "", lastline);
         }
         if (WIFEXITED (status))
         {
             ret = WEXITSTATUS (status);
             if (ret)
             {
                 logg ("^Database load exited with status %d\n", ret);
                 return ret;
             }
             if (firstline[0])
                 logg ("^Database successfully loaded, but there is stderr output\n");
             return 0;
         }
         if (WIFSIGNALED (status))
         {
             logg ("!Database load killed by signal %d\n", WTERMSIG (status));
b35baf68
             return FCE_TESTFAIL;
6df88f29
         }
         logg ("^Unknown status from wait: %d\n", status);
b35baf68
         return FCE_TESTFAIL;
536cf542
     }
 }
 #else
6df88f29
 static int
 test_database_wrap (const char *file, const char *newdb, int bytecode)
536cf542
 {
b35baf68
     int ret = FCE_TESTFAIL;
536cf542
     __try
     {
6df88f29
         ret = test_database (file, newdb, bytecode);
     }
     __except (logg ("!Exception during database testing, code %08x\n",
                     GetExceptionCode ()), EXCEPTION_CONTINUE_SEARCH)
     {
536cf542
     }
     return ret;
 }
 #endif
 
6df88f29
 static int
 checkdbdir (void)
8fe6ad48
 {
6df88f29
     DIR *dir;
     struct dirent *dent;
     char fname[512], broken[513];
     int ret, fret = 0;
 
     if (!(dir = opendir (dbdir)))
     {
         logg ("!checkdbdir: Can't open directory %s\n", dbdir);
         return -1;
     }
 
     while ((dent = readdir (dir)))
     {
         if (dent->d_ino)
         {
             if (cli_strbcasestr (dent->d_name, ".cld")
                 || cli_strbcasestr (dent->d_name, ".cvd"))
             {
                 snprintf (fname, sizeof (fname), "%s" PATHSEP "%s", dbdir,
                           dent->d_name);
                 if ((ret = cl_cvdverify (fname)))
                 {
                     fret = -1;
                     mprintf ("!Corrupted database file %s: %s\n", fname,
                              cl_strerror (ret));
                     snprintf (broken, sizeof (broken), "%s.broken", fname);
                     if (!access (broken, R_OK))
                         unlink (broken);
                     if (rename (fname, broken))
                     {
                         if (unlink (fname))
                             mprintf
                                 ("!Can't remove broken database file %s, please delete it manually and restart freshclam\n",
                                  fname);
                     }
                     else
                     {
                         mprintf ("Corrupted database file renamed to %s\n",
                                  broken);
                     }
                 }
             }
         }
     }
 
     closedir (dir);
f8202681
     return fret;
8fe6ad48
 }
 
14547300
 static const char * dns_label(const char * ip)
 {
c6bfda94
     static const char hex[] = "0123456789ABCDEF";
     char *c, *n = NULL, *m = g_label, *cc = g_label;
     const char *p, *q;
     int r, cs = 0;
     int has_ipv4 = strchr(ip, '.')?1:0;
 
     strcpy(g_label, "0000000000000000"
                     "0000000000000000");
 
     if (strchr(ip, ':')) { /* IPv6 */
         p = ip;
         c = g_label;
         while ((q = strchr(p, ':'))) {
             r = q - p;
             if (r>0 && r<5) {
                 memcpy(c+4-r, p, r);
                 c+=4;
             }
             p = q + 1;
             if (++cs > 7)
                 break;
             if (*p == ':') { /* check :: */
 		cc = c - 1;
                 if (has_ipv4)
                     n = g_label + 23;
                 else
                     n = g_label + 31;
                 break;
             }
         }
         if (q == NULL) {
             /* do last ipv6 segment */
             q = p+strlen(p);
             r = q - p;
             if (r>0 && r<5)
                 memcpy(c+4-r, p, r);
         }
         if (n) { /* go backward to :: */
             int k = 0;
             if (has_ipv4)
                 q = strrchr(p, ':');
             else
                 q = p + strlen(p) - 1;
             if (!(*q == ':' && *--q == ':'))
                 while (n > cc) {
                     if (*q == ':') {
                         if (*(q-1) == ':')
                             break;
                         n -= 4-k;
                         k = 0;
                     } else {
                         *n = *q;
                         n--;
                         k++;
                     }
                     q--;
                 }
         }
         if (has_ipv4) {
             m = g_label + 24;
             ip = strrchr(ip, ':') + 1;
         }
     }
 
     if (has_ipv4) {
         uint8_t x;
         do {
             x = atoi(ip);
             *m++ = hex[x>>4];
             *m++ = hex[x&0x0F];
             ip = strchr(ip, '.');
         } while (ip++ && *m && *(m+1));
         *m = '\0';
     }
 
     return g_label;
14547300
 }
 
536cf542
 extern int sigchld_wait;
 
6df88f29
 static int
 updatedb (const char *dbname, const char *hostname, char *ip, int *signo,
           const struct optstruct *opts, const char *dnsreply, char *localip,
           int outdated, struct mirdat *mdat, int logerr, int extra,
           unsigned int attempt)
d7c59961
 {
6df88f29
     struct cl_cvd *current, *remote;
     const struct optstruct *opt;
     unsigned int nodb = 0, currver = 0, newver = 0, port = 0, i, j;
f1d5ea92
     int ret, ims = -1, iscld = 0, field = 0;
6df88f29
     char *pt, cvdfile[32], cldfile[32], localname[32], *tmpdir =
         NULL, *newfile, *newfile2, newdb[32];
60f2d041
     char extradbinfo[256], *extradnsreply = NULL, squery[256];
6df88f29
     const char *proxy = NULL, *user = NULL, *pass = NULL, *uas = NULL;
     unsigned int flevel = cl_retflevel (), remote_flevel = 0, maxattempts;
     unsigned int can_whitelist = 0, mirror_stats = 0;
a3a0367b
 #ifdef _WIN32
6df88f29
     unsigned int w32 = 1;
a3a0367b
 #else
6df88f29
     unsigned int w32 = 0;
a3a0367b
 #endif
6df88f29
     int ctimeout, rtimeout;
 
 
     if (cli_strbcasestr (hostname, ".clamav.net"))
         mirror_stats = 1;
 
     snprintf (cvdfile, sizeof (cvdfile), "%s.cvd", dbname);
     snprintf (cldfile, sizeof (cldfile), "%s.cld", dbname);
 
b35baf68
     if (!extra)
     {
         field = textrecordfield(dbname);
     }
 
6df88f29
     if (!(current = currentdb (dbname, localname)))
     {
         nodb = 1;
     }
     else
     {
         mdat->dbflevel = current->fl;
     }
 
     if (!nodb && !extra && dnsreply)
     {
b35baf68
         if (!field)
6df88f29
         {
             logg ("!updatedb: Unknown database name (%s) passed.\n", dbname);
             cl_cvdfree (current);
b35baf68
             return FCE_FAILEDUPDATE;
6df88f29
         }
 
138b5aba
         if ((pt = cli_strtok (dnsreply, field, ":")))
6df88f29
         {
             if (!cli_isnumber (pt))
             {
                 logg ("^Broken database version in TXT record.\n");
             }
             else
             {
                 newver = atoi (pt);
                 logg ("*%s version from DNS: %d\n", cvdfile, newver);
             }
             free (pt);
         }
         else
         {
             logg ("^Invalid DNS reply. Falling back to HTTP mode.\n");
         }
d7c59961
     }
e30bb458
 #ifdef HAVE_RESOLV_H
6df88f29
     else if (!nodb && extra && !optget (opts, "no-dns")->enabled)
     {
         snprintf (extradbinfo, sizeof (extradbinfo), "%s.cvd.clamav.net",
                   dbname);
         if ((extradnsreply = dnsquery (extradbinfo, T_TXT, NULL)))
         {
             if ((pt = cli_strtok (extradnsreply, 1, ":")))
             {
                 int rt;
                 time_t ct;
 
                 rt = atoi (pt);
                 free (pt);
                 time (&ct);
                 if ((int) ct - rt > 10800)
                 {
                     logg ("^DNS record is older than 3 hours.\n");
                     free (extradnsreply);
                     extradnsreply = NULL;
                 }
             }
             else
             {
                 logg ("^No timestamp in TXT record for %s\n", cvdfile);
                 free (extradnsreply);
                 extradnsreply = NULL;
             }
             if ((pt = cli_strtok (extradnsreply, 0, ":")))
             {
                 if (!cli_isnumber (pt))
                 {
                     logg ("^Broken database version in TXT record for %s\n",
                           cvdfile);
                 }
                 else
                 {
                     newver = atoi (pt);
                     logg ("*%s version from DNS: %d\n", cvdfile, newver);
                 }
                 free (pt);
             }
             else
             {
                 logg ("^Invalid DNS reply. Falling back to HTTP mode.\n");
             }
         }
e30bb458
     }
 #endif
d7c59961
 
6df88f29
     if (dnsreply && !extra)
     {
         if ((pt = cli_strtok (dnsreply, 5, ":")))
         {
             remote_flevel = atoi (pt);
             free (pt);
             if (remote_flevel && (remote_flevel - flevel < 4))
                 can_whitelist = 1;
         }
a68aa31f
     }
 
d7c59961
     /* Initialize proxy settings */
6df88f29
     if ((opt = optget (opts, "HTTPProxyServer"))->enabled)
     {
         proxy = opt->strarg;
         if (strncasecmp (proxy, "http://", 7) == 0)
             proxy += 7;
 
         if ((opt = optget (opts, "HTTPProxyUsername"))->enabled)
         {
             user = opt->strarg;
             if ((opt = optget (opts, "HTTPProxyPassword"))->enabled)
             {
                 pass = opt->strarg;
             }
             else
             {
                 logg ("HTTPProxyUsername requires HTTPProxyPassword\n");
                 if (current)
                     cl_cvdfree (current);
b35baf68
                 return FCE_CONFIG;
6df88f29
             }
         }
 
         if ((opt = optget (opts, "HTTPProxyPort"))->enabled)
             port = opt->numarg;
 
         logg ("Connecting via %s\n", proxy);
     }
 
     if ((opt = optget (opts, "HTTPUserAgent"))->enabled)
         uas = opt->strarg;
 
     ctimeout = optget (opts, "ConnectTimeout")->numarg;
     rtimeout = optget (opts, "ReceiveTimeout")->numarg;
 
     if (!nodb && !newver)
     {
         if (optget (opts, "PrivateMirror")->enabled)
         {
114cdb6b
             /*
              * For a private mirror, get the CLD instead of the CVD.
              */
6df88f29
             remote =
                 remote_cvdhead (cldfile, localname, hostname, ip, localip,
                                 proxy, port, user, pass, uas, &ims, ctimeout,
                                 rtimeout, mdat, logerr, can_whitelist,
                                 attempt);
114cdb6b
             if (!remote && (ims != 0)) {
                 /*
                  * Failed to get CLD update, and it's unknown if the status is up-to-date.
                  * Attempt to get the CVD instead.
                  */
f1d5ea92
                 iscld = -1;
6df88f29
                 remote =
                     remote_cvdhead (cvdfile, localname, hostname, ip, localip,
                                     proxy, port, user, pass, uas, &ims,
                                     ctimeout, rtimeout, mdat, logerr,
                                     can_whitelist, attempt);
f1d5ea92
             }
6df88f29
         }
         else
             remote =
                 remote_cvdhead (cvdfile, localname, hostname, ip, localip,
                                 proxy, port, user, pass, uas, &ims, ctimeout,
                                 rtimeout, mdat, logerr, can_whitelist,
                                 attempt);
 
         if (!nodb && !ims)
         {
             logg ("%s is up to date (version: %d, sigs: %d, f-level: %d, builder: %s)\n", localname, current->version, current->sigs, current->fl, current->builder);
             *signo += current->sigs;
edd1af40
 #ifdef HAVE_RESOLV_H
6df88f29
             if (mirror_stats && strlen (ip))
             {
                 snprintf (squery, sizeof (squery),
                           "%s.%u.%u.%u.%u.%s.ping.clamav.net", dbname,
14547300
                           current->version, flevel, 1, w32, dns_label(ip));
6df88f29
                 dnsquery (squery, T_A, NULL);
             }
edd1af40
 #endif
6df88f29
             cl_cvdfree (current);
b35baf68
             return FC_UPTODATE;
6df88f29
         }
d7c59961
 
6df88f29
         if (!remote)
         {
d26c933d
             if (proxy)
                 logg ("^Can't read %s header from %s\n", cvdfile, hostname);
             else
                 logg ("^Can't read %s header from %s (IP: %s)\n", cvdfile,
                       hostname, ip);
edd1af40
 #ifdef HAVE_RESOLV_H
6df88f29
             if (mirror_stats && strlen (ip))
             {
                 snprintf (squery, sizeof (squery),
                           "%s.%u.%u.%u.%u.%s.ping.clamav.net", dbname,
14547300
                           current->version + 1, flevel, 0, w32, dns_label(ip));
6df88f29
                 dnsquery (squery, T_A, NULL);
             }
edd1af40
 #endif
6df88f29
             cl_cvdfree (current);
b35baf68
             return FCE_FAILEDGET;
6df88f29
         }
d7c59961
 
6df88f29
         newver = remote->version;
         cl_cvdfree (remote);
d7c59961
     }
 
6df88f29
     if (!nodb && (current->version >= newver))
     {
         logg ("%s is up to date (version: %d, sigs: %d, f-level: %d, builder: %s)\n", localname, current->version, current->sigs, current->fl, current->builder);
 
         if (!outdated && flevel < current->fl)
         {
             /* display warning even for already installed database */
             logg ("^Current functionality level = %d, recommended = %d\n",
                   flevel, current->fl);
             logg ("Please check if ClamAV tools are linked against the proper version of libclamav\n");
964a1e73
             logg ("DON'T PANIC! Read https://www.clamav.net/documents/installing-clamav\n");
6df88f29
         }
d7c59961
 
6df88f29
         *signo += current->sigs;
         cl_cvdfree (current);
b35baf68
         return FC_UPTODATE;
d7c59961
     }
 
6df88f29
     if (current)
     {
         currver = current->version;
         cl_cvdfree (current);
28861d8a
     }
d7c59961
 
6df88f29
     if (!optget (opts, "ScriptedUpdates")->enabled)
         nodb = 1;
011b4f29
 
6df88f29
     newfile = cli_gentemp (updtmpdir);
7ff792d0
     if(!newfile)
         return FCE_MEM;
 
6df88f29
     if (nodb)
     {
         if (optget (opts, "PrivateMirror")->enabled)
         {
             ret = 0;
f1d5ea92
             if (iscld >= 0)
6df88f29
                 ret =
                     getcvd (cldfile, newfile, hostname, ip, localip, proxy,
                             port, user, pass, uas, newver, ctimeout, rtimeout,
                             mdat, logerr, can_whitelist, opts, attempt);
f1d5ea92
             if (ret || iscld < 0)
6df88f29
                 ret =
                     getcvd (cvdfile, newfile, hostname, ip, localip, proxy,
                             port, user, pass, uas, newver, ctimeout, rtimeout,
                             mdat, logerr, can_whitelist, opts, attempt);
f1d5ea92
             else
                 iscld = 1;
6df88f29
         }
         else
e542ad45
         {
6df88f29
             ret =
                 getcvd (cvdfile, newfile, hostname, ip, localip, proxy, port,
                         user, pass, uas, newver, ctimeout, rtimeout, mdat,
                         logerr, can_whitelist, opts, attempt);
e542ad45
         }
6df88f29
 
         if (ret)
         {
edd1af40
 #ifdef HAVE_RESOLV_H
6df88f29
             if (mirror_stats && strlen (ip))
             {
                 snprintf (squery, sizeof (squery),
                           "%s.%u.%u.%u.%u.%s.ping.clamav.net", dbname, 0,
14547300
                           flevel, 0, w32, dns_label(ip));
6df88f29
                 dnsquery (squery, T_A, NULL);
             }
edd1af40
 #endif
6df88f29
             memset (ip, 0, 16);
             free (newfile);
             return ret;
         }
f1d5ea92
         if (iscld > 0)
             snprintf (newdb, sizeof (newdb), "%s.cld", dbname);
         else
             snprintf (newdb, sizeof (newdb), "%s.cvd", dbname);
6df88f29
 
     }
     else
     {
         ret = 0;
 
         tmpdir = cli_gentemp (updtmpdir);
7ff792d0
 	if(!tmpdir){
 	    free(newfile);
 	    return FCE_MEM;
 	}    
 
6df88f29
         maxattempts = optget (opts, "MaxAttempts")->numarg;
         for (i = currver + 1; i <= newver; i++)
         {
             for (j = 1; j <= maxattempts; j++)
             {
                 int llogerr = logerr;
                 if (logerr)
                     llogerr = (j == maxattempts);
                 ret =
                     getpatch (dbname, tmpdir, i, hostname, ip, localip, proxy,
                               port, user, pass, uas, ctimeout, rtimeout, mdat,
                               llogerr, can_whitelist, opts,
                               attempt == 1 ? j : attempt);
b35baf68
                 if (ret == FCE_CONNECTION || ret == FCE_FAILEDGET)
6df88f29
                 {
edd1af40
 #ifdef HAVE_RESOLV_H
6df88f29
                     if (mirror_stats && strlen (ip))
                     {
                         snprintf (squery, sizeof (squery),
                                   "%s.%u.%u.%u.%u.%s.ping.clamav.net", dbname,
14547300
                                   i, flevel, 0, w32, dns_label(ip));
6df88f29
                         dnsquery (squery, T_A, NULL);
                     }
edd1af40
 #endif
6df88f29
                     memset (ip, 0, 16);
                     continue;
                 }
                 else
                 {
                     break;
                 }
             }
             if (ret)
                 break;
         }
 
         if (ret)
         {
             cli_rmdirs (tmpdir);
             free (tmpdir);
b35baf68
             if (ret != FCE_EMPTYFILE)
6df88f29
                 logg ("^Incremental update failed, trying to download %s\n",
                       cvdfile);
             mirman_whitelist (mdat, 2);
             ret =
                 getcvd (cvdfile, newfile, hostname, ip, localip, proxy, port,
                         user, pass, uas, newver, ctimeout, rtimeout, mdat,
                         logerr, can_whitelist, opts, attempt);
             if (ret)
             {
edd1af40
 #ifdef HAVE_RESOLV_H
6df88f29
                 if (mirror_stats && strlen (ip))
                 {
                     snprintf (squery, sizeof (squery),
                               "%s.%u.%u.%u.%u.%s.ping.clamav.net", dbname, 0,
14547300
                               flevel, 0, w32, dns_label(ip));
6df88f29
                     dnsquery (squery, T_A, NULL);
                 }
edd1af40
 #endif
6df88f29
                 free (newfile);
                 return ret;
             }
             snprintf (newdb, sizeof (newdb), "%s.cvd", dbname);
         }
         else
         {
             if (buildcld
                 (tmpdir, dbname, newfile,
                  optget (opts, "CompressLocalDatabase")->enabled) == -1)
             {
                 logg ("!Can't create local database\n");
                 cli_rmdirs (tmpdir);
                 free (tmpdir);
                 free (newfile);
b35baf68
                 return FCE_FAILEDUPDATE;
6df88f29
             }
             snprintf (newdb, sizeof (newdb), "%s.cld", dbname);
             cli_rmdirs (tmpdir);
             free (tmpdir);
         }
     }
 
     if (!(current = cl_cvdhead (newfile)))
     {
         logg ("!Can't parse new database %s\n", newfile);
         unlink (newfile);
         free (newfile);
b35baf68
         return FCE_FILE;
6df88f29
     }
 
     if (optget (opts, "TestDatabases")->enabled && strlen (newfile) > 4)
     {
         newfile2 = strdup (newfile);
         if (!newfile2)
         {
             logg ("!Can't allocate memory for filename!\n");
             unlink (newfile);
             free (newfile);
dc862f42
             cl_cvdfree(current);
b35baf68
             return FCE_TESTFAIL;
6df88f29
         }
         newfile2[strlen (newfile2) - 4] = '.';
         newfile2[strlen (newfile2) - 3] = 'c';
         newfile2[strlen (newfile2) - 2] = strstr (newdb, ".cld") ? 'l' : 'v';
         newfile2[strlen (newfile2) - 1] = 'd';
         if (rename (newfile, newfile2) == -1)
         {
             logg ("!Can't rename %s to %s: %s\n", newfile, newfile2,
                   strerror (errno));
             unlink (newfile);
             free (newfile);
             free (newfile2);
dc862f42
             cl_cvdfree(current);
b35baf68
             return FCE_DBDIRACCESS;
6df88f29
         }
         free (newfile);
         newfile = newfile2;
         sigchld_wait = 0;       /* we need to wait() for the child ourselves */
         if (test_database_wrap
             (newfile, newdb, optget (opts, "Bytecode")->enabled))
         {
             logg ("!Failed to load new database\n");
             unlink (newfile);
             free (newfile);
dc862f42
             cl_cvdfree(current);
b35baf68
             return FCE_TESTFAIL;
6df88f29
         }
         sigchld_wait = 1;
361b3285
     }
 
be4bf7f4
 #ifdef _WIN32
6df88f29
     if (!access (newdb, R_OK) && unlink (newdb))
     {
         logg ("!Can't unlink %s. Please fix the problem manually and try again.\n", newdb);
         unlink (newfile);
         free (newfile);
         cl_cvdfree (current);
b35baf68
         return FCE_EMPTYFILE;
195be7d4
     }
 #endif
 
6df88f29
     if (rename (newfile, newdb) == -1)
     {
         logg ("!Can't rename %s to %s: %s\n", newfile, newdb,
               strerror (errno));
         unlink (newfile);
         free (newfile);
         cl_cvdfree (current);
b35baf68
         return FCE_DBDIRACCESS;
9d193ff2
     }
6df88f29
     free (newfile);
011b4f29
 
6df88f29
     if (!nodb && !access (localname, R_OK) && strcmp (newdb, localname))
         if (unlink (localname))
             logg ("^Can't unlink the old database file %s. Please remove it manually.\n", localname);
99ca7f53
 
f1d5ea92
     if (!optget (opts, "ScriptedUpdates")->enabled && !optget (opts, "PrivateMirror")->enabled)
6df88f29
     {
         snprintf (localname, sizeof (localname), "%s.cld", dbname);
         if (!access (localname, R_OK))
             if (unlink (localname))
                 logg ("^Can't unlink the old database file %s. Please remove it manually.\n", localname);
284e1ee4
     }
 
6df88f29
     logg ("%s updated (version: %d, sigs: %d, f-level: %d, builder: %s)\n",
           newdb, current->version, current->sigs, current->fl,
           current->builder);
d7c59961
 
6df88f29
     if (flevel < current->fl)
     {
         logg ("^Your ClamAV installation is OUTDATED!\n");
         logg ("^Current functionality level = %d, recommended = %d\n", flevel,
               current->fl);
964a1e73
         logg ("DON'T PANIC! Read https://www.clamav.net/documents/installing-clamav\n");
3af45725
 
d7c59961
     }
 
     *signo += current->sigs;
edd1af40
 #ifdef HAVE_RESOLV_H
6df88f29
     if (mirror_stats && strlen (ip))
     {
         snprintf (squery, sizeof (squery),
                   "%s.%u.%u.%u.%u.%s.ping.clamav.net", dbname,
14547300
                   current->version, flevel, 1, w32, dns_label(ip));
6df88f29
         dnsquery (squery, T_A, NULL);
a3a0367b
     }
edd1af40
 #endif
6df88f29
     cl_cvdfree (current);
d7c59961
     return 0;
e3aaff8e
 }
d7c59961
 
6df88f29
 static int
 updatecustomdb (const char *url, int *signo, const struct optstruct *opts,
                 char *localip, int logerr)
3cdc51b8
 {
6df88f29
     const struct optstruct *opt;
     unsigned int port = 0, sigs = 0;
     int ret;
     char *pt, *host, urlcpy[256], *newfile = NULL, mtime[36], *newfile2;
     const char *proxy = NULL, *user = NULL, *pass = NULL, *uas =
         NULL, *rpath, *dbname;
     int ctimeout, rtimeout;
     STATBUF sb;
     struct cl_cvd *cvd;
 
     if (strlen (url) > sizeof (urlcpy) - 1)
     {
059ca614
         logg ("!DatabaseCustomURL: URL must be shorter than %llu\n",
               (long long unsigned)sizeof (urlcpy));
b35baf68
         return FCE_FAILEDUPDATE;
688b5cd3
     }
 
6df88f29
     if (!strncasecmp (url, "http://", 7))
     {
         strncpy (urlcpy, url, sizeof (urlcpy));
59a60382
         urlcpy[sizeof(urlcpy)-1] = '\0';
6df88f29
         host = &urlcpy[7];
         if (!(pt = strchr (host, '/')))
         {
             logg ("!DatabaseCustomURL: Incorrect URL\n");
b35baf68
             return FCE_FAILEDUPDATE;
6df88f29
         }
         *pt = 0;
         rpath = &url[pt - urlcpy + 1];
         dbname = strrchr (url, '/') + 1;
         if (!dbname || strlen (dbname) < 4)
         {
             logg ("DatabaseCustomURL: Incorrect URL\n");
b35baf68
             return FCE_FAILEDUPDATE;
6df88f29
         }
 
         /* Initialize proxy settings */
         if ((opt = optget (opts, "HTTPProxyServer"))->enabled)
         {
             proxy = opt->strarg;
             if (strncasecmp (proxy, "http://", 7) == 0)
                 proxy += 7;
 
             if ((opt = optget (opts, "HTTPProxyUsername"))->enabled)
             {
                 user = opt->strarg;
                 if ((opt = optget (opts, "HTTPProxyPassword"))->enabled)
                 {
                     pass = opt->strarg;
                 }
                 else
                 {
                     logg ("HTTPProxyUsername requires HTTPProxyPassword\n");
b35baf68
                     return FCE_CONFIG;
6df88f29
                 }
             }
             if ((opt = optget (opts, "HTTPProxyPort"))->enabled)
                 port = opt->numarg;
             logg ("Connecting via %s\n", proxy);
         }
 
         if ((opt = optget (opts, "HTTPUserAgent"))->enabled)
             uas = opt->strarg;
 
         ctimeout = optget (opts, "ConnectTimeout")->numarg;
         rtimeout = optget (opts, "ReceiveTimeout")->numarg;
 
         *mtime = 0;
d9b6b8c7
         if (CLAMSTAT (dbname, &sb) != -1)
6df88f29
             Rfc2822DateTime (mtime, sb.st_mtime);
 
         newfile = cli_gentemp (updtmpdir);
         ret =
             getfile (rpath, newfile, host, NULL, localip, proxy, port, user,
                      pass, uas, ctimeout, rtimeout, NULL, logerr, 0,
                      *mtime ? mtime : NULL, opts, 1);
         if (ret == 1)
         {
             logg ("%s is up to date (version: custom database)\n", dbname);
             unlink (newfile);
             free (newfile);
b35baf68
             return FC_UPTODATE;
6df88f29
         }
         else if (ret > 1)
         {
             logg ("%cCan't download %s from %s\n", logerr ? '!' : '^', dbname,
                   host);
             unlink (newfile);
             free (newfile);
             return ret;
         }
 
     }
     else if (!strncasecmp (url, "file://", 7))
     {
a8b6bc00
         time_t dbtime, rtime;
6df88f29
         rpath = &url[7];
3cdc51b8
 #ifdef _WIN32
6df88f29
         dbname = strrchr (rpath, '\\');
3cdc51b8
 #else
6df88f29
         dbname = strrchr (rpath, '/');
3cdc51b8
 #endif
6df88f29
         if (!dbname || strlen (dbname++) < 5)
         {
             logg ("DatabaseCustomURL: Incorrect URL\n");
b35baf68
             return FCE_FAILEDUPDATE;
6df88f29
         }
 
a8b6bc00
         if (CLAMSTAT (rpath, &sb) == -1)
         {
 	    logg ("DatabaseCustomURL: file %s missing\n", rpath);
 	    return FCE_FAILEDUPDATE;
         }
         rtime = sb.st_mtime;
         dbtime = (CLAMSTAT (dbname, &sb) != -1) ? sb.st_mtime : 0;
         if (dbtime > rtime)
         {
             logg ("%s is up to date (version: custom database)\n", dbname);
             return FC_UPTODATE;
         }
 
6df88f29
         newfile = cli_gentemp (updtmpdir);
         if (!newfile)
b35baf68
             return FCE_FAILEDUPDATE;
6df88f29
 
         /* FIXME: preserve file permissions, calculate % */
         if (cli_filecopy (rpath, newfile) == -1)
         {
             logg ("DatabaseCustomURL: Can't copy file %s into database directory\n", rpath);
             free (newfile);
b35baf68
             return FCE_FAILEDUPDATE;
6df88f29
         }
         logg ("Downloading %s [100%%]\n", dbname);
     }
     else
     {
         logg ("!DatabaseCustomURL: Not supported protocol\n");
b35baf68
         return FCE_FAILEDUPDATE;
6df88f29
     }
 
     if (optget (opts, "TestDatabases")->enabled && strlen (newfile) > 4)
     {
         newfile2 = malloc (strlen (newfile) + strlen (dbname) + 1);
         if (!newfile2)
         {
             unlink (newfile);
             free (newfile);
b35baf68
             return FCE_TESTFAIL;
6df88f29
         }
         sprintf (newfile2, "%s%s", newfile, dbname);
         newfile2[strlen (newfile) + strlen (dbname)] = 0;
         if (rename (newfile, newfile2) == -1)
         {
             logg ("!Can't rename %s to %s: %s\n", newfile, newfile2,
                   strerror (errno));
             unlink (newfile);
             free (newfile);
             free (newfile2);
b35baf68
             return FCE_DBDIRACCESS;
6df88f29
         }
         free (newfile);
         newfile = newfile2;
         sigchld_wait = 0;       /* we need to wait() for the child ourselves */
         if (test_database_wrap
             (newfile, dbname, optget (opts, "Bytecode")->enabled))
         {
             logg ("!Failed to load new database\n");
             unlink (newfile);
             free (newfile);
b35baf68
             return FCE_TESTFAIL;
6df88f29
         }
         sigchld_wait = 1;
3cdc51b8
     }
 
 #ifdef _WIN32
6df88f29
     if (!access (dbname, R_OK) && unlink (dbname))
     {
         logg ("!Can't unlink %s. Please fix the problem manually and try again.\n", dbname);
         unlink (newfile);
         free (newfile);
b35baf68
         return FCE_EMPTYFILE;
3cdc51b8
     }
 #endif
 
6df88f29
     if (rename (newfile, dbname) == -1)
     {
         logg ("!Can't rename %s to %s: %s\n", newfile, dbname,
               strerror (errno));
         unlink (newfile);
         free (newfile);
b35baf68
         return FCE_DBDIRACCESS;
3cdc51b8
     }
6df88f29
     free (newfile);
3cdc51b8
 
6df88f29
     if (cli_strbcasestr (dbname, ".cld") || cli_strbcasestr (dbname, ".cvd"))
     {
         if ((cvd = cl_cvdhead (dbname)))
         {
             sigs = cvd->sigs;
             cl_cvdfree (cvd);
         }
     }
     else if (cli_strbcasestr (dbname, ".cbc"))
     {
         sigs = 1;
     }
     else
     {
         sigs = countlines (dbname);
b9d1c339
     }
 
6df88f29
     logg ("%s updated (version: custom database, sigs: %u)\n", dbname, sigs);
b9d1c339
     *signo += sigs;
3cdc51b8
     return 0;
 }
 
6df88f29
 int
 downloadmanager (const struct optstruct *opts, const char *hostname,
                  unsigned int attempt)
d7c59961
 {
6df88f29
     time_t currtime;
c9a070c9
     int ret, custret = 0, updated = 0, outdated = 0, signo = 0, logerr;
6df88f29
     unsigned int ttl;
     char ipaddr[46], *dnsreply = NULL, *pt, *localip = NULL, *newver = NULL;
     const struct optstruct *opt;
     struct mirdat mdat;
d7c59961
 #ifdef HAVE_RESOLV_H
6df88f29
     const char *dnsdbinfo;
d7c59961
 #endif
 
6df88f29
     logerr = (optget (opts, "MaxAttempts")->numarg == attempt);
6b8d41e3
 
6df88f29
     pt = cli_gentemp (dbdir);
     if (!pt)
b35baf68
         return FCE_DBDIRACCESS;
6df88f29
     strncpy (updtmpdir, pt, sizeof (updtmpdir));
     updtmpdir[sizeof (updtmpdir) - 1] = '\0';
     free (pt);
     if (mkdir (updtmpdir, 0755))
     {
         logg ("!Can't create temporary directory %s\n", updtmpdir);
         logg ("Hint: The database directory must be writable for UID %d or GID %d\n", getuid (), getgid ());
b35baf68
         return FCE_DBDIRACCESS;
af38c8ae
     }
 
6df88f29
     time (&currtime);
     logg ("ClamAV update process started at %s", ctime (&currtime));
     logg ("*Using IPv6 aware code\n");
d7c59961
 
 #ifdef HAVE_RESOLV_H
6df88f29
     dnsdbinfo = optget (opts, "DNSDatabaseInfo")->strarg;
 
     if (optget (opts, "no-dns")->enabled)
     {
         dnsreply = NULL;
     }
     else
     {
         if ((dnsreply = dnsquery (dnsdbinfo, T_TXT, &ttl)))
         {
             logg ("*TTL: %d\n", ttl);
 
             if ((pt = cli_strtok (dnsreply, 3, ":")))
             {
                 int rt;
                 time_t ct;
 
                 rt = atoi (pt);
                 free (pt);
                 time (&ct);
                 if ((int) ct - rt > 10800)
                 {
                     logg ("^DNS record is older than 3 hours.\n");
                     free (dnsreply);
                     dnsreply = NULL;
                 }
 
             }
             else
             {
                 free (dnsreply);
                 dnsreply = NULL;
             }
 
             if (dnsreply)
             {
                 int vwarning = 1;
 
                 if ((pt = cli_strtok (dnsreply, 4, ":")))
                 {
                     if (*pt == '0')
                         vwarning = 0;
 
                     free (pt);
                 }
 
                 if ((newver = cli_strtok (dnsreply, 0, ":")))
                 {
                     char vstr[32];
 
                     logg ("*Software version from DNS: %s\n", newver);
                     strncpy (vstr, get_version (), 32);
                     vstr[31] = 0;
                     if (vwarning && !strstr (vstr, "devel")
3447198a
                         && !strstr (vstr, "beta")
6df88f29
                         && !strstr (vstr, "rc"))
                     {
                         pt = strchr (vstr, '-');
                         if ((pt && strncmp (vstr, newver, pt - vstr))
                             || (!pt && strcmp (vstr, newver)))
                         {
                             logg ("^Your ClamAV installation is OUTDATED!\n");
                             logg ("^Local version: %s Recommended version: %s\n", vstr, newver);
964a1e73
                             logg ("DON'T PANIC! Read https://www.clamav.net/documents/upgrading-clamav\n");
6df88f29
                             outdated = 1;
                         }
                     }
                 }
             }
         }
 
         if (!dnsreply)
         {
             logg ("^Invalid DNS reply. Falling back to HTTP mode.\n");
         }
d7c59961
     }
 #endif /* HAVE_RESOLV_H */
 
6df88f29
     if ((opt = optget (opts, "LocalIPAddress"))->enabled)
         localip = opt->strarg;
d7c59961
 
6df88f29
     if (optget (opts, "HTTPProxyServer")->enabled
         || optget (opts, "PrivateMirror")->enabled)
         mirman_read ("mirrors.dat", &mdat, 0);
a7db63d1
     else
6df88f29
         mirman_read ("mirrors.dat", &mdat, 1);
376307a0
 
6df88f29
     memset (ipaddr, 0, sizeof (ipaddr));
d7c59961
 
a4604627
     /* custom dbs */
6df88f29
     if ((opt = optget (opts, "DatabaseCustomURL"))->enabled)
     {
         while (opt)
         {
             if ((custret =
                  updatecustomdb (opt->strarg, &signo, opts, localip,
                                  logerr)) == 0)
                 updated = 1;
             opt = opt->nextarg;
         }
     }
 
     if ((opt = optget (opts, "update-db"))->enabled)
     {
         const char *u_dnsreply;
         int u_extra;
 
         while (opt)
         {
             if (!strcmp (opt->strarg, "custom"))
             {
                 if (!optget (opts, "DatabaseCustomURL")->enabled)
                 {
                     logg ("!--update-db=custom requires DatabaseCustomURL\n");
b35baf68
                     custret = FCE_CONFIG;
6df88f29
                 }
                 free (dnsreply);
                 free (newver);
                 mirman_write ("mirrors.dat", dbdir, &mdat);
                 mirman_free (&mdat);
                 cli_rmdirs (updtmpdir);
                 return custret;
             }
 
             if (!strcmp (opt->strarg, "main")
                 || !strcmp (opt->strarg, "daily")
                 || !strcmp (opt->strarg, "safebrowsing")
                 || !strcmp (opt->strarg, "bytecode"))
             {
                 u_dnsreply = dnsreply;
                 u_extra = 0;
             }
             else
             {
                 u_dnsreply = NULL;
                 u_extra = 1;
             }
             if ((ret =
                  updatedb (opt->strarg, hostname, ipaddr, &signo, opts,
                            u_dnsreply, localip, outdated, &mdat, logerr,
                            u_extra, attempt)) > 50)
             {
                 if (dnsreply)
                     free (dnsreply);
                 if (newver)
                     free (newver);
                 mirman_write ("mirrors.dat", dbdir, &mdat);
                 mirman_free (&mdat);
                 cli_rmdirs (updtmpdir);
                 return ret;
             }
             else if (ret == 0)
                 updated = 1;
 
             opt = opt->nextarg;
         }
 
     }
     else
     {
         if ((ret =
              updatedb ("main", hostname, ipaddr, &signo, opts, dnsreply,
                        localip, outdated, &mdat, logerr, 0, attempt)) > 50)
         {
             if (dnsreply)
                 free (dnsreply);
             if (newver)
                 free (newver);
             mirman_write ("mirrors.dat", dbdir, &mdat);
             mirman_free (&mdat);
             cli_rmdirs (updtmpdir);
             return ret;
         }
         else if (ret == 0)
             updated = 1;
 
         /* if ipaddr[0] != 0 it will use it to connect to the web host */
         if ((ret =
              updatedb ("daily", hostname, ipaddr, &signo, opts, dnsreply,
                        localip, outdated, &mdat, logerr, 0, attempt)) > 50)
         {
             if (dnsreply)
                 free (dnsreply);
             if (newver)
                 free (newver);
             mirman_write ("mirrors.dat", dbdir, &mdat);
             mirman_free (&mdat);
             cli_rmdirs (updtmpdir);
             return ret;
         }
         else if (ret == 0)
             updated = 1;
 
         if (!optget (opts, "SafeBrowsing")->enabled)
         {
             const char *safedb = NULL;
 
             if (!access ("safebrowsing.cvd", R_OK))
                 safedb = "safebrowsing.cvd";
             else if (!access ("safebrowsing.cld", R_OK))
                 safedb = "safebrowsing.cld";
 
             if (safedb)
             {
                 if (unlink (safedb))
                     logg ("^SafeBrowsing is disabled but can't remove old %s\n", safedb);
                 else
                     logg ("*%s removed\n", safedb);
             }
         }
         else if ((ret =
                   updatedb ("safebrowsing", hostname, ipaddr, &signo, opts,
                             dnsreply, localip, outdated, &mdat, logerr, 0,
                             attempt)) > 50)
         {
             if (dnsreply)
                 free (dnsreply);
             if (newver)
                 free (newver);
             mirman_write ("mirrors.dat", dbdir, &mdat);
             mirman_free (&mdat);
             cli_rmdirs (updtmpdir);
             return ret;
         }
         else if (ret == 0)
             updated = 1;
 
         if (!optget (opts, "Bytecode")->enabled)
         {
             const char *dbname = NULL;
 
             if (!access ("bytecode.cvd", R_OK))
                 dbname = "bytecode.cvd";
             else if (!access ("bytecode.cld", R_OK))
                 dbname = "bytecode.cld";
 
             if (dbname)
             {
                 if (unlink (dbname))
                     logg ("^Bytecode is disabled but can't remove old %s\n",
                           dbname);
                 else
                     logg ("*%s removed\n", dbname);
             }
         }
         else if ((ret =
                   updatedb ("bytecode", hostname, ipaddr, &signo, opts,
                             dnsreply, localip, outdated, &mdat, logerr, 0,
                             attempt)) > 50)
         {
             if (dnsreply)
                 free (dnsreply);
             if (newver)
                 free (newver);
             mirman_write ("mirrors.dat", dbdir, &mdat);
             mirman_free (&mdat);
             cli_rmdirs (updtmpdir);
             return ret;
         }
         else if (ret == 0)
             updated = 1;
 
         /* handle extra dbs */
         if ((opt = optget (opts, "ExtraDatabase"))->enabled)
         {
             while (opt)
             {
                 if ((ret =
                      updatedb (opt->strarg, hostname, ipaddr, &signo, opts,
                                NULL, localip, outdated, &mdat, logerr, 1,
                                attempt)) > 50)
                 {
                     if (dnsreply)
                         free (dnsreply);
                     if (newver)
                         free (newver);
                     mirman_write ("mirrors.dat", dbdir, &mdat);
                     mirman_free (&mdat);
                     cli_rmdirs (updtmpdir);
                     return ret;
                 }
                 else if (ret == 0)
                     updated = 1;
                 opt = opt->nextarg;
             }
         }
     }
 
     if (dnsreply)
         free (dnsreply);
 
     mirman_write ("mirrors.dat", dbdir, &mdat);
     mirman_free (&mdat);
 
     cli_rmdirs (updtmpdir);
 
17bb2b9b
     if (updated && checkdbdir () < 0)
6df88f29
     {
         if (newver)
             free (newver);
b35baf68
         return FCE_BADCVD;
6df88f29
     }
 
     if (updated)
     {
         if (optget (opts, "HTTPProxyServer")->enabled || !ipaddr[0])
         {
             logg ("Database updated (%d signatures) from %s\n", signo,
                   hostname);
         }
         else
         {
             logg ("Database updated (%d signatures) from %s (IP: %s)\n",
                   signo, hostname, ipaddr);
         }
d7c59961
 
 #ifdef BUILD_CLAMD
6df88f29
         if ((opt = optget (opts, "NotifyClamd"))->active)
             notify (opt->strarg);
d7c59961
 #endif
 
6df88f29
         if ((opt = optget (opts, "OnUpdateExecute"))->enabled)
             execute ("OnUpdateExecute", opt->strarg, opts);
     }
 
     if (outdated)
     {
         if ((opt = optget (opts, "OnOutdatedExecute"))->enabled)
         {
             char *cmd = strdup (opt->strarg);
 
616570eb
             if (!cmd)
             {
                 free (newver);
                 return FCE_MEM;
             }
 
6df88f29
             if ((pt = newver))
             {
                 while (*pt)
                 {
                     if (!strchr ("0123456789.", *pt))
                     {
                         logg ("!downloadmanager: OnOutdatedExecute: Incorrect version number string\n");
                         free (newver);
                         newver = NULL;
                         break;
                     }
                     pt++;
                 }
             }
 
             if (newver && (pt = strstr (cmd, "%v")))
             {
                 char *buffer =
                     (char *) malloc (strlen (cmd) + strlen (newver) + 10);
 
                 if (!buffer)
                 {
                     logg ("!downloadmanager: Can't allocate memory for buffer\n");
                     free (cmd);
                     if (newver)
                         free (newver);
b35baf68
                     return FCE_MEM;
6df88f29
                 }
 
                 *pt = 0;
                 pt += 2;
                 strcpy (buffer, cmd);
                 strcat (buffer, newver);
                 strcat (buffer, pt);
                 free (cmd);
                 cmd = strdup (buffer);
                 free (buffer);
616570eb
                 if (!cmd)
                 {
                     free (newver);
                     return FCE_MEM;
                 }
6df88f29
             }
 
             if (newver)
                 execute ("OnOutdatedExecute", cmd, opts);
 
             free (cmd);
         }
     }
 
     if (newver)
         free (newver);
d7c59961
 
341b343f
     return updated ? 0 : FC_UPTODATE;
d7c59961
 }