libclamav/cvd.c
8139fd99
 /*
2023340a
  *  Copyright (C) 2007-2008 Sourcefire, Inc.
8139fd99
  *
2023340a
  *  Authors: Tomasz Kojm
8139fd99
  *
  *  This program is free software; you can redistribute it and/or modify
bb34cb31
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
8139fd99
  *
  *  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.
8139fd99
  */
 
6d6e8271
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
8139fd99
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
b58fdfc2
 #ifdef	HAVE_UNISTD_H
5ff81952
 #include <unistd.h>
b58fdfc2
 #endif
 #include "zlib.h"
c48c9d2b
 #include <time.h>
b58fdfc2
 #include <errno.h>
8139fd99
 
 #include "clamav.h"
6a2532ca
 #include "others.h"
 #include "dsig.h"
e4ae7726
 #include "str.h"
079229d6
 #include "cvd.h"
056d95dc
 #include "readdb.h"
8139fd99
 
 #define TAR_BLOCKSIZE 512
 
afff80ef
 #ifndef	O_BINARY
 #define	O_BINARY	0
 #endif
 
 static int cli_untgz(int fd, const char *destdir)
8139fd99
 {
a7ac5978
 	char *path, osize[13], name[101], type;
8139fd99
 	char block[TAR_BLOCKSIZE];
a7ac5978
 	int nbytes, nread, nwritten, in_block = 0, fdd;
 	unsigned int size, pathlen = strlen(destdir) + 100 + 5;
8139fd99
 	FILE *outfile = NULL;
638d881d
 	struct stat foo;
8139fd99
 	gzFile *infile;
 
a7ac5978
 
8139fd99
     cli_dbgmsg("in cli_untgz()\n");
 
a7ac5978
     if((fdd = dup(fd)) == -1) {
 	cli_errmsg("cli_untgz: Can't duplicate descriptor %d\n", fd);
 	return -1;
     }
 
     if((infile = gzdopen(fdd, "rb")) == NULL) {
 	cli_errmsg("cli_untgz: Can't gzdopen() descriptor %d, errno = %d\n", fdd, errno);
638d881d
 	if(fstat(fdd, &foo) == 0)
 	    close(fdd);
8139fd99
 	return -1;
     }
 
a7ac5978
     path = (char *) cli_calloc(sizeof(char), pathlen);
     if(!path) {
 	cli_errmsg("cli_untgz: Can't allocate memory for path\n");
68a364d4
 	gzclose(infile);
e12c29d2
 	return -1;
     }
8139fd99
 
     while(1) {
 
 	nread = gzread(infile, block, TAR_BLOCKSIZE);
 
a7ac5978
 	if(!in_block && !nread)
8139fd99
 	    break;
 
 	if(nread != TAR_BLOCKSIZE) {
a7ac5978
 	    cli_errmsg("cli_untgz: Incomplete block read\n");
 	    free(path);
9e431a95
 	    gzclose(infile);
8139fd99
 	    return -1;
 	}
 
 	if(!in_block) {
 	    if (block[0] == '\0')  /* We're done */
 		break;
 
658f19f8
 	    strncpy(name, block, 100);
 	    name[100] = '\0';
4fac726f
 
 	    if(strchr(name, '/')) {
a7ac5978
 		cli_errmsg("cli_untgz: Slash separators are not allowed in CVD\n");
 		free(path);
4fac726f
 	        gzclose(infile);
 		return -1;
 	    }
 
a7ac5978
 	    snprintf(path, pathlen, "%s/%s", destdir, name);
 	    cli_dbgmsg("cli_untgz: Unpacking %s\n", path);
8139fd99
 	    type = block[156];
 
 	    switch(type) {
 		case '0':
 		case '\0':
 		    break;
 		case '5':
a7ac5978
 		    cli_errmsg("cli_untgz: Directories are not supported in CVD\n");
 		    free(path);
9e431a95
 	            gzclose(infile);
8139fd99
 		    return -1;
 		default:
a7ac5978
 		    cli_errmsg("cli_untgz: Unknown type flag '%c'\n", type);
 		    free(path);
9e431a95
 	            gzclose(infile);
8139fd99
 		    return -1;
 	    }
 	    in_block = 1;
 
 	    if(outfile) {
 		if(fclose(outfile)) {
a7ac5978
 		    cli_errmsg("cli_untgz: Cannot close file %s\n", path);
 		    free(path);
9e431a95
 	            gzclose(infile);
8139fd99
 		    return -1;
 		}
 		outfile = NULL;
 	    }
 
a7ac5978
 	    if(!(outfile = fopen(path, "wb"))) {
 		cli_errmsg("cli_untgz: Cannot create file %s\n", path);
 		free(path);
9e431a95
 	        gzclose(infile);
8139fd99
 		return -1;
 	    }
 
658f19f8
 	    strncpy(osize, block + 124, 12);
 	    osize[12] = '\0';
8139fd99
 
bddfdc19
 	    if((sscanf(osize, "%o", &size)) == 0) {
a7ac5978
 		cli_errmsg("cli_untgz: Invalid size in header\n");
 		free(path);
9e431a95
 	        gzclose(infile);
bcf3dc79
 		fclose(outfile);
8139fd99
 		return -1;
 	    }
 
 	} else { /* write or continue writing file contents */
 	    nbytes = size > TAR_BLOCKSIZE ? TAR_BLOCKSIZE : size;
 	    nwritten = fwrite(block, 1, nbytes, outfile);
 
 	    if(nwritten != nbytes) {
a7ac5978
 		cli_errmsg("cli_untgz: Wrote %d instead of %d (%s)\n", nwritten, nbytes, path);
 		free(path);
9e431a95
 	        gzclose(infile);
8139fd99
 		return -1;
 	    }
 
 	    size -= nbytes;
 	    if(size == 0)
 		in_block = 0;
 	}
     }
 
6a2532ca
     if(outfile)
 	fclose(outfile);
 
9e431a95
     gzclose(infile);
a7ac5978
     free(path);
8139fd99
     return 0;
 }
 
