clamscan/manager.c
e3aaff8e
 /*
7a2997f1
  *  Copyright (C) 2002 - 2007 Tomasz Kojm <tkojm@clamav.net>
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
  *
  *  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>
34f71e0e
 #ifdef C_WINDOWS
 #include <sys/utime.h>
 #else
e3aaff8e
 #include <sys/wait.h>
4790a32f
 #include <utime.h>
34f71e0e
 #endif
 #ifdef HAVE_GRP_H
e3aaff8e
 #include <grp.h>
34f71e0e
 #endif
 #ifdef HAVE_PWD_H
 #include <pwd.h>
 #endif
e3aaff8e
 #include <fcntl.h>
34f71e0e
 #ifdef	HAVE_UNISTD_H
e3aaff8e
 #include <unistd.h>
34f71e0e
 #endif
e3aaff8e
 #include <sys/types.h>
 #include <signal.h>
 #include <errno.h>
 
 #include "manager.h"
7a2997f1
 #include "others.h"
e3aaff8e
 #include "treewalk.h"
7a2997f1
 #include "global.h"
 
 #include "shared/options.h"
 #include "shared/output.h"
 #include "shared/misc.h"
 
 #include "libclamav/clamav.h"
 #include "libclamav/others.h"
 #include "libclamav/matcher-ac.h"
 #include "libclamav/str.h"
e3aaff8e
 
 #ifdef C_LINUX
 dev_t procdev;
 #endif
 
34f71e0e
 #ifdef C_WINDOWS
 #undef P_tmpdir
 #define P_tmpdir    "C:\\WINDOWS\\TEMP"
 #endif
 
 #ifndef	O_BINARY
 #define	O_BINARY    0
 #endif
 
7a2997f1
 static int scandirs(const char *dirname, struct cl_engine *engine, const struct passwd *user, const struct optstruct *opt, const struct cl_limits *limits, int options)
 {
     return treewalk(dirname, engine, user, opt, limits, options, 1);
 }
 
 static int scanstdin(const struct cl_engine *engine, const struct cl_limits *limits, int options)
 {
 	int ret;
 	const char *virname, *tmpdir;
 	char *file, buff[FILEBUFF];
 	FILE *fs;
 
 
     /* check write access */
     tmpdir = getenv("TMPDIR");
 
     if(tmpdir == NULL)
 #ifdef P_tmpdir
 	tmpdir = P_tmpdir;
 #else
 	tmpdir = "/tmp";
 #endif
 
     if(checkaccess(tmpdir, CLAMAVUSER, W_OK) != 1) {
 	logg("!Can't write to temporary directory\n");
 	return 64;
     }
 
     file = cli_gentemp(tmpdir);
 
     if(!(fs = fopen(file, "wb"))) {
 	logg("!Can't open %s for writing\n", file);
 	return 63;
     }
 
     while((ret = fread(buff, 1, FILEBUFF, stdin)))
 	fwrite(buff, 1, ret, fs);
 
     fclose(fs);
 
     logg("*Checking %s\n", file);
     info.files++;
 
     if((ret = cl_scanfile(file, &virname, &info.blocks, engine, limits, options)) == CL_VIRUS) {
 	logg("stdin: %s FOUND\n", virname);
 	info.ifiles++;
 
 	if(bell)
 	    fprintf(stderr, "\007");
 
     } else if(ret == CL_CLEAN) {
 	if(!printinfected)
 	    mprintf("stdin: OK\n");
     } else
 	if(!printinfected)
 	    logg("stdin: %s\n", cl_strerror(ret));
 
     unlink(file);
     free(file);
     return ret;
 }
8000d078
 
