mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
575 lines
13 KiB
575 lines
13 KiB
/*
|
|
* Copyright (C) 2007-2008 Sourcefire, Inc.
|
|
*
|
|
* Authors: Tomasz Kojm
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "clamav-config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#include "zlib.h"
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
|
|
#include "clamav.h"
|
|
#include "others.h"
|
|
#include "dsig.h"
|
|
#include "str.h"
|
|
#include "cvd.h"
|
|
#include "readdb.h"
|
|
|
|
#define TAR_BLOCKSIZE 512
|
|
|
|
int cli_untgz(int fd, const char *destdir)
|
|
{
|
|
char *path, osize[13], name[101], type;
|
|
char block[TAR_BLOCKSIZE];
|
|
int nbytes, nread, nwritten, in_block = 0, fdd;
|
|
unsigned int size, pathlen = strlen(destdir) + 100 + 5;
|
|
FILE *outfile = NULL;
|
|
struct stat foo;
|
|
gzFile *infile;
|
|
|
|
|
|
cli_dbgmsg("in cli_untgz()\n");
|
|
|
|
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);
|
|
if(fstat(fdd, &foo) == 0)
|
|
close(fdd);
|
|
return -1;
|
|
}
|
|
|
|
path = (char *) cli_calloc(sizeof(char), pathlen);
|
|
if(!path) {
|
|
cli_errmsg("cli_untgz: Can't allocate memory for path\n");
|
|
gzclose(infile);
|
|
return -1;
|
|
}
|
|
|
|
while(1) {
|
|
|
|
nread = gzread(infile, block, TAR_BLOCKSIZE);
|
|
|
|
if(!in_block && !nread)
|
|
break;
|
|
|
|
if(nread != TAR_BLOCKSIZE) {
|
|
cli_errmsg("cli_untgz: Incomplete block read\n");
|
|
free(path);
|
|
gzclose(infile);
|
|
return -1;
|
|
}
|
|
|
|
if(!in_block) {
|
|
if (block[0] == '\0') /* We're done */
|
|
break;
|
|
|
|
strncpy(name, block, 100);
|
|
name[100] = '\0';
|
|
|
|
if(strchr(name, '/')) {
|
|
cli_errmsg("cli_untgz: Slash separators are not allowed in CVD\n");
|
|
free(path);
|
|
gzclose(infile);
|
|
return -1;
|
|
}
|
|
|
|
snprintf(path, pathlen, "%s/%s", destdir, name);
|
|
cli_dbgmsg("cli_untgz: Unpacking %s\n", path);
|
|
type = block[156];
|
|
|
|
switch(type) {
|
|
case '0':
|
|
case '\0':
|
|
break;
|
|
case '5':
|
|
cli_errmsg("cli_untgz: Directories are not supported in CVD\n");
|
|
free(path);
|
|
gzclose(infile);
|
|
return -1;
|
|
default:
|
|
cli_errmsg("cli_untgz: Unknown type flag '%c'\n", type);
|
|
free(path);
|
|
gzclose(infile);
|
|
return -1;
|
|
}
|
|
in_block = 1;
|
|
|
|
if(outfile) {
|
|
if(fclose(outfile)) {
|
|
cli_errmsg("cli_untgz: Cannot close file %s\n", path);
|
|
free(path);
|
|
gzclose(infile);
|
|
return -1;
|
|
}
|
|
outfile = NULL;
|
|
}
|
|
|
|
if(!(outfile = fopen(path, "wb"))) {
|
|
cli_errmsg("cli_untgz: Cannot create file %s\n", path);
|
|
free(path);
|
|
gzclose(infile);
|
|
return -1;
|
|
}
|
|
|
|
strncpy(osize, block + 124, 12);
|
|
osize[12] = '\0';
|
|
|
|
if((sscanf(osize, "%o", &size)) == 0) {
|
|
cli_errmsg("cli_untgz: Invalid size in header\n");
|
|
free(path);
|
|
gzclose(infile);
|
|
fclose(outfile);
|
|
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) {
|
|
cli_errmsg("cli_untgz: Wrote %d instead of %d (%s)\n", nwritten, nbytes, path);
|
|
free(path);
|
|
gzclose(infile);
|
|
return -1;
|
|
}
|
|
|
|
size -= nbytes;
|
|
if(size == 0)
|
|
in_block = 0;
|
|
}
|
|
}
|
|
|
|
if(outfile)
|
|
fclose(outfile);
|
|
|
|
gzclose(infile);
|
|
free(path);
|
|
return 0;
|
|
}
|
|
|
|
static int cli_tgzload(int fd, struct cl_engine **engine, unsigned int *signo, unsigned int options)
|
|
{
|
|
char osize[13], name[101];
|
|
char block[TAR_BLOCKSIZE];
|
|
int nread, fdd, ret;
|
|
unsigned int type, size, pad, compr = 1;
|
|
off_t off;
|
|
struct cli_dbio dbio;
|
|
|
|
#define CLOSE_DBIO \
|
|
if(compr) \
|
|
gzclose(dbio.gzs); \
|
|
else \
|
|
fclose(dbio.fs)
|
|
|
|
cli_dbgmsg("in cli_tgzload()\n");
|
|
|
|
lseek(fd, 512, SEEK_SET);
|
|
if(cli_readn(fd, block, 7) != 7)
|
|
return CL_EFORMAT; /* truncated file? */
|
|
|
|
if(!strncmp(block, "COPYING", 7))
|
|
compr = 0;
|
|
|
|
lseek(fd, 512, SEEK_SET);
|
|
|
|
if((fdd = dup(fd)) == -1) {
|
|
cli_errmsg("cli_tgzload: Can't duplicate descriptor %d\n", fd);
|
|
return CL_EIO;
|
|
}
|
|
|
|
if(compr) {
|
|
if((dbio.gzs = gzdopen(fdd, "rb")) == NULL) {
|
|
cli_errmsg("cli_tgzload: Can't gzdopen() descriptor %d, errno = %d\n", fdd, errno);
|
|
return CL_EIO;
|
|
}
|
|
dbio.fs = NULL;
|
|
} else {
|
|
if((dbio.fs = fdopen(fdd, "rb")) == NULL) {
|
|
cli_errmsg("cli_tgzload: Can't fdopen() descriptor %d, errno = %d\n", fdd, errno);
|
|
return CL_EIO;
|
|
}
|
|
dbio.gzs = NULL;
|
|
}
|
|
|
|
while(1) {
|
|
|
|
if(compr)
|
|
nread = gzread(dbio.gzs, block, TAR_BLOCKSIZE);
|
|
else
|
|
nread = fread(block, 1, TAR_BLOCKSIZE, dbio.fs);
|
|
|
|
if(!nread)
|
|
break;
|
|
|
|
if(nread != TAR_BLOCKSIZE) {
|
|
cli_errmsg("cli_tgzload: Incomplete block read\n");
|
|
CLOSE_DBIO;
|
|
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");
|
|
CLOSE_DBIO;
|
|
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");
|
|
CLOSE_DBIO;
|
|
return CL_EMALFDB;
|
|
default:
|
|
cli_errmsg("cli_tgzload: Unknown type flag '%c'\n", type);
|
|
CLOSE_DBIO;
|
|
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");
|
|
CLOSE_DBIO;
|
|
return CL_EMALFDB;
|
|
}
|
|
dbio.size = size;
|
|
|
|
/* cli_dbgmsg("cli_tgzload: Loading %s, size: %u\n", name, size); */
|
|
if(compr)
|
|
off = (off_t) gzseek(dbio.gzs, 0, SEEK_CUR);
|
|
else
|
|
off = ftell(dbio.fs);
|
|
|
|
if(CLI_DBEXT(name)) {
|
|
ret = cli_load(name, engine, signo, options, &dbio);
|
|
if(ret) {
|
|
cli_errmsg("cli_tgzload: Can't load %s\n", name);
|
|
CLOSE_DBIO;
|
|
return CL_EMALFDB;
|
|
}
|
|
}
|
|
pad = size % TAR_BLOCKSIZE ? (TAR_BLOCKSIZE - (size % TAR_BLOCKSIZE)) : 0;
|
|
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);
|
|
}
|
|
}
|
|
|
|
CLOSE_DBIO;
|
|
return CL_SUCCESS;
|
|
}
|
|
|
|
struct cl_cvd *cl_cvdparse(const char *head)
|
|
{
|
|
struct cl_cvd *cvd;
|
|
char *pt;
|
|
|
|
|
|
if(strncmp(head, "ClamAV-VDB:", 11)) {
|
|
cli_errmsg("cli_cvdparse: Not a CVD file\n");
|
|
return NULL;
|
|
}
|
|
|
|
if(!(cvd = (struct cl_cvd *) cli_malloc(sizeof(struct cl_cvd)))) {
|
|
cli_errmsg("cl_cvdparse: Can't allocate memory for cvd\n");
|
|
return NULL;
|
|
}
|
|
|
|
if(!(cvd->time = cli_strtok(head, 1, ":"))) {
|
|
cli_errmsg("cli_cvdparse: Can't parse the creation time\n");
|
|
free(cvd);
|
|
return NULL;
|
|
}
|
|
|
|
if(!(pt = cli_strtok(head, 2, ":"))) {
|
|
cli_errmsg("cli_cvdparse: Can't parse the version number\n");
|
|
free(cvd->time);
|
|
free(cvd);
|
|
return NULL;
|
|
}
|
|
cvd->version = atoi(pt);
|
|
free(pt);
|
|
|
|
if(!(pt = cli_strtok(head, 3, ":"))) {
|
|
cli_errmsg("cli_cvdparse: Can't parse the number of signatures\n");
|
|
free(cvd->time);
|
|
free(cvd);
|
|
return NULL;
|
|
}
|
|
cvd->sigs = atoi(pt);
|
|
free(pt);
|
|
|
|
if(!(pt = cli_strtok(head, 4, ":"))) {
|
|
cli_errmsg("cli_cvdparse: Can't parse the functionality level\n");
|
|
free(cvd->time);
|
|
free(cvd);
|
|
return NULL;
|
|
}
|
|
cvd->fl = atoi(pt);
|
|
free(pt);
|
|
|
|
if(!(cvd->md5 = cli_strtok(head, 5, ":"))) {
|
|
cli_errmsg("cli_cvdparse: Can't parse the MD5 checksum\n");
|
|
free(cvd->time);
|
|
free(cvd);
|
|
return NULL;
|
|
}
|
|
|
|
if(!(cvd->dsig = cli_strtok(head, 6, ":"))) {
|
|
cli_errmsg("cli_cvdparse: Can't parse the digital signature\n");
|
|
free(cvd->time);
|
|
free(cvd->md5);
|
|
free(cvd);
|
|
return NULL;
|
|
}
|
|
|
|
if(!(cvd->builder = cli_strtok(head, 7, ":"))) {
|
|
cli_errmsg("cli_cvdparse: Can't parse the builder name\n");
|
|
free(cvd->time);
|
|
free(cvd->md5);
|
|
free(cvd->dsig);
|
|
free(cvd);
|
|
return NULL;
|
|
}
|
|
|
|
if((pt = cli_strtok(head, 8, ":"))) {
|
|
cvd->stime = atoi(pt);
|
|
free(pt);
|
|
} else {
|
|
cli_dbgmsg("cli_cvdparse: No creation time in seconds (old file format)\n");
|
|
cvd->stime = 0;
|
|
}
|
|
|
|
return cvd;
|
|
}
|
|
|
|
struct cl_cvd *cl_cvdhead(const char *file)
|
|
{
|
|
FILE *fs;
|
|
char head[513], *pt;
|
|
int i;
|
|
unsigned int bread;
|
|
|
|
|
|
if((fs = fopen(file, "rb")) == NULL) {
|
|
cli_errmsg("cl_cvdhead: Can't open file %s\n", file);
|
|
return NULL;
|
|
}
|
|
|
|
if(!(bread = fread(head, 1, 512, fs))) {
|
|
cli_errmsg("cl_cvdhead: Can't read CVD header in %s\n", file);
|
|
fclose(fs);
|
|
return NULL;
|
|
}
|
|
|
|
fclose(fs);
|
|
|
|
head[bread] = 0;
|
|
if((pt = strpbrk(head, "\n\r")))
|
|
*pt = 0;
|
|
|
|
for(i = bread - 1; i > 0 && (head[i] == ' ' || head[i] == '\n' || head[i] == '\r'); head[i] = 0, i--);
|
|
|
|
return cl_cvdparse(head);
|
|
}
|
|
|
|
void cl_cvdfree(struct cl_cvd *cvd)
|
|
{
|
|
free(cvd->time);
|
|
free(cvd->md5);
|
|
free(cvd->dsig);
|
|
free(cvd->builder);
|
|
free(cvd);
|
|
}
|
|
|
|
static int cli_cvdverify(FILE *fs, struct cl_cvd *cvdpt, unsigned int cld)
|
|
{
|
|
struct cl_cvd *cvd;
|
|
char *md5, head[513];
|
|
int i;
|
|
|
|
|
|
fseek(fs, 0, SEEK_SET);
|
|
if(fread(head, 1, 512, fs) != 512) {
|
|
cli_errmsg("cli_cvdverify: Can't read CVD header\n");
|
|
return CL_ECVD;
|
|
}
|
|
|
|
head[512] = 0;
|
|
for(i = 511; i > 0 && (head[i] == ' ' || head[i] == 10); head[i] = 0, i--);
|
|
|
|
if((cvd = cl_cvdparse(head)) == NULL)
|
|
return CL_ECVD;
|
|
|
|
if(cvdpt)
|
|
memcpy(cvdpt, cvd, sizeof(struct cl_cvd));
|
|
|
|
if(cld) {
|
|
cl_cvdfree(cvd);
|
|
return CL_SUCCESS;
|
|
}
|
|
|
|
md5 = cli_md5stream(fs, NULL);
|
|
cli_dbgmsg("MD5(.tar.gz) = %s\n", md5);
|
|
|
|
if(strncmp(md5, cvd->md5, 32)) {
|
|
cli_dbgmsg("cli_cvdverify: MD5 verification error\n");
|
|
free(md5);
|
|
cl_cvdfree(cvd);
|
|
return CL_EMD5;
|
|
}
|
|
|
|
#ifdef HAVE_LIBGMP
|
|
if(cli_versig(md5, cvd->dsig)) {
|
|
cli_dbgmsg("cli_cvdverify: Digital signature verification error\n");
|
|
free(md5);
|
|
cl_cvdfree(cvd);
|
|
return CL_EDSIG;
|
|
}
|
|
#endif
|
|
|
|
free(md5);
|
|
cl_cvdfree(cvd);
|
|
return CL_SUCCESS;
|
|
}
|
|
|
|
int cl_cvdverify(const char *file)
|
|
{
|
|
FILE *fs;
|
|
int ret;
|
|
|
|
|
|
if((fs = fopen(file, "rb")) == NULL) {
|
|
cli_errmsg("cl_cvdverify: Can't open file %s\n", file);
|
|
return CL_EOPEN;
|
|
}
|
|
|
|
ret = cli_cvdverify(fs, NULL, 0);
|
|
fclose(fs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int cli_cvdload(FILE *fs, struct cl_engine **engine, unsigned int *signo, short warn, unsigned int options, unsigned int cld)
|
|
{
|
|
char *dir;
|
|
struct cl_cvd cvd;
|
|
int ret;
|
|
time_t s_time;
|
|
int cfd;
|
|
|
|
cli_dbgmsg("in cli_cvdload()\n");
|
|
|
|
/* verify */
|
|
|
|
if((ret = cli_cvdverify(fs, &cvd, cld)))
|
|
return ret;
|
|
|
|
if(cvd.stime && warn) {
|
|
time(&s_time);
|
|
if((int) s_time - cvd.stime > 604800) {
|
|
cli_warnmsg("**************************************************\n");
|
|
cli_warnmsg("*** The virus database is older than 7 days! ***\n");
|
|
cli_warnmsg("*** Please update it as soon as possible. ***\n");
|
|
cli_warnmsg("**************************************************\n");
|
|
}
|
|
}
|
|
|
|
if(cvd.fl > cl_retflevel()) {
|
|
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");
|
|
}
|
|
|
|
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");
|
|
return CL_EIO;
|
|
}
|
|
|
|
if(options & CL_DB_CVDNOTMP) {
|
|
|
|
return cli_tgzload(cfd, engine, signo, options | CL_DB_OFFICIAL);
|
|
|
|
} else {
|
|
|
|
if(!(dir = cli_gentemp(NULL)))
|
|
return CL_EMEM;
|
|
|
|
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);
|
|
return CL_ECVDEXTR;
|
|
}
|
|
|
|
/* load extracted directory */
|
|
ret = cl_load(dir, engine, signo, options | CL_DB_OFFICIAL);
|
|
|
|
cli_rmdirs(dir);
|
|
free(dir);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|