sigtool/sigtool.c
e3aaff8e
 /*
8ca8a18e
  *  Copyright (C) 2002 - 2007 Tomasz Kojm <tkojm@clamav.net>
bb34cb31
  *  CDIFF code (C) 2006 Sensory Networks, Inc.
  *  Written by Tomasz Kojm
e3aaff8e
  *
  *  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.
e3aaff8e
  *
  *  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
  */
 
 
6d6e8271
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e3aaff8e
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
8139fd99
 #include <zlib.h>
 #include <time.h>
 #include <locale.h>
ae307914
 #include <sys/types.h>
08d6b1e3
 #include <sys/stat.h>
 #include <fcntl.h>
ae307914
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
2d70a403
 #include <sys/wait.h>
0a2ad257
 #include <dirent.h>
e3aaff8e
 
f0d0a4c0
 #ifdef HAVE_TERMIOS_H
 #include <termios.h>
 #endif
 
6a31c2b4
 #include "vba.h"
e3aaff8e
 
3d167464
 #include "shared/options.h"
 #include "shared/output.h"
 #include "shared/cfgparser.h"
 #include "shared/misc.h"
bb990ecd
 #include "shared/cdiff.h"
d1c685b8
 #include "shared/sha256.h"
e3aaff8e
 
e48dcea9
 #include "libclamav/clamav.h"
3d167464
 #include "libclamav/cvd.h"
 #include "libclamav/others.h"
 #include "libclamav/str.h"
 #include "libclamav/ole2_extract.h"
 #include "libclamav/htmlnorm.h"
e3aaff8e
 
6b5d21a3
 #define MAX_DEL_LOOKAHEAD   200
e3aaff8e
 
3d167464
 static int hexdump(void)
 {
 	char buffer[FILEBUFF], *pt;
 	int bytes;
6a2532ca
 
e3aaff8e
 
3d167464
     while((bytes = read(0, buffer, FILEBUFF)) > 0) {
 	pt = cli_str2hex(buffer, bytes);
 	if(write(1, pt, 2 * bytes) == -1) {
efa239e2
 	    mprintf("!hexdump: Can't write to stdout\n");
3d167464
 	    free(pt);
 	    return -1;
 	}
 	free(pt);
e3aaff8e
     }
 
3d167464
     if(bytes == -1)
 	return -1;
e3aaff8e
 
3d167464
     return 0;
 }
e3aaff8e
 
60892bc1
 static int md5sig(struct optstruct *opt, unsigned int mdb)
3d167464
 {
 	char *md5, *filename;
 	int i;
 	struct stat sb;
a36e6e5c
 
bcf3dc79
 
3d167464
     if(opt->filename) {
 	for(i = 0; (filename = cli_strtok(opt->filename, i, "\t")); i++) {
 	    if(stat(filename, &sb) == -1) {
efa239e2
 		mprintf("!md5sig: Can't access file %s\n", filename);
3d167464
 		perror("md5sig");
 		free(filename);
 		return -1;
 	    } else {
 		if((sb.st_mode & S_IFMT) == S_IFREG) {
 		    if((md5 = cli_md5file(filename))) {
60892bc1
 			if(mdb)
797c5b1e
 			    mprintf("%u:%s:%s\n", (unsigned int) sb.st_size, md5, filename);
60892bc1
 			else
797c5b1e
 			    mprintf("%s:%u:%s\n", md5, (unsigned int) sb.st_size, filename);
3d167464
 			free(md5);
 		    } else {
efa239e2
 			mprintf("!md5sig: Can't generate MD5 checksum for %s\n", filename);
3d167464
 			free(filename);
 			return -1;
bcf3dc79
 		    }
 		}
 	    }
 
3d167464
 	    free(filename);
08d6b1e3
 	}
 
3d167464
     } else { /* stream */
 	md5 = cli_md5stream(stdin, NULL);
 	if(!md5) {
efa239e2
 	    mprintf("!md5sig: Can't generate MD5 checksum for input stream\n");
3d167464
 	    return -1;
ae307914
 	}
efa239e2
 	mprintf("%s\n", md5);
3d167464
 	free(md5);
     }
8139fd99
 
3d167464
     return 0;
 }
8139fd99
 
3d167464
 static int htmlnorm(struct optstruct *opt)
 {
 	int fd;
0f387b1b
 
 
3d167464
     if((fd = open(opt_arg(opt, "html-normalise"), O_RDONLY)) == -1) {
efa239e2
 	mprintf("!htmlnorm: Can't open file %s\n", opt_arg(opt, "html-normalise"));
3d167464
 	return -1;
     }
55216b6e
 
692bda68
     html_normalise_fd(fd, ".", NULL, NULL);
3d167464
     close(fd);
55216b6e
 
3d167464
     return 0;
 }
8139fd99
 
ec5e029e
 static int utf16decode(struct optstruct *opt)
 {
 	const char *fname;
 	char *newname, buff[512], *decoded;
4f411f34
 	int fd1, fd2, bytes;
ec5e029e
 
 
     fname = opt_arg(opt, "utf16-decode");
     if((fd1 = open(fname, O_RDONLY)) == -1) {
 	mprintf("!utf16decode: Can't open file %s\n", fname);
 	return -1;
     }
 
     newname = malloc(strlen(fname) + 7);
     sprintf(newname, "%s.ascii", fname);
 
     if((fd2 = open(newname, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU)) < 0) {
 	mprintf("!utf16decode: Can't create file %s\n", newname);
 	free(newname);
 	close(fd1);
 	return -1;
     }
 
     while((bytes = read(fd1, buff, sizeof(buff))) > 0) {
 	decoded = cli_utf16toascii(buff, bytes);
 	if(decoded) {
 	    if(write(fd2, decoded, strlen(decoded)) == -1) {
 		mprintf("!utf16decode: Can't write to file %s\n", newname);
 		free(decoded);
 		unlink(newname);
 		free(newname);
 		close(fd1);
 		close(fd2);
 		return -1;
 	    }
 	    free(decoded);
 	}
     }
 
     free(newname);
     close(fd1);
     close(fd2);
 
     return 0;
 }
 
3d167464
 static unsigned int countlines(const char *filename)
 {
 	FILE *fd;
 	char buff[1024];
 	unsigned int lines = 0;
8139fd99
 
0a2ad257
 
3d167464
     if((fd = fopen(filename, "r")) == NULL)
 	return 0;
0a2ad257
 
3d167464
     while(fgets(buff, sizeof(buff), fd)) {
 	if(buff[0] == '#') continue;
 	lines++;
     }
9d0b7ebd
 
3d167464
     fclose(fd);
     return lines;
 }
9d0b7ebd
 
5b68299b
 static char *getdsig(const char *host, const char *user, const char *data, unsigned int datalen, unsigned short mode)
