libclamav/cvd.c
4cd4319e
 /*
8b242bb9
  *  Copyright (C) 2003 - 2004 Tomasz Kojm <tkojm@clamav.net>
4cd4319e
  *
  *  untgz() is based on public domain minitar utility by Charles G. Waldman
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
8b242bb9
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
4cd4319e
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <zlib.h>
93ef7dc2
 #include <time.h>
4cd4319e
 
 #include "clamav.h"
c0eb3ceb
 #include "others.h"
 #include "dsig.h"
442d8407
 #include "str.h"
4cd4319e
 
 #define TAR_BLOCKSIZE 512
 
 int cli_untgz(int fd, const char *destdir)
 {
 	char *fullname, osize[13], name[101], type;
 	char block[TAR_BLOCKSIZE];
 	int nbytes, nread, nwritten, in_block = 0;
 	unsigned int size;
 	FILE *outfile = NULL;
 	gzFile *infile;
 
     cli_dbgmsg("in cli_untgz()\n");
 
     if((infile = gzdopen(fd, "rb")) == NULL) {
 	cli_errmsg("Can't gzdopen() descriptor %d\n", fd);
 	return -1;
     }
 
 
     fullname = (char *) calloc(sizeof(char), strlen(destdir) + 100 + 5);
 
     while(1) {
 
 	nread = gzread(infile, block, TAR_BLOCKSIZE);
 
 	if(!in_block && nread == 0)
 	    break;
 
 	if(nread != TAR_BLOCKSIZE) {
 	    cli_errmsg("Incomplete block read.\n");
 	    free(fullname);
9c1c9007
 	    gzclose(infile);
4cd4319e
 	    return -1;
 	}
 
 	if(!in_block) {
 	    if (block[0] == '\0')  /* We're done */
 		break;
 
7cc3891c
 	    strncpy(name, block, 100);
 	    name[100] = '\0';
4cd4319e
 	    strcpy(fullname, destdir);
 	    strcat(fullname, "/");
 	    strcat(fullname, name);
 	    cli_dbgmsg("Unpacking %s\n",fullname);
 	    type = block[156];
 
 	    switch(type) {
 		case '0':
 		case '\0':
 		    break;
 		case '5':
 		    cli_errmsg("Directories in CVD are not supported.\n");
 		    free(fullname);
9c1c9007
 	            gzclose(infile);
4cd4319e
 		    return -1;
 		default:
 		    cli_errmsg("Unknown type flag %c.\n",type);
 		    free(fullname);
9c1c9007
 	            gzclose(infile);
4cd4319e
 		    return -1;
 	    }
 
 	    in_block = 1;
 
 	    if(outfile) {
 		if(fclose(outfile)) {
 		    cli_errmsg("Cannot close file %s.\n", fullname);
 		    free(fullname);
9c1c9007
 	            gzclose(infile);
4cd4319e
 		    return -1;
 		}
 		outfile = NULL;
 	    }
 
 	    if(!(outfile = fopen(fullname, "wb"))) {
 		cli_errmsg("Cannot create file %s.\n", fullname);
 		free(fullname);
9c1c9007
 	        gzclose(infile);
4cd4319e
 		return -1;
 	    }
 
7cc3891c
 	    strncpy(osize, block + 124, 12);
 	    osize[12] = '\0';
4cd4319e
 
233c32f2
 	    if((sscanf(osize, "%o", &size)) == 0) {
4cd4319e
 		cli_errmsg("Invalid size in header.\n");
 		free(fullname);
9c1c9007
 	        gzclose(infile);
25133aef
 		fclose(outfile);
4cd4319e
 		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("Wrote %d instead of %d (%s).\n", nwritten, nbytes, fullname);
 		free(fullname);
9c1c9007
 	        gzclose(infile);
4cd4319e
 		return -1;
 	    }
 
 	    size -= nbytes;
 	    if(size == 0)
 		in_block = 0;
 	}
     }
 
c0eb3ceb
     if(outfile)
 	fclose(outfile);
 
9c1c9007
     gzclose(infile);
     free(fullname);
4cd4319e
     return 0;
 }
 
442d8407
 struct cl_cvd *cl_cvdparse(const char *head)
