clamd/scanner.c
b151ef55
 /*
f58ad3be
  *  Copyright (C) 2002 - 2005 Tomasz Kojm <tkojm@clamav.net>
b151ef55
  *
  *  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.
  */
 
8b242bb9
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
b151ef55
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/stat.h>
 #include <sys/types.h>
e1c43c9b
 #include <sys/time.h>
b151ef55
 #include <sys/wait.h>
771e8818
 #include <sys/param.h>
b151ef55
 #include <dirent.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
0c5220a5
 #include <arpa/inet.h>
 #include <netdb.h>
b151ef55
 #include <clamav.h>
1bffe926
 #include <pthread.h>
e1c43c9b
 
a45ec9cc
 #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
 #include <limits.h>
8c7e16b8
 #include <stddef.h>
a45ec9cc
 #endif
 
36f2038b
 #include "cfgparser.h"
b151ef55
 #include "others.h"
 #include "scanner.h"
 #include "defaults.h"
36f2038b
 #include "memory.h"
 #include "shared.h"
 #include "output.h"
b151ef55
 
f91f55e0
 #include "../libclamav/others.h"
 
096e5bbd
 #ifdef C_LINUX
 dev_t procdev; /* /proc device */
 #endif
 
771e8818
 /* Maximum filenames under various systems - njh */
 #ifndef	NAME_MAX	/* e.g. Linux */
 # ifdef	MAXNAMELEN	/* e.g. Solaris */
 #   define	NAME_MAX	MAXNAMELEN
 # else
 #   ifdef	FILENAME_MAX	/* e.g. SCO */
 #     define	NAME_MAX	FILENAME_MAX
 #   endif
 # endif
 #endif
 
1bffe926
 pthread_mutex_t gh_mutex = PTHREAD_MUTEX_INITIALIZER;
 
b151ef55
 int checksymlink(const char *path)
 {
 	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;
 }
 
 /* :set nowrap, if you don't like this style ;)) */
5aad82e2
 int dirscan(const char *dirname, const char **virname, unsigned long int *scanned, const struct cl_node *root, const struct cl_limits *limits, int options, const struct cfgstruct *copt, int odesc, unsigned int *reclev, short contscan)