e3aaff8e
 int scanmanager(const struct optstruct *opt)
 {
 	mode_t fmode;
e91adae9
 	int ret = 0, extunpacker = 0, fmodeint, i, x;
7a2997f1
 	unsigned int options = 0, dboptions = 0;
 	struct cl_engine *engine = NULL;
 	struct cl_limits limits;
e3aaff8e
 	struct passwd *user = NULL;
 	struct stat sb;
b782aece
 	char *fullpath = NULL, cwd[1024];
7b304dee
 
e3aaff8e
 
e91adae9
     if(opt_check(opt, "unzip") || opt_check(opt, "unrar") || opt_check(opt, "arj") ||
        opt_check(opt, "unzoo") || opt_check(opt, "jar") || opt_check(opt, "lha") ||
        opt_check(opt, "tar") || opt_check(opt, "tgz") || opt_check(opt, "deb"))
 	    extunpacker = 1;
 
e3aaff8e
 /* njh@bandsman.co.uk: BeOS */
34f71e0e
 #if !defined(C_CYGWIN) && !defined(C_OS2) && !defined(C_BEOS) && !defined(C_WINDOWS)
e91adae9
     if(extunpacker && !geteuid()) {
7a2997f1
 	if((user = getpwnam(CLAMAVUSER)) == NULL) {
e91adae9
 	    logg("!Can't get information about user "CLAMAVUSER" (required to run external unpackers)\n");
e3aaff8e
 	    exit(60); /* this is critical problem, so we just exit here */
 	}
     }
 #endif
 
af7d0dde
     if(!opt_check(opt, "no-phishing-sigs"))
9f8098c0
 	dboptions |= CL_DB_PHISHING;
e3aaff8e
 
9f8098c0
     if(!opt_check(opt,"no-phishing-scan-urls"))
 	dboptions |= CL_DB_PHISHING_URLS;
19b3e182
     if(!opt_check(opt,"no-phishing-restrictedscan")) {
 	/* not scanning all domains, check only URLs with domains from .pdb */
6e965052
 	options |= CL_SCAN_PHISHING_DOMAINLIST;
19b3e182
     }
     if(opt_check(opt,"phishing-ssl")) {
6e965052
 	options |= CL_SCAN_PHISHING_BLOCKSSL;
19b3e182
     }
     if(opt_check(opt,"phishing-cloak")) {
6e965052
 	options |= CL_SCAN_PHISHING_BLOCKCLOAK;
19b3e182
     }
a68507c5
 
a4453bce
     if(opt_check(opt, "dev-ac-only"))
83fa5305
 	dboptions |= CL_DB_ACONLY;
 
70edb085
     if(opt_check(opt, "detect-pua"))
 	dboptions |= CL_DB_PUA;
 
7b8edc5c
     if(opt_check(opt, "database")) {
7a2997f1
 	if((ret = cl_load(opt_arg(opt, "database"), &engine, &info.sigs, dboptions))) {
9f0e5585
 	    logg("!%s\n", cl_strerror(ret));
d6449522
 	    return 50;
e3aaff8e
 	}
 
     } else {
98ce643b
 	    char *dbdir = freshdbdir();
908db4df
 
7a2997f1
 	if((ret = cl_load(dbdir, &engine, &info.sigs, dboptions))) {
9f0e5585
 	    logg("!%s\n", cl_strerror(ret));
98ce643b
 	    free(dbdir);
e3aaff8e
 	    return 50;
 	}
98ce643b
 	free(dbdir);
e3aaff8e
     }
 
7a2997f1
     if(!engine) {
9f0e5585
 	logg("!Can't initialize the virus database\n");
e3aaff8e
 	return 50;
     }
 
7a2997f1
     if((ret = cl_build(engine)) != 0) {
9f0e5585
 	logg("!Database initialization error: %s\n", cl_strerror(ret));;
2d70a403
 	return 50;
     }
e3aaff8e
 
7a2997f1
     /* set limits */
     memset(&limits, 0, sizeof(struct cl_limits));
e3aaff8e
 
7b8edc5c
     if(opt_check(opt, "max-space")) {
e3aaff8e
 	char *cpy, *ptr;
7b8edc5c
 	ptr = opt_arg(opt, "max-space");
e3aaff8e
 	if(tolower(ptr[strlen(ptr) - 1]) == 'm') {
65d08d61
 	    cpy = calloc(strlen(ptr), 1);
658f19f8
 	    strncpy(cpy, ptr, strlen(ptr) - 1);
7a2997f1
 	    limits.maxfilesize = atoi(cpy) * 1024 * 1024;
e3aaff8e
 	    free(cpy);
 	} else
7a2997f1
 	    limits.maxfilesize = atoi(ptr) * 1024;
e3aaff8e
     } else
7a2997f1
 	limits.maxfilesize = 10485760;
e3aaff8e
 
7b8edc5c
     if(opt_check(opt, "max-files"))
7a2997f1
 	limits.maxfiles = atoi(opt_arg(opt, "max-files"));
e3aaff8e
     else
7a2997f1
         limits.maxfiles = 500;
e3aaff8e
 
7b8edc5c
     if(opt_check(opt, "max-recursion"))
7a2997f1
         limits.maxreclevel = atoi(opt_arg(opt, "max-recursion"));
e3aaff8e
     else
7a2997f1
         limits.maxreclevel = 8;
e3aaff8e
 
dab42957
     if(opt_check(opt, "max-mail-recursion"))
7a2997f1
         limits.maxmailrec = atoi(opt_arg(opt, "max-mail-recursion"));
dab42957
     else
7a2997f1
         limits.maxmailrec = 64;
dab42957
 
7b8edc5c
     if(opt_check(opt, "max-ratio"))
7a2997f1
         limits.maxratio = atoi(opt_arg(opt, "max-ratio"));
467f8b1e
     else
7a2997f1
         limits.maxratio = 250;
e3aaff8e
 
6ef42bc3
     /* set options */
 
7b8edc5c
     if(opt_check(opt, "disable-archive") || opt_check(opt, "no-archive"))
3805ebcb
 	options &= ~CL_SCAN_ARCHIVE;
6ef42bc3
     else
3805ebcb
 	options |= CL_SCAN_ARCHIVE;
6ef42bc3
 
7b8edc5c
     if(opt_check(opt, "detect-broken"))
453581ae
 	options |= CL_SCAN_BLOCKBROKEN;
6ef42bc3
 
7b8edc5c
     if(opt_check(opt, "block-encrypted"))
08d6b1e3
 	options |= CL_SCAN_BLOCKENCRYPTED;
6ef42bc3
 
7b8edc5c
     if(opt_check(opt, "block-max"))
3805ebcb
 	options |= CL_SCAN_BLOCKMAX;
d272908a
 
7b8edc5c
     if(opt_check(opt, "no-pe"))
3805ebcb
 	options &= ~CL_SCAN_PE;
6ef42bc3
     else
3805ebcb
 	options |= CL_SCAN_PE;
6ef42bc3
 
3f97a1e7
     if(opt_check(opt, "no-elf"))
 	options &= ~CL_SCAN_ELF;
     else
 	options |= CL_SCAN_ELF;
 
7b8edc5c
     if(opt_check(opt, "no-ole2"))
3805ebcb
 	options &= ~CL_SCAN_OLE2;
6ef42bc3
     else
3805ebcb
 	options |= CL_SCAN_OLE2;
6ef42bc3
 
c5107e70
     if(opt_check(opt, "no-pdf"))
 	options &= ~CL_SCAN_PDF;
     else
 	options |= CL_SCAN_PDF;
 
7b8edc5c
     if(opt_check(opt, "no-html"))
3805ebcb
 	options &= ~CL_SCAN_HTML;
6ef42bc3
     else
3805ebcb
 	options |= CL_SCAN_HTML;
6ef42bc3
 
7b8edc5c
     if(opt_check(opt, "no-mail")) {
3805ebcb
 	options &= ~CL_SCAN_MAIL;
6ef42bc3
     } else {
3805ebcb
 	options |= CL_SCAN_MAIL;
6ef42bc3
 
7b8edc5c
 	if(opt_check(opt, "mail-follow-urls"))
3805ebcb
 	    options |= CL_SCAN_MAILURL;
6ef42bc3
     }
 
7b8edc5c
     if(opt_check(opt, "no-algorithmic"))
6fd2fb47
 	options &= ~CL_SCAN_ALGORITHMIC;
1b661cef
     else
6fd2fb47
 	options |= CL_SCAN_ALGORITHMIC;
1b661cef
 
e3aaff8e
 #ifdef C_LINUX
d9b55a82
     procdev = (dev_t) 0;
53c7b870
     if(stat("/proc", &sb) != -1 && !sb.st_size)
e3aaff8e
 	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))) {
9f0e5585
 	    logg("!Can't get absolute pathname of current working directory\n");
2d70a403
 	    ret = 57;
 	} else