3d167464
 {
5b68299b
 	char buff[512], cmd[128], pass[30], *pt;
3d167464
         struct sockaddr_in server;
 	int sockd, bread, len;
f0d0a4c0
 #ifdef HAVE_TERMIOS_H
 	struct termios old, new;
 #endif
9d0b7ebd
 
 
4f411f34
     if((pt = getenv("SIGNDPASS"))) {
 	strncpy(pass, pt, sizeof(pass));
     } else {
 	fflush(stdin);
 	mprintf("Password: ");
 
 #ifdef HAVE_TERMIOS_H
 	if(tcgetattr(0, &old)) {
 	    mprintf("!getdsig: tcgetattr() failed\n");
 	    return NULL;
 	}
 	new = old;
 	new.c_lflag &= ~ECHO;
 	if(tcsetattr(0, TCSAFLUSH, &new)) {
 	    mprintf("!getdsig: tcsetattr() failed\n");
 	    return NULL;
 	}
 #endif
 
 	if(fgets(pass, sizeof(pass), stdin)) {
 	    cli_chomp(pass);
 	} else {
 	    mprintf("!getdsig: Can't get password\n");
 	    return NULL;
 	}
 
 #ifdef HAVE_TERMIOS_H
 	if(tcsetattr(0, TCSAFLUSH, &old)) {
797c5b1e
 	    mprintf("!getdsig: tcsetattr() failed\n");
4f411f34
 	    memset(pass, 0, strlen(pass));
 	    return NULL;
 	}
 #endif
 	mprintf("\n");
     }
 
3d167464
 #ifdef PF_INET
     if((sockd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
 #else
     if((sockd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 #endif
 	perror("socket()");
efa239e2
 	mprintf("!getdsig: Can't create socket\n");
4f411f34
 	memset(pass, 0, strlen(pass));
3d167464
 	return NULL;
     }
9d0b7ebd
 
3d167464
     server.sin_family = AF_INET;
     server.sin_addr.s_addr = inet_addr(host);
     server.sin_port = htons(33101);
e3aaff8e
 
3d167464
     if(connect(sockd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) < 0) {
         close(sockd);
 	perror("connect()");
efa239e2
 	mprintf("!getdsig: Can't connect to ClamAV Signing Service at %s\n", host);
4f411f34
 	memset(pass, 0, strlen(pass));
3d167464
 	return NULL;
e3aaff8e
     }
3d167464
     memset(cmd, 0, sizeof(cmd));
f0d0a4c0
 
5b68299b
     if(mode == 1)
 	snprintf(cmd, sizeof(cmd) - datalen, "ClamSignPSS:%s:%s:", user, pass);
     else
 	snprintf(cmd, sizeof(cmd) - datalen, "ClamSign:%s:%s:", user, pass);
 
3d167464
     len = strlen(cmd);
     pt = cmd + len;
5b68299b
     memcpy(pt, data, datalen);
     len += datalen;
e4ae7726
 
3d167464
     if(write(sockd, cmd, len) < 0) {
efa239e2
 	mprintf("!getdsig: Can't write to socket\n");
3d167464
 	close(sockd);
 	memset(cmd, 0, len);
 	memset(pass, 0, strlen(pass));
 	return NULL;
     }
19e2ac07
 
3d167464
     memset(cmd, 0, len);
     memset(pass, 0, strlen(pass));
     memset(buff, 0, sizeof(buff));
e4ae7726
 
827bb6ca
     if((bread = cli_readn(sockd, buff, sizeof(buff))) > 0) {
3d167464
 	if(!strstr(buff, "Signature:")) {
efa239e2
 	    mprintf("!getdsig: Error generating digital signature\n");
 	    mprintf("!getdsig: Answer from remote server: %s\n", buff);
3d167464
 	    close(sockd);
 	    return NULL;
 	} else {
827bb6ca
 	    mprintf("Signature received (length = %u)\n", strlen(buff) - 10);
3d167464
 	}
827bb6ca
     } else {
 	mprintf("!getdsig: Communication error with remote server\n");
 	close(sockd);
 	return NULL;
19e2ac07
     }
e4ae7726
 
3d167464
     close(sockd);
827bb6ca
 
3d167464
     pt = buff;
     pt += 10;
     return strdup(pt);
e4ae7726
 }
 
e3bcc33a
 static int writeinfo(const char *db, const char *header)
0a4afbbd
 {
 	FILE *fh;
 	int i;
 	struct stat sb;
 	char file[32], *md5;
70edb085
 	const char *const extlist[] = { "db", "fp", "hdb", "hdu", "mdb", "mdu", "ndb", "ndu", "pdb", "wdb", "rmd", "zmd", "sdb", "cfg", NULL };
0a4afbbd
 
 
     snprintf(file, sizeof(file), "%s.info", db);
     if(stat(file, &sb) != -1) {
 	if(unlink(file) == -1) {
efa239e2
 	    mprintf("!writeinfo: Can't unlink %s\n", file);
0a4afbbd
 	    return -1;
 	}
     }
 
     if(!(fh = fopen(file, "w"))) {
efa239e2
 	mprintf("!writeinfo: Can't create file %s\n", file);
0a4afbbd
 	return -1;
     }
 
e3bcc33a
     if(fprintf(fh, "%s\n", header) < 0) {
 	mprintf("!writeinfo: Can't write to %s\n", file);
0a4afbbd
 	fclose(fh);
 	return -1;
     }
 
     for(i = 0; extlist[i]; i++) {
 	snprintf(file, sizeof(file), "%s.%s", db, extlist[i]);
 	if(stat(file, &sb) != -1) {
 	    if(!(md5 = cli_md5file(file))) {
efa239e2
 		mprintf("!writeinfo: Can't generate MD5 checksum for %s\n", file);
0a4afbbd
 		fclose(fh);
 		return -1;
 	    }
 	    if(fprintf(fh, "%s.%s:%s\n", db, extlist[i], md5) < 0) {
efa239e2
 		mprintf("!writeinfo: Can't write to info file\n");
0a4afbbd
 		fclose(fh);
 		free(md5);
 		return -1;
 	    }
 	    free(md5);
 	}
     }
 
     fclose(fh);
     return 0;
 }
 
1aa405c3
 static int diffdirs(const char *old, const char *new, const char *patch);
5b68299b
 static int verifydiff(const char *diff, const char *cvd, const char *incdir);
 
 static int script2cdiff(const char *script, const char *builder, struct optstruct *opt)
 {
 	char *cdiff, *pt, buffer[FILEBUFF];
 	unsigned char digest[32];
 	SHA256_CTX ctx;
 	struct stat sb;
 	FILE *scripth, *cdiffh;
 	gzFile *gzh;
 	unsigned int ver, osize;
 	int bytes;
 
 
     if(stat(script, &sb) == -1) {
 	mprintf("!script2diff: Can't stat file %s\n", script);
 	return -1;
     }
     osize = (unsigned int) sb.st_size;
 
     cdiff = strdup(script);
     pt = strstr(cdiff, ".script");
     if(!pt) {
 	mprintf("!script2cdiff: Incorrect file name (no .script extension)\n");
 	free(cdiff);
 	return -1;
     }
     strcpy(pt, ".cdiff");
 
     if(!(pt = strchr(script, '-'))) {
 	mprintf("!script2cdiff: Incorrect file name syntax\n");
 	free(cdiff);
 	return -1;
     }
     sscanf(++pt, "%u.script", &ver);
 
     if(!(cdiffh = fopen(cdiff, "wb"))) {
 	mprintf("!script2cdiff: Can't open %s for writing\n", cdiff);
 	free(cdiff);
 	return -1;
     }
 
     if(fprintf(cdiffh, "ClamAV-Diff:%u:%u:", ver, osize) < 0) {
 	mprintf("!script2cdiff: Can't write to %s\n", cdiff);
 	fclose(cdiffh);
 	free(cdiff);
 	return -1;
     }
     fclose(cdiffh);
 
     if(!(scripth = fopen(script, "rb"))) {
 	mprintf("!script2cdiff: Can't open file %s for reading\n", script);
 	unlink(cdiff);
 	free(cdiff);
 	return -1;
     }
 
     if(!(gzh = gzopen(cdiff, "ab"))) {
 	mprintf("!script2cdiff: Can't open file %s for appending\n", cdiff);
 	unlink(cdiff);
 	free(cdiff);
 	fclose(scripth);
 	return -1;
     }
 
     while((bytes = fread(buffer, 1, sizeof(buffer), scripth)) > 0) {
 	if(!gzwrite(gzh, buffer, bytes)) {
 	    mprintf("!script2cdiff: Can't gzwrite to %s\n", cdiff);
 	    unlink(cdiff);
 	    free(cdiff);
 	    fclose(scripth);
 	    gzclose(gzh);
 	    return -1;
 	}
     }
     fclose(scripth);
     gzclose(gzh);
 
     if(!(cdiffh = fopen(cdiff, "rb"))) {
 	mprintf("!script2cdiff: Can't open %s for reading/writing\n", cdiff);
 	unlink(cdiff);
 	free(cdiff);
 	return -1;
     }
 
     sha256_init(&ctx);
 
     while((bytes = fread(buffer, 1, sizeof(buffer), cdiffh)))
 	sha256_update(&ctx, (unsigned char *) buffer, bytes);
 
     fclose(cdiffh);
     sha256_final(&ctx);
     sha256_digest(&ctx, digest);
 
     if(!(pt = getdsig(opt_arg(opt, "server"), builder, (char *) digest, 32, 1))) {
 	mprintf("!script2cdiff: Can't get digital signature from remote server\n");
 	unlink(cdiff);
 	free(cdiff);
 	return -1;
     }
 
     if(!(cdiffh = fopen(cdiff, "ab"))) {
 	mprintf("!script2cdiff: Can't open %s for appending\n", cdiff);
 	unlink(cdiff);
 	free(cdiff);
 	return -1;
     }
     fprintf(cdiffh, ":%s", pt);
     free(pt);
     fclose(cdiffh);
 
     mprintf("Created %s\n", cdiff);
     free(cdiff);
 
     return 0;
 }
1aa405c3
 
3d167464
 static int build(struct optstruct *opt)
8139fd99
 {
050c6e4d
 	int ret, inc = 1, dn;
3d167464
 	size_t bytes;
d08d2bd0
 	unsigned int sigs = 0, oldsigs = 0, lines = 0, version, real_header, fl;
8139fd99
 	struct stat foo;
1aa405c3
 	char buffer[FILEBUFF], *tarfile, *gzfile, header[513], smbuff[32],
797c5b1e
 	     builder[32], *pt, olddb[512], patch[32], broken[32];
 	const char *dbname;
200e94b5
         struct cl_engine *engine = NULL;
3d167464
 	FILE *tar, *cvd;
8139fd99
 	gzFile *gz;
 	time_t timet;
 	struct tm *brokent;
0a4afbbd
 	struct cl_cvd *oldcvd;
8139fd99
 
 
3d167464
     if(!opt_check(opt, "server")) {
efa239e2
 	mprintf("!build: --server is required for --build\n");
3d167464
 	return -1;
8139fd99
     }
 
3d167464
     if(stat("COPYING", &foo) == -1) {
efa239e2
 	mprintf("!build: COPYING file not found in current working directory.\n");
3d167464
 	return -1;
8139fd99
     }
 
3d167464
     if(stat("main.db", &foo) == -1 && stat("daily.db", &foo) == -1 &&
        stat("main.hdb", &foo) == -1 && stat("daily.hdb", &foo) == -1 &&
70edb085
        stat("main.hdu", &foo) == -1 && stat("daily.hdu", &foo) == -1 &&
c09d6c19
        stat("main.mdb", &foo) == -1 && stat("daily.mdb", &foo) == -1 &&
70edb085
        stat("main.mdu", &foo) == -1 && stat("daily.mdu", &foo) == -1 &&
3d167464
        stat("main.ndb", &foo) == -1 && stat("daily.ndb", &foo) == -1 &&
70edb085
        stat("main.ndu", &foo) == -1 && stat("daily.ndu", &foo) == -1 &&
508268f3
        stat("main.pdb", &foo) == -1 && stat("daily.pdb", &foo) == -1 &&
0a4afbbd
        stat("main.sdb", &foo) == -1 && stat("daily.sdb", &foo) == -1 &&
3d167464
        stat("main.zmd", &foo) == -1 && stat("daily.zmd", &foo) == -1 &&
        stat("main.rmd", &foo) == -1 && stat("daily.rmd", &foo) == -1)
     {
efa239e2
 	mprintf("!build: No virus database file  found in current directory\n");
3d167464
 	return -1;
8139fd99
     }
 
4f411f34
     if((ret = cl_load(".", &engine, &sigs, CL_DB_STDOPT))) {
efa239e2
 	mprintf("!build: Can't load database: %s\n", cl_strerror(ret));
3d167464
 	return -1;
     } else {
200e94b5
 	cl_free(engine);
3d167464
     }
8139fd99
 
3d167464
     if(!sigs) {
efa239e2
 	mprintf("!build: There are no signatures in database files\n");
0f387b1b
     } else {
3d167464
 	lines = countlines("main.db") + countlines("daily.db") +
 		countlines("main.hdb") + countlines("daily.hdb") +
70edb085
 		countlines("main.hdu") + countlines("daily.hdu") +
c09d6c19
 		countlines("main.mdb") + countlines("daily.mdb") +
70edb085
 		countlines("main.mdu") + countlines("daily.mdu") +
3d167464
 		countlines("main.ndb") + countlines("daily.ndb") +
70edb085
 		countlines("main.ndu") + countlines("daily.ndu") +
0a4afbbd
 		countlines("main.sdb") + countlines("daily.sdb") +
3d167464
 		countlines("main.zmd") + countlines("daily.zmd") +
 		countlines("main.rmd") + countlines("daily.rmd") +
 		countlines("main.fp") + countlines("daily.fp");
 
 	if(lines != sigs) {
d08d2bd0
 	    mprintf("^build: Signatures in database: %d, loaded by libclamav: %d\n", lines, sigs);
 	    mprintf("^build: Please check the current directory and remove unnecessary databases\n");
 	    mprintf("^build: or install the latest ClamAV version.\n");
0f387b1b
 	}
     }
8139fd99
 
0a4afbbd
     /* try to read cvd header of current database */
1aa405c3
     dbname = opt_arg(opt, "build");
     if(strstr(dbname, "main"))
 	dbname = "main";
     else
 	dbname = "daily";
 
 
c4cbb847
     if(opt->filename) {
 	if(cli_strbcasestr(opt->filename, ".cvd")) {
 	    strncpy(olddb, opt->filename, sizeof(olddb));
 	    inc = 0;
 	} else if(cli_strbcasestr(opt->filename, ".inc")) {
 	    snprintf(olddb, sizeof(olddb), "%s/%s.info", opt->filename, dbname);
 	} else {
 	    mprintf("!build: The optional argument points to neither CVD nor incremental directory\n");
 	    return -1;
 	}
 
     } else {
 	pt = freshdbdir();
 	snprintf(olddb, sizeof(olddb), "%s/%s.inc/%s.info", pt, dbname, dbname);
 	if(stat(olddb, &foo) == -1) {
 	    inc = 0;
 	    snprintf(olddb, sizeof(olddb), "%s/%s.cvd", pt, dbname);
 	}
 	free(pt);
     }
1aa405c3
 
     if(!(oldcvd = cl_cvdhead(olddb))) {
c4cbb847
 	mprintf("^build: CAN'T READ CVD HEADER OF CURRENT DATABASE %s\n", olddb);
0a4afbbd
 	sleep(3);
     }
 
     if(oldcvd) {
 	version = oldcvd->version + 1;
b336436b
 	oldsigs = oldcvd->sigs;
0a4afbbd
 	cl_cvdfree(oldcvd);
     } else {
 	fflush(stdin);
efa239e2
 	mprintf("Version number: ");
0a4afbbd
 	scanf("%u", &version);
     }
 
b336436b
     mprintf("Total sigs: %u\n", sigs);
     if(sigs > oldsigs)
 	mprintf("New sigs: %u\n", sigs - oldsigs);
 
e3bcc33a
     strcpy(header, "ClamAV-VDB:");
 
     /* time */
     time(&timet);
     brokent = localtime(&timet);
     setlocale(LC_TIME, "C");
     strftime(smbuff, sizeof(smbuff), "%d %b %Y %H-%M %z", brokent);
     strcat(header, smbuff);
 
     /* version */
     sprintf(smbuff, ":%d:", version);
     strcat(header, smbuff);
 
     /* number of signatures */
     sprintf(smbuff, "%d:", sigs);
     strcat(header, smbuff);
 
     /* functionality level */
d08d2bd0
     if(!strcmp(dbname, "main")) {
 	fflush(stdin);
 	mprintf("Functionality level: ");
a47ed53d
 	if(fgets(smbuff, sizeof(smbuff), stdin)) {
 	    cli_chomp(smbuff);
 	} else {
 	    mprintf("!build: Can't get functionality level\n");
 	    return -1;
 	}
 	fl = atoi(smbuff);
 	if(!fl || fl > 99) {
 	    mprintf("!build: Incorrect functionality level\n");
 	    return -1;
 	}
d08d2bd0
     } else {
 	fl = cl_retflevel();
     }
     sprintf(smbuff, "%u:", fl);
e3bcc33a
     strcat(header, smbuff);
 
     real_header = strlen(header);
 
     /* add fake MD5 and dsig (for writeinfo) */
     strcat(header, "X:X:");
 
4f411f34
     if((pt = getenv("SIGNDUSER"))) {
 	strncpy(builder, pt, sizeof(builder));
e3bcc33a
     } else {
4f411f34
 	/* ask for builder name */
 	fflush(stdin);
 	mprintf("Builder name: ");
 	if(fgets(builder, sizeof(builder), stdin)) {
 	    cli_chomp(builder);
 	} else {
 	    mprintf("!build: Can't get builder name\n");
 	    return -1;
 	}
e3bcc33a
     }
 
     /* add builder */
     strcat(header, builder);
 
     /* add current time */
     sprintf(header + strlen(header), ":%d", (int) timet);
 
1aa405c3
     if(writeinfo(dbname, header) == -1) {
efa239e2
 	mprintf("!build: Can't generate info file\n");
0a4afbbd
 	return -1;
     }
 
e3bcc33a
     header[real_header] = 0;
 
3d167464
     if(!(tarfile = cli_gentemp("."))) {
efa239e2
 	mprintf("!build: Can't generate temporary name for tarfile\n");
3d167464
 	return -1;
     }
8139fd99
 
     switch(fork()) {
 	case -1:
efa239e2
 	    mprintf("!build: Can't fork.\n");
3d167464
 	    free(tarfile);
 	    return -1;
8139fd99
 	case 0:
 	    {
797c5b1e
 		const char *args[] = { "tar", "-cvf", NULL, "COPYING", "main.db",
3d167464
 				 "daily.db", "main.hdb", "daily.hdb",
70edb085
 				 "main.hdu", "daily.hdu", "main.ndb",
 				 "daily.ndb", "main.ndu", "daily.ndu",
 				 "main.sdb", "daily.sdb", "main.zmd",
 				 "daily.zmd", "main.rmd", "daily.rmd",
7021b545
 				 "main.fp", "daily.fp", "daily.ft", "main.mdb",
70edb085
 				 "daily.mdb", "main.mdu", "daily.mdu",
508268f3
 				 "daily.info", "main.info", "main.wdb",
 				 "daily.wdb", "main.pdb", "daily.pdb",
0714ac10
 				 "main.cfg", "daily.cfg",
7dec7955
 				 NULL };
0aaf4b37
 		args[2] = tarfile;
1aa405c3
 		if(!opt_check(opt, "debug")) {
050c6e4d
 		    if((dn = open("/dev/null", O_WRONLY)) == -1) {
 			mprintf("^Cannot open /dev/null\n");
 			close(1);
 			close(2);
 		    } else {
 			dup2(dn, 1);
 			dup2(dn, 2);
 			close(dn);
 		    }
1aa405c3
 		}
8139fd99
 		execv("/bin/tar", args);
efa239e2
 		mprintf("!build: Can't execute tar\n");
8139fd99
 		perror("tar");
3d167464
 		free(tarfile);
 		return -1;
8139fd99
 	    }
 	default:
 	    wait(NULL);
     }
 
     if(stat(tarfile, &foo) == -1) {
efa239e2
 	mprintf("!build: Tar archive was not created\n");
3d167464
 	free(tarfile);
 	return -1;
8139fd99
     }
 
     if((tar = fopen(tarfile, "rb")) == NULL) {
efa239e2
 	mprintf("!build: Can't open file %s\n", tarfile);
3d167464
 	free(tarfile);
 	return -1;
     }
 
     if(!(gzfile = cli_gentemp("."))) {
efa239e2
 	mprintf("!build: Can't generate temporary name for gzfile\n");
3d167464
 	free(tarfile);
 	fclose(tar);
 	return -1;
8139fd99
     }
 
     if((gz = gzopen(gzfile, "wb")) == NULL) {
efa239e2
 	mprintf("!build: Can't open file %s to write.\n", gzfile);
3d167464
 	free(tarfile);
 	fclose(tar);
 	free(gzfile);
 	return -1;
8139fd99
     }
 
3d167464
     while((bytes = fread(buffer, 1, FILEBUFF, tar)) > 0) {
 	if(!gzwrite(gz, buffer, bytes)) {
efa239e2
 	    mprintf("!build: Can't gzwrite to %s\n", gzfile);
3d167464
 	    fclose(tar);
 	    gzclose(gz);
 	    free(tarfile);
 	    free(gzfile);
 	    return -1;
 	}
     }
8139fd99
 
     fclose(tar);
3d167464
     gzclose(gz);
8139fd99
     unlink(tarfile);
     free(tarfile);
 
     /* MD5 */
3d167464
     if(!(pt = cli_md5file(gzfile))) {
efa239e2
 	mprintf("!build: Can't generate MD5 checksum for gzfile\n");
3d167464
 	unlink(gzfile);
 	free(gzfile);
 	return -1;
     }
658f19f8
     strcat(header, pt);
ae307914
     free(pt);
658f19f8
     strcat(header, ":");
8139fd99
 
     /* digital signature */
3d167464
     if(!(tar = fopen(gzfile, "rb"))) {
efa239e2
 	mprintf("!build: Can't open file %s for reading\n", gzfile);
3d167464
 	unlink(gzfile);
 	free(gzfile);
 	return -1;
     }
 
     if(!(pt = cli_md5stream(tar, (unsigned char *) buffer))) {
efa239e2
 	mprintf("!build: Can't generate MD5 checksum for %s\n", gzfile);
3d167464
 	unlink(gzfile);
 	free(gzfile);
 	return -1;
     }
335d1663
     free(pt);
3d167464
     rewind(tar);
 
5b68299b
     if(!(pt = getdsig(opt_arg(opt, "server"), builder, buffer, 16, 0))) {
efa239e2
 	mprintf("!build: Can't get digital signature from remote server\n");
0f387b1b
 	unlink(gzfile);
3d167464
 	free(gzfile);
 	fclose(tar);
 	return -1;
ae307914
     }
658f19f8
     strcat(header, pt);
ae307914
     free(pt);
658f19f8
     strcat(header, ":");
8139fd99
 
3365db23
     /* add builder */
e3bcc33a
     strcat(header, builder);
8139fd99
 
3365db23
     /* add current time */
     sprintf(header + strlen(header), ":%d", (int) timet);
 
8139fd99
     /* fill up with spaces */
3d167464
     while(strlen(header) < sizeof(header) - 1)
658f19f8
 	strcat(header, " ");
8139fd99
 
     /* build the final database */
7b8edc5c
     pt = opt_arg(opt, "build");
3d167464
     if(!(cvd = fopen(pt, "wb"))) {
efa239e2
 	mprintf("!build: Can't create final database %s\n", pt);
0f387b1b
 	unlink(gzfile);
3d167464
 	free(gzfile);
 	fclose(tar);
 	return -1;
8139fd99
     }
 
3d167464
     if(fwrite(header, 1, 512, cvd) != 512) {
efa239e2
 	mprintf("!build: Can't write to %s\n", pt);
3d167464
 	fclose(cvd);
 	fclose(tar);
 	unlink(pt);
 	unlink(gzfile);
 	free(gzfile);
 	return -1;
8139fd99
     }
 
3d167464
     while((bytes = fread(buffer, 1, FILEBUFF, tar)) > 0) {
 	if(fwrite(buffer, 1, bytes, cvd) != bytes) {
 	    fclose(tar);
 	    fclose(cvd);
 	    unlink(pt);
efa239e2
 	    mprintf("!build: Can't write to %s\n", gzfile);
3d167464
 	    unlink(gzfile);
 	    free(gzfile);
 	    return -1;
 	}
     }
8139fd99
 
     fclose(tar);
     fclose(cvd);
3d167464
     if(unlink(gzfile) == -1) {
efa239e2
 	mprintf("^build: Can't unlink %s\n", gzfile);
3d167464
 	return -1;
     }
8139fd99
     free(gzfile);
 
5b68299b
     mprintf("Created %s\n", pt);
3d167464
 
1aa405c3
     /* generate patch */
c4cbb847
     if(opt->filename) {
 	strncpy(olddb, opt->filename, sizeof(olddb));
1aa405c3
     } else {
c4cbb847
 	if(inc) {
 	    pt = freshdbdir();
 	    snprintf(olddb, sizeof(olddb), "%s/%s.inc", pt, dbname);
 	    free(pt);
 	} else {
 	    pt = freshdbdir();
 	    snprintf(olddb, sizeof(olddb), "%s/%s.cvd", pt, dbname);
 	    free(pt);
 	}
     }
1aa405c3
 
c4cbb847
     if(!inc) {
1aa405c3
 	pt = cli_gentemp(NULL);
 	if(mkdir(pt, 0700)) {
 	    mprintf("!build: Can't create temporary directory %s\n", pt);
 	    return -1;
 	}
 	if(cvd_unpack(olddb, pt) == -1) {
 	    mprintf("!build: Can't unpack CVD file %s\n", olddb);
66ba1785
 	    cli_rmdirs(pt);
1aa405c3
 	    free(pt);
 	    return -1;
 	}
 	strncpy(olddb, pt, sizeof(olddb));
     }
 
     pt = cli_gentemp(NULL);
     if(mkdir(pt, 0700)) {
 	mprintf("!build: Can't create temporary directory %s\n", pt);
 	free(pt);
 	if(!inc)
66ba1785
 	    cli_rmdirs(olddb);
1aa405c3
 	return -1;
     }
     if(cvd_unpack(opt_arg(opt, "build"), pt) == -1) {
 	mprintf("!build: Can't unpack CVD file %s\n", opt_arg(opt, "build"));
66ba1785
 	cli_rmdirs(pt);
1aa405c3
 	free(pt);
 	if(!inc)
66ba1785
 	    cli_rmdirs(olddb);
1aa405c3
 	return -1;
     }
 
     if(!strcmp(dbname, "main"))
5b68299b
 	snprintf(patch, sizeof(patch), "main-%u.script", version);
1aa405c3
     else
5b68299b
 	snprintf(patch, sizeof(patch), "daily-%u.script", version);
1aa405c3
 
     ret = diffdirs(olddb, pt, patch);
 
66ba1785
     cli_rmdirs(pt);
1aa405c3
     free(pt);
 
     if(ret == -1) {
 	if(!inc)
66ba1785
 	    cli_rmdirs(olddb);
1aa405c3
 	return -1;
     }
 
5b68299b
     ret = verifydiff(patch, NULL, olddb);
1aa405c3
 
     if(!inc)
66ba1785
 	cli_rmdirs(olddb);
1aa405c3
 
     if(ret == -1) {
 	snprintf(broken, sizeof(broken), "%s.broken", patch);
 	if(rename(patch, broken)) {
 	    unlink(patch);
 	    mprintf("!Generated file is incorrect, removed");
 	} else {
 	    mprintf("!Generated file is incorrect, renamed to %s\n", broken);
 	}
5b68299b
     } else {
 	ret = script2cdiff(patch, builder, opt);
1aa405c3
     }
 
     return ret;
3d167464
 }
 
 static int unpack(struct optstruct *opt)
 {
 	char *name, *dbdir;
1aa405c3
 	struct stat sb;
3d167464
 
 
     if(opt_check(opt, "unpack-current")) {
 	dbdir = freshdbdir();
8ca8a18e
 	name = malloc(strlen(dbdir) + strlen(opt_arg(opt, "unpack-current")) + 32);
1aa405c3
 	sprintf(name, "%s/%s.inc", dbdir, opt_arg(opt, "unpack-current"));
 	if(stat(name, &sb) != -1) {
 
 	    if(dircopy(name, ".") == -1) {
 		mprintf("!unpack: Can't copy incremental directory %s to local directory\n", name);
 		free(name);
 		free(dbdir);
 		return -1;
 	    }
 
 	    return 0;
 
 	} else {
 	    sprintf(name, "%s/%s.cvd", dbdir, opt_arg(opt, "unpack-current"));
 	}
3d167464
 	free(dbdir);
1aa405c3
 
3d167464
     } else
 	name = strdup(opt_arg(opt, "unpack"));
8139fd99
 
3d167464
     if(cvd_unpack(name, ".") == -1) {
efa239e2
 	mprintf("!unpack: Can't unpack CVD file %s\n", name);
3d167464
 	free(name);
 	return -1;
     }
 
     free(name);
2d70a403
     return 0;
8139fd99
 }
 
3d167464
 static int cvdinfo(struct optstruct *opt)
8139fd99
 {
 	struct cl_cvd *cvd;
 	char *pt;
6a2532ca
 	int ret;
8139fd99
 
3d167464
 
7b8edc5c
     pt = opt_arg(opt, "info");
6a2532ca
     if((cvd = cl_cvdhead(pt)) == NULL) {
efa239e2
 	mprintf("!cvdinfo: Can't read/parse CVD header of %s\n", pt);
3d167464
 	return -1;
8139fd99
     }
d22970ec
     mprintf("File: %s\n", pt);
8139fd99
 
3d167464
     pt = strchr(cvd->time, '-');
     *pt = ':';
efa239e2
     mprintf("Build time: %s\n", cvd->time);
     mprintf("Version: %d\n", cvd->version);
     mprintf("Signatures: %d\n", cvd->sigs);
     mprintf("Functionality level: %d\n", cvd->fl);
     mprintf("Builder: %s\n", cvd->builder);
     mprintf("MD5: %s\n", cvd->md5);
     mprintf("Digital signature: %s\n", cvd->dsig);
8139fd99
 
1cdb6cfb
 #ifndef HAVE_LIBGMP
efa239e2
     mprintf("^Digital signature support not compiled in.\n");
6a2532ca
 #endif
 
3d167464
     pt = opt_arg(opt, "info");
6a2532ca
     if((ret = cl_cvdverify(pt)))
efa239e2
 	mprintf("!cvdinfo: Verification: %s\n", cl_strerror(ret));
6a2532ca
     else
efa239e2
 	mprintf("Verification OK.\n");
6a2532ca
 
3d167464
     cl_cvdfree(cvd);
     return 0;
8139fd99
 }
 
3d167464
 static int listdb(const char *filename);
ae307914
 
3d167464
 static int listdir(const char *dirname)
 {
 	DIR *dd;
 	struct dirent *dent;
 	char *dbfile;
ae307914
 
 
3d167464
     if((dd = opendir(dirname)) == NULL) {
efa239e2
         mprintf("!listdir: Can't open directory %s\n", dirname);
3d167464
         return -1;
ae307914
     }
 
3d167464
     while((dent = readdir(dd))) {
 #ifndef C_INTERIX
 	if(dent->d_ino)
 #endif
 	{
 	    if(strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..") &&
 	    (cli_strbcasestr(dent->d_name, ".db")  ||
 	     cli_strbcasestr(dent->d_name, ".hdb") ||
70edb085
 	     cli_strbcasestr(dent->d_name, ".hdu") ||
c09d6c19
 	     cli_strbcasestr(dent->d_name, ".mdb") ||
70edb085
 	     cli_strbcasestr(dent->d_name, ".mdu") ||
3d167464
 	     cli_strbcasestr(dent->d_name, ".ndb") ||
70edb085
 	     cli_strbcasestr(dent->d_name, ".ndu") ||
0a4afbbd
 	     cli_strbcasestr(dent->d_name, ".sdb") ||
3d167464
 	     cli_strbcasestr(dent->d_name, ".zmd") ||
 	     cli_strbcasestr(dent->d_name, ".rmd") ||
89f4fd0f
 	     cli_strbcasestr(dent->d_name, ".inc") ||
3d167464
 	     cli_strbcasestr(dent->d_name, ".cvd"))) {
ae307914
 
8ca8a18e
 		dbfile = (char *) malloc(strlen(dent->d_name) + strlen(dirname) + 2);
3d167464
 		if(!dbfile) {
efa239e2
 		    mprintf("!listdir: Can't allocate memory for dbfile\n");
3d167464
 		    closedir(dd);
 		    return -1;
 		}
 		sprintf(dbfile, "%s/%s", dirname, dent->d_name);
ae307914
 
3d167464
 		if(listdb(dbfile) == -1) {
efa239e2
 		    mprintf("!listdb: Error listing database %s\n", dbfile);
3d167464
 		    free(dbfile);
 		    closedir(dd);
 		    return -1;
 		}
 		free(dbfile);
 	    }
2d70a403
 	}
     }
ae307914
 
3d167464
     closedir(dd);
     return 0;
0f387b1b
 }
0a2ad257
 
3d167464
 static int listdb(const char *filename)
0a2ad257
 {
3d167464
 	FILE *fd;
 	char *buffer, *pt, *start, *dir;
 	int line = 0;
0a2ad257
 	const char *tmpdir;
 
 
89f4fd0f
     if(cli_strbcasestr(filename, ".inc")) { /* incremental directory */
 	if(listdir(filename) == -1) {
 	    mprintf("!listdb: Can't list incremental directory %s\n", filename);
 	    return -1;
 	}
 	return 0;
     }
 
0a2ad257
     if((fd = fopen(filename, "rb")) == NULL) {
efa239e2
 	mprintf("!listdb: Can't open file %s\n", filename);
0a2ad257
 	return -1;
     }
 
8ca8a18e
     if(!(buffer = (char *) malloc(FILEBUFF))) {
efa239e2
 	mprintf("!listdb: Can't allocate memory for buffer\n");
bcf3dc79
 	fclose(fd);
0a2ad257
 	return -1;
     }
 
     /* check for CVD file */
     fgets(buffer, 12, fd);
     rewind(fd);
 
     if(!strncmp(buffer, "ClamAV-VDB:", 11)) {
3d167464
 	free(buffer);
 	fclose(fd);
0a2ad257
 
 	tmpdir = getenv("TMPDIR");
 	if(tmpdir == NULL)
 #ifdef P_tmpdir
 	    tmpdir = P_tmpdir;
 #else
 	    tmpdir = "/tmp";
 #endif
 
3d167464
 	if(!(dir = cli_gentemp(tmpdir))) {
efa239e2
 	    mprintf("!listdb: Can't generate temporary name\n");
0a2ad257
 	    return -1;
 	}
 
3d167464
 	if(mkdir(dir, 0700)) {
efa239e2
 	    mprintf("!listdb: Can't create temporary directory %s\n", dir);
0a2ad257
 	    free(dir);
 	    return -1;
 	}
 
3d167464
 	if(cvd_unpack(filename, dir) == -1) {
efa239e2
 	    mprintf("!listdb: Can't unpack CVD file %s\n", filename);
66ba1785
 	    cli_rmdirs(dir);
0a2ad257
 	    free(dir);
 	    return -1;
 	}
 
 	/* list extracted directory */
3d167464
 	if(listdir(dir) == -1) {
89f4fd0f
 	    mprintf("!listdb: Can't list directory %s\n", filename);
66ba1785
 	    cli_rmdirs(dir);
3d167464
 	    free(dir);
 	    return -1;
 	}
0a2ad257
 
66ba1785
 	cli_rmdirs(dir);
0a2ad257
 	free(dir);
 
 	return 0;
     }
 
3d167464
     if(cli_strbcasestr(filename, ".db")) { /* old style database */
3365db23
 
 	while(fgets(buffer, FILEBUFF, fd)) {
 	    line++;
 	    pt = strchr(buffer, '=');
 	    if(!pt) {
efa239e2
 		mprintf("!listdb: Malformed pattern line %d (file %s)\n", line, filename);
3365db23
 		fclose(fd);
 		free(buffer);
 		return -1;
 	    }
0a2ad257
 
3365db23
 	    start = buffer;
 	    *pt = 0;
0a2ad257
 
3365db23
 	    if((pt = strstr(start, " (Clam)")))
 		*pt = 0;
 
efa239e2
 	    mprintf("%s\n", start);
0a2ad257
 	}
 
70edb085
     } else if(cli_strbcasestr(filename, ".hdb") || cli_strbcasestr(filename, ".hdu") || cli_strbcasestr(filename, ".mdb") || cli_strbcasestr(filename, ".mdu")) { /* hash database */
0a2ad257
 
3365db23
 	while(fgets(buffer, FILEBUFF, fd)) {
 	    line++;
 	    cli_chomp(buffer);
 	    start = cli_strtok(buffer, 2, ":");
 
 	    if(!start) {
efa239e2
 		mprintf("!listdb: Malformed pattern line %d (file %s)\n", line, filename);
3365db23
 		fclose(fd);
 		free(buffer);
 		return -1;
 	    }
 
 	    if((pt = strstr(start, " (Clam)")))
 		*pt = 0;
 
efa239e2
 	    mprintf("%s\n", start);
3365db23
 	    free(start);
 	}
0a2ad257
 
70edb085
     } else if(cli_strbcasestr(filename, ".ndb") || cli_strbcasestr(filename, ".ndu") || cli_strbcasestr(filename, ".sdb") || cli_strbcasestr(filename, ".zmd") || cli_strbcasestr(filename, ".rmd")) {
7ec67e94
 
 	while(fgets(buffer, FILEBUFF, fd)) {
 	    line++;
 	    cli_chomp(buffer);
 	    start = cli_strtok(buffer, 0, ":");
 
 	    if(!start) {
efa239e2
 		mprintf("!listdb: Malformed pattern line %d (file %s)\n", line, filename);
7ec67e94
 		fclose(fd);
 		free(buffer);
 		return -1;
 	    }
 
 	    if((pt = strstr(start, " (Clam)")))
 		*pt = 0;
 
efa239e2
 	    mprintf("%s\n", start);
7ec67e94
 	    free(start);
 	}
0a2ad257
     }
 
     fclose(fd);
     free(buffer);
     return 0;
 }
 
3d167464
 static int listsigs(struct optstruct *opt)
0a2ad257
 {
 	int ret;
 	const char *name;
98ce643b
 	char *dbdir;
0a2ad257
 
3d167464
 
0a2ad257
     mprintf_stdout = 1;
 
7b8edc5c
     if((name = opt_arg(opt, "list-sigs"))) {
0a2ad257
 	ret = listdb(name);
98ce643b
     } else {
 	dbdir = freshdbdir();
 	ret = listdir(dbdir);
 	free(dbdir);
     }
0a2ad257
 
3d167464
     return ret;
 }
 
 static int vbadump(struct optstruct *opt)
 {
 	int fd, hex_output=0;
 	char *dir;
 
 
     if(opt_check(opt, "vba-hex"))
 	hex_output = 1;
  
     if((fd = open(opt_arg(opt, "vba"), O_RDONLY)) == -1) {
 	if((fd = open(opt_arg(opt, "vba-hex"), O_RDONLY)) == -1) {
efa239e2
 	    mprintf("!vbadump: Can't open file %s\n", opt_arg(opt, "vba"));
3d167464
 	    return -1;
 	}
     }
 
     /* generate the temporary directory */
     dir = cli_gentemp(NULL);
     if(mkdir(dir, 0700)) {
efa239e2
 	mprintf("!vbadump: Can't create temporary directory %s\n", dir);
3d167464
 	free(dir);
 	close(fd);
         return -1;
     }
 
     if(cli_ole2_extract(fd, dir, NULL)) {
 	cli_rmdirs(dir);
         free(dir);
 	close(fd);
         return -1;
     }
 
     close(fd);
     sigtool_vba_scandir(dir, hex_output);
66ba1785
     cli_rmdirs(dir);
3d167464
     free(dir);
     return 0;
ee039e40
 }
 
5b68299b
 static int rundiff(struct optstruct *opt)
bb990ecd
 {
 	int fd, ret;
5b68299b
 	unsigned short mode;
 	const char *diff;
 
bb990ecd
 
5b68299b
     diff = opt_arg(opt, "run-cdiff");
     if(strstr(diff, ".cdiff")) {
 	mode = 1;
     } else if(strstr(diff, ".script")) {
 	mode = 0;
     } else {
 	mprintf("!rundiff: Incorrect file name (no .cdiff/.script extension)\n");
 	return -1;
     }
bb990ecd
 
5b68299b
     if((fd = open(diff, O_RDONLY)) == -1) {
 	mprintf("!rundiff: Can't open file %s\n", diff);
bb990ecd
 	return -1;
6edef583
     }
bb990ecd
 
5b68299b
     ret = cdiff_apply(fd, mode);
bb990ecd
     close(fd);
 
     return ret;
 }
 
37eca903
 static int compare(const char *oldpath, const char *newpath, FILE *diff)
 {
 	FILE *old, *new;
fb30f402
 	char obuff[1024], nbuff[1024], tbuff[1024], *pt, *omd5, *nmd5;
 	unsigned int oline = 0, tline, found, i;
 	long opos;
37eca903
 
 
     if(!(new = fopen(newpath, "r"))) {
 	mprintf("!compare: Can't open file %s for reading\n", newpath);
 	return -1;
     }
 
     if((omd5 = cli_md5file(oldpath))) {
 	if(!(nmd5 = cli_md5file(newpath))) {
 	    mprintf("!compare: Can't get MD5 checksum of %s\n", newpath);
 	    free(omd5);
 	    return -1;
 	}
 	if(!strcmp(omd5, nmd5)) {
 	    free(omd5);
 	    free(nmd5);
 	    return 0;
 	}
 	free(omd5);
 	free(nmd5);
     }
 
     fprintf(diff, "OPEN %s\n", newpath);
 
     old = fopen(oldpath, "r");
 
     while(fgets(nbuff, sizeof(nbuff), new)) {
 	cli_chomp(nbuff);
 
 	if(!old) {
 	    fprintf(diff, "ADD %s\n", nbuff);
 	} else {
 	    if(fgets(obuff, sizeof(obuff), old)) {
fb30f402
 		oline++;
37eca903
 		cli_chomp(obuff);
 		if(!strcmp(nbuff, obuff)) {
 		    continue;
 		} else {
fb30f402
 		    tline = 0;
 		    found = 0;
 		    opos = ftell(old);
 		    while(fgets(tbuff, sizeof(tbuff), old)) {
 			tline++;
 			cli_chomp(tbuff);
 
 			if(tline > MAX_DEL_LOOKAHEAD)
 			    break;
 
 			if(!strcmp(tbuff, nbuff)) {
 			    found = 1;
 			    break;
 			}
 		    }
 		    fseek(old, opos, SEEK_SET);
 
 		    if(found) {
 			strncpy(tbuff, obuff, sizeof(tbuff));
 			for(i = 0; i < tline; i++) {
 			    tbuff[16] = 0;
 			    if((pt = strchr(tbuff, ' ')))
 				*pt = 0;
 			    fprintf(diff, "DEL %u %s\n", oline + i, tbuff);
 			    fgets(tbuff, sizeof(tbuff), old);
 			}
 			oline += tline;
 
 		    } else {
 			obuff[16] = 0;
37eca903
 			if((pt = strchr(obuff, ' ')))
 			    *pt = 0;
fb30f402
 			fprintf(diff, "XCHG %u %s %s\n", oline, obuff, nbuff);
37eca903
 		    }
 		}
 	    } else {
 		fclose(old);
 		old = NULL;
 		fprintf(diff, "ADD %s\n", nbuff);
 	    }
 	}
     }
 
fb30f402
     if(old) {
 	while(fgets(obuff, sizeof(obuff), old)) {
 	    oline++;
 	    obuff[16] = 0;
 	    if((pt = strchr(obuff, ' ')))
 		*pt = 0;
 	    fprintf(diff, "DEL %u %s\n", oline, obuff);
 	}
37eca903
 	fclose(old);
fb30f402
     }
 
37eca903
     fprintf(diff, "CLOSE\n");
     return 0;
 }
 
5b68299b
 static int verifydiff(const char *diff, const char *cvd, const char *incdir)
6edef583
 {
1aa405c3
 	char *tempdir, cwd[512], buff[1024], info[32], *md5, *pt;
 	const char *cpt;
6edef583
 	FILE *fh;
 	int ret = 0, fd;
5b68299b
 	unsigned short mode;
 
6edef583
 
5b68299b
     if(strstr(diff, ".cdiff")) {
 	mode = 1;
     } else if(strstr(diff, ".script")) {
 	mode = 0;
     } else {
 	mprintf("!verifydiff: Incorrect file name (no .cdiff/.script extension)\n");
 	return -1;
     }
6edef583
 
     tempdir = cli_gentemp(NULL);
     if(!tempdir) {
5b68299b
 	mprintf("!verifydiff: Can't generate temporary name for tempdir\n");
6edef583
 	return -1;
     }
 
     if(mkdir(tempdir, 0700) == -1) {
5b68299b
 	mprintf("!verifydiff: Can't create directory %s\n", tempdir);
6edef583
 	free(tempdir);
 	return -1;
     }
 
1aa405c3
     if(cvd) {
 	if(cvd_unpack(cvd, tempdir) == -1) {
5b68299b
 	    mprintf("!verifydiff: Can't unpack CVD file %s\n", cvd);
66ba1785
 	    cli_rmdirs(tempdir);
1aa405c3
 	    free(tempdir);
 	    return -1;
 	}
     } else {
 	if(dircopy(incdir, tempdir) == -1) {
5b68299b
 	    mprintf("!verifydiff: Can't copy dir %s to %s\n", incdir, tempdir);
66ba1785
 	    cli_rmdirs(tempdir);
1aa405c3
 	    free(tempdir);
 	    return -1;
 	}
6edef583
     }
 
     if((fd = open(diff, O_RDONLY)) == -1) {
5b68299b
 	mprintf("!verifydiff: Can't open diff file %s\n", diff);
66ba1785
 	cli_rmdirs(tempdir);
6edef583
 	free(tempdir);
 	return -1;
     }
 
     getcwd(cwd, sizeof(cwd));
 
     if(chdir(tempdir) == -1) {
5b68299b
 	mprintf("!verifydiff: Can't chdir to %s\n", tempdir);
66ba1785
 	cli_rmdirs(tempdir);
6edef583
 	free(tempdir);
 	close(fd);
 	return -1;
     }
 
5b68299b
     if(cdiff_apply(fd, mode) == -1) {
 	mprintf("!verifydiff: Can't apply %s\n", diff);
6edef583
 	chdir(cwd);
66ba1785
 	cli_rmdirs(tempdir);
6edef583
 	free(tempdir);
 	close(fd);
 	return -1;
     }
     close(fd);
 
1aa405c3
     cvd ? (cpt = cvd) : (cpt = incdir);
 
a47ed53d
     if(strstr(cpt, "main"))
6edef583
 	strcpy(info, "main.info");
     else
 	strcpy(info, "daily.info");
 
     if(!(fh = fopen(info, "r"))) {
5b68299b
 	mprintf("!verifydiff: Can't open %s\n", info);
6edef583
 	chdir(cwd);
66ba1785
 	cli_rmdirs(tempdir);
6edef583
 	free(tempdir);
 	return -1;
     }
 
     fgets(buff, sizeof(buff), fh);
 
     if(strncmp(buff, "ClamAV-VDB", 10)) {
5b68299b
 	mprintf("!verifydiff: Incorrect info file %s\n", info);
6edef583
 	chdir(cwd);
66ba1785
 	cli_rmdirs(tempdir);
6edef583
 	free(tempdir);
 	return -1;
     }
 
     while(fgets(buff, sizeof(buff), fh)) {
 	cli_chomp(buff);
 	if(!(pt = strchr(buff, ':'))) {
5b68299b
 	    mprintf("!verifydiff: Incorrect format of %s\n", info);
6edef583
 	    ret = -1;
 	    break;
 	}
 	*pt++ = 0;
 	if(!(md5 = cli_md5file(buff))) {
5b68299b
 	    mprintf("!verifydiff: Can't generate MD5 for %s\n", buff);
6edef583
 	    ret = -1;
 	    break;
 	}
 	if(strcmp(pt, md5)) {
5b68299b
 	    mprintf("!verifydiff: %s has incorrect checksum\n", buff);
6edef583
 	    ret = -1;
 	    break;
 	}
     }
 
     fclose(fh);
     chdir(cwd);
66ba1785
     cli_rmdirs(tempdir);
6edef583
     free(tempdir);
 
1aa405c3
     if(!ret) {
 	if(cvd)
 	    mprintf("Verification: %s correctly applies to %s\n", diff, cvd);
 	else
b336436b
 	    mprintf("Verification: %s correctly applies to the previous version\n", diff);
1aa405c3
     }
6edef583
 
     return ret;
 }
 
1aa405c3
 static int diffdirs(const char *old, const char *new, const char *patch)
37eca903
 {
 	FILE *diff;
 	DIR *dd;
 	struct dirent *dent;
1aa405c3
 	char cwd[512], opath[1024];
 
 
     getcwd(cwd, sizeof(cwd));
 
     if(!(diff = fopen(patch, "w"))) {
         mprintf("!diffdirs: Can't open %s for writing\n", patch);
 	return -1;
     }
 
     if(chdir(new) == -1) {
 	mprintf("!diffdirs: Can't chdir to %s\n", new);
 	fclose(diff);
 	return -1;
     }
 
     if((dd = opendir(new)) == NULL) {
         mprintf("!diffdirs: Can't open directory %s\n", new);
 	fclose(diff);
 	return -1;
     }
 
     while((dent = readdir(dd))) {
 #ifndef C_INTERIX
 	if(dent->d_ino)
 #endif
 	{
 	    if(!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
 		continue;
 
 	    snprintf(opath, sizeof(opath), "%s/%s", old, dent->d_name);
 	    if(compare(opath, dent->d_name, diff) == -1) {
 		fclose(diff);
 		unlink(patch);
 		closedir(dd);
 		return -1;
 	    }
 	}
     }
 
     closedir(dd);
 
     fclose(diff);
5b68299b
     mprintf("Generated diff file %s\n", patch);
1aa405c3
     chdir(cwd);
 
     return 0;
 }
 
 static int makediff(struct optstruct *opt)
 {
 	char *odir, *ndir, name[32], broken[32];
37eca903
 	struct cl_cvd *cvd;
 	unsigned int oldver, newver;
1aa405c3
 	int ret;
37eca903
 
 
     if(!opt->filename) {
 	mprintf("!makediff: --diff requires two arguments\n");
 	return -1;
     }
 
     if(!(cvd = cl_cvdhead(opt->filename))) {
 	mprintf("!makediff: Can't read CVD header from %s\n", opt->filename);
 	return -1;
     }
     newver = cvd->version;
     free(cvd);
 
     if(!(cvd = cl_cvdhead(opt_arg(opt, "diff")))) {
 	mprintf("!makediff: Can't read CVD header from %s\n", opt_arg(opt, "diff"));
 	return -1;
     }
     oldver = cvd->version;
     free(cvd);
 
     if(oldver + 1 != newver) {
 	mprintf("!makediff: The old CVD must be %u\n", newver - 1);
 	return -1;
     }
 
     odir = cli_gentemp(NULL);
     if(!odir) {
 	mprintf("!makediff: Can't generate temporary name for odir\n");
 	return -1;
     }
 
     if(mkdir(odir, 0700) == -1) {
 	mprintf("!makediff: Can't create directory %s\n", odir);
 	free(odir);
 	return -1;
     }
 
     if(cvd_unpack(opt_arg(opt, "diff"), odir) == -1) {
 	mprintf("!makediff: Can't unpack CVD file %s\n", opt_arg(opt, "diff"));
66ba1785
 	cli_rmdirs(odir);
37eca903
 	free(odir);
 	return -1;
     }
 
     ndir = cli_gentemp(NULL);
     if(!ndir) {
 	mprintf("!makediff: Can't generate temporary name for ndir\n");
66ba1785
 	cli_rmdirs(odir);
37eca903
 	free(odir);
 	return -1;
     }
 
     if(mkdir(ndir, 0700) == -1) {
 	mprintf("!makediff: Can't create directory %s\n", ndir);
 	free(ndir);
66ba1785
 	cli_rmdirs(odir);
37eca903
 	free(odir);
 	return -1;
     }
 
     if(cvd_unpack(opt->filename, ndir) == -1) {
 	mprintf("!makediff: Can't unpack CVD file %s\n", opt->filename);
66ba1785
 	cli_rmdirs(odir);
 	cli_rmdirs(ndir);
37eca903
 	free(odir);
 	free(ndir);
 	return -1;
     }
 
     if(strstr(opt->filename, "main"))
5b68299b
 	snprintf(name, sizeof(name), "main-%u.script", newver);
37eca903
     else
5b68299b
 	snprintf(name, sizeof(name), "daily-%u.script", newver);
37eca903
 
1aa405c3
     ret = diffdirs(odir, ndir, name);
37eca903
 
66ba1785
     cli_rmdirs(odir);
     cli_rmdirs(ndir);
37eca903
     free(odir);
     free(ndir);
6edef583
 
1aa405c3
     if(ret == -1)
 	return -1;
6edef583
 
5b68299b
     if(verifydiff(name, opt_arg(opt, "diff"), NULL) == -1) {
6edef583
 	snprintf(broken, sizeof(broken), "%s.broken", name);
 	if(rename(name, broken)) {
 	    unlink(name);
 	    mprintf("!Generated file is incorrect, removed");
 	} else {
 	    mprintf("!Generated file is incorrect, renamed to %s\n", broken);
 	}
 	return -1;
     }
 
37eca903
     return 0;
 }
 
797c5b1e
 static void help(void)
ee039e40
 {
     mprintf("\n");
4d89cb07
     mprintf("             Clam AntiVirus: Signature Tool (sigtool)  "VERSION"\n");
61409916
     mprintf("    (C) 2002 - 2007 ClamAV Team - http://www.clamav.net/team\n\n");
ee039e40
 
     mprintf("    --help                 -h              show help\n");
     mprintf("    --version              -V              print version number and exit\n");
     mprintf("    --quiet                                be quiet, output only error messages\n");
     mprintf("    --debug                                enable debug messages\n");
     mprintf("    --stdout                               write to stdout instead of stderr\n");
     mprintf("    --hex-dump                             convert data from stdin to a hex\n");
     mprintf("                                           string and print it on stdout\n");
bcf3dc79
     mprintf("    --md5 [FILES]                          generate MD5 checksum from stdin\n");
     mprintf("                                           or MD5 sigs for FILES\n");
60892bc1
     mprintf("    --mdb [FILES]                          generate .mdb sigs\n");
08d6b1e3
     mprintf("    --html-normalise=FILE                  create normalised parts of HTML file\n");
ec5e029e
     mprintf("    --utf16-decode=FILE                    decode UTF16 encoded files\n");
ee039e40
     mprintf("    --info=FILE            -i FILE         print database information\n");
c4cbb847
     mprintf("    --build=NAME [cvd/inc] -b NAME         build a CVD file\n");
ee039e40
     mprintf("    --server=ADDR                          ClamAV Signing Service address\n");
     mprintf("    --unpack=FILE          -u FILE         Unpack a CVD file\n");
1aa405c3
     mprintf("    --unpack-current=SHORTNAME             Unpack local CVD/INCDIR in cwd\n");
ee039e40
     mprintf("    --list-sigs[=FILE]     -l[FILE]        List signature names\n");
9d0b7ebd
     mprintf("    --vba=FILE                             Extract VBA/Word6 macro code\n");
     mprintf("    --vba-hex=FILE                         Extract Word6 macro code with hex values\n");
37eca903
     mprintf("    --diff=OLD NEW         -d OLD NEW      Create diff for OLD and NEW CVDs\n");
bb990ecd
     mprintf("    --run-cdiff=FILE       -r FILE         Execute update script FILE in cwd\n");
1aa405c3
     mprintf("    --verify-cdiff=DIFF CVD/INCDIR         Verify DIFF against CVD\n");
ee039e40
     mprintf("\n");
 
3d167464
     return;
 }
 
 int main(int argc, char **argv)
 {
 	int ret = 1;
         struct optstruct *opt;
f25fa788
 	struct stat sb;
37eca903
 	const char *short_options = "hvVb:i:u:l::r:d:";
3d167464
 	static struct option long_options[] = {
 	    {"help", 0, 0, 'h'},
 	    {"quiet", 0, 0, 0},
 	    {"debug", 0, 0, 0},
 	    {"verbose", 0, 0, 'v'},
 	    {"stdout", 0, 0, 0},
 	    {"version", 0, 0, 'V'},
 	    {"tempdir", 1, 0, 0},
 	    {"hex-dump", 0, 0, 0},
 	    {"md5", 0, 0, 0},
60892bc1
 	    {"mdb", 0, 0, 0},
3d167464
 	    {"html-normalise", 1, 0, 0},
ec5e029e
 	    {"utf16-decode", 1, 0, 0},
3d167464
 	    {"build", 1, 0, 'b'},
 	    {"server", 1, 0, 0},
 	    {"unpack", 1, 0, 'u'},
 	    {"unpack-current", 1, 0, 0},
 	    {"info", 1, 0, 'i'},
 	    {"list-sigs", 2, 0, 'l'},
 	    {"vba", 1, 0 ,0},
 	    {"vba-hex", 1, 0, 0},
37eca903
 	    {"diff", 1, 0, 'd'},
bb990ecd
 	    {"run-cdiff", 1, 0, 'r'},
6edef583
 	    {"verify-cdiff", 1, 0, 0},
3d167464
 	    {0, 0, 0, 0}
     	};
 
     opt = opt_parse(argc, argv, short_options, long_options, NULL);
     if(!opt) {
 	mprintf("!Can't parse the command line\n");
 	return 1;
     }
 
     if(opt_check(opt, "quiet"))
 	mprintf_quiet = 1;
 
     if(opt_check(opt, "stdout"))
 	mprintf_stdout = 1;
 
     if(opt_check(opt, "debug"))
 	cl_debug();
 
     if(opt_check(opt, "version")) {
0aa3ba06
 	print_version(NULL);
3d167464
 	opt_free(opt);
 	return 0;
     }
 
     if(opt_check(opt, "help")) {
 	opt_free(opt);
     	help();
6edef583
 	return 0;
3d167464
     }
 
     if(opt_check(opt, "hex-dump"))
 	ret = hexdump();
     else if(opt_check(opt, "md5"))
60892bc1
 	ret = md5sig(opt, 0);
     else if(opt_check(opt, "mdb"))
 	ret = md5sig(opt, 1);
3d167464
     else if(opt_check(opt, "html-normalise"))
 	ret = htmlnorm(opt);
ec5e029e
     else if(opt_check(opt, "utf16-decode"))
 	ret = utf16decode(opt);
3d167464
     else if(opt_check(opt, "build"))
 	ret = build(opt);
     else if(opt_check(opt, "unpack"))
 	ret = unpack(opt);
     else if(opt_check(opt, "unpack-current"))
 	ret = unpack(opt);
     else if(opt_check(opt, "info"))
 	ret = cvdinfo(opt);
     else if(opt_check(opt, "list-sigs"))
 	ret = listsigs(opt);
     else if(opt_check(opt, "vba") || opt_check(opt, "vba-hex"))
 	ret = vbadump(opt);
37eca903
     else if(opt_check(opt, "diff"))
 	ret = makediff(opt);
bb990ecd
     else if(opt_check(opt, "run-cdiff"))
5b68299b
 	ret = rundiff(opt);
6edef583
     else if(opt_check(opt, "verify-cdiff")) {
 	if(!opt->filename) {
1aa405c3
 	    mprintf("!--verify-cdiff requires two arguments\n");
6edef583
 	    ret = -1;
 	} else {
f25fa788
 	    if(stat(opt->filename, &sb) == -1) {
 		mprintf("--verify-cdiff: Can't get status of %s\n", opt->filename);
 		ret = -1;
 	    } else {
 		if(S_ISDIR(sb.st_mode))
5b68299b
 		    ret = verifydiff(opt_arg(opt, "verify-cdiff"), NULL, opt->filename);
f25fa788
 		else
5b68299b
 		    ret = verifydiff(opt_arg(opt, "verify-cdiff"), opt->filename, NULL);
f25fa788
 	    }
6edef583
 	}
     } else
3d167464
 	help();
 
     opt_free(opt);
     return ret ? 1 : 0;
0a2ad257
 }