15850fc6
 static int cli_tgzload(int fd, struct cl_engine *engine, unsigned int *signo, unsigned int options)
056d95dc
 {
 	char osize[13], name[101];
 	char block[TAR_BLOCKSIZE];
 	int nread, fdd, ret;
4d7a1184
 	unsigned int type, size, pad, compr = 1;
e8ae4fae
 	off_t off;
 	struct cli_dbio dbio;
056d95dc
 
e8ae4fae
 #define CLOSE_DBIO	    \
     if(compr)		    \
 	gzclose(dbio.gzs);  \
     else		    \
 	fclose(dbio.fs)
056d95dc
 
9d193ff2
     cli_dbgmsg("in cli_tgzload()\n");
056d95dc
 
4d7a1184
     lseek(fd, 512, SEEK_SET);
     if(cli_readn(fd, block, 7) != 7)
 	return CL_EFORMAT; /* truncated file? */
 
     if(!strncmp(block, "COPYING", 7))
 	compr = 0;
 
e8ae4fae
     lseek(fd, 512, SEEK_SET);
 
056d95dc
     if((fdd = dup(fd)) == -1) {
 	cli_errmsg("cli_tgzload: Can't duplicate descriptor %d\n", fd);
871177cd
 	return CL_EDUP;
056d95dc
     }
 
e8ae4fae
     if(compr) {
 	if((dbio.gzs = gzdopen(fdd, "rb")) == NULL) {
 	    cli_errmsg("cli_tgzload: Can't gzdopen() descriptor %d, errno = %d\n", fdd, errno);
871177cd
 	    return CL_EOPEN;
e8ae4fae
 	}
283363ff
 	dbio.fs = NULL;
e8ae4fae
     } else {
 	if((dbio.fs = fdopen(fdd, "rb")) == NULL) {
 	    cli_errmsg("cli_tgzload: Can't fdopen() descriptor %d, errno = %d\n", fdd, errno);
871177cd
 	    return CL_EOPEN;
e8ae4fae
 	}
283363ff
 	dbio.gzs = NULL;
056d95dc
     }
 
     while(1) {
 
e8ae4fae
 	if(compr)
 	    nread = gzread(dbio.gzs, block, TAR_BLOCKSIZE);
 	else
 	    nread = fread(block, 1, TAR_BLOCKSIZE, dbio.fs);
056d95dc
 
 	if(!nread)
 	    break;
 
 	if(nread != TAR_BLOCKSIZE) {
 	    cli_errmsg("cli_tgzload: Incomplete block read\n");
e8ae4fae
 	    CLOSE_DBIO;
056d95dc
 	    return CL_EMALFDB;
 	}
 
 	if(block[0] == '\0')  /* We're done */
 	    break;
 
 	strncpy(name, block, 100);
 	name[100] = '\0';
 
 	if(strchr(name, '/')) {
 	    cli_errmsg("cli_tgzload: Slash separators are not allowed in CVD\n");
e8ae4fae
 	    CLOSE_DBIO;
056d95dc
 	    return CL_EMALFDB;
 	}
 
 	type = block[156];
 
 	switch(type) {
 	    case '0':
 	    case '\0':
 		break;
 	    case '5':
 		cli_errmsg("cli_tgzload: Directories are not supported in CVD\n");
e8ae4fae
 		CLOSE_DBIO;
056d95dc
 		return CL_EMALFDB;
 	    default:
 		cli_errmsg("cli_tgzload: Unknown type flag '%c'\n", type);
e8ae4fae
 		CLOSE_DBIO;
056d95dc
 		return CL_EMALFDB;
 	}
 
 	strncpy(osize, block + 124, 12);
 	osize[12] = '\0';
 
 	if((sscanf(osize, "%o", &size)) == 0) {
 	    cli_errmsg("cli_tgzload: Invalid size in header\n");
e8ae4fae
 	    CLOSE_DBIO;
056d95dc
 	    return CL_EMALFDB;
 	}
e8ae4fae
 	dbio.size = size;
056d95dc
 
 	/* cli_dbgmsg("cli_tgzload: Loading %s, size: %u\n", name, size); */
e8ae4fae
 	if(compr)
 	    off = (off_t) gzseek(dbio.gzs, 0, SEEK_CUR);
 	else
 	    off = ftell(dbio.fs);
 
056d95dc
 	if(CLI_DBEXT(name)) {
e8ae4fae
 	    ret = cli_load(name, engine, signo, options, &dbio);
056d95dc
 	    if(ret) {
283363ff
 		cli_errmsg("cli_tgzload: Can't load %s\n", name);
e8ae4fae
 		CLOSE_DBIO;
056d95dc
 		return CL_EMALFDB;
 	    }
 	}
0a26aef4
 	pad = size % TAR_BLOCKSIZE ? (TAR_BLOCKSIZE - (size % TAR_BLOCKSIZE)) : 0;
e8ae4fae
 	if(compr) {
 	    if(off == gzseek(dbio.gzs, 0, SEEK_CUR))
 		gzseek(dbio.gzs, size + pad, SEEK_CUR);
 	    else if(pad)
 		gzseek(dbio.gzs, pad, SEEK_CUR);
 	} else {
 	    if(off == ftell(dbio.fs))
 		fseek(dbio.fs, size + pad, SEEK_CUR);
 	    else if(pad)
 		fseek(dbio.fs, pad, SEEK_CUR);
 	}
056d95dc
     }
 
e8ae4fae
     CLOSE_DBIO;
     return CL_SUCCESS;
056d95dc
 }
 