4cd4319e
 {
442d8407
 	char *pt;
4cd4319e
 	struct cl_cvd *cvd;
c0eb3ceb
 
     if(strncmp(head, "ClamAV-VDB:", 11)) {
442d8407
 	cli_dbgmsg("Not a CVD head.\n");
c0eb3ceb
 	return NULL;
     }
4cd4319e
 
     cvd = (struct cl_cvd *) cli_calloc(1, sizeof(struct cl_cvd));
 
e8217f5a
     if(!(cvd->time = cli_strtok(head, 1, ":"))) {
03186fe8
 	cli_errmsg("CVD -> Can't extract time from header.\n");
 	free(cvd);
 	return NULL;
     }
 
e8217f5a
     if(!(pt = cli_strtok(head, 2, ":"))) {
03186fe8
 	cli_errmsg("CVD -> Can't extract version from header.\n");
 	free(cvd->time);
 	free(cvd);
 	return NULL;
     }
4cd4319e
     cvd->version = atoi(pt);
     free(pt);
 
e8217f5a
     if(!(pt = cli_strtok(head, 3, ":"))) {
03186fe8
 	cli_errmsg("CVD -> Can't extract signature number from header.\n");
 	free(cvd->time);
 	free(cvd);
 	return NULL;
     }
4cd4319e
     cvd->sigs = atoi(pt);
     free(pt);
 
e8217f5a
     if(!(pt = cli_strtok(head, 4, ":"))) {
03186fe8
 	cli_errmsg("CVD -> Can't extract functionality level from header.\n");
 	free(cvd->time);
 	free(cvd);
 	return NULL;
     }
4cd4319e
     cvd->fl = (short int) atoi(pt);
     free(pt);
 
e8217f5a
     if(!(cvd->md5 = cli_strtok(head, 5, ":"))) {
03186fe8
 	cli_errmsg("CVD -> Can't extract MD5 checksum from header.\n");
 	free(cvd->time);
 	free(cvd);
 	return NULL;
     }
 
e8217f5a
     if(!(cvd->dsig = cli_strtok(head, 6, ":"))) {
03186fe8
 	cli_errmsg("CVD -> Can't extract digital signature from header.\n");
 	free(cvd->time);
 	free(cvd->md5);
 	free(cvd);
 	return NULL;
     }
 
e8217f5a
     if(!(cvd->builder = cli_strtok(head, 7, ":"))) {
03186fe8
 	cli_errmsg("CVD -> Can't extract builder name from header.\n");
 	free(cvd->time);
 	free(cvd->md5);
 	free(cvd->dsig);
 	free(cvd);
 	return NULL;
     }
4cd4319e
 
93ef7dc2
     if((pt = cli_strtok(head, 8, ":"))) {
 	cvd->stime = atoi(pt);
 	free(pt);
     } else
 	cli_dbgmsg("CVD -> No creation time in seconds (old file format)\n");
 
 
4cd4319e
     return cvd;
 }
 
 struct cl_cvd *cl_cvdhead(const char *file)
 {
 	FILE *fd;
442d8407
 	char head[513];
 	int i;
4cd4319e
 
     if((fd = fopen(file, "rb")) == NULL) {
442d8407
 	cli_dbgmsg("Can't open CVD file %s\n", file);
4cd4319e
 	return NULL;
     }
 
93ef7dc2
     if((i = fread(head, 1, 512, fd)) != 512) {
9c1c9007
 	cli_dbgmsg("Short read (%d) while reading CVD head from %s\n", i, file);
 	fclose(fd);
442d8407
 	return NULL;
     }
 
     fclose(fd);
 
     head[512] = 0;
     for(i = 511; i > 0 && (head[i] == ' ' || head[i] == 10); head[i] = 0, i--);
 
     return cl_cvdparse(head);
4cd4319e
 }
 
 void cl_cvdfree(struct cl_cvd *cvd)
 {
     free(cvd->time);
     free(cvd->md5);
     free(cvd->dsig);
     free(cvd->builder);
     free(cvd);
 }
 
93ef7dc2
 int cli_cvdverify(FILE *fd, struct cl_cvd *cvdpt)
c0eb3ceb
 {
442d8407
 	struct cl_cvd *cvd;
 	char *md5, head[513];
 	int i;
 
     fseek(fd, 0, SEEK_SET);
     if(fread(head, 1, 512, fd) != 512) {
 	cli_dbgmsg("Can't read CVD head from stream\n");
 	return CL_ECVD;
     }
 
     head[512] = 0;
     for(i = 511; i > 0 && (head[i] == ' ' || head[i] == 10); head[i] = 0, i--);
c0eb3ceb
 
442d8407
     if((cvd = cl_cvdparse(head)) == NULL)
c0eb3ceb
 	return CL_ECVD;
 
996d199e
     if(cvdpt)
93ef7dc2
 	memcpy(cvdpt, cvd, sizeof(struct cl_cvd));
c0eb3ceb
 
d2a12ffd
     md5 = cli_md5stream(fd, NULL);
c0eb3ceb
     cli_dbgmsg("MD5(.tar.gz) = %s\n", md5);
 
442d8407
     if(strncmp(md5, cvd->md5, 32)) {
c0eb3ceb
 	cli_dbgmsg("MD5 verification error.\n");
9c1c9007
 	free(md5);
 	cl_cvdfree(cvd);
c0eb3ceb
 	return CL_EMD5;
     }
 
 #ifdef HAVE_GMP
442d8407
     if(cli_versig(md5, cvd->dsig)) {
c0eb3ceb
 	cli_dbgmsg("Digital signature verification error.\n");
9c1c9007
 	free(md5);
 	cl_cvdfree(cvd);
c0eb3ceb
 	return CL_EDSIG;
     }
 #endif
 
442d8407
     free(md5);
9c1c9007
     cl_cvdfree(cvd);
c0eb3ceb
     return 0;
 }
 