7a2997f1
 	    ret = scandirs(cwd, engine, user, opt, &limits, options);
2d70a403
 
     } else if(!strcmp(opt->filename, "-")) { /* read data from stdin */
7a2997f1
 	ret = scanstdin(engine, &limits, options);
e3aaff8e
 
     } else {
2d70a403
 	char *thefilename;
493b5445
 	for (x = 0; (thefilename = cli_strtok(opt->filename, x, "\t")) != NULL; x++) {
2d70a403
 	    if((fmodeint = fileinfo(thefilename, 2)) == -1) {
0ae41a2d
 		logg("^Can't access file %s\n", thefilename);
2d70a403
 		perror(thefilename);
 		ret = 56;
 	    } else {
a8b056dc
 		int slash = 1;
 		for(i = strlen(thefilename) - 1; i > 0 && slash; i--) {
 		    if(thefilename[i] == '/')
 			thefilename[i] = 0;
 		    else
 			slash = 0;
 		}
 
2d70a403
 		fmode = (mode_t) fmodeint;
 
e91adae9
 		if(extunpacker && (thefilename[0] != '/' && thefilename[0] != '\\' && thefilename[1] != ':')) {
2d70a403
 		    /* we need to complete the path */
b782aece
 		    if(!getcwd(cwd, sizeof(cwd))) {
9f0e5585
 			logg("!Can't get absolute pathname of current working directory\n");
2d70a403
 			return 57;
 		    } else {
8ca8a18e
 			fullpath = malloc(512);
e3aaff8e
 #ifdef NO_SNPRINTF
2d70a403
 			sprintf(fullpath, "%s/%s", cwd, thefilename);
e3aaff8e
 #else
2d70a403
 			snprintf(fullpath, 512, "%s/%s", cwd, thefilename);
e3aaff8e
 #endif
0ae41a2d
 			logg("*Full path: %s\n", fullpath);
2d70a403
 		    }
 		} else
a8b056dc
 		    fullpath = thefilename;
2d70a403
 
 		switch(fmode & S_IFMT) {
 		    case S_IFREG:
7a2997f1
 			ret = scanfile(fullpath, engine, user, opt, &limits, options);
2d70a403
 			break;
 
 		    case S_IFDIR:
7a2997f1
 			ret = scandirs(fullpath, engine, user, opt, &limits, options);
2d70a403
 			break;
 
 		    default:
9f0e5585
 			logg("!Not supported file type (%s)\n", thefilename);
2d70a403
 			ret = 52;
 		}
e3aaff8e
 
e91adae9
 		if(extunpacker && (thefilename[0] != '/' && thefilename[0] != '\\' && thefilename[1] != ':')) {
2d70a403
 		    free(fullpath);
 		    fullpath = NULL;
 		}
 	    }
 	    free(thefilename);
e3aaff8e
 	}
     }
 
7a2997f1
     /* free the engine */
     cl_free(engine);
e3aaff8e
 
     /* overwrite return code */
7a2997f1
     if(info.ifiles)