e4ae7726
 struct cl_cvd *cl_cvdparse(const char *head)
8139fd99
 {
 	struct cl_cvd *cvd;
a7ac5978
 	char *pt;
 
6a2532ca
 
     if(strncmp(head, "ClamAV-VDB:", 11)) {
a7ac5978
 	cli_errmsg("cli_cvdparse: Not a CVD file\n");
6a2532ca
 	return NULL;
     }
8139fd99
 
a7ac5978
     if(!(cvd = (struct cl_cvd *) cli_malloc(sizeof(struct cl_cvd)))) {
e12c29d2
 	cli_errmsg("cl_cvdparse: Can't allocate memory for cvd\n");
 	return NULL;
     }
8139fd99
 
2d70a403
     if(!(cvd->time = cli_strtok(head, 1, ":"))) {
a7ac5978
 	cli_errmsg("cli_cvdparse: Can't parse the creation time\n");
976bcd2a
 	free(cvd);
 	return NULL;
     }
 
2d70a403
     if(!(pt = cli_strtok(head, 2, ":"))) {
a7ac5978
 	cli_errmsg("cli_cvdparse: Can't parse the version number\n");
976bcd2a
 	free(cvd->time);
 	free(cvd);
 	return NULL;
     }
8139fd99
     cvd->version = atoi(pt);
     free(pt);
 
2d70a403
     if(!(pt = cli_strtok(head, 3, ":"))) {
a7ac5978
 	cli_errmsg("cli_cvdparse: Can't parse the number of signatures\n");
976bcd2a
 	free(cvd->time);
 	free(cvd);
 	return NULL;
     }
8139fd99
     cvd->sigs = atoi(pt);
     free(pt);
 
2d70a403
     if(!(pt = cli_strtok(head, 4, ":"))) {
a7ac5978
 	cli_errmsg("cli_cvdparse: Can't parse the functionality level\n");
976bcd2a
 	free(cvd->time);
 	free(cvd);
 	return NULL;
     }
b5134815
     cvd->fl = atoi(pt);
8139fd99
     free(pt);
 
2d70a403
     if(!(cvd->md5 = cli_strtok(head, 5, ":"))) {
a7ac5978
 	cli_errmsg("cli_cvdparse: Can't parse the MD5 checksum\n");
976bcd2a
 	free(cvd->time);
 	free(cvd);
 	return NULL;
     }
 
2d70a403
     if(!(cvd->dsig = cli_strtok(head, 6, ":"))) {
a7ac5978
 	cli_errmsg("cli_cvdparse: Can't parse the digital signature\n");
976bcd2a
 	free(cvd->time);
 	free(cvd->md5);
 	free(cvd);
 	return NULL;
     }
 
2d70a403
     if(!(cvd->builder = cli_strtok(head, 7, ":"))) {
a7ac5978
 	cli_errmsg("cli_cvdparse: Can't parse the builder name\n");
976bcd2a
 	free(cvd->time);
 	free(cvd->md5);
 	free(cvd->dsig);
 	free(cvd);
 	return NULL;
     }
8139fd99
 
c48c9d2b
     if((pt = cli_strtok(head, 8, ":"))) {
 	cvd->stime = atoi(pt);
 	free(pt);
a7ac5978
     } else {
 	cli_dbgmsg("cli_cvdparse: No creation time in seconds (old file format)\n");
 	cvd->stime = 0;
     }
c48c9d2b
 
8139fd99
     return cvd;
 }
 
 struct cl_cvd *cl_cvdhead(const char *file)
 {
1ccd6e94
 	FILE *fs;
bd5f3ce0
 	char head[513], *pt;
e4ae7726
 	int i;
b4bd1749
 	unsigned int bread;
 
8139fd99
 
1ccd6e94
     if((fs = fopen(file, "rb")) == NULL) {
a7ac5978
 	cli_errmsg("cl_cvdhead: Can't open file %s\n", file);
8139fd99
 	return NULL;
     }
 
b4bd1749
     if(!(bread = fread(head, 1, 512, fs))) {
a7ac5978
 	cli_errmsg("cl_cvdhead: Can't read CVD header in %s\n", file);
1ccd6e94
 	fclose(fs);
e4ae7726
 	return NULL;
     }
 
1ccd6e94
     fclose(fs);
e4ae7726
 
b4bd1749
     head[bread] = 0;
bd5f3ce0
     if((pt = strpbrk(head, "\n\r")))
 	*pt = 0;
     
b4bd1749
     for(i = bread - 1; i > 0 && (head[i] == ' ' || head[i] == '\n' || head[i] == '\r'); head[i] = 0, i--);
e4ae7726
 
     return cl_cvdparse(head);
8139fd99
 }
 
 void cl_cvdfree(struct cl_cvd *cvd)
 {
     free(cvd->time);
     free(cvd->md5);
     free(cvd->dsig);
     free(cvd->builder);
     free(cvd);
 }
 
