/*
 *  Copyright (C) 2002, 2003 Tomasz Kojm <zolw@konarski.edu.pl>
 *
 *  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
 */

#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>

#include "defaults.h"
#include "others.h"
#include "options.h"
#include "manager.h"
#include "treewalk.h"
#include "shared.h"
#include "mbox.h"

#ifdef C_LINUX
dev_t procdev;
#endif

int filecopy(const char *src, const char *dest)
{

#ifdef C_DARWIN
	/* On Mac OS X use ditto and copy resource fork, too. */
	char *ditto = (char *) mcalloc(strlen(src) + strlen(dest) + 30, sizeof(char));
	sprintf(ditto, "/usr/bin/ditto --rsrc %s %s", src, dest);

	if(system(ditto)) {
		free(ditto);
		return -1;
	}

	free(ditto);
	return 0;

#else
	char buffer[FILEBUFF];
	int s, d, bytes;

	if((s = open(src, O_RDONLY)) == -1)
		return -1;

	if((d = open(dest, O_CREAT|O_WRONLY|O_TRUNC)) == -1) {
		close(s);
		return -1;
	}

	while((bytes = read(s, buffer, FILEBUFF)) > 0)
		write(d, buffer, bytes);
	close(s);

	/* njh@bandsman.co.uk: check result of close for NFS file */
	return close(d);

#endif //__APPLE_CC__

}


int fileinfo(const char *filename, short i)
{
	struct stat infostruct;

	if(stat(filename, &infostruct) == -1) 
		return(-1);

	switch(i) {

		case 1: /* size */
			return infostruct.st_size;
		case 2: /* permissions */
			return (mode_t)infostruct.st_mode;
		case 3: /* modification time */
			return infostruct.st_mtime;
		case 4: /* UID */
			return infostruct.st_uid;
		case 5: /* GID */
			return infostruct.st_gid;
		default:
			mprintf("!fileinfo(): Unknown option.\n");
			exit(1);
	}   
}


int checkaccess(const char *path, const char *username, int mode)
{
	struct passwd *user;
	int ret = 0, status;

	if(!geteuid()) {

		if((user = getpwnam(username)) == NULL) {
			return -1; 
		}   

		switch(fork()) {
			case -1: 
				return -2; 

			case 0:
				if(setgid(user->pw_gid)) {
					fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int) user->pw_gid);
					exit(0);
				}   

				if(setuid(user->pw_uid)) {
					fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int) user->pw_uid);
					exit(0);
				}   

				if(access(path, mode))
					exit(0);
				else
					exit(1);

			default:
				wait(&status);
				if(WIFEXITED(status) && WEXITSTATUS(status) == 1)
					ret = 1;
		}

	} else {
		if(!access(path, mode))
			ret = 1;
	}

	return ret;
}


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;
	char *fullpath = NULL, cwd[200];


/* njh@bandsman.co.uk: BeOS */
#if !defined(C_CYGWIN) && !defined(C_BEOS)
    if(!getuid()) {
		if((user = getpwnam(UNPUSER)) == NULL) {
			printf("@Can't get information about user "UNPUSER".\n");
		//	exit(60); /* this is critical problem, so we just exit here */
		}
    }