e3aaff8e
 	ret = 1;
     else if(ret < 50) /* hopefully no error detected */ 
 	ret = 0; /* just make sure it's 0 */
 
     return ret;
 }
 
7a2997f1
 /*
  * -1 -> can't fork
  * -2 -> can't execute
  * -3 -> external signal
  * 0 -> OK
  */
34f71e0e
 
 #ifdef C_WINDOWS
34efd8f1
 static int clamav_unpack(const char *prog, const char **args, const char *tmpdir, const struct passwd *user, const struct optstruct *opt)
34f71e0e
 {
     /* TODO: use spamvp(P_WAIT, prog, args); */
     cli_errmsg("clamav_unpack is not supported under Windows yet\n");
     return -1;
 }
 #else
34efd8f1
 static int clamav_unpack(const char *prog, const char **args, const char *tmpdir, const struct passwd *user, const struct optstruct *opt)
e3aaff8e
 {
7a2997f1
 	pid_t pid;
 	int status, wret, fdevnull;
 	unsigned int maxfiles, maxspace;
 	struct s_du n;
e3aaff8e
 
7a2997f1
 
     if(opt_check(opt, "max-files"))
 	maxfiles = atoi(opt_arg(opt, "max-files"));
     else
 	maxfiles = 0;
 
     if(opt_check(opt, "max-space")) {
 	    char *cpy, *ptr;
 	ptr = opt_arg(opt, "max-space");
 	if(tolower(ptr[strlen(ptr) - 1]) == 'm') { /* megabytes */
65d08d61
 	    cpy = calloc(strlen(ptr), 1);
7a2997f1
 	    strncpy(cpy, ptr, strlen(ptr) - 1);
 	    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
 	    if(!geteuid() && user) {
 
 #ifdef HAVE_SETGROUPS
 		if(setgroups(1, &user->pw_gid)) {
 		    fprintf(stderr, "ERROR: setgroups() failed\n");
 		    exit(1);
 		}
 #endif
 
 		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
 	    }
7a2997f1
 #endif
 	    chdir(tmpdir);
8e65f09c
 
7a2997f1
 	    if(printinfected) {
   	        fdevnull = open("/dev/null", O_WRONLY);
 		if(fdevnull == -1) {
 		    logg("Non fatal error: cannot open /dev/null. Continuing with full output\n");
 		    printinfected = 0;
 		} else {
 		    dup2(fdevnull,1);
 		    dup2(fdevnull,2);
 		}
e3aaff8e
 	    }
7a2997f1
 
 	    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)) {
34efd8f1
 			    logg("*n.files: %u, n.space: %lu\n", n.files, n.space);
7a2997f1
 			    kill(pid, 9); /* stop it immediately */
 			}
 		}
 	    } else
 		waitpid(pid, &status, 0);
 
 
 	    if(WIFSIGNALED(status)) {
 		switch(WTERMSIG(status)) {
 
 		    case 9:
 			logg("\nUnpacker process %d stopped due to exceeded limits\n", pid);
 			return 0;
 		    case 6: /* abort */
 			logg("^Can't run %s\n", prog);
 			return -2;
 		    default:
 			logg("^\nUnpacker stopped with external signal %d\n", WTERMSIG(status));
 			return -3;
 		}
 	    } else if(WIFEXITED(status))
 		return 0;
e3aaff8e
     }
8e65f09c
 
7a2997f1
     return 0;
 }
34f71e0e
 #endif
e3aaff8e
 
