/* * Copyright (C) 2003 - 2006 Tomasz Kojm * * 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 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 #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include "zlib.h" #include #include #include "clamav.h" #include "others.h" #include "dsig.h" #include "str.h" #include "cvd.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; 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); return -1; } path = (char *) cli_calloc(sizeof(char), pathlen); if(!path) { cli_errmsg("cli_untgz: Can't allocate memory for path\n"); 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; } 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) { 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)); 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_GMP 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 0; } 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); fclose(fs); return ret; } int cli_cvdload(FILE *fs, struct cl_engine **engine, unsigned int *signo, short warn, unsigned int options) { 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))) 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"); } dir = cli_gentemp(NULL); if(mkdir(dir, 0700)) { cli_errmsg("cli_cvdload(): Can't create temporary directory %s\n", dir); free(dir); return CL_ETMPDIR; } 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(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); cli_rmdirs(dir); free(dir); return ret; }