clamav-devel/clamscan/manager.c
e3aaff8e
 /*
6ccc6990
  *  Copyright (C) 2002 - 2004 Tomasz Kojm <tkojm@clamav.net>
e3aaff8e
  *
  *  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.
  *
  *  Sat May 18 15:23:21 CEST 2002: included cpu autodetection from Magnus Ekdahl
  *  Wed Mar  5 03:45:31 CET 2003: included --move code from Damien Curtain
  */
 
5c07666a
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e3aaff8e
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <grp.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <signal.h>
 #include <clamav.h>
 #include <errno.h>
 
266f3967
 #ifdef HAVE_REGEX_H
 #include <regex.h>
 #endif
 
e3aaff8e
 #include "defaults.h"
 #include "others.h"
 #include "options.h"
 #include "manager.h"
 #include "treewalk.h"
 #include "shared.h"
2d70a403
 #include "str.h"
 #include "strrcpy.h"
afb48b28
 #include "memory.h"
 #include "output.h"
908db4df
 #include "cfgparser.h"
8000d078
 #include "../libclamav/others.h"
e3aaff8e
 
 #ifdef C_LINUX
 dev_t procdev;
 #endif
 
8000d078
 
e3aaff8e
 int scanmanager(const struct optstruct *opt)
 {
 	mode_t fmode;
 	int ret = 0, compression = 0, fmodeint;
 	struct cl_node *trie = NULL;
 	struct cl_limits *limits = NULL;
 	struct passwd *user = NULL;
 	struct stat sb;
b782aece
 	char *fullpath = NULL, cwd[1024];
908db4df
 	struct cfgstruct *copt, *cpt;
 	struct cl_cvd *d1, *d2;
 	const char *dbdir;
e3aaff8e
 
 /* njh@bandsman.co.uk: BeOS */
 #if !defined(C_CYGWIN) && !defined(C_BEOS)
11f30313
     if(!geteuid()) {
e3aaff8e
 	if((user = getpwnam(UNPUSER)) == NULL) {
 	    mprintf("@Can't get information about user "UNPUSER".\n");
 	    exit(60); /* this is critical problem, so we just exit here */
 	}
     }
 #endif
 
84446268
     if(optl(opt, "unzip") || optl(opt, "unrar") || optl(opt, "arj") ||
        optl(opt, "unzoo") || optl(opt, "jar") || optl(opt, "lha") ||
        optl(opt, "tar") || optl(opt, "tgz") || optl(opt, "deb"))
e3aaff8e
 	    compression = 1;
 
     /* now initialize the database */
 
     if(optc(opt, 'd')) {
 	stat(getargc(opt, 'd'), &sb);
 	switch(sb.st_mode & S_IFMT) {
 	    case S_IFREG:
 		if((ret = cl_loaddb(getargc(opt, 'd'), &trie, &claminfo.signs))) {
049a18b9
 		    mprintf("@%s\n", cl_strerror(ret));
e3aaff8e
 		    return 50;
 		}
 		break;
             case S_IFDIR:
 		if((ret = cl_loaddbdir(getargc(opt, 'd'), &trie, &claminfo.signs))) {
049a18b9
 		    mprintf("@%s\n", cl_strerror(ret));
e3aaff8e
 		    return 50;
 		}
 		break;
             default:
 		mprintf("@%s: Not supported database file type\n", getargc(opt, 'd'));
 		return 50;
 	}
 
     } else {
908db4df
 	/* try to find fresh directory */
 	dbdir = cl_retdbdir();
cc71d7c2
 	if((copt = parsecfg(CONFDIR"/clamav.conf", 0))) {
908db4df
 	    if((cpt = cfgopt(copt, "DatabaseDirectory")) || (cpt = cfgopt(copt, "DataDirectory"))) {
 		if(strcmp(cl_retdbdir(), cpt->strarg)) {
 			char *daily = (char *) mmalloc(strlen(cpt->strarg) + strlen(cl_retdbdir()) + 15);
 		    sprintf(daily, "%s/daily.cvd", cpt->strarg);
 		    if((d1 = cl_cvdhead(daily))) {
 			sprintf(daily, "%s/daily.cvd", cl_retdbdir());
 			if((d2 = cl_cvdhead(daily))) {
 			    free(daily);
 			    if(d1->version > d2->version)
 				dbdir = cpt->strarg;
 			    else
 				dbdir = cl_retdbdir();
 			    cl_cvdfree(d2);
 			} else {
 			    free(daily);
 			    dbdir = cpt->strarg;
 			}
 			cl_cvdfree(d1);
 		    } else {
 			free(daily);
 			dbdir = cl_retdbdir();
 		    }
 		}
 	    }
 	}
 
 	if((ret = cl_loaddbdir(dbdir, &trie, &claminfo.signs))) {
049a18b9
 	    mprintf("@%s\n", cl_strerror(ret));
e3aaff8e
 	    return 50;
 	}
908db4df
 
 	if(copt)
 	    freecfg(copt);
e3aaff8e
     }
 
 
     if(!trie) {
 	mprintf("@Can't initialize the virus database.\n");
 	return 50;
     }
 
8000d078
     if((ret = cl_build(trie)) != 0) {
2d70a403
 	mprintf("@Database initialization error: %s\n", cl_strerror(ret));;
 	return 50;
     }
e3aaff8e
 
     /* set (default) limits */
 
     limits = (struct cl_limits *) calloc(1, sizeof(struct cl_limits));
 
     if(optl(opt, "max-space")) {
 	char *cpy, *ptr;
 	ptr = getargl(opt, "max-space");
 	if(tolower(ptr[strlen(ptr) - 1]) == 'm') {
 	    cpy = mcalloc(strlen(ptr), sizeof(char));
658f19f8
 	    strncpy(cpy, ptr, strlen(ptr) - 1);
e3aaff8e
 	    limits->maxfilesize = atoi(cpy) * 1024 * 1024;
 	    free(cpy);
 	} else
 	    limits->maxfilesize = atoi(ptr) * 1024;
     } else
 	limits->maxfilesize = 10485760;
 
     if(optl(opt, "max-files"))
 	limits->maxfiles = atoi(getargl(opt, "max-files"));
     else
         limits->maxfiles = 500;
 
     if(optl(opt, "max-recursion"))
         limits->maxreclevel = atoi(getargl(opt, "max-recursion"));
     else
         limits->maxreclevel = 5;
 
467f8b1e
     if(optl(opt, "max-ratio"))
         limits->maxratio = atoi(getargl(opt, "max-ratio"));
     else
         limits->maxratio = 200;
e3aaff8e
 
 #ifdef C_LINUX
     if(stat("/proc", &sb) == -1)
 	procdev = 0;
     else
 	procdev = sb.st_dev;
 #endif
 
     /* check filetype */
2d70a403
     if(opt->filename == NULL || strlen(opt->filename) == 0) {
 
 	/* we need full path for some reasons (eg. archive handling) */
b782aece
 	if(!getcwd(cwd, sizeof(cwd))) {
2d70a403
 	    mprintf("@Can't get absolute pathname of current working directory.\n");
 	    ret = 57;
 	} else
 	    ret = scandirs(cwd, trie, user, opt, limits);
 
     } else if(!strcmp(opt->filename, "-")) { /* read data from stdin */
a36e6e5c
 	ret = checkstdin(trie, limits);
e3aaff8e
 
     } else {
2d70a403
 	int x;
 	char *thefilename;
 	for (x=0; (thefilename = cli_strtok(opt->filename, x, "\t")) != NULL; x++) {
 	    if((fmodeint = fileinfo(thefilename, 2)) == -1) {
 		mprintf("@Can't access file %s\n", thefilename);
 		perror(thefilename);
 		ret = 56;
 	    } else {
 		fmode = (mode_t) fmodeint;
 
 		if(compression && (thefilename[0] != '/')) {
 		    /* we need to complete the path */
b782aece
 		    if(!getcwd(cwd, sizeof(cwd))) {
2d70a403
 			mprintf("@Can't get absolute pathname of current working directory.\n");
 			return 57;
 		    } else {
 			fullpath = mcalloc(512, sizeof(char));
e3aaff8e
 #ifdef NO_SNPRINTF
2d70a403
 			sprintf(fullpath, "%s/%s", cwd, thefilename);
e3aaff8e
 #else
2d70a403
 			snprintf(fullpath, 512, "%s/%s", cwd, thefilename);
e3aaff8e
 #endif
2d70a403
 			mprintf("*Full path: %s\n", fullpath);
 		    }
 		} else
 		    fullpath = (char *) thefilename;
 
 		switch(fmode & S_IFMT) {
 		    case S_IFREG:
 			ret = scanfile(fullpath, trie, user, opt, limits);
 			break;
 
 		    case S_IFDIR:
 			ret = scandirs(fullpath, trie, user, opt, limits);
 			break;
 
 		    default:
 			mprintf("@Not supported file type (%s)\n", thefilename);
 			ret = 52;
 		}
e3aaff8e
 
2d70a403
 		if(compression && thefilename[0] != '/') {
 		    free(fullpath);
 		    fullpath = NULL;
 		}
 	    }
 	    free(thefilename);
e3aaff8e
 	}
     }
 
     /* free the trie */
8000d078
     cl_free(trie);
e3aaff8e
 
     free(limits);
 
     /* overwrite return code */
     if(claminfo.ifiles)
 	ret = 1;
     else if(ret < 50) /* hopefully no error detected */ 
 	ret = 0; /* just make sure it's 0 */
 
     return ret;
 }
 