9d193ff2
 static int cli_cvdverify(FILE *fs, struct cl_cvd *cvdpt, unsigned int cld)
6a2532ca
 {
e4ae7726
 	struct cl_cvd *cvd;
 	char *md5, head[513];
 	int i;
 
a7ac5978
 
1ccd6e94
     fseek(fs, 0, SEEK_SET);
     if(fread(head, 1, 512, fs) != 512) {
a7ac5978
 	cli_errmsg("cli_cvdverify: Can't read CVD header\n");
e4ae7726
 	return CL_ECVD;
     }
 
     head[512] = 0;
     for(i = 511; i > 0 && (head[i] == ' ' || head[i] == 10); head[i] = 0, i--);
6a2532ca
 
e4ae7726
     if((cvd = cl_cvdparse(head)) == NULL)
6a2532ca
 	return CL_ECVD;
 
e685f551
     if(cvdpt)
c48c9d2b
 	memcpy(cvdpt, cvd, sizeof(struct cl_cvd));
6a2532ca
 
9d193ff2
     if(cld) {
 	cl_cvdfree(cvd);
 	return CL_SUCCESS;
     }
 
1ccd6e94
     md5 = cli_md5stream(fs, NULL);
6a2532ca
     cli_dbgmsg("MD5(.tar.gz) = %s\n", md5);
 
e4ae7726
     if(strncmp(md5, cvd->md5, 32)) {
a7ac5978
 	cli_dbgmsg("cli_cvdverify: MD5 verification error\n");
9e431a95
 	free(md5);
 	cl_cvdfree(cvd);
871177cd
 	return CL_EVERIFY;
6a2532ca
     }
 
e4ae7726
     if(cli_versig(md5, cvd->dsig)) {
a7ac5978
 	cli_dbgmsg("cli_cvdverify: Digital signature verification error\n");
9e431a95
 	free(md5);
 	cl_cvdfree(cvd);
871177cd
 	return CL_EVERIFY;
6a2532ca
     }
 
e4ae7726
     free(md5);
9e431a95
     cl_cvdfree(cvd);
9d193ff2
     return CL_SUCCESS;
6a2532ca
 }
 