7a2997f1
 static void move_infected(const char *filename, const struct optstruct *opt)
 {
34efd8f1
 	char *movedir, *movefilename, numext[4 + 1];
 	const char *tmp;
 	struct stat ofstat, mfstat;
7a2997f1
 	int n, len, movefilename_size;
 	int moveflag = opt_check(opt, "move");
 	struct utimbuf ubuf;
 
 
     if((moveflag && !(movedir = opt_arg(opt, "move"))) ||
 	(!moveflag && !(movedir = opt_arg(opt, "copy")))) {
         /* Should never reach here */
34efd8f1
         logg("!opt_arg() returned NULL\n");
7a2997f1
         info.notmoved++;
         return;
e3aaff8e
     }
 
7a2997f1
     if(access(movedir, W_OK|X_OK) == -1) {
 	logg("!Can't %s file '%s': cannot write to '%s': %s\n", (moveflag) ? "move" : "copy", filename, movedir, strerror(errno));
         info.notmoved++;
         return;
e3aaff8e
     }
 
7a2997f1
     if(!(tmp = strrchr(filename, '/')))
34efd8f1
 	tmp = filename;
2df4fcca
 
7a2997f1
     movefilename_size = sizeof(char) * (strlen(movedir) + strlen(tmp) + sizeof(numext) + 2);
2df4fcca
 
8ca8a18e
     if(!(movefilename = malloc(movefilename_size))) {
         logg("!malloc() failed\n");
7a2997f1
 	exit(71);
     }
e3aaff8e
 
7a2997f1
     if(!(cli_strrcpy(movefilename, movedir))) {
         logg("!cli_strrcpy() returned NULL\n");
         info.notmoved++;
         free(movefilename);
         return;
     }
e3aaff8e
 
7a2997f1
     strcat(movefilename, "/");
e3aaff8e
 
7a2997f1
     if(!(strcat(movefilename, tmp))) {
         logg("!strcat() returned NULL\n");
         info.notmoved++;
         free(movefilename);
         return;
     }
5f20a6ce
 
34efd8f1
     stat(filename, &ofstat);
7a2997f1
 
     if(!stat(movefilename, &mfstat)) {
34efd8f1
         if(ofstat.st_ino == mfstat.st_ino) { /* It's the same file*/
7a2997f1
             logg("File excluded '%s'\n", filename);
             info.notmoved++;
             free(movefilename);
             return;
         } 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
     }
 
7a2997f1
     if(!moveflag || rename(filename, movefilename) == -1) {
 	if(filecopy(filename, movefilename) == -1) {
 	    logg("!Can't %s '%s' to '%s': %s\n", (moveflag) ? "move" : "copy", filename, movefilename, strerror(errno));
 	    info.notmoved++;
 	    free(movefilename);
 	    return;
 	}
e3aaff8e
 
34efd8f1
 	chmod(movefilename, ofstat.st_mode);
7a2997f1
 #ifndef C_OS2
34efd8f1
 	chown(movefilename, ofstat.st_uid, ofstat.st_gid);
7a2997f1
 #endif
e3aaff8e
 
34efd8f1
 	ubuf.actime = ofstat.st_atime;
 	ubuf.modtime = ofstat.st_mtime;
7a2997f1
 	utime(movefilename, &ubuf);
e3aaff8e
 
7a2997f1
 	if(moveflag && unlink(filename)) {
 	    logg("!Can't unlink '%s': %s\n", filename, strerror(errno));
 	    info.notremoved++;            
 	    free(movefilename);
 	    return;
e3aaff8e
 	}
     }
 
7a2997f1
     logg("%s: %s to '%s'\n", filename, (moveflag) ? "moved" : "copied", movefilename);
 
     free(movefilename);
 }
 
 static int checkfile(const char *filename, const struct cl_engine *engine, const struct cl_limits *limits, int options, short printclean)
 {
 	int fd, ret;
 	const char *virname;
 
 
     logg("*Scanning %s\n", filename);
 
34f71e0e
     if((fd = open(filename, O_RDONLY|O_BINARY)) == -1) {
7a2997f1
 	logg("^Can't open file %s\n", filename);
 	return 54;
e3aaff8e
     }
7a2997f1
 
     if((ret = cl_scandesc(fd, &virname, &info.blocks, engine, limits, options)) == CL_VIRUS) {
 	logg("%s: %s FOUND\n", filename, virname);
 	info.ifiles++;
 
 	if(bell)
 	    fprintf(stderr, "\007");
 
     } else if(ret == CL_CLEAN) {
 	if(!printinfected && printclean)
 	    mprintf("%s: OK\n", filename);
     } else
 	if(!printinfected)
 	    logg("%s: %s\n", filename, cl_strerror(ret));
 
     close(fd);
e3aaff8e
     return ret;
 }
 
7a2997f1
 static int scancompressed(const char *filename, struct cl_engine *engine, const struct passwd *user, const struct optstruct *opt, const struct cl_limits *limits, int options)