b151ef55
 {
 	DIR *dd;
 	struct dirent *dent;
ec748835
 #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
8c7e16b8
 	union {
 	    struct dirent d;
 	    char b[offsetof(struct dirent, d_name) + NAME_MAX + 1];
 	} result;
a45ec9cc
 #endif
b151ef55
 	struct stat statbuf;
 	struct cfgstruct *cpt;
 	char *fname;
511eef51
 	int ret = 0, scanret = 0, maxdirrec = 0;
b151ef55
 
511eef51
 
     if((cpt = cfgopt(copt, "MaxDirectoryRecursion")))
 	maxdirrec = cpt->numarg;
     else
 	maxdirrec = CL_DEFAULT_MAXDIRREC;
 
     if(maxdirrec) {
 	if(*reclev > maxdirrec) {
 	    logg("*Directory recursion limit exceeded at %s\n", dirname);
 	    return 0;
b151ef55
 	}
511eef51
 	(*reclev)++;
b151ef55
     }
 
     if((dd = opendir(dirname)) != NULL) {
ec748835
 #ifdef HAVE_READDIR_R_3
8c7e16b8
 	while(!readdir_r(dd, &result.d, &dent) && dent) {
ec748835
 #elif defined(HAVE_READDIR_R_2)
8c7e16b8
 	while((dent = (struct dirent *) readdir_r(dd, &result.d))) {
ec748835
 #else
b151ef55
 	while((dent = readdir(dd))) {
ec748835
 #endif
f4cbee32
 	    if (!is_fd_connected(odesc)) {
 		logg("Client disconnected\n");
 		closedir(dd);
 		return 1;
 	    }
618a038b
 #ifndef C_INTERIX
 	    if(dent->d_ino)
 #endif
 	    {
b151ef55
 		if(strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) {
 		    /* build the full name */
36f2038b
 		    fname = (char *) mcalloc(strlen(dirname) + strlen(dent->d_name) + 2, sizeof(char));
b151ef55
 		    sprintf(fname, "%s/%s", dirname, dent->d_name);
 
 		    /* stat the file */
 		    if(lstat(fname, &statbuf) != -1) {
 			if((S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) || (S_ISLNK(statbuf.st_mode) && (checksymlink(fname) == 1) && cfgopt(copt, "FollowDirectorySymlinks"))) {
 			    if(dirscan(fname, virname, scanned, root, limits, options, copt, odesc, reclev, contscan) == 1) {
 				free(fname);
 				closedir(dd);
 				return 1;
 			    }
e8217f5a
 			} else {
 			    if(S_ISREG(statbuf.st_mode) || (S_ISLNK(statbuf.st_mode) && (checksymlink(fname) == 2) && cfgopt(copt, "FollowFileSymlinks"))) {
096e5bbd
 
 #ifdef C_LINUX
b44f4315
 				if(procdev && (statbuf.st_dev == procdev))
 				    scanret = CL_CLEAN;
 				else
096e5bbd
 #endif
b44f4315
 				    scanret = cl_scanfile(fname, virname, scanned, root, limits, options);
 
096e5bbd
 				if(scanret == CL_VIRUS) {
 
b151ef55
 				    mdprintf(odesc, "%s: %s FOUND\n", fname, *virname);
 				    logg("%s: %s FOUND\n", fname, *virname);
a500bc14
 				    virusaction(fname, *virname, copt);
b151ef55
 				    if(!contscan) {
 					closedir(dd);
 					free(fname);
 					return 1;
 				    } else
 					ret = 2;
ee1b32cc
 
442d8407
 				} else if(scanret != CL_CLEAN) {
a6935bfa
 				    mdprintf(odesc, "%s: %s ERROR\n", fname, cl_strerror(scanret));
3c572030
 				    logg("%s: %s ERROR\n", fname, cl_strerror(scanret));
ee1b32cc
 				    if(scanret == CL_EMEM) {
 					closedir(dd);
 				        free(fname);
 				        return -2;
 				    }
 
096e5bbd
 				} else if(logok) {
50099661
 				    logg("%s: OK\n", fname);
b151ef55
 				}
e8217f5a
 			    }
 			}
b151ef55
 		    }
 
 		    free(fname);
 		}
 	    }
 	}
e8217f5a
 	closedir(dd);
b151ef55
     } else {
 	return -1;
     }
 
     (*reclev)--;
     return ret;
 
 }
 
 int scan(const char *filename, unsigned long int *scanned, const struct cl_node *root, const struct cl_limits *limits, int options, const struct cfgstruct *copt, int odesc, short contscan)
 {
 	struct stat sb;
d99b1840
 	int ret = 0;
 	unsigned int reclev = 0;
5aad82e2
 	const char *virname;
b151ef55
 
 
     /* stat file */
     if(lstat(filename, &sb) == -1) {
8b243778
 	mdprintf(odesc, "%s: lstat() failed. ERROR\n", filename);
b151ef55
 	return -1;
     }
 
e82a5185
     /* check permissions  */
     if(access(filename, R_OK)) {
 	mdprintf(odesc, "%s: Access denied. ERROR\n", filename);
 	return -1;
     }
 
b151ef55
     switch(sb.st_mode & S_IFMT) {
 	case S_IFLNK:
 	    if(!cfgopt(copt, "FollowFileSymlinks"))
 		break;
 	    /* else go to the next case */
 	case S_IFREG: 
 	    if(sb.st_size == 0) { /* empty file */
 		mdprintf(odesc, "%s: Empty file\n", filename);
 		return 0;
 	    }
096e5bbd
 #ifdef C_LINUX
b44f4315
 	    if(procdev && (sb.st_dev == procdev))
 		ret = CL_CLEAN;
 	    else
096e5bbd
 #endif
b44f4315
 		ret = cl_scanfile(filename, &virname, scanned, root, limits, options);
096e5bbd
 
b151ef55
 	    if(ret == CL_VIRUS) {
 		mdprintf(odesc, "%s: %s FOUND\n", filename, virname);
 		logg("%s: %s FOUND\n", filename, virname);
a500bc14
 		virusaction(filename, virname, copt);
b151ef55
 	    } else if(ret != CL_CLEAN) {
c6259ac5
 		mdprintf(odesc, "%s: %s ERROR\n", filename, cl_strerror(ret));
 		logg("%s: %s ERROR\n", filename, cl_strerror(ret));
ee1b32cc
 		if(ret == CL_EMEM)
 		    return -2;
50099661
 	    } else if (logok) {
 		logg("%s: OK\n", filename);
 	    }
b151ef55
 	    break;
 	case S_IFDIR:
 	    ret = dirscan(filename, &virname, scanned, root, limits, options, copt, odesc, &reclev, contscan);
 	    break;
 	default:
8b243778
 	    mdprintf(odesc, "%s: Not supported file type. ERROR\n", filename);
b151ef55
 	    return -1;
     }
 
     if(!ret)
 	mdprintf(odesc, "%s: OK\n", filename);
 
     return ret;
 }
 
ee218f69
 int scanfd(const int fd, unsigned long int *scanned, const struct cl_node *root, const struct cl_limits *limits, int options, const struct cfgstruct *copt, int odesc, short contscan)
 {
 	int ret;
 	const char *virname;
 	struct stat statbuf;
a500bc14
 	char fdstr[32];      
ee218f69
 
 
     if(fstat(fd, &statbuf) == -1)
 	return -1;
 
     if(!S_ISREG(statbuf.st_mode))
 	return -1;
 
a500bc14
     snprintf(fdstr, sizeof(fdstr), "fd[%d]", fd);
 
ee218f69
     ret = cl_scandesc(fd, &virname, scanned, root, limits, options);
 
     if(ret == CL_VIRUS) {
a500bc14
 	mdprintf(odesc, "%s: %s FOUND\n", fdstr, virname);
 	logg("%s: %s FOUND\n", fdstr, virname);
 	virusaction(fdstr, virname, copt);
ee218f69
     } else if(ret != CL_CLEAN) {
a500bc14
 	mdprintf(odesc, "%s: %s ERROR\n", fdstr, cl_strerror(ret));
 	logg("%s: %s ERROR\n", fdstr, cl_strerror(ret));
ee218f69
     } else {
a500bc14
 	mdprintf(odesc, "%s: OK\n", fdstr);
ee218f69
         if(logok)
a500bc14
 	    logg("%s: OK\n", fdstr); 
ee218f69
     }
 
     return ret;
 }
 
b151ef55
 int scanstream(int odesc, unsigned long int *scanned, const struct cl_node *root, const struct cl_limits *limits, int options, const struct cfgstruct *copt)
 {
f58ad3be
 	int ret, portscan = CL_DEFAULT_MAXPORTSCAN, sockfd, port = 0, acceptd;
166069c2
 	int tmpd, bread, retval, timeout, btread, min_port, max_port;
b151ef55
 	long int size = 0, maxsize = 0;
166069c2
 	short bound = 0, rnd_port_first = 1;
5aad82e2
 	const char *virname;
0c5220a5
 	char buff[FILEBUFF];
b151ef55
 	struct sockaddr_in server;
0c5220a5
 	struct hostent *he;
b151ef55
 	struct cfgstruct *cpt;
e1ef4066
 	FILE *tmp = NULL;
b151ef55
 
3cb43aaa
 
166069c2
     /* get min port */
     if((cpt = cfgopt(copt, "StreamMinPort"))) {
 	if(cpt->numarg < 1024 || cpt->numarg > 65535)
 	    min_port = 1024;
 	else 
 	    min_port = cpt->numarg;
     } else 
 	min_port = 1024;
 
     /* get max port */
     if((cpt = cfgopt(copt, "StreamMaxPort"))) {
 	if(cpt->numarg < min_port || cpt->numarg > 65535)
 	    max_port = 65535;
 	else
 	    max_port = cpt->numarg;
     } else
bf22198b
 	max_port = 2048;
166069c2
 
     /* bind to a free port */
cfa196eb
     while(!bound && --portscan) {
166069c2
 	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;
 	}
b151ef55
 
 	memset((char *) &server, 0, sizeof(server));
 	server.sin_family = AF_INET;
 	server.sin_port = htons(port);
0c5220a5
 
 	if((cpt = cfgopt(copt, "TCPAddr"))) {
1bffe926
 	    pthread_mutex_lock(&gh_mutex);
 	    if((he = gethostbyname(cpt->strarg)) == 0) {
0c5220a5
 		logg("!gethostbyname(%s) error: %s\n", cpt->strarg);
 		mdprintf(odesc, "gethostbyname(%s) ERROR\n", cpt->strarg);
1bffe926
 		pthread_mutex_unlock(&gh_mutex);
0c5220a5
 		return -1;
 	    }
 	    server.sin_addr = *(struct in_addr *) he->h_addr_list[0];
e1064f1a
 	    pthread_mutex_unlock(&gh_mutex);
0c5220a5
 	} else
 	    server.sin_addr.s_addr = INADDR_ANY;
b151ef55
 
 	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
 	    continue;
 
 	if(bind(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) == -1)
 	    close(sockfd);
 	else
e1ef4066
 	    bound = 1;
b151ef55
     }
3cb43aaa
 
     if((cpt = cfgopt(copt, "ReadTimeout")))
0b1fc504
 	timeout = cpt->numarg;
3cb43aaa
     else
0b1fc504
 	timeout = CL_DEFAULT_SCANTIMEOUT;
3cb43aaa
 
     if(timeout == 0)
65d1b047
     	timeout = -1;
b151ef55
 
e1ef4066
     if(!bound && !portscan) {
b151ef55
 	logg("!ScanStream: Can't find any free port.\n");
8b243778
 	mdprintf(odesc, "Can't find any free port. ERROR\n");
3cb43aaa
 	close(sockfd);
b151ef55
 	return -1;
     } else {
 	listen(sockfd, 1);
 	mdprintf(odesc, "PORT %d\n", port);
     }
 
3cb43aaa
     switch(retval = poll_fd(sockfd, timeout)) {
 	case 0: /* timeout */
8b243778
 	    mdprintf(odesc, "Accept timeout. ERROR\n");
3cb43aaa
 	    logg("!ScanStream: accept timeout.\n");
 	    close(sockfd);
 	    return -1;
 	case -1:
8b243778
 	    mdprintf(odesc, "Accept poll. ERROR\n");
3cb43aaa
 	    logg("!ScanStream: accept poll failed.\n");
 	    close(sockfd);
 	    return -1;
4775d04e
     }
 
b151ef55
     if((acceptd = accept(sockfd, NULL, NULL)) == -1) {
 	close(sockfd);
 	mdprintf(odesc, "accept() ERROR\n");
 	logg("!ScanStream: accept() failed.\n");
 	return -1;
     }
 
e1ef4066
     logg("*Accepted connection on port %d, fd %d\n", port, acceptd);
4cd4319e
 
3cb43aaa
     if((tmp = tmpfile()) == NULL) {
 	shutdown(sockfd, 2);
 	close(sockfd);
 	close(acceptd);
8b243778
 	mdprintf(odesc, "tempfile() failed. ERROR\n");
3cb43aaa
 	logg("!ScanStream: Can't create temporary file.\n");
 	return -1;
     }
     tmpd = fileno(tmp);
 
     if((cpt = cfgopt(copt, "StreamMaxLength")))
 	maxsize = cpt->numarg;
511eef51
     else
 	maxsize = CL_DEFAULT_STREAMMAXLEN;
 
3cb43aaa
 
     btread = sizeof(buff);
 
     while((retval = poll_fd(acceptd, timeout)) == 1) {
 	bread = read(acceptd, buff, btread);
 	if(bread <= 0)
 	    break;
 	size += bread;
 
 	if(writen(tmpd, buff, bread) != bread) {
b151ef55
 	    shutdown(sockfd, 2);
 	    close(sockfd);
e1ef4066
 	    close(acceptd);
3cb43aaa
 	    mdprintf(odesc, "Temporary file -> write ERROR\n");
 	    logg("!ScanStream: Can't write to temporary file.\n");
 	    if(tmp)
 		fclose(tmp);
1ae303c2
 	    close(tmpd);
b151ef55
 	    return -1;
 	}
 
3cb43aaa
 	if(maxsize && (size + btread >= maxsize)) {
 	    btread = (maxsize - size); /* only read up to max */
b151ef55
 
9c8806fb
 	    if(btread <= 0) {
3cb43aaa
 		logg("^ScanStream: Size limit reached ( max: %d)\n", maxsize);
 	    	break; /* Scan what we have */
4775d04e
 	    }
b151ef55
 	}
3cb43aaa
     }
b151ef55
 
3cb43aaa
     switch(retval) {
 	case 0: /* timeout */
fdbbe9f3
 	    mdprintf(odesc, "read timeout ERROR\n");
 	    logg("!ScanStream: read timeout.\n");
1ae303c2
 	    break;
3cb43aaa
 	case -1:
fdbbe9f3
 	    mdprintf(odesc, "read poll ERROR\n");
 	    logg("!ScanStream: read poll failed.\n");
1ae303c2
 	    break;
3cb43aaa
     }
b151ef55
 
3cb43aaa
     lseek(tmpd, 0, SEEK_SET);
     ret = cl_scandesc(tmpd, &virname, scanned, root, limits, options);
     if(tmp)
 	fclose(tmp);
b151ef55
 
     close(acceptd);
     close(sockfd);
 
     if(ret == CL_VIRUS) {
 	mdprintf(odesc, "stream: %s FOUND\n", virname);
 	logg("stream: %s FOUND\n", virname);
a500bc14
 	virusaction("stream", virname, copt);
b151ef55
     } else if(ret != CL_CLEAN) {
c6259ac5
 	mdprintf(odesc, "stream: %s ERROR\n", cl_strerror(ret));
 	logg("stream: %s ERROR\n", cl_strerror(ret));
50099661
     } else {
b151ef55
 	mdprintf(odesc, "stream: OK\n");
ee218f69
         if(logok)
50099661
 	    logg("stream: OK\n"); 
     }
b151ef55
 
     return ret;
 }