clamd/scanner.c
e3aaff8e
 /*
a617b5ea
  *  Copyright (C) 2002 - 2007 Tomasz Kojm <tkojm@clamav.net>
bb34cb31
  *  MULTISCAN code (C) 2006 Sensory Networks, Inc.
  *  Written by Tomasz Kojm
e3aaff8e
  *
  *  This program is free software; you can redistribute it and/or modify
bb34cb31
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
e3aaff8e
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
e3aaff8e
  */
 
67118e92
 #ifdef	_MSC_VER
 #include <winsock.h>
 #endif
 
98ac8d19
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e3aaff8e
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
67118e92
 #ifdef	HAVE_UNISTD_H
e3aaff8e
 #include <unistd.h>
67118e92
 #endif
a7282c2f
 #include <errno.h>
e3aaff8e
 #include <sys/stat.h>
 #include <sys/types.h>
67118e92
 #ifndef	C_WINDOWS
31e6c6fb
 #include <sys/time.h>
e3aaff8e
 #include <sys/wait.h>
15edd45f
 #include <sys/param.h>
1a4994e3
 #include <signal.h>
e3aaff8e
 #include <dirent.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
a0283d44
 #include <arpa/inet.h>
 #include <netdb.h>
67118e92
 #endif
d9855a95
 #include <pthread.h>
31e6c6fb
 
2bb229f6
 #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
 #include <limits.h>
88794204
 #include <stddef.h>
2bb229f6
 #endif
 
bd8603aa
 #include "libclamav/clamav.h"
 #include "libclamav/others.h"
 
 #include "shared/cfgparser.h"
 #include "shared/output.h"
53725d8c
 #include "shared/misc.h"
bd8603aa
 
e3aaff8e
 #include "others.h"
 #include "scanner.h"
afb48b28
 #include "shared.h"
a7282c2f
 #include "network.h"
a617b5ea
 #include "thrmgr.h"
e3aaff8e
 
c695dab4
 #ifdef C_LINUX
 dev_t procdev; /* /proc device */
 #endif
 
a617b5ea
 extern int progexit;
 
67118e92
 #ifndef	C_WINDOWS
 #define	closesocket(s)	close(s)
 #endif
 
a617b5ea
 struct multi_tag {
     int sd;
     unsigned int options;
     const struct cfgstruct *copt;
     char *fname;
     const struct cl_engine *engine;
     const struct cl_limits *limits;
 };
 
 static int checksymlink(const char *path)
e3aaff8e
 {
 	struct stat statbuf;
 
     if(stat(path, &statbuf) == -1)
 	return -1;
 
     if(S_ISDIR(statbuf.st_mode))
 	return 1;
 
     if(S_ISREG(statbuf.st_mode))
 	return 2;
 
     return 0;
 }
 
a617b5ea
 static int dirscan(const char *dirname, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, const struct cfgstruct *copt, int odesc, unsigned int *reclev, unsigned int type, threadpool_t *multi_pool)