266f3967
 int match_regex(const char *filename, const char *pattern)
 {
 #ifdef HAVE_REGEX_H
 	regex_t reg;
 	int match, flags;
 #ifndef C_CYGWIN
 	flags = 0;
 #else
 	flags = REG_ICASE; /* case insensitive on Windows */
 #endif	
 	if(regcomp(&reg, pattern, flags) != 0) {
 	    mprintf("!%s: Could not parse regular expression %s.\n", filename, pattern);
 	    logg("!%s: Could not parse regular expression %s.\n", filename, pattern);
 		return 2;
 	}
 	match = (regexec(&reg, filename, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1;
 	regfree(&reg);
 	return match;
 #else /* HAVE_REGEX_H */
 	return strstr(filename, pattern) ? 1 : 0;
 #endif
 }
 
e3aaff8e
 int scanfile(const char *filename, struct cl_node *root, const struct passwd *user, const struct optstruct *opt, const struct cl_limits *limits)
 {
 	int ret, options = 0, included;
 	struct optnode *optnode;
 	char *argument;
 #ifdef C_LINUX
 	struct stat sb;
 
     /* argh, don't scan /proc files */
     if(procdev)
 	if(stat(filename, &sb) != -1)
 	    if(sb.st_dev == procdev) {
8515ab9e
 		if(!printinfected)
 		    mprintf("%s: Excluded (/proc).\n", filename);
e3aaff8e
 		return 0;
 	    }
266f3967
 #endif    
     
e3aaff8e
     if(optl(opt, "exclude")) {
 	argument = getfirstargl(opt, "exclude", &optnode);
 	while (argument) {
266f3967
 	    if(match_regex(filename, argument) == 1) {
e3aaff8e
 		if(!printinfected)
 		    mprintf("%s: Excluded.\n", filename);
 		return 0;
 	    }
 	    argument = getnextargl(&optnode, "exclude");
 	}
     }
266f3967
      
    if(optl(opt, "include")) {
e3aaff8e
 	included = 0;
 	argument = getfirstargl(opt, "include",&optnode);
 	while (argument && !included) {
266f3967
 	    if(match_regex(filename, argument) == 1)
e3aaff8e
 		included = 1;
 	    argument = getnextargl(&optnode, "include");
 	}
 
 	if (!included) {
 	    if(!printinfected)
 		mprintf("%s: Excluded.\n", filename);
 	    return 0;
 	}
 	
     }
 
     if(fileinfo(filename, 1) == 0) {
 	if(!printinfected)
 	    mprintf("%s: Empty file.\n", filename);
 	return 0;
     }
 
af22ece1
     if(optl(opt, "disable-archive") || optl(opt, "no-archive"))
e3aaff8e
 	options &= ~CL_ARCHIVE;
     else
 	options |= CL_ARCHIVE;
 
20c3d44d
     if(optl(opt, "detect-broken"))
 	options |= CL_BROKEN;
 
5484e03c
     if(optl(opt, "block-encrypted"))
0f34221a
 	options |= CL_ENCRYPTED;
 
a9082ea2
     if(optl(opt, "no-pe"))
 	options &= ~CL_PE;
     else
 	options |= CL_PE;
 
47bbbc56
     if(optl(opt, "no-ole2"))
 	options &= ~CL_OLE2;
     else
 	options |= CL_OLE2;
 
888f5794
     if(optl(opt, "no-html"))
 	options &= ~CL_HTML;
     else
 	options |= CL_HTML;
 
a36e6e5c
     if(optl(opt, "no-mail")) {
 	options &= ~CL_MAIL;
     } else {
e3aaff8e
 	options |= CL_MAIL;
 
a36e6e5c
 	if(optl(opt, "mail-follow-urls"))
 	    options |= CL_MAILURL;
     }
 
e3aaff8e
     /* 
      * check the extension  - this is a special case, normally we don't need to
      * do this (libclamav detects archive by its magic string), but here we
      * want to know the exit code from internal unpacker and try to use
      * external (if provided) when internal cannot extract data.
      */
 
afb48b28
     if((cli_strbcasestr(filename, ".zip") || cli_strbcasestr(filename, ".rar")) && (options & CL_ARCHIVE)) {
e3aaff8e
 	/* try to use internal archivers */
bddfdc19
 	if((ret = checkfile(filename, root, limits, options)) == CL_VIRUS) {
e3aaff8e
 	    if(optl(opt, "remove")) {
 		if(unlink(filename)) {
 		    mprintf("%s: Can't remove.\n", filename);
 		    logg("%s: Can't remove.\n", filename);
 		    claminfo.notremoved++;
 		} else {
 		    mprintf("%s: Removed.\n", filename);
 		    logg("%s: Removed.\n", filename);
 		}
 	    } else if (optl(opt, "move"))
 		move_infected(filename, opt);
 
 	    return 1;
 
 	} else if(ret == CL_CLEAN)
 	    return 0;
 	/* in other case try to continue with external archivers */
 	options &= ~CL_ARCHIVE; /* and disable decompression for the below checkfile() */
6ccc6990
 	claminfo.files--; /* don't count it */
e3aaff8e
     }
 
afb48b28
     if((cli_strbcasestr(filename, ".zip") && optl(opt, "unzip"))
     || (cli_strbcasestr(filename, ".rar") && optl(opt, "unrar"))
     || (cli_strbcasestr(filename, ".arj") && optl(opt, "arj"))
     || (cli_strbcasestr(filename, ".zoo") && optl(opt, "unzoo"))
     || (cli_strbcasestr(filename, ".jar") && optl(opt, "jar"))
     || (cli_strbcasestr(filename, ".lzh") && optl(opt, "lha"))
     || (cli_strbcasestr(filename, ".tar") && optl(opt, "tar"))
     || (cli_strbcasestr(filename, ".deb") && optl(opt, "deb"))
     || ((cli_strbcasestr(filename, ".tar.gz") || cli_strbcasestr(filename, ".tgz")) 
e3aaff8e
 	 && (optl(opt, "tgz") || optl(opt, "deb"))) ) {
 
 	/* check permissions */
56bfccb2
 	switch(checkaccess(filename, UNPUSER, R_OK)) {
e3aaff8e
 	    case -1:
 		mprintf("@Can't get information about user "UNPUSER".\n");
56bfccb2
 		exit(60); /* this is a critical problem so we just exit here */
e3aaff8e
 	    case -2:
56bfccb2
 		mprintf("@Can't fork.\n");
 		exit(61);
e3aaff8e
 	    case 0: /* read access denied */
11f30313
 		if(geteuid()) {
e3aaff8e
 		    if(!printinfected)
 			mprintf("%s: Access denied to archive.\n", filename);
 		} else {
 
 		    if(limits && limits->maxfilesize)
 			if(fileinfo(filename, 1) / 1024 > limits->maxfilesize) {
 			    if(!printinfected)
 				mprintf("%s: Archive too big.\n", filename);
 			    return 0;
 			}
 
 		    return(scandenied(filename, root, user, opt, limits));
 		}
 		return 0;
 	    case 1:
 		return(scancompressed(filename, root, user, opt, limits));
 	}
     }
 
11f30313
     if(geteuid())
56bfccb2
 	if(checkaccess(filename, NULL, R_OK) != 1) {
 	    if(!printinfected)
 		mprintf("%s: Access denied.\n", filename);
 	    return 0;
e3aaff8e
 	}
 
     if((ret = checkfile(filename, root, limits, options)) == CL_VIRUS) {
 	if(optl(opt, "remove")) {
 	    if(unlink(filename)) {
 		mprintf("%s: Can't remove.\n", filename);
 		logg("%s: Can't remove.\n", filename);
 		claminfo.notremoved++;
 	    } else {
 		mprintf("%s: Removed.\n", filename);
 		logg("%s: Removed.\n", filename);
 	    }
 	} else if (optl(opt, "move"))
             move_infected(filename, opt);
     }
     return ret;
 }
 
 /* it has guaranteed read access to the archive */
 int scancompressed(const char *filename, struct cl_node *root, const struct passwd *user, const struct optstruct *opt, const struct cl_limits *limits)
 {
 	int ret = 0;
 	char *tmpdir, *gendir, *userprg;
 	struct stat statbuf;
 
     stat(filename, &statbuf);
 
     if(!S_ISREG(statbuf.st_mode)) {
 	mprintf("^Suspected archive %s is not a regular file.\n", filename);
 	return 0; /* hmm ? */
     }
 
     /* check write access */
 
590135f9
     tmpdir = getenv("TMPDIR");
e3aaff8e
 
     if(tmpdir == NULL)
 #ifdef P_tmpdir
 	tmpdir = P_tmpdir;
 #else
 	tmpdir = "/tmp";
 #endif
 
56bfccb2
     if(checkaccess(tmpdir, UNPUSER, W_OK) != 1) {
e3aaff8e
 	mprintf("@Can't write to the temporary directory.\n");
 	exit(64);
     }
 
     /* generate the temporary directory */
 
8000d078
     gendir = cli_gentemp(tmpdir);
e3aaff8e
     if(mkdir(gendir, 0700)) {
 	mprintf("@Can't create the temporary directory %s\n", gendir);
 	exit(63); /* critical */
     }
 
     if(user)
 	chown(gendir, user->pw_uid, user->pw_gid);
 
 
     /* unpack file  - as unprivileged user */
afb48b28
     if(cli_strbcasestr(filename, ".zip")) {
e3aaff8e
 	char *args[] = { "unzip", "-P", "clam", "-o", (char *) filename, NULL };
 
 	if((userprg = getargl(opt, "unzip")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("unzip", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".rar")) { 
e3aaff8e
 	char *args[] = { "unrar", "x", "-p-", "-y", (char *) filename, NULL };
 	if((userprg = getargl(opt, "unrar")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("unrar", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".arj")) { 
e3aaff8e
         char *args[] = { "arj", "x","-y", (char *) filename, NULL };
         if((userprg = getargl(opt, "arj")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("arj", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".zoo")) { 
e3aaff8e
 	char *args[] = { "unzoo", "-x","-j","./", (char *) filename, NULL };
 	if((userprg = getargl(opt, "unzoo")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("unzoo", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".jar")) { 
e3aaff8e
 	char *args[] = { "unzip", "-P", "clam", "-o", (char *) filename, NULL };
 	if((userprg = getargl(opt, "jar")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("unzip", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".lzh")) { 
e3aaff8e
 	char *args[] = { "lha", "xf", (char *) filename, NULL };
 	if((userprg = getargl(opt, "lha")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("lha", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".tar")) { 
e3aaff8e
 	char *args[] = { "tar", "-xpvf", (char *) filename, NULL };
 	if((userprg = getargl(opt, "tar")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("tar", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".deb")) { 
e3aaff8e
 	char *args[] = { "ar", "x", (char *) filename, NULL };
 	if((userprg = getargl(opt, "deb")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("ar", args, gendir, user, opt);
 
afb48b28
     } else if((cli_strbcasestr(filename, ".tar.gz") || cli_strbcasestr(filename, ".tgz"))) {
e3aaff8e
 	char *args[] = { "tar", "-zxpvf", (char *) filename, NULL };
 	if((userprg = getargl(opt, "tgz")))
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("tar", args, gendir, user, opt);
     }
 
     /* fix permissions of extracted files */
     fixperms(gendir);
 
     if(!ret) /* execute successful */
 	ret = treewalk(gendir, root, user, opt, limits);
 
     /* remove the directory  - as clamav */
     clamav_rmdirs(gendir);
 
     /* free gendir - it's not necessary now */
     free(gendir);
 
     switch(ret) {
 	case -1:
 	    mprintf("@Can't fork().\n");
 	    exit(61); /* this is critical problem, so we just exit here */
 	case -2:
 	    mprintf("@Can't execute some unpacker. Check paths and permissions on the temporary directory.\n");
 	    /* This is no longer a critical error (since 0.24). We scan
 	     * raw archive.
 	     */
 	    if(!printinfected)
 		mprintf("(raw) ");
 
 	    if((ret = checkfile(filename, root, limits, 0)) == CL_VIRUS) {
 		if(optl(opt, "remove")) {
 		    if(unlink(filename)) {
 			mprintf("%s: Can't remove.\n", filename);
 			logg("%s: Can't remove.\n", filename);
 			claminfo.notremoved++;
 		    } else {
 			mprintf("%s: Removed.\n", filename);
 			logg("%s: Removed.\n", filename);
 		    }
 		} else if (optl(opt, "move"))
 		    move_infected(filename, opt);
 	    }
 	    return ret;
 	case -3:
 	    return 0;
 	case 0:
 	    /* no viruses found in archive, we scan just in case the same
 	     * archive
 	     */
 	    if(!printinfected)
 		mprintf("(raw) ");
 
 	    if((ret = checkfile(filename, root, limits, 0)) == CL_VIRUS) {
 		if(optl(opt, "remove")) {
 		    if(unlink(filename)) {
 			mprintf("%s: Can't remove.\n", filename);
 			logg("%s: Can't remove.\n", filename);
 			claminfo.notremoved++;
 		    } else {
 			mprintf("%s: Removed.\n", filename);
 			logg("%s: Removed.\n", filename);
 		    }
 		} else if (optl(opt, "move"))
 		    move_infected(filename, opt);
 	    }
 	    return ret;
 	case 1:
 	    logg("%s: Infected Archive FOUND\n", filename);
 	    mprintf("%s: Infected Archive FOUND\n", filename);
af22ece1
 
 	    if(bell)
ed026d36
 		fprintf(stderr, "\007");
af22ece1
 
e3aaff8e
 	    if(optl(opt, "remove")) {
 		if(unlink(filename)) {
 		    mprintf("%s: Can't remove.\n", filename);
 		    logg("%s: Can't remove.\n", filename);
 		    claminfo.notremoved++;
 		} else {
 		    mprintf("%s: Removed.\n", filename);
 		    logg("%s: Removed.\n", filename);
 		}
 	    } else if (optl(opt, "move"))
 		move_infected(filename, opt);
 
 	    return 1;
 	default:
 	    mprintf("@Strange value (%d) returned in scancompressed()\n", ret);
 	    return 0;
     }
 }
 
 int scandenied(const char *filename, struct cl_node *root, const struct passwd *user, const struct optstruct *opt, const struct cl_limits *limits)
 {
 	char *tmpdir, *gendir, *tmpfile, *pt;
 	struct stat statbuf;
 	int ret;
 
     stat(filename, &statbuf);
     if(!S_ISREG(statbuf.st_mode)) {
 	mprintf("^Suspected archive %s is not a regular file.\n", filename);
 	return 0;
     }
 
     /* check write access */
 
590135f9
     tmpdir = getenv("TMPDIR");
e3aaff8e
 
     if(tmpdir == NULL)
 #ifdef P_tmpdir
 	tmpdir = P_tmpdir;
 #else
 	tmpdir = "/tmp";
 #endif
 
 
56bfccb2
     if(checkaccess(tmpdir, UNPUSER, W_OK) != 1) {
e3aaff8e
 	mprintf("@Can't write to the temporary directory %s.\n", tmpdir);
 	exit(64);
     }
 
     /* generate the temporary directory */
8000d078
     gendir = cli_gentemp(tmpdir);
e3aaff8e
     if(mkdir(gendir, 0700)) {
 	mprintf("@Can't create the temporary directory %s\n", gendir);
 	exit(63); /* critical */
     }
 
     tmpfile = (char *) mcalloc(strlen(gendir) + strlen(filename) + 10, sizeof(char));
     pt = strrchr(filename, '/');
     if(!pt)
 	pt = (char *) filename;
     else
 	pt += 1;
 
     sprintf(tmpfile, "%s/%s", gendir, pt);
 
     if(filecopy(filename, tmpfile) == -1) {
 	mprintf("!I/O error.\n");
 	perror("copyfile()");
 	exit(58);
     }
 
     fixperms(gendir);
 
     if(user) {
 	chown(gendir, user->pw_uid, user->pw_gid);
 	chown(tmpfile, user->pw_uid, user->pw_gid);
     }
 
     if((ret = treewalk(gendir, root, user, opt, limits)) == 1) {
 	logg("(Real infected archive: %s)\n", filename);
 	mprintf("(Real infected archive: %s)\n", filename);
 
 	if(optl(opt, "remove")) {
 	    if(unlink(filename)) {
 		mprintf("%s: Can't remove.\n", filename);
 		logg("%s: Can't remove.\n", filename);
 		claminfo.notremoved++;
 	    } else {
 	        mprintf("%s: Removed.\n", filename);
 	        logg("%s: Removed.\n", filename);
 	    }
 	} else if (optl(opt, "move"))
 	    move_infected(filename, opt);
     }
 
     /* remove the directory  - as clamav */
     clamav_rmdirs(gendir);
 
     free(gendir);
     free(tmpfile);
 
     return ret;
 }
 
 int scandirs(const char *dirname, struct cl_node *root, const struct passwd *user, const struct optstruct *opt, const struct cl_limits *limits)
 {
 	return treewalk(dirname, root, user, opt, limits);
 }
 
 int checkfile(const char *filename, const struct cl_node *root, const struct cl_limits *limits, int options)
 {
 	int fd, ret;
fb787a06
 	const char *virname;
e3aaff8e
 
a9082ea2
 
     mprintf("*Scanning %s\n", filename);
 
e3aaff8e
     if((fd = open(filename, O_RDONLY)) == -1) {
 	mprintf("@Can't open file %s\n", filename);
 	return 54;
     }
 
     claminfo.files++;
 
     if((ret = cl_scandesc(fd, &virname, &claminfo.blocks, root, limits, options)) == CL_VIRUS) {
 	mprintf("%s: %s FOUND\n", filename, virname);
 	logg("%s: %s FOUND\n", filename, virname);
 	claminfo.ifiles++;
af22ece1
 
 	if(bell)
ed026d36
 	    fprintf(stderr, "\007");
af22ece1
 
e3aaff8e
     } else if(ret == CL_CLEAN) {
 	if(!printinfected)
 	    mprintf("%s: OK\n", filename);
     } else
 	if(!printinfected)
049a18b9
 	    mprintf("%s: %s\n", filename, cl_strerror(ret));
e3aaff8e
 
     close(fd);
     return ret;
 }
 
 int checkstdin(const struct cl_node *root, const struct cl_limits *limits)
 {
 	int ret;
fb787a06
 	const char *virname;
e3aaff8e
 
 
     claminfo.files++;
 
     if((ret = cl_scandesc(0, &virname, &claminfo.blocks, root, limits, CL_RAW)) == CL_VIRUS) {
 	mprintf("stdin: %s FOUND\n", virname);
 	claminfo.ifiles++;
af22ece1
 
 	if(bell)
ed026d36
 	    fprintf(stderr, "\007");
af22ece1
 
e3aaff8e
     } else if(ret == CL_CLEAN) {
 	if(!printinfected)
 	    mprintf("stdin: OK\n");
     } else
 	if(!printinfected)
049a18b9
 	    mprintf("stdin: %s\n", cl_strerror(ret));
e3aaff8e
 
     return ret;
 }
 
 /*
  * -1 -> can't fork
  * -2 -> can't execute
  * -3 -> external signal
  * 0 -> OK
  */
 
 int clamav_unpack(const char *prog, char **args, const char *tmpdir, const struct passwd *user, const struct optstruct *opt)
 {
 	pid_t pid;
 	int status, wret, maxfiles, maxspace, fdevnull;
 	struct s_du n;
 
 
     if(optl(opt, "max-files"))
 	maxfiles = atoi(getargl(opt, "max-files"));
     else
 	maxfiles = 0;
 
     if(optl(opt, "max-space")) {
 	    char *cpy, *ptr;
 	ptr = getargl(opt, "max-space");
 	if(tolower(ptr[strlen(ptr) - 1]) == 'm') { /* megabytes */
 	    cpy = mcalloc(strlen(ptr), sizeof(char));
658f19f8
 	    strncpy(cpy, ptr, strlen(ptr) - 1);
e3aaff8e
 	    maxspace = atoi(cpy) * 1024;
 	    free(cpy);
 	} else /* default - kilobytes */
 	    maxspace = atoi(ptr);
     } else
 	maxspace = 0;
 
 
     switch(pid = fork()) {
 	case -1:
 	    return -1;
 	case 0:
 #ifndef C_CYGWIN
11f30313
 	    if(!geteuid() && user) {
a7d9bef2
 
 #ifdef HAVE_SETGROUPS
9f51cb51
 		if(setgroups(1, &user->pw_gid)) {
 		    fprintf(stderr, "ERROR: setgroups() failed.\n");
 		    exit(1);
 		}
a7d9bef2
 #endif
9f51cb51
 
 		if(setgid(user->pw_gid)) {
 		    fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int) user->pw_gid);
 		    exit(1);
 		}
 
 		if(setuid(user->pw_uid)) {
 		    fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int) user->pw_uid);
 		    exit(1);
 		}
e3aaff8e
 	    }
 #endif
 	    chdir(tmpdir);
 
 	    if(printinfected) {
   	        fdevnull = open("/dev/null", O_WRONLY);
 		if(fdevnull == -1) {
 		    mprintf("Non fatal error: cannot open /dev/null. Continuing with full output\n");
 		    printinfected = 0;
 		} else {
 		    dup2(fdevnull,1);
 		    dup2(fdevnull,2);
 		}
 	    }
 
 	    if(strchr(prog, '/')) /* we have full path */
 		execv(prog, args);
 	    else
 		execvp(prog, args);
 	    perror("execv(p)");
 	    abort();
 	    break;
 	default:
 
 	    if(maxfiles || maxspace) {
 		while(!(wret = waitpid(pid, &status, WNOHANG))) {
 		    memset(&n, 0, sizeof(struct s_du));
 
 		    if(!du(tmpdir, &n))
 			if((maxfiles && n.files > maxfiles) || (maxspace && n.space > maxspace)) {
 			    mprintf("*n.files: %d, n.space: %d\n", n.files, n.space);
 			    kill(pid, 9); /* stop it immediately */
 			}
 		}
 	    } else
 		waitpid(pid, &status, 0);
 
 
 	    if(WIFSIGNALED(status)) {
 		switch(WTERMSIG(status)) {
 
 		    case 9:
 			mprintf("\nUnpacker process %d stopped due to exceeded limits.\n", pid);
 			return 0;
 		    case 6: /* abort */
 			mprintf("@Can't run %s\n", prog);
 			return -2;
 		    default:
 			mprintf("@\nUnpacker stopped with external signal %d\n", WTERMSIG(status));
 			return -3;
 		}
 	    } else if(WIFEXITED(status))
 		return 0;
     }
 
     return 0;
 }
 
 void move_infected(const char *filename, const struct optstruct *opt)
 {
266f3967
     char *movedir, *movefilename, *tmp, numext[4 + 1];
e3aaff8e
     struct stat fstat, mfstat;
266f3967
     int n, len, movefilename_size;
e3aaff8e
 
266f3967
 
     if(!(movedir = getargl(opt, "move"))) {
e3aaff8e
         /* Should never reach here */
         mprintf("@error moving file '%s'.\n", filename);
         mprintf("clamscan: getargc() returned NULL.\n");
         logg("clamscan: getargc() returned NULL.\n");
         claminfo.notmoved++;
         return;
     }
 
266f3967
     if(access(movedir, W_OK|X_OK) == -1) {
e3aaff8e
         mprintf("@error moving file '%s'.\n", filename);
         mprintf("clamscan: cannot write to '%s': %s.\n", movedir, strerror(errno));
         logg("clamscan: cannot write to '%s': %s.\n", movedir, strerror(errno));
         claminfo.notmoved++;
         return;
     }
266f3967
 
     if(!(tmp = strrchr(filename, '/'))) {
e3aaff8e
         mprintf("@error moving file '%s'.\n", filename);
         mprintf("clamscan: '%s' does not appear to be a valid filename.\n", filename);
         logg("clamscan: '%s' does not appear to be a valid filename.\n", filename);
         claminfo.notmoved++;
         return;
     }
266f3967
 
     movefilename_size = sizeof(char) * (strlen(movedir) + strlen(tmp) + sizeof(numext) + 1);
 
     if(!(movefilename = malloc(movefilename_size))) {
e3aaff8e
         mprintf("@error moving file '%s'.\n", filename);
         mprintf("clamscan: malloc() returned NULL.\n");
         logg("clamscan: malloc() returned NULL.\n");
         claminfo.notmoved++;
         return;
     }
266f3967
 
     if(!(strrcpy(movefilename, movedir))) {
e3aaff8e
         mprintf("@error moving file '%s'.\n", filename);
         mprintf("clamscan: strrcpy() returned NULL.\n");
         logg("clamscan: strrcpy() returned NULL.\n");
         claminfo.notmoved++;
         free(movefilename);
         return;
     }
266f3967
 
     if(!(strcat(movefilename, tmp))) {
e3aaff8e
         mprintf("@error moving file '%s'.\n", filename);
         mprintf("clamscan: strcat() returned NULL.\n");
         logg("clamscan: strcat() returned NULL.\n");
         claminfo.notmoved++;
         free(movefilename);
         return;
     }
266f3967
 
e3aaff8e
     stat(filename, &fstat);
 
266f3967
     if(!stat(movefilename, &mfstat)) {
e3aaff8e
         if(fstat.st_ino == mfstat.st_ino) { /* It's the same file*/
             mprintf("clamscan: file excluded '%s'.\n", filename);
             logg("clamscan: file excluded '%s'.\n", filename);
             claminfo.notmoved++;
             free(movefilename);
             return;
266f3967
         } else {
             /* file exists - try to append an ordinal number to the
 	     * quranatined file in an attempt not to overwrite existing
 	     * files in quarantine  
 	     */
             len = strlen(movefilename);
             n = 0;        		        		
             do {
                 /* reset the movefilename to it's initial value by
 		 * truncating to the original filename length
 		 */
                 movefilename[len] = 0;
                 /* append .XXX */
                 sprintf(numext, ".%03d", n++);
                 strcat(movefilename, numext);            	
             } while(!stat(movefilename, &mfstat) && (n < 1000));
        }
e3aaff8e
     }
266f3967
 
     if(filecopy(filename, movefilename) == -1) {
e3aaff8e
         mprintf("@error moving file '%s'.\n", filename);
         mprintf("clamscan: cannot move '%s' to '%s': %s.\n", filename, movefilename, strerror(errno));
         logg("clamscan: cannot move '%s' to '%s': %s.\n", filename, movefilename, strerror(errno));
         claminfo.notmoved++;
         free(movefilename);
         return;
     }
 
     chmod(movefilename, fstat.st_mode);
     chown(movefilename, fstat.st_uid, fstat.st_gid);
266f3967
 
     if(unlink(filename)) {
e3aaff8e
         mprintf("@error moving file '%s'.\n", filename);
         mprintf("clamscan: cannot unlink '%s': %s.\n", filename, strerror(errno));
         logg("clamscan: cannot unlink '%s': %s.\n", filename, strerror(errno));
         claminfo.notremoved++;            
266f3967
     } else {
e3aaff8e
         mprintf("%s: moved to '%s'.\n", filename, movefilename);
         logg("%s: moved to '%s'.\n", filename, movefilename);
     }
 
     free(movefilename);
 }