#endif


    if(optl(opt, "unzip") || optl(opt, "unrar") || optl(opt, "unace") ||
       optl(opt, "arj") || optl(opt, "unzoo") || optl(opt, "jar") ||
       optl(opt, "lha") || optl(opt, "tar") || optl(opt, "tgz") ||
       optl(opt, "deb"))
	    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))) {
					printf("@%s\n", cl_perror(ret));
					return 50;
				}
				break;
			case S_IFDIR:
				printf("Jacob 1: %s\n", getargc(opt, 'd'));
				if((ret = cl_loaddbdir(getargc(opt, 'd'), &trie, &claminfo.signs))) {
					printf("@%s\n", cl_perror(ret));
					return 50;
				}
				break;
			default:
				printf("@%s: Not supported database file type\n", getargc(opt, 'd'));
				return 50;
		}

	} else {
		if((ret = cl_loaddbdir(cl_retdbdir(), &trie, &claminfo.signs))) {
			printf("@%s\n", cl_perror(ret));
			return 50;
		}
		printf("The address of trie %d\n", trie);
	}


	if(!trie) {
		printf("@Can't initialize the virus database.\n");
		return 50;
	}

    /* build the proper trie */
    cl_buildtrie(trie);

    /* 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 = calloc(strlen(ptr), sizeof(char));
	    strncpy(cpy, ptr, strlen(ptr) - 1);
	    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;


#ifdef C_LINUX
    if(stat("/proc", &sb) == -1)
	procdev = 0;
    else
	procdev = sb.st_dev;
#endif

    /* check filetype */
    if(!strcmp(opt->filename, "-")) { /* read data from stdin */
	/*
	 * njh@bandsman.co.uk: treat the input as a mailbox, the program
	 * can then be used as a filter called when mail is received
	 */
	if(optc(opt, 'm')) {
		const char *tmpdir;
		char *dir;

		/* njh@bandsman.co.uk: BeOS */
#if !defined(C_CYGWIN) && !defined(C_BEOS)
		if((user = getpwnam(UNPUSER)) == NULL) {
		    printf("@Can't get information about user %s\n", UNPUSER);
		    exit(60); /* this is critical problem, so we just exit here */
		}
#endif

		if((tmpdir = getargl(opt, "tempdir")) == NULL)
			tmpdir = getenv("TMPDIR");

		if(tmpdir == NULL)
#ifdef P_tmpdir
			tmpdir = P_tmpdir;
#else
		printf("Ha, this is tmp file\n");
			//tmpdir = "/tmp";
			tmpdir = "/data/local/tmp/";
#endif

		if(checkaccess(tmpdir, UNPUSER, W_OK) != 1) {
			printf("@Can't write to the temporary directory.\n");
			exit(64);
		}
		/* generate the temporary directory */

		dir = cli_gentemp(tmpdir);
		if(mkdir(dir, 0700)) {
			printf("@Can't create the temporary directory %s\n", dir);
			exit(63); /* critical */
		}

		if(user)
			chown(dir, user->pw_uid, user->pw_gid);

		/*
		 * Extract the attachments into the temporary directory
		 */
		ret = cl_mbox(dir, 0);

		if(ret == 0) {
			/* fix permissions of extracted files */
			fixperms(dir);

			if(ret == 0) /* execute successful */
				ret = treewalk(dir, trie, user, opt, limits);

			/* remove the directory - as clamav */
			clamav_rmdirs(dir);

			/* free dir - it's not necessary now */
			free(dir);
		}
	} else
	    ret = checkstdin(trie, limits);

    } else if(strlen(opt->filename) == 0) {

	/* we need full path for some reasons (eg. archive handling) */
	if(!getcwd(cwd, 200)) {
	    printf("@Can't get absolute pathname of current working directory.\n");
	    ret = 57;
	} else
	    ret = scandirs(cwd, trie, user, opt, limits);

    } else if((fmodeint = fileinfo(opt->filename, 2)) == -1) {
               printf("@Can't access file %s\n", opt->filename);
               perror(opt->filename);
               ret = 56;
    } else {
	fmode = (mode_t) fmodeint;

        if(compression && (opt->filename[0] != '/')) {
	    /* we need to complete the path */
            if(!getcwd(cwd, 200)) {
		printf("@Can't get absolute pathname of current working directory.\n");
                return 57;
            } else {
		fullpath = calloc(512, sizeof(char));
#ifdef NO_SNPRINTF
                sprintf(fullpath, "%s/%s", cwd, opt->filename);
#else
                snprintf(fullpath, 512, "%s/%s", cwd, opt->filename);
#endif
                printf("*Full path: %s\n", fullpath);
            }
        } else
	    fullpath = (char *) opt->filename;

        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:
                printf("@Not supported file type (%s)\n", opt->filename);
                ret = 52;
	}

	if(compression && fullpath) {
	    free(fullpath);
	    fullpath = NULL;
	}
    }


    /* free the trie */
    cl_freetrie(trie);

    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;
}

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) {
		printf("%s: Excluded (/proc).\n", filename);
		return 0;
	    }