e4ae7726
 int cl_cvdverify(const char *file)
6a2532ca
 {
1ccd6e94
 	FILE *fs;
e4ae7726
 	int ret;
6a2532ca
 
a7ac5978
 
1ccd6e94
     if((fs = fopen(file, "rb")) == NULL) {
a7ac5978
 	cli_errmsg("cl_cvdverify: Can't open file %s\n", file);
e4ae7726
 	return CL_EOPEN;
6a2532ca
     }
 
9d193ff2
     ret = cli_cvdverify(fs, NULL, 0);
1ccd6e94
     fclose(fs);
e4ae7726
 
     return ret;
6a2532ca
 }
 
15850fc6
 int cli_cvdload(FILE *fs, struct cl_engine *engine, unsigned int *signo, unsigned int daily, unsigned int options, unsigned int cld)
8139fd99
 {
1ccd6e94
         char *dir;
c48c9d2b
 	struct cl_cvd cvd;
a7ac5978
 	int ret;
fc83da82
 	time_t s_time;
2782c743
 	int cfd;
8139fd99
 
     cli_dbgmsg("in cli_cvdload()\n");
 
6a2532ca
     /* verify */
8139fd99
 
9d193ff2
     if((ret = cli_cvdverify(fs, &cvd, cld)))
6a2532ca
 	return ret;
8139fd99
 
ac1b219c
     if(cvd.stime && daily) {
fc83da82
 	time(&s_time);
72a82dd6
 	if(cvd.stime > s_time) {
 	    if(cvd.stime - (unsigned int ) s_time > 3600) {
 		cli_warnmsg("******************************************************\n");
 		cli_warnmsg("***      Virus database timestamp in the future!   ***\n");
 		cli_warnmsg("***  Please check the timezone and clock settings  ***\n");
 		cli_warnmsg("******************************************************\n");
 	    }
 	} else if((unsigned int) s_time - cvd.stime > 604800) {
c48c9d2b
 	    cli_warnmsg("**************************************************\n");
a7ac5978
 	    cli_warnmsg("***  The virus database is older than 7 days!  ***\n");
 	    cli_warnmsg("***   Please update it as soon as possible.    ***\n");
c48c9d2b
 	    cli_warnmsg("**************************************************\n");
 	}
     }
 
059662de
     if(cvd.fl > cl_retflevel()) {
61409916
 	cli_warnmsg("***********************************************************\n");
 	cli_warnmsg("***  This version of the ClamAV engine is outdated.     ***\n");
 	cli_warnmsg("*** DON'T PANIC! Read http://www.clamav.net/support/faq ***\n");
 	cli_warnmsg("***********************************************************\n");
059662de
     }
 
2782c743
     cfd = fileno(fs);
     /* use only operations on file descriptors, and not on the FILE* from here on 
      * if we seek the FILE*, the underlying descriptor may not seek as expected
      * (for example on OpenBSD, cygwin, etc.).
      * So seek the descriptor directly.
      */ 
     if(lseek(cfd, 512, SEEK_SET) == -1) {
 	cli_errmsg("cli_cvdload(): lseek(fs, 512, SEEK_SET) failed\n");
871177cd
 	return CL_ESEEK;
2782c743
     }
 
ac1b219c
     if(daily) {
15850fc6
 	engine->dbversion[0] = cvd.version;
 	engine->dbversion[1] = cvd.stime;
ac1b219c
     }
 
056d95dc
     if(options & CL_DB_CVDNOTMP) {
8139fd99
 
b5513f8d
 	return cli_tgzload(cfd, engine, signo, options | CL_DB_OFFICIAL);
8139fd99
 
056d95dc
     } else {
8139fd99
 
33068e09
 	if(!(dir = cli_gentemp(engine->tmpdir)))
5fc380f1
 	    return CL_EMEM;
 
056d95dc
 	if(mkdir(dir, 0700)) {
 	    cli_errmsg("cli_cvdload(): Can't create temporary directory %s\n", dir);
 	    free(dir);
 	    return CL_ETMPDIR;
 	}
 
 	if(cli_untgz(cfd, dir)) {
 	    cli_errmsg("cli_cvdload(): Can't unpack CVD file.\n");
 	    free(dir);
871177cd
 	    return CL_ECVD;
056d95dc
 	}
 
 	/* load extracted directory */
b5513f8d
 	ret = cl_load(dir, engine, signo, options | CL_DB_OFFICIAL);
056d95dc
 
 	cli_rmdirs(dir);
 	free(dir);
 
 	return ret;
     }
8139fd99
 }
afff80ef
 
 int cli_cvdunpack(const char *file, const char *dir)
 {
 	int fd, ret;
 
 
     fd = open(file, O_RDONLY|O_BINARY);
     if(fd == -1)
 	return -1;
 
     if(lseek(fd, 512, SEEK_SET) < 0) {
 	close(fd);
 	return -1;
     }
 
     ret = cli_untgz(fd, dir);
     close(fd);
     return ret;
 }