e3aaff8e
 {
 	DIR *dd;
 	struct dirent *dent;
72a1b240
 #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
88794204
 	union {
 	    struct dirent d;
 	    char b[offsetof(struct dirent, d_name) + NAME_MAX + 1];
 	} result;
2bb229f6
 #endif
e3aaff8e
 	struct stat statbuf;
 	char *fname;
bd8603aa
 	int ret = 0, scanret = 0;
 	unsigned int maxdirrec = 0;
a617b5ea
 	struct multi_tag *scandata;
e3aaff8e
 
c6dbfbcb
 
81837459
     maxdirrec = cfgopt(copt, "MaxDirectoryRecursion")->numarg;
c6dbfbcb
     if(maxdirrec) {
 	if(*reclev > maxdirrec) {
 	    logg("*Directory recursion limit exceeded at %s\n", dirname);
 	    return 0;
e3aaff8e
 	}
c6dbfbcb
 	(*reclev)++;
e3aaff8e
     }
 
     if((dd = opendir(dirname)) != NULL) {
72a1b240
 #ifdef HAVE_READDIR_R_3
88794204
 	while(!readdir_r(dd, &result.d, &dent) && dent) {
72a1b240
 #elif defined(HAVE_READDIR_R_2)
88794204
 	while((dent = (struct dirent *) readdir_r(dd, &result.d))) {
72a1b240
 #else
e3aaff8e
 	while((dent = readdir(dd))) {
72a1b240
 #endif
e0909dc6
 	    if (!is_fd_connected(odesc)) {
 		logg("Client disconnected\n");
 		closedir(dd);
 		return 1;
 	    }
a617b5ea
 
             if(progexit) {
 		closedir(dd);
 		return 1;
 	    }
 
67118e92
 #if	(!defined(C_INTERIX)) && (!defined(C_WINDOWS)) && (!defined(C_CYGWIN))
feeaa333
 	    if(dent->d_ino)
 #endif
 	    {
e3aaff8e
 		if(strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) {
 		    /* build the full name */
8ca8a18e
 		    fname = (char *) malloc(strlen(dirname) + strlen(dent->d_name) + 2);
a617b5ea
                     if(!fname) {
 			logg("!Can't allocate memory for fname\n");
 			closedir(dd);
 			return -2;
 		    }
e3aaff8e
 		    sprintf(fname, "%s/%s", dirname, dent->d_name);
 
 		    /* stat the file */
 		    if(lstat(fname, &statbuf) != -1) {
81837459
 			if((S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) || (S_ISLNK(statbuf.st_mode) && (checksymlink(fname) == 1) && cfgopt(copt, "FollowDirectorySymlinks")->enabled)) {
a617b5ea
 			    if(dirscan(fname, virname, scanned, engine, limits, options, copt, odesc, reclev, type, multi_pool) == 1) {
e3aaff8e
 				free(fname);
 				closedir(dd);
 				return 1;
 			    }
a617b5ea
 			    free(fname);
2d70a403
 			} else {
81837459
 			    if(S_ISREG(statbuf.st_mode) || (S_ISLNK(statbuf.st_mode) && (checksymlink(fname) == 2) && cfgopt(copt, "FollowFileSymlinks")->enabled)) {
c695dab4
 
 #ifdef C_LINUX
19ca43de
 				if(procdev && (statbuf.st_dev == procdev))
a617b5ea
 				    free(fname);
19ca43de
 				else
c695dab4
 #endif
a617b5ea
 				{
 				    if(type == TYPE_MULTISCAN) {
 
8ca8a18e
 					scandata = (struct multi_tag *) malloc(sizeof(struct multi_tag));
a617b5ea
 					if(!scandata) {
 					    logg("!Can't allocate memory for scandata\n");
 					    free(fname);
 					    closedir(dd);
 					    return -2;
 					}
 					scandata->sd = odesc;
 					scandata->options = options;
 					scandata->copt = copt;
 					scandata->fname = fname;
 					scandata->engine = engine;
 					scandata->limits = limits;
 					if(!thrmgr_dispatch(multi_pool, scandata)) {
 					    logg("!thread dispatch failed for multi_pool (file %s)\n", fname);
 					    mdprintf(odesc, "ERROR: Can't scan file %s\n", fname);
 					    free(fname);
 					    free(scandata);
 					    closedir(dd);
 					    return 1;
 					}
 
 					while(!multi_pool->thr_idle) /* non-critical */
 #ifdef C_WINDOWS
 					    Sleep(1);
 #else
 					    usleep(200);
 #endif
c695dab4
 
a617b5ea
 				    } else { /* CONTSCAN, SCAN */
 
 					scanret = cl_scanfile(fname, virname, scanned, engine, limits, options);
 
 					if(scanret == CL_VIRUS) {
 
 					    mdprintf(odesc, "%s: %s FOUND\n", fname, *virname);
 					    logg("%s: %s FOUND\n", fname, *virname);
 					    virusaction(fname, *virname, copt);
 					    if(type == TYPE_SCAN) {
 						closedir(dd);
 						free(fname);
 						return 1;
 					    } else /* CONTSCAN */
 						ret = 2;
 
 					} else if(scanret != CL_CLEAN) {
 					    mdprintf(odesc, "%s: %s ERROR\n", fname, cl_strerror(scanret));
 					    logg("%s: %s ERROR\n", fname, cl_strerror(scanret));
 					    if(scanret == CL_EMEM) {
 						closedir(dd);
 						free(fname);
 						return -2;
 					    }
 
 					} else if(logok) {
 					    logg("%s: OK\n", fname);
 					}
e3aaff8e
 					free(fname);
6c507c40
 				    }
e3aaff8e
 				}
2d70a403
 			    }
 			}
a617b5ea
 		    } else {
 			free(fname);
e3aaff8e
 		    }
 		}
 	    }
 	}
2d70a403
 	closedir(dd);
e3aaff8e
     } else {
 	return -1;
     }
 
     (*reclev)--;
     return ret;
a617b5ea
 }
 
 static void multiscanfile(void *arg)
 {
 	struct multi_tag *tag = (struct multi_tag *) arg;
 	const char *virname;
 #ifndef	C_WINDOWS
         sigset_t sigset;
 #endif
 	int ret;
 
 
 #ifndef	C_WINDOWS
     /* ignore all signals */
     sigfillset(&sigset);
     pthread_sigmask(SIG_SETMASK, &sigset, NULL);
 #endif
e3aaff8e
 
a617b5ea
     ret = cl_scanfile(tag->fname, &virname, NULL, tag->engine, tag->limits, tag->options);
 
     if(ret == CL_VIRUS) {
 	mdprintf(tag->sd, "%s: %s FOUND\n", tag->fname, virname);
 	logg("%s: %s FOUND\n", tag->fname, virname);
 	virusaction(tag->fname, virname, tag->copt);
     } else if(ret != CL_CLEAN) {
 	mdprintf(tag->sd, "%s: %s ERROR\n", tag->fname, cl_strerror(ret));
 	logg("%s: %s ERROR\n", tag->fname, cl_strerror(ret));
     } else if(logok) {
 	logg("%s: OK\n", tag->fname);
     }
 
     free(tag->fname);
     free(tag);
     return;
e3aaff8e
 }
 
a617b5ea
 int scan(const char *filename, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, const struct cfgstruct *copt, int odesc, unsigned int type)
e3aaff8e
 {
 	struct stat sb;
bd8603aa
 	int ret = 0;
 	unsigned int reclev = 0;
fb787a06
 	const char *virname;
a617b5ea
 	threadpool_t *multi_pool = NULL;
e3aaff8e
 
 
     /* stat file */
     if(lstat(filename, &sb) == -1) {
65a18d2e
 	mdprintf(odesc, "%s: lstat() failed. ERROR\n", filename);
e3aaff8e
 	return -1;
     }
 
e0591f05
     /* check permissions  */
     if(access(filename, R_OK)) {
 	mdprintf(odesc, "%s: Access denied. ERROR\n", filename);
 	return -1;
     }
 
e3aaff8e
     switch(sb.st_mode & S_IFMT) {
67118e92
 #ifdef	S_IFLNK
e3aaff8e
 	case S_IFLNK:
81837459
 	    if(!cfgopt(copt, "FollowFileSymlinks")->enabled)
e3aaff8e
 		break;
 	    /* else go to the next case */
67118e92
 #endif
e3aaff8e
 	case S_IFREG: 
 	    if(sb.st_size == 0) { /* empty file */
 		mdprintf(odesc, "%s: Empty file\n", filename);
 		return 0;
 	    }
c695dab4
 #ifdef C_LINUX
19ca43de
 	    if(procdev && (sb.st_dev == procdev))
 		ret = CL_CLEAN;
 	    else
c695dab4
 #endif
a57e3d41
 		ret = cl_scanfile(filename, &virname, scanned, engine, limits, options);
c695dab4
 
e3aaff8e
 	    if(ret == CL_VIRUS) {
 		mdprintf(odesc, "%s: %s FOUND\n", filename, virname);
 		logg("%s: %s FOUND\n", filename, virname);
557b40cc
 		virusaction(filename, virname, copt);
e3aaff8e
 	    } else if(ret != CL_CLEAN) {
049a18b9
 		mdprintf(odesc, "%s: %s ERROR\n", filename, cl_strerror(ret));
 		logg("%s: %s ERROR\n", filename, cl_strerror(ret));
6c507c40
 		if(ret == CL_EMEM)
 		    return -2;
ee039e40
 	    } else if (logok) {
 		logg("%s: OK\n", filename);
 	    }
e3aaff8e
 	    break;
 	case S_IFDIR:
a617b5ea
 	    if(type == TYPE_MULTISCAN) {
 		    int idletimeout = cfgopt(copt, "IdleTimeout")->numarg;
 		    int max_threads = cfgopt(copt, "MaxThreads")->numarg;
 
 		if((multi_pool = thrmgr_new(max_threads, idletimeout, multiscanfile)) == NULL) {
 		    logg("!thrmgr_new failed for multi_pool\n");
 		    mdprintf(odesc, "thrmgr_new failed for multi_pool ERROR\n");
 		    return -1;
 		}
 	    }
 
 	    ret = dirscan(filename, &virname, scanned, engine, limits, options, copt, odesc, &reclev, type, multi_pool);
 
 	    if(multi_pool)
 		thrmgr_destroy(multi_pool);
 
e3aaff8e
 	    break;
 	default:
65a18d2e
 	    mdprintf(odesc, "%s: Not supported file type. ERROR\n", filename);
e3aaff8e
 	    return -1;
     }
 
     if(!ret)
 	mdprintf(odesc, "%s: OK\n", filename);
 
8765287e
     /* mdprintf(odesc, "\n"); */ /* Terminate response with a blank line boundary */
e3aaff8e
     return ret;
 }
 
a57e3d41
 int scanfd(const int fd, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, const struct cfgstruct *copt, int odesc)
7708ddfc
 {
 	int ret;
 	const char *virname;
 	struct stat statbuf;
557b40cc
 	char fdstr[32];      
7708ddfc
 
 
     if(fstat(fd, &statbuf) == -1)
 	return -1;
 
     if(!S_ISREG(statbuf.st_mode))
 	return -1;
 
557b40cc
     snprintf(fdstr, sizeof(fdstr), "fd[%d]", fd);
 
a57e3d41
     ret = cl_scandesc(fd, &virname, scanned, engine, limits, options);
7708ddfc
 
     if(ret == CL_VIRUS) {
557b40cc
 	mdprintf(odesc, "%s: %s FOUND\n", fdstr, virname);
 	logg("%s: %s FOUND\n", fdstr, virname);
 	virusaction(fdstr, virname, copt);
7708ddfc
     } else if(ret != CL_CLEAN) {
557b40cc
 	mdprintf(odesc, "%s: %s ERROR\n", fdstr, cl_strerror(ret));
 	logg("%s: %s ERROR\n", fdstr, cl_strerror(ret));
7708ddfc
     } else {
557b40cc
 	mdprintf(odesc, "%s: OK\n", fdstr);
7708ddfc
         if(logok)
557b40cc
 	    logg("%s: OK\n", fdstr); 
7708ddfc
     }
 
     return ret;
 }
 
a57e3d41
 int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, const struct cfgstruct *copt)
e3aaff8e
 {
1095156a
 	int ret, sockfd, acceptd;
 	int tmpd, bread, retval, timeout, btread;
 	unsigned int port = 0, portscan = 1000, min_port, max_port;
 	unsigned long int size = 0, maxsize = 0;
29c4d783
 	short bound = 0, rnd_port_first = 1;
fb787a06
 	const char *virname;
a0283d44
 	char buff[FILEBUFF];
e3aaff8e
 	struct sockaddr_in server;
a7282c2f
 	struct hostent he;
1095156a
 	const struct cfgstruct *cpt;
0e621e7d
 	char *tmpname;
e3aaff8e
 
32c970f8
 
29c4d783
     /* get min port */
81837459
     min_port = cfgopt(copt, "StreamMinPort")->numarg;
     if(min_port < 1024 || min_port > 65535)
29c4d783
 	min_port = 1024;
 
     /* get max port */
81837459
     max_port = cfgopt(copt, "StreamMaxPort")->numarg;
     if(max_port < min_port || max_port > 65535)
 	max_port = 65535;
29c4d783
 
     /* bind to a free port */
9d3c38ba
     while(!bound && --portscan) {
29c4d783
 	if(rnd_port_first) {
 	    /* try a random port first */
 	    port = min_port + cli_rndnum(max_port - min_port + 1);
 	    rnd_port_first = 0;
 	} else {
 	    /* try the neighbor ports */
 	    if(--port < min_port)
 		port=max_port;
 	}
e3aaff8e
 
 	memset((char *) &server, 0, sizeof(server));
 	server.sin_family = AF_INET;
 	server.sin_port = htons(port);
a0283d44
 
81837459
 	if((cpt = cfgopt(copt, "TCPAddr"))->enabled) {
a7282c2f
 	    if(r_gethostbyname(cpt->strarg, &he, buff, sizeof(buff)) == -1) {
 		logg("!r_gethostbyname(%s) error: %s\n", cpt->strarg, strerror(errno));
 		mdprintf(odesc, "r_gethostbyname(%s) ERROR\n", cpt->strarg);
a0283d44
 		return -1;
 	    }
a7282c2f
 	    server.sin_addr = *(struct in_addr *) he.h_addr_list[0];
a0283d44
 	} else
 	    server.sin_addr.s_addr = INADDR_ANY;
e3aaff8e
 
 	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
 	    continue;
 
 	if(bind(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) == -1)
67118e92
 	    closesocket(sockfd);
e3aaff8e
 	else
55216b6e
 	    bound = 1;
e3aaff8e
     }
32c970f8
 
81837459
     timeout = cfgopt(copt, "ReadTimeout")->numarg;
32c970f8
     if(timeout == 0)
5b9ac68d
     	timeout = -1;
e3aaff8e
 
55216b6e
     if(!bound && !portscan) {
e3aaff8e
 	logg("!ScanStream: Can't find any free port.\n");
65a18d2e
 	mdprintf(odesc, "Can't find any free port. ERROR\n");
32c970f8
 	close(sockfd);
e3aaff8e
 	return -1;
     } else {
 	listen(sockfd, 1);
1095156a
 	if(mdprintf(odesc, "PORT %u\n", port) <= 0) {
c477e3ba
 	    logg("!ScanStream: error transmitting port.\n");
 	    close(sockfd);
 	    return -1;
 	}
e3aaff8e
     }
 
f22f13d9
     switch(retval = poll_fd(sockfd, timeout, 0)) {
32c970f8
 	case 0: /* timeout */
65a18d2e
 	    mdprintf(odesc, "Accept timeout. ERROR\n");
1095156a
 	    logg("!ScanStream %u: accept timeout.\n", port);
32c970f8
 	    close(sockfd);
 	    return -1;
 	case -1:
65a18d2e
 	    mdprintf(odesc, "Accept poll. ERROR\n");
1095156a
 	    logg("!ScanStream %u: accept poll failed.\n", port);
32c970f8
 	    close(sockfd);
 	    return -1;
c238ac42
     }
 
e3aaff8e
     if((acceptd = accept(sockfd, NULL, NULL)) == -1) {
 	close(sockfd);
 	mdprintf(odesc, "accept() ERROR\n");
1095156a
 	logg("!ScanStream %u: accept() failed.\n", port);
e3aaff8e
 	return -1;
     }
 
1095156a
     logg("*Accepted connection on port %u, fd %d\n", port, acceptd);
8139fd99
 
0e621e7d
     if ((tmpname = cli_gentempdesc(NULL, &tmpd)) == NULL) {
32c970f8
 	shutdown(sockfd, 2);
 	close(sockfd);
 	close(acceptd);
65a18d2e
 	mdprintf(odesc, "tempfile() failed. ERROR\n");
1095156a
 	logg("!ScanStream %u: Can't create temporary file.\n", port);
32c970f8
 	return -1;
     }
 
81837459
     maxsize = cfgopt(copt, "StreamMaxLength")->numarg;
32c970f8
 
     btread = sizeof(buff);
 
f22f13d9
     while((retval = poll_fd(acceptd, timeout, 0)) == 1) {
67118e92
 	bread = recv(acceptd, buff, btread, 0);
32c970f8
 	if(bread <= 0)
 	    break;
 	size += bread;
 
 	if(writen(tmpd, buff, bread) != bread) {
e3aaff8e
 	    shutdown(sockfd, 2);
67118e92
 	    closesocket(sockfd);
 	    closesocket(acceptd);
32c970f8
 	    mdprintf(odesc, "Temporary file -> write ERROR\n");
1095156a
 	    logg("!ScanStream %u: Can't write to temporary file.\n", port);
0e621e7d
 	    close(tmpd);
81837459
 	    if(!cfgopt(copt, "LeaveTemporaryFiles")->enabled)
0e621e7d
 		unlink(tmpname);
 	    free(tmpname);
e3aaff8e
 	    return -1;
 	}
 
32c970f8
 	if(maxsize && (size + btread >= maxsize)) {
 	    btread = (maxsize - size); /* only read up to max */
e3aaff8e
 
d0bc0ec1
 	    if(btread <= 0) {
1095156a
 		logg("^ScanStream %u: Size limit reached (max: %lu)\n", port, maxsize);
32c970f8
 	    	break; /* Scan what we have */
c238ac42
 	    }
e3aaff8e
 	}
32c970f8
     }
e3aaff8e
 
32c970f8
     switch(retval) {
 	case 0: /* timeout */
720fd280
 	    mdprintf(odesc, "read timeout ERROR\n");
1095156a
 	    logg("!ScanStream %u: read timeout.\n", port);
0e621e7d
 	    break;
32c970f8
 	case -1:
720fd280
 	    mdprintf(odesc, "read poll ERROR\n");
1095156a
 	    logg("!ScanStream %u: read poll failed.\n", port);
0e621e7d
 	    break;
32c970f8
     }
e3aaff8e
 
6d5c43a1
     if(retval == 1) {
 	lseek(tmpd, 0, SEEK_SET);
a57e3d41
 	ret = cl_scandesc(tmpd, &virname, scanned, engine, limits, options);
6d5c43a1
     } else {
     	ret = -1;
     }
0e621e7d
     close(tmpd);
81837459
     if(!cfgopt(copt, "LeaveTemporaryFiles")->enabled)
0e621e7d
 	unlink(tmpname);
     free(tmpname);
e3aaff8e
 
67118e92
     closesocket(acceptd);
     closesocket(sockfd);
e3aaff8e
 
     if(ret == CL_VIRUS) {
 	mdprintf(odesc, "stream: %s FOUND\n", virname);
1095156a
 	logg("stream %u: %s FOUND\n", port, virname);
557b40cc
 	virusaction("stream", virname, copt);
e3aaff8e
     } else if(ret != CL_CLEAN) {
6d5c43a1
     	if(retval == 1) {
 	    mdprintf(odesc, "stream: %s ERROR\n", cl_strerror(ret));
1095156a
 	    logg("stream %u: %s ERROR\n", port, cl_strerror(ret));
6d5c43a1
 	}
ee039e40
     } else {
e3aaff8e
 	mdprintf(odesc, "stream: OK\n");
7708ddfc
         if(logok)
1095156a
 	    logg("stream %u: OK\n", port); 
ee039e40
     }
e3aaff8e
 
     return ret;
 }