#endif

    if(optl(opt, "exclude")) {
	argument = getfirstargl(opt, "exclude", &optnode);
	while (argument) {
	    if(strstr(filename, argument)) {
		if(!printinfected)
		    printf("%s: Excluded.\n", filename);
		return 0;
	    }
	    argument = getnextargl(&optnode, "exclude");
	}
    }

    if(optl(opt, "include")) {
	included = 0;
	argument = getfirstargl(opt, "include",&optnode);
	while (argument && !included) {
	    if(strstr(filename, argument))
		included = 1;
	    argument = getnextargl(&optnode, "include");
	}

	if (!included) {
	    if(!printinfected)
		printf("%s: Excluded.\n", filename);
	    return 0;
	}
	
    }


    if(fileinfo(filename, 1) == 0) {
	if(!printinfected)
	    printf("%s: Empty file.\n", filename);
	return 0;
    }

    if(optl(opt, "disable-archive"))
	options &= ~CL_ARCHIVE;
    else
	options |= CL_ARCHIVE;

    if(optc(opt, 'm'))
	options |= CL_MAIL;

    /* 
     * 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.
     */

    if((strcasestr(filename, ".zip") || strcasestr(filename, ".rar")) && (options & CL_ARCHIVE)) {
	/* try to use internal archivers */
	if((ret = checkfile(filename, root, limits, CL_ARCHIVE)) == CL_VIRUS) {
	    if(optl(opt, "remove")) {
		if(unlink(filename)) {
		    printf("%s: Can't remove.\n", filename);
		    //logg("%s: Can't remove.\n", filename);
		    claminfo.notremoved++;
		} else {
		    printf("%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() */
    }

    if((strcasestr(filename, ".zip") && optl(opt, "unzip"))
    || (strcasestr(filename, ".rar") && optl(opt, "unrar"))
    || (strcasestr(filename, ".ace") && optl(opt, "unace"))
    || (strcasestr(filename, ".arj") && optl(opt, "arj"))
    || (strcasestr(filename, ".zoo") && optl(opt, "unzoo"))
    || (strcasestr(filename, ".jar") && optl(opt, "jar"))
    || (strcasestr(filename, ".lzh") && optl(opt, "lha"))
    || (strcasestr(filename, ".tar") && optl(opt, "tar"))
    || (strcasestr(filename, ".deb") && optl(opt, "deb"))
    || ((strcasestr(filename, ".tar.gz") || strcasestr(filename, ".tgz")) 
	 && (optl(opt, "tgz") || optl(opt, "deb"))) ) {

	/* check permissions */
	switch(checkaccess(filename, UNPUSER, R_OK)) {
	    case -1:
		printf("@Can't get information about user "UNPUSER".\n");
		exit(60); /* this is critical problem, so we just exit here */
	    case -2:
		printf("@Can't get information about current user.\n");
		exit(59); /* this is critical problem, so we just exit here */
	    case 0: /* read access denied */
		if(getuid()) {
		    if(!printinfected)
			printf("%s: Access denied to archive.\n", filename);
		} else {

		    if(limits && limits->maxfilesize)
			if(fileinfo(filename, 1) / 1024 > limits->maxfilesize) {
			    if(!printinfected)
				printf("%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));
	}
    }

    if(getuid())
	switch(checkaccess(filename, NULL, R_OK)) {
	    case -2:
		printf("@Can't get information about current user.\n");
		exit(59); /* this is critical problem, so we just exit here */
	    case 0: /* read access denied */
		if(!printinfected)
		    printf("%s: Access denied.\n", filename);
		return 0;
	}

    if((ret = checkfile(filename, root, limits, options)) == CL_VIRUS) {
	if(optl(opt, "remove")) {
	    if(unlink(filename)) {
		printf("%s: Can't remove.\n", filename);
		//logg("%s: Can't remove.\n", filename);
		claminfo.notremoved++;
	    } else {
		printf("%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)) {
	printf("^Suspected archive %s is not a regular file.\n", filename);
	return 0; /* hmm ? */
    }

    /* check write access */

    if((tmpdir = getargl(opt, "tempdir")) == NULL)
	/* njh@bandsman.co.uk: use TMPDIR as an alternative */
	tmpdir = getenv("TMPDIR");

    if(tmpdir == NULL)
#ifdef P_tmpdir
	tmpdir = P_tmpdir;
#else
	tmpdir = "/tmp";
#endif

    if(checkaccess(tmpdir, UNPUSER, W_OK) != 1) {
	printf("@Can't write to the temporary directory.\n");
	exit(64);
    }

    /* generate the temporary directory */

    gendir = cli_gentemp(tmpdir);
    if(mkdir(gendir, 0700)) {
	printf("@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 */
    if(strcasestr(filename, ".zip")) {
	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);

    } else if(strcasestr(filename, ".rar")) { 
	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);

    } else if(strcasestr(filename, ".ace")) { 
	char *args[] = { "unace", "x", "-y", (char *) filename, NULL };
	if((userprg = getargl(opt, "unace")))
	    ret = clamav_unpack(userprg, args, gendir, user, opt);
	else
	    ret = clamav_unpack("unace", args, gendir, user, opt);

    } else if(strcasestr(filename, ".arj")) { 
        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);

    } else if(strcasestr(filename, ".zoo")) { 
	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);

    } else if(strcasestr(filename, ".jar")) { 
	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);

    } else if(strcasestr(filename, ".lzh")) { 
	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);

    } else if(strcasestr(filename, ".tar")) { 
	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);

    } else if(strcasestr(filename, ".deb")) { 
	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);

    } else if((strcasestr(filename, ".tar.gz") || strcasestr(filename, ".tgz"))) {
	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:
	    printf("@Can't fork().\n");
	    exit(61); /* this is critical problem, so we just exit here */
	case -2:
	    printf("@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)
		printf("(raw) ");

	    if((ret = checkfile(filename, root, limits, 0)) == CL_VIRUS) {
		if(optl(opt, "remove")) {
		    if(unlink(filename)) {
			printf("%s: Can't remove.\n", filename);
			//logg("%s: Can't remove.\n", filename);
			claminfo.notremoved++;
		    } else {
			printf("%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)
		printf("(raw) ");

	    if((ret = checkfile(filename, root, limits, 0)) == CL_VIRUS) {
		if(optl(opt, "remove")) {
		    if(unlink(filename)) {
			printf("%s: Can't remove.\n", filename);
			//logg("%s: Can't remove.\n", filename);
			claminfo.notremoved++;
		    } else {
			printf("%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);
	    printf("%s: Infected Archive FOUND\n", filename);
	    if(optl(opt, "remove")) {
		if(unlink(filename)) {
		    printf("%s: Can't remove.\n", filename);
		    //logg("%s: Can't remove.\n", filename);
		    claminfo.notremoved++;
		} else {
		    printf("%s: Removed.\n", filename);
		    //logg("%s: Removed.\n", filename);
		}
	    } else if (optl(opt, "move"))
		move_infected(filename, opt);

	    return 1;
	default:
	    printf("@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)) {
	printf("^Suspected archive %s is not a regular file.\n", filename);
	return 0;
    }

    /* check write access */

    if((tmpdir = getargl(opt, "tempdir")) == NULL)
        tmpdir = getenv("TMPDIR");

    if(tmpdir == NULL)
#ifdef P_tmpdir
	tmpdir = P_tmpdir;
#else
	tmpdir = "/tmp";
#endif


    if(checkaccess(tmpdir, UNPUSER, W_OK) != 1) {
	printf("@Can't write to the temporary directory %s.\n", tmpdir);
	exit(64);
    }

    /* generate the temporary directory */
    gendir = cli_gentemp(tmpdir);
    if(mkdir(gendir, 0700)) {
	printf("@Can't create the temporary directory %s\n", gendir);
	exit(63); /* critical */
    }

    tmpfile = (char *) calloc(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) {
	printf("!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);
	printf("(Real infected archive: %s)\n", filename);

	if(optl(opt, "remove")) {
	    if(unlink(filename)) {
		printf("%s: Can't remove.\n", filename);
		//logg("%s: Can't remove.\n", filename);
		claminfo.notremoved++;
	    } else {
	        printf("%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;
	char *virname;

    if((fd = open(filename, O_RDONLY)) == -1) {
	printf("@Can't open file %s\n", filename);
	return 54;
    }

    claminfo.files++;

    if((ret = cl_scandesc(fd, &virname, &claminfo.blocks, root, limits, options)) == CL_VIRUS) {
	printf("%s: %s FOUND\n", filename, virname);
	//logg("%s: %s FOUND\n", filename, virname);
	claminfo.ifiles++;
    } else if(ret == CL_CLEAN) {
	if(!printinfected)
	    printf("%s: OK\n", filename);
    } else
	if(!printinfected)
	    printf("%s: %s\n", filename, cl_perror(ret));

    close(fd);
    return ret;
}

int checkstdin(const struct cl_node *root, const struct cl_limits *limits)
{
	int ret;
	char *virname;


    claminfo.files++;

    if((ret = cl_scandesc(0, &virname, &claminfo.blocks, root, limits, CL_RAW)) == CL_VIRUS) {
	printf("stdin: %s FOUND\n", virname);
	claminfo.ifiles++;
    } else if(ret == CL_CLEAN) {
	if(!printinfected)
	    printf("stdin: OK\n");
    } else
	if(!printinfected)
	    printf("stdin: %s\n", cl_perror(ret));

    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 = calloc(strlen(ptr), sizeof(char));
	    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(!getuid() && user) {
		setgroups(1, &user->pw_gid);
		setgid(user->pw_gid);
		setuid(user->pw_uid);
	    }
#endif
	    chdir(tmpdir);

	    if(printinfected) {
  	        fdevnull = open("/dev/null", O_WRONLY);
		if(fdevnull == -1) {
		    printf("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)) {
			    printf("*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:
			printf("\nUnpacker process %d stopped due to exceeded limits.\n", pid);
			return 0;
		    case 6: /* abort */
			printf("@Can't run %s\n", prog);
			return -2;
		    default:
			printf("@\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)
{
    char *movedir, *movefilename, *tmp;
    struct stat fstat, mfstat;

    if(!(movedir = getargl(opt, "move")))
    {
        /* Should never reach here */
        printf("@error moving file '%s'.\n", filename);
        printf("clamscan: getargc() returned NULL.\n");
        //logg("clamscan: getargc() returned NULL.\n");
        claminfo.notmoved++;
        return;
    }

    if(access(movedir, W_OK|X_OK) == -1)
    {
        printf("@error moving file '%s'.\n", filename);
        printf("clamscan: cannot write to '%s': %s.\n", movedir, strerror(errno));
        //logg("clamscan: cannot write to '%s': %s.\n", movedir, strerror(errno));
        claminfo.notmoved++;
        return;
    }
    
    if(!(tmp = strrchr(filename, '/')))
    {
        printf("@error moving file '%s'.\n", filename);
        printf("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;
    }
    
    if(!(movefilename = malloc(sizeof(char) * (strlen(movedir) + strlen(tmp)))))
    {
        printf("@error moving file '%s'.\n", filename);
        printf("clamscan: malloc() returned NULL.\n");
        //logg("clamscan: malloc() returned NULL.\n");
        claminfo.notmoved++;
        return;
    }
    
    if(!(strrcpy(movefilename, movedir)))
    {
        printf("@error moving file '%s'.\n", filename);
        printf("clamscan: strrcpy() returned NULL.\n");
        //logg("clamscan: strrcpy() returned NULL.\n");
        claminfo.notmoved++;
        free(movefilename);
        return;
    }
    
    if(!(strcat(movefilename, tmp)))
    {
        printf("@error moving file '%s'.\n", filename);
        printf("clamscan: strcat() returned NULL.\n");
        //logg("clamscan: strcat() returned NULL.\n");
        claminfo.notmoved++;
        free(movefilename);
        return;
    }
    
    stat(filename, &fstat);

    if(!stat(movefilename, &mfstat))
    {
        if(fstat.st_ino == mfstat.st_ino) { /* It's the same file*/
            printf("clamscan: file excluded '%s'.\n", filename);
            //logg("clamscan: file excluded '%s'.\n", filename);
            claminfo.notmoved++;
            free(movefilename);
            return;
        }
    }
    
    if(filecopy(filename, movefilename) == -1)
    {
        printf("@error moving file '%s'.\n", filename);
        printf("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);
    
    if(unlink(filename))
    {
        printf("@error moving file '%s'.\n", filename);
        printf("clamscan: cannot unlink '%s': %s.\n", filename, strerror(errno));
        //logg("clamscan: cannot unlink '%s': %s.\n", filename, strerror(errno));
        claminfo.notremoved++;            
    }
    else
    {
        printf("%s: moved to '%s'.\n", filename, movefilename);
        //logg("%s: moved to '%s'.\n", filename, movefilename);
    }

    free(movefilename);
}