e3aaff8e
 {
 	int ret = 0;
34efd8f1
 	char *gendir, *userprg;
 	const char *tmpdir;
e3aaff8e
 	struct stat statbuf;
 
14dee074
 
e3aaff8e
     stat(filename, &statbuf);
 
     if(!S_ISREG(statbuf.st_mode)) {
9f0e5585
 	logg("^Suspect archive %s (not a regular file)\n", filename);
e3aaff8e
 	return 0; /* hmm ? */
     }
 
     /* check write access */
 
590135f9
     tmpdir = getenv("TMPDIR");
e3aaff8e
 
     if(tmpdir == NULL)
 #ifdef P_tmpdir
 	tmpdir = P_tmpdir;
 #else
 	tmpdir = "/tmp";
 #endif
 
7a2997f1
     if(checkaccess(tmpdir, CLAMAVUSER, W_OK) != 1) {
9f0e5585
 	logg("!Can't write to the temporary directory\n");
e3aaff8e
 	exit(64);
     }
 
     /* generate the temporary directory */
 
8000d078
     gendir = cli_gentemp(tmpdir);
e3aaff8e
     if(mkdir(gendir, 0700)) {
9f0e5585
 	logg("!Can't create the temporary directory %s\n", gendir);
e3aaff8e
 	exit(63); /* critical */
     }
 
34f71e0e
 #if !defined(C_OS2) && !defined(C_WINDOWS)
     /* FIXME: do the correct native windows way */
e3aaff8e
     if(user)
 	chown(gendir, user->pw_uid, user->pw_gid);
75ccac9f
 #endif
e3aaff8e
 
     /* unpack file  - as unprivileged user */
afb48b28
     if(cli_strbcasestr(filename, ".zip")) {
34efd8f1
 	const char *args[] = { "unzip", "-P", "clam", "-o", NULL, NULL };
0aaf4b37
 	/* Sun's SUNWspro C compiler doesn't allow direct initialisation
 	 * with a variable
 	 */
34efd8f1
 	args[4] = filename;
e3aaff8e
 
7b8edc5c
 	if((userprg = opt_arg(opt, "unzip")))
e3aaff8e
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("unzip", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".rar")) { 
34efd8f1
 	const char *args[] = { "unrar", "x", "-p-", "-y", NULL, NULL };
 	args[4] = filename;
7b8edc5c
 	if((userprg = opt_arg(opt, "unrar")))
e3aaff8e
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("unrar", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".arj")) { 
34efd8f1
         const char *args[] = { "arj", "x","-y", NULL, NULL };
 	args[3] = filename;
7b8edc5c
         if((userprg = opt_arg(opt, "arj")))
e3aaff8e
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("arj", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".zoo")) { 
34efd8f1
 	const char *args[] = { "unzoo", "-x","-j","./", NULL, NULL };
 	args[4] = filename;
7b8edc5c
 	if((userprg = opt_arg(opt, "unzoo")))
e3aaff8e
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("unzoo", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".jar")) { 
34efd8f1
 	const char *args[] = { "unzip", "-P", "clam", "-o", NULL, NULL };
 	args[4] = filename;
7b8edc5c
 	if((userprg = opt_arg(opt, "jar")))
e3aaff8e
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("unzip", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".lzh")) { 
34efd8f1
 	const char *args[] = { "lha", "xf", NULL, NULL };
 	args[2] = filename;
7b8edc5c
 	if((userprg = opt_arg(opt, "lha")))
e3aaff8e
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("lha", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".tar")) { 
34efd8f1
 	const char *args[] = { "tar", "-xpvf", NULL, NULL };
 	args[2] = filename;
7b8edc5c
 	if((userprg = opt_arg(opt, "tar")))
e3aaff8e
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("tar", args, gendir, user, opt);
 
afb48b28
     } else if(cli_strbcasestr(filename, ".deb")) { 
34efd8f1
 	const char *args[] = { "ar", "x", NULL, NULL };
 	args[2] = filename;
7b8edc5c
 	if((userprg = opt_arg(opt, "deb")))
e3aaff8e
 	    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"))) {
34efd8f1
 	const char *args[] = { "tar", "-zxpvf", NULL, NULL };
 	args[2] = filename;
7b8edc5c
 	if((userprg = opt_arg(opt, "tgz")))
e3aaff8e
 	    ret = clamav_unpack(userprg, args, gendir, user, opt);
 	else
 	    ret = clamav_unpack("tar", args, gendir, user, opt);
     }
 
     /* fix permissions of extracted files */
     fixperms(gendir);
 
14dee074
     if(!ret) { /* execute successful */
 	    short oldrec = recursion;
 
 	recursion = 1;
7a2997f1
 	ret = treewalk(gendir, engine, user, opt, limits, options, 1);
14dee074
 	recursion = oldrec;
     }
e3aaff8e
 
     /* remove the directory  - as clamav */
7b8edc5c
     if(!opt_check(opt, "leave-temps"))
d1eb32e7
 	clamav_rmdirs(gendir);
e3aaff8e
 
     /* free gendir - it's not necessary now */
     free(gendir);
 
     switch(ret) {
 	case -1:
9f0e5585
 	    logg("!Can't fork()\n");
e3aaff8e
 	    exit(61); /* this is critical problem, so we just exit here */
 	case -2:
0ae41a2d
 	    logg("^Can't execute some unpacker. Check paths and permissions on the temporary directory\n");
e3aaff8e
 	    /* This is no longer a critical error (since 0.24). We scan
 	     * raw archive.
 	     */
7a2997f1
 	    if((ret = checkfile(filename, engine, limits, 0, 0)) == CL_VIRUS) {
7b8edc5c
 		if(opt_check(opt, "remove")) {
e3aaff8e
 		    if(unlink(filename)) {
9f0e5585
 			logg("^%s: Can't remove\n", filename);
7a2997f1
 			info.notremoved++;
e3aaff8e
 		    } else {
8e65f09c
 			logg("%s: Removed\n", filename);
e3aaff8e
 		    }
c6d2bbbc
 		} else if (opt_check(opt, "move") || opt_check(opt, "copy"))
e3aaff8e
 		    move_infected(filename, opt);
 	    }
 	    return ret;
 	case -3:
 	    return 0;
 	case 0:
14dee074
 	    /* no viruses found in archive, we scan just in case a raw file
e3aaff8e
 	     */
7a2997f1
 	    if((ret = checkfile(filename, engine, limits, 0, 1)) == CL_VIRUS) {
7b8edc5c
 		if(opt_check(opt, "remove")) {
e3aaff8e
 		    if(unlink(filename)) {
9f0e5585
 			logg("^%s: Can't remove\n", filename);
7a2997f1
 			info.notremoved++;
e3aaff8e
 		    } else {
8e65f09c
 			logg("%s: Removed\n", filename);
e3aaff8e
 		    }
c6d2bbbc
 		} else if (opt_check(opt, "move") || opt_check(opt, "copy"))
e3aaff8e
 		    move_infected(filename, opt);
 	    }
 	    return ret;
 	case 1:
14dee074
 	    logg("%s: Infected.Archive FOUND\n", filename);
af22ece1
 
 	    if(bell)
ed026d36
 		fprintf(stderr, "\007");
af22ece1
 
7b8edc5c
 	    if(opt_check(opt, "remove")) {
e3aaff8e
 		if(unlink(filename)) {
9f0e5585
 		    logg("^%s: Can't remove\n", filename);
7a2997f1
 		    info.notremoved++;
e3aaff8e
 		} else {
8e65f09c
 		    logg("%s: Removed\n", filename);
e3aaff8e
 		}
c6d2bbbc
 	    } else if (opt_check(opt, "move") || opt_check(opt, "copy"))
e3aaff8e
 		move_infected(filename, opt);
 
 	    return 1;
 	default:
0ae41a2d
 	    logg("^Strange value (%d) returned in scancompressed()\n", ret);
e3aaff8e
 	    return 0;
     }
 }
 