442d8407
 int cl_cvdverify(const char *file)
c0eb3ceb
 {
 	FILE *fd;
442d8407
 	int ret;
c0eb3ceb
 
     if((fd = fopen(file, "rb")) == NULL) {
 	cli_errmsg("Can't open CVD file %s\n", file);
442d8407
 	return CL_EOPEN;
c0eb3ceb
     }
 
93ef7dc2
     ret = cli_cvdverify(fd, NULL);
442d8407
     fclose(fd);
 
     return ret;
c0eb3ceb
 }
 
4e91e4c7
 int cli_cvdload(FILE *fd, struct cl_node **root, unsigned int *signo, short warn)
4cd4319e
 {
7b7b3ca5
         char *dir, *tmp, *buffer;
93ef7dc2
 	struct cl_cvd cvd;
c0eb3ceb
 	int bytes, ret;
4cd4319e
 	FILE *tmpd;
93ef7dc2
 	time_t stime;
 
4cd4319e
 
     cli_dbgmsg("in cli_cvdload()\n");
 
c0eb3ceb
     /* verify */
4cd4319e
 
93ef7dc2
     if((ret = cli_cvdverify(fd, &cvd)))
c0eb3ceb
 	return ret;
4cd4319e
 
4e91e4c7
     if(cvd.stime && warn) {
93ef7dc2
 	time(&stime);
 	if((int) stime - cvd.stime > 604800) {
 	    cli_warnmsg("**************************************************\n");
 	    cli_warnmsg("***  The virus database is older than 7 days.  ***\n");
 	    cli_warnmsg("***        Please update it IMMEDIATELY!       ***\n");
 	    cli_warnmsg("**************************************************\n");
 	}
     }
 
558d2f03
     if(cvd.fl > cl_retflevel()) {
d5e37688
 	cli_warnmsg("********************************************************\n");
 	cli_warnmsg("***  This version of the ClamAV engine is outdated.  ***\n");
 	cli_warnmsg("*** DON'T PANIC! Read http://www.clamav.net/faq.html ***\n");
 	cli_warnmsg("********************************************************\n");
558d2f03
     }
 
c0eb3ceb
     fseek(fd, 512, SEEK_SET);
4cd4319e
 
e5fa5bab
     dir = cli_gentemp(NULL);
4cd4319e
     if(mkdir(dir, 0700)) {
 	cli_errmsg("cli_cvdload():  Can't create temporary directory %s\n", dir);
 	return CL_ETMPDIR;
     }
 
     /* 
     if(cli_untgz(fileno(fd), dir)) {
 	cli_errmsg("cli_cvdload(): Can't unpack CVD file.\n");
 	return CL_ECVDEXTR;
     }
     */
 
c0eb3ceb
     /* FIXME: it seems there is some problem with current position indicator
      * after gzdopen() call in cli_untgz(). Temporarily we need this wrapper:
4cd4319e
      */
 
 	    /* start */
 
e5fa5bab
 	    tmp = cli_gentemp(NULL);
4cd4319e
 	    if((tmpd = fopen(tmp, "wb+")) == NULL) {
 		cli_errmsg("Can't create temporary file %s\n", tmp);
 		free(dir);
 		free(tmp);
7b7b3ca5
 		return CL_ETMPFILE;
4cd4319e
 	    }
7b7b3ca5
 
25133aef
 	    if(!(buffer = (char *) cli_malloc(FILEBUFF))) {
 		free(dir);
 		free(tmp);
 		fclose(tmpd);
7b7b3ca5
 		return CL_EMEM;
25133aef
 	    }
7b7b3ca5
 
 	    while((bytes = fread(buffer, 1, FILEBUFF, fd)) > 0)
4cd4319e
 		fwrite(buffer, 1, bytes, tmpd);
 
7b7b3ca5
 	    free(buffer);
 
4cd4319e
 	    fflush(tmpd);
 	    fseek(tmpd, 0L, SEEK_SET);
 
 	    if(cli_untgz(fileno(tmpd), dir)) {
d4494521
 		perror("cli_untgz");
4cd4319e
 		cli_errmsg("cli_cvdload(): Can't unpack CVD file.\n");
 		cli_rmdirs(dir);
 		free(dir);
25133aef
 		fclose(tmpd);
4cd4319e
 		unlink(tmp);
 		free(tmp);
 		return CL_ECVDEXTR;
 	    }
 
 	    fclose(tmpd);
 	    unlink(tmp);
 	    free(tmp);
 
 	    /* end */
 
     /* load extracted directory */
06d4e856
     cl_loaddbdir(dir, root, signo);
4cd4319e
 
     cli_rmdirs(dir);
     free(dir);
 
     return 0;
 }