7a2997f1
 static int scandenied(const char *filename, struct cl_engine *engine, const struct passwd *user, const struct optstruct *opt, const struct cl_limits *limits, int options)
e3aaff8e
 {
34efd8f1
 	char *gendir, *tmp_file;
 	const char *tmpdir, *pt;
e3aaff8e
 	struct stat statbuf;
 	int ret;
 
     stat(filename, &statbuf);
     if(!S_ISREG(statbuf.st_mode)) {
9f0e5585
 	logg("^Suspect archive %s (not a regular file)\n", filename);
e3aaff8e
 	return 0;
     }
 
     /* check write access */
 
590135f9
     tmpdir = getenv("TMPDIR");
e3aaff8e
 
     if(tmpdir == NULL)
 #ifdef P_tmpdir
 	tmpdir = P_tmpdir;
 #else
 	tmpdir = "/tmp";
 #endif
 
 
7a2997f1
     if(checkaccess(tmpdir, CLAMAVUSER, W_OK) != 1) {
9f0e5585
 	logg("!Can't write to the temporary directory %s\n", tmpdir);
e3aaff8e
 	exit(64);
     }
 
     /* generate the temporary directory */
8000d078
     gendir = cli_gentemp(tmpdir);
e3aaff8e
     if(mkdir(gendir, 0700)) {
0ae41a2d
 	logg("^Can't create the temporary directory %s\n", gendir);
e3aaff8e
 	exit(63); /* critical */
     }
 
34efd8f1
     tmp_file = (char *) malloc(strlen(gendir) + strlen(filename) + 10);
e3aaff8e
     pt = strrchr(filename, '/');
     if(!pt)
34efd8f1
 	pt = filename;
e3aaff8e
     else
 	pt += 1;
 
34efd8f1
     sprintf(tmp_file, "%s/%s", gendir, pt);
e3aaff8e
 
34efd8f1
     if(filecopy(filename, tmp_file) == -1) {
0ae41a2d
 	logg("!I/O error\n");
e3aaff8e
 	perror("copyfile()");
 	exit(58);
     }
 
     fixperms(gendir);
 
34f71e0e
 #if !defined(C_OS2) && !defined(C_WINDOWS)
e3aaff8e
     if(user) {
 	chown(gendir, user->pw_uid, user->pw_gid);
34efd8f1
 	chown(tmp_file, user->pw_uid, user->pw_gid);
e3aaff8e
     }
75ccac9f
 #endif
e3aaff8e
 
7a2997f1
     if((ret = treewalk(gendir, engine, user, opt, limits, options, 1)) == 1) {
e3aaff8e
 	logg("(Real infected archive: %s)\n", filename);
 
7b8edc5c
 	if(opt_check(opt, "remove")) {
e3aaff8e
 	    if(unlink(filename)) {
9f0e5585
 		logg("^%s: Can't remove\n", filename);
7a2997f1
 		info.notremoved++;
e3aaff8e
 	    } else {
8e65f09c
 	        logg("%s: Removed\n", filename);
e3aaff8e
 	    }
c6d2bbbc
 	} else if (opt_check(opt, "move") || opt_check(opt, "copy"))
e3aaff8e
 	    move_infected(filename, opt);
     }
 
     /* remove the directory  - as clamav */
     clamav_rmdirs(gendir);
 
     free(gendir);
34efd8f1
     free(tmp_file);
e3aaff8e
 
     return ret;
 }
 
7a2997f1
 int scanfile(const char *filename, struct cl_engine *engine, const struct passwd *user, const struct optstruct *opt, const struct cl_limits *limits, unsigned int options)
e3aaff8e
 {
7a2997f1
 	int ret, included, printclean = 1;
 	const struct optnode *optnode;
 	char *argument;
 #ifdef C_LINUX
 	struct stat sb;
6ef42bc3
 
7a2997f1
     /* argh, don't scan /proc files */
     if(procdev)
 	if(stat(filename, &sb) != -1)
 	    if(sb.st_dev == procdev) {
 		if(!printinfected)
 		    logg("%s: Excluded (/proc)\n", filename);
 		return 0;
 	    }
 #endif    
6ef42bc3
 
7a2997f1
     if(opt_check(opt, "exclude")) {
 	argument = opt_firstarg(opt, "exclude", &optnode);
 	while(argument) {
 	    if(match_regex(filename, argument) == 1) {
 		if(!printinfected)
 		    logg("%s: Excluded\n", filename);
 		return 0;
 	    }
 	    argument = opt_nextarg(&optnode, "exclude");
 	}
6ef42bc3
     }
 
7a2997f1
    if(opt_check(opt, "include")) {
 	included = 0;
 	argument = opt_firstarg(opt, "include", &optnode);
 	while(argument && !included) {
 	    if(match_regex(filename, argument) == 1) {
 		included = 1;
 		break;
 	    }
 	    argument = opt_nextarg(&optnode, "include");
 	}
6ef42bc3
 
7a2997f1
 	if(!included) {
 	    if(!printinfected)
 		logg("%s: Excluded\n", filename);
 	    return 0;
 	}
6ef42bc3
     }
 
7a2997f1
     if(fileinfo(filename, 1) == 0) {
e3aaff8e
 	if(!printinfected)
7a2997f1
 	    logg("%s: Empty file\n", filename);
 	return 0;
     }
e3aaff8e
 
34f71e0e
 #ifndef C_WINDOWS
7a2997f1
     if(geteuid())
 	if(checkaccess(filename, NULL, R_OK) != 1) {
 	    if(!printinfected)
 		logg("%s: Access denied\n", filename);
 	    return 0;
 	}
34f71e0e
 #endif
e3aaff8e
 
7a2997f1
     info.files++;
e3aaff8e
 
7a2997f1
     /* 
      * 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.
      */
a7d9bef2
 
7a2997f1
     if((cli_strbcasestr(filename, ".zip") || cli_strbcasestr(filename, ".rar")) && (options & CL_SCAN_ARCHIVE)) {
 	/* try to use internal archivers */
 	if((ret = checkfile(filename, engine, limits, options, 1)) == CL_VIRUS) {
 	    if(opt_check(opt, "remove")) {
 		if(unlink(filename)) {
 		    logg("^%s: Can't remove\n", filename);
 		    info.notremoved++;
 		} else {
 		    logg("%s: Removed\n", filename);
9f51cb51
 		}
7a2997f1
 	    } else if (opt_check(opt, "move") || opt_check(opt, "copy"))
 		move_infected(filename, opt);
9f51cb51
 
7a2997f1
 	    return 1;
9f51cb51
 
7a2997f1
 	} else if(ret == CL_CLEAN) {
 	    return 0;
 	} else if(ret == 54) {
 	    return ret;
 	}
e3aaff8e
 
7a2997f1
 	/* in other case try to continue with external archivers */
 	options &= ~CL_SCAN_ARCHIVE; /* and disable decompression for the checkfile() below */
 	printclean = 0;
     }
e3aaff8e
 
7a2997f1
     if((cli_strbcasestr(filename, ".zip") && opt_check(opt, "unzip"))
     || (cli_strbcasestr(filename, ".rar") && opt_check(opt, "unrar"))
     || (cli_strbcasestr(filename, ".arj") && opt_check(opt, "arj"))
     || (cli_strbcasestr(filename, ".zoo") && opt_check(opt, "unzoo"))
     || (cli_strbcasestr(filename, ".jar") && opt_check(opt, "jar"))
     || (cli_strbcasestr(filename, ".lzh") && opt_check(opt, "lha"))
     || (cli_strbcasestr(filename, ".tar") && opt_check(opt, "tar"))
     || (cli_strbcasestr(filename, ".deb") && opt_check(opt, "deb"))
     || ((cli_strbcasestr(filename, ".tar.gz") || cli_strbcasestr(filename, ".tgz")) 
 	 && (opt_check(opt, "tgz") || opt_check(opt, "deb"))) ) {
e3aaff8e
 
7a2997f1
 	/* check permissions */
 	switch(checkaccess(filename, CLAMAVUSER, R_OK)) {
 	    case -1:
 		logg("^Can't get information about user "CLAMAVUSER"\n");
 		exit(60); /* this is a critical problem so we just exit here */
 	    case -2:
 		logg("^Can't fork\n");
 		exit(61);
 	    case 0: /* read access denied */
 		if(geteuid()) {
 		    if(!printinfected)
 			logg("^%s: Access denied to archive\n", filename);
 		} else {
e3aaff8e
 
7a2997f1
 		    if(limits && limits->maxfilesize)
 			if((unsigned int) fileinfo(filename, 1) / 1024 > limits->maxfilesize) {
 			    if(!printinfected)
 				logg("^%s: Archive too big\n", filename);
 			    return 0;
e3aaff8e
 			}
 
7a2997f1
 		    return(scandenied(filename, engine, user, opt, limits, options));
e3aaff8e
 		}
 		return 0;
7a2997f1
 	    case 1:
 		return(scancompressed(filename, engine, user, opt, limits, options));
4790a32f
 	}
e3aaff8e
     }
 
7a2997f1
     if((ret = checkfile(filename, engine, limits, options, printclean)) == CL_VIRUS) {
 	if(opt_check(opt, "remove")) {
 	    if(unlink(filename)) {
 		logg("^%s: Can't remove\n", filename);
 		info.notremoved++;
 	    } else {
 		logg("%s: Removed\n", filename);
 	    }
 	} else if (opt_check(opt, "move") || opt_check(opt, "copy"))
             move_infected(filename, opt);
     }
     return ret;
e3aaff8e
 }