clamd/scanner.c
e3aaff8e
 /*
086eab5c
  *  Copyright (C) 2007-2009 Sourcefire, Inc.
  *
  *  Authors: Tomasz Kojm, Török Edvin
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
  */
 
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>
e0bb54d7
 #include <dirent.h>
 #ifndef	_WIN32
31e6c6fb
 #include <sys/time.h>
e3aaff8e
 #include <sys/wait.h>
15edd45f
 #include <sys/param.h>
1a4994e3
 #include <signal.h>
e3aaff8e
 #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"
 
064b4a0c
 #include "shared/optparser.h"
bd8603aa
 #include "shared/output.h"
53725d8c
 #include "shared/misc.h"
bd8603aa
 
e3aaff8e
 #include "others.h"
 #include "scanner.h"
afb48b28
 #include "shared.h"
a617b5ea
 #include "thrmgr.h"
949c6fe5
 #include "server.h"
e3aaff8e
 
c695dab4
 #ifdef C_LINUX
 dev_t procdev; /* /proc device */
 #endif
 
949c6fe5
 extern int progexit;
 extern time_t reloaded_time;
 extern pthread_mutex_t reload_mutex;
a617b5ea
 
949c6fe5
 #define BUFFSIZE 1024
 int scan_callback(struct stat *sb, char *filename, const char *msg, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data)
e3aaff8e
 {
949c6fe5
     struct scan_cb_data *scandata = data->data;
     const char *virname;
     int ret;
     int type = scandata->type;
     const struct optstruct *opt;
 
7ff6ec03
     /* detect disconnected socket, 
      * this should NOT detect half-shutdown sockets (SHUT_WR) */
     if (send(scandata->conn->sd, &ret, 0, 0) == -1 && errno != EINTR) {
36e4bc14
 	logg("$Client disconnected while command was active!\n");
 	thrmgr_group_terminate(scandata->conn->group);
727e0560
 	if (reason == visit_file)
 	    free(filename);
36e4bc14
 	return CL_BREAK;
     }
 
949c6fe5
     if (thrmgr_group_need_terminate(scandata->conn->group)) {
 	logg("^Client disconnected while scanjob was active\n");
 	if (reason == visit_file)
 	    free(filename);
 	return CL_BREAK;
     }
     scandata->total++;
     switch (reason) {
 	case error_mem:
 	    if (msg)
 		logg("!Memory allocation failed during cli_ftw() on %s\n",
 		     msg);
 	    else
 		logg("!Memory allocation failed during cli_ftw()\n");
 	    scandata->errors++;
 	    return CL_EMEM;
 	case error_stat:
7e98915f
 	    conn_reply_errno(scandata->conn, msg, "lstat() failed:");
949c6fe5
 	    logg("^lstat() failed on: %s\n", msg);
 	    scandata->errors++;
 	    return CL_SUCCESS;
 	case warning_skipped_dir:
 	    logg("^Directory recursion limit reached, skipping %s\n",
 		     msg);
 	    return CL_SUCCESS;
 	case warning_skipped_link:
fb6fe4f5
 	    logg("$Skipping symlink: %s\n", msg);
949c6fe5
 	    return CL_SUCCESS;
 	case warning_skipped_special:
 	    if (msg == scandata->toplevel_path)
 		conn_reply(scandata->conn, msg, "Not supported file type", "ERROR");
 	    logg("*Not supported file type: %s\n", msg);
 	    return CL_SUCCESS;
 	case visit_directory_toplev:
 	    return CL_SUCCESS;
 	case visit_file:
 	    break;
     }
e3aaff8e
 
949c6fe5
     /* check whether the file is excluded */
 #ifdef C_LINUX
     if(procdev && sb && (sb->st_dev == procdev)) {
 	free(filename);
 	return CL_SUCCESS;
     }
2bb229f6
 #endif
f45d19ac
 
949c6fe5
     if(sb && sb->st_size == 0) { /* empty file */
 	if (msg == scandata->toplevel_path)
 	    conn_reply_single(scandata->conn, filename, "Empty file");
 	free(filename);
 	return CL_SUCCESS;
e3aaff8e
     }
 
949c6fe5
     if (type == TYPE_MULTISCAN) {
 	client_conn_t *client_conn = (client_conn_t *) calloc(1, sizeof(struct client_conn_tag));
 	if(client_conn) {
 	    client_conn->scanfd = -1;
 	    client_conn->sd = scandata->odesc;
 	    client_conn->filename = filename;
 	    client_conn->cmdtype = COMMAND_MULTISCANFILE;
 	    client_conn->term = scandata->conn->term;
 	    client_conn->options = scandata->options;
 	    client_conn->opts = scandata->opts;
 	    client_conn->group = scandata->group;
 	    if(cl_engine_addref(scandata->engine)) {
 		logg("!cl_engine_addref() failed\n");
 		free(filename);
 		return CL_EMEM;
 	    } else {
 		client_conn->engine = scandata->engine;
 		pthread_mutex_lock(&reload_mutex);
 		client_conn->engine_timestamp = reloaded_time;
 		pthread_mutex_unlock(&reload_mutex);
 		if(!thrmgr_group_dispatch(scandata->thr_pool, scandata->group, client_conn)) {
 		    logg("!thread dispatch failed\n");
 		    free(filename);
 		    return CL_EMEM;
e3aaff8e
 		}
 	    }
949c6fe5
 	} else {
 	    logg("!Can't allocate memory for client_conn\n");
 	    scandata->errors++;
 	    free(filename);
 	    return CL_EMEM;
e3aaff8e
 	}
949c6fe5
 	return CL_SUCCESS;
e3aaff8e
     }
 
949c6fe5
     if (access(filename, R_OK)) {
727e0560
 	if (conn_reply(scandata->conn, filename, "Access denied.", "ERROR") == -1) {
 	    free(filename);
0378a9ab
 	    return CL_ETIMEOUT;
727e0560
 	}
949c6fe5
 	logg("*Access denied: %s\n", filename);
 	scandata->errors++;
 	free(filename);
 	return CL_SUCCESS;
a617b5ea
     }
 
949c6fe5
     thrmgr_setactivetask(filename,
 			 type == TYPE_MULTISCAN ? "MULTISCANFILE" : NULL);
     ret = cl_scanfile(filename, &virname, &scandata->scanned, scandata->engine, scandata->options);
     thrmgr_setactivetask(NULL, NULL);
e3aaff8e
 
0378a9ab
     if (thrmgr_group_need_terminate(scandata->conn->group)) {
727e0560
 	free(filename);
0378a9ab
 	logg("*Client disconnected while scanjob was active\n");
 	return ret == CL_ETIMEOUT ? ret : CL_BREAK;
     }
 
949c6fe5
     if (ret == CL_VIRUS) {
 	scandata->infected++;
727e0560
 	if (conn_reply(scandata->conn, filename, virname, "FOUND") == -1) {
 	    free(filename);
0378a9ab
 	    return CL_ETIMEOUT;
727e0560
 	}
949c6fe5
 	logg("~%s: %s FOUND\n", filename, virname);
 	virusaction(filename, virname, scandata->opts);
     } else if (ret != CL_CLEAN) {
 	scandata->errors++;
727e0560
 	if (conn_reply(scandata->conn, filename, cl_strerror(ret), "ERROR") == -1) {
 	    free(filename);
0378a9ab
 	    return CL_ETIMEOUT;
727e0560
 	}
949c6fe5
 	logg("~%s: %s ERROR\n", filename, cl_strerror(ret));
     } else if (logok) {
 	logg("~%s: OK\n", filename);
e3aaff8e
     }
 
949c6fe5
     free(filename);
     if(ret == CL_EMEM) /* stop scanning */
 	return ret;
e0591f05
 
949c6fe5
     if (type == TYPE_SCAN) {
 	/* virus -> break */
 	return ret;
e3aaff8e
     }
 
949c6fe5
     /* keep scanning always */
     return CL_SUCCESS;
e3aaff8e
 }
 
51bbedb1
 int scan_pathchk(const char *path, struct cli_ftw_cbdata *data)
 {
 	struct scan_cb_data *scandata = data->data;
 	const struct optstruct *opt;
2086dc5c
 	struct stat statbuf;
51bbedb1
 
     if((opt = optget(scandata->opts, "ExcludePath"))->enabled) {
 	while(opt) {
 	    if(match_regex(path, opt->strarg) == 1) {
 		if(scandata->type != TYPE_MULTISCAN)
 		    conn_reply_single(scandata->conn, path, "Excluded");
2086dc5c
 		return 1;
51bbedb1
 	    }
 	    opt = (const struct optstruct *) opt->nextarg;
 	}
     }
2086dc5c
 
     if(!optget(scandata->opts, "CrossFilesystems")->enabled) {
 	if(stat(path, &statbuf) == 0) {
 	    if(statbuf.st_dev != scandata->dev) {
 		if(scandata->type != TYPE_MULTISCAN)
 		    conn_reply_single(scandata->conn, path, "Excluded (another filesystem)");
 		return 1;
 	    }
 	}
     }
 
51bbedb1
     return 0;
 }
 
949c6fe5
 int scanfd(const int fd, const client_conn_t *conn, unsigned long int *scanned,
 	   const struct cl_engine *engine,
 	   unsigned int options, const struct optstruct *opts, int odesc, int stream)
725a2969
 {
 	int ret;
 	const char *virname;
 	struct stat statbuf;
 	char fdstr[32];
 
949c6fe5
 	if (stream)
 	    strncpy(fdstr, "stream", sizeof(fdstr));
 	else
 	    snprintf(fdstr, sizeof(fdstr), "fd[%d]", fd);
 	if(fstat(fd, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
 		logg("%s: Not a regular file. ERROR\n", fdstr);
0378a9ab
 		if (conn_reply(conn, fdstr, "Not a regular file", "ERROR") == -1)
 		    return CL_ETIMEOUT;
725a2969
 		return -1;
949c6fe5
 	}
725a2969
 
aa22174b
 	thrmgr_setactivetask(fdstr, NULL);
370892d0
 	ret = cl_scandesc(fd, &virname, scanned, engine, options);
aa22174b
 	thrmgr_setactivetask(NULL, NULL);
725a2969
 
0378a9ab
 	if (thrmgr_group_need_terminate(conn->group)) {
 	    logg("*Client disconnected while scanjob was active\n");
 	    return ret == CL_ETIMEOUT ? ret : CL_BREAK;
 	}
 
725a2969
 	if(ret == CL_VIRUS) {
0378a9ab
 		if (conn_reply(conn, fdstr, virname, "FOUND") == -1)
 		    ret = CL_ETIMEOUT;
725a2969
 		logg("%s: %s FOUND\n", fdstr, virname);
064b4a0c
 		virusaction(fdstr, virname, opts);
725a2969
 	} else if(ret != CL_CLEAN) {
0378a9ab
 		if (conn_reply(conn, fdstr, cl_strerror(ret), "ERROR") == -1)
 		    ret = CL_ETIMEOUT;
725a2969
 		logg("%s: %s ERROR\n", fdstr, cl_strerror(ret));
 	} else {
0378a9ab
 		if (conn_reply_single(conn, fdstr, "OK") == CL_ETIMEOUT)
 		    ret = CL_ETIMEOUT;
725a2969
 		if(logok)
 			logg("%s: OK\n", fdstr);
 	}
 	return ret;
 }
 
949c6fe5
 int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, char term)
e3aaff8e
 {
1095156a
 	int ret, sockfd, acceptd;
5f6edb22
 	int tmpd, bread, retval, firsttimeout, timeout, btread;
949c6fe5
 	unsigned int port = 0, portscan, min_port, max_port;
 	unsigned long int quota = 0, maxsize = 0;
 	short bound = 0;
fb787a06
 	const char *virname;
a0283d44
 	char buff[FILEBUFF];
47b68859
 	char peer_addr[32];
e3aaff8e
 	struct sockaddr_in server;
47b68859
 	struct sockaddr_in peer;
 	socklen_t addrlen;
0e621e7d
 	char *tmpname;
e3aaff8e
 
32c970f8
 
064b4a0c
     min_port = optget(opts, "StreamMinPort")->numarg;
     max_port = optget(opts, "StreamMaxPort")->numarg;
949c6fe5
 
     /* search for a free port to bind to */
     port = cli_rndnum(max_port - min_port);
     bound = 0;
     for (portscan = 0; portscan < 1000; portscan++) {
 	port = (port - 1) % (max_port - min_port + 1);
e3aaff8e
 
 	memset((char *) &server, 0, sizeof(server));
 	server.sin_family = AF_INET;
949c6fe5
 	server.sin_port = htons(min_port + port);
 	server.sin_addr.s_addr = htonl(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);
949c6fe5
 	else {
55216b6e
 	    bound = 1;
949c6fe5
 	    break;
 	}
e3aaff8e
     }
949c6fe5
     port += min_port;
32c970f8
 
064b4a0c
     timeout = optget(opts, "ReadTimeout")->numarg;
32c970f8
     if(timeout == 0)
5f6edb22
 	timeout = -1;
 
     firsttimeout = optget(opts, "CommandReadTimeout")->numarg;
     if (firsttimeout == 0)
 	firsttimeout = -1;
e3aaff8e
 
55216b6e
     if(!bound && !portscan) {
e3aaff8e
 	logg("!ScanStream: Can't find any free port.\n");
949c6fe5
 	mdprintf(odesc, "Can't find any free port. ERROR%c", term);
a9d3aa14
 	closesocket(sockfd);
e3aaff8e
 	return -1;
     } else {
 	listen(sockfd, 1);
949c6fe5
 	if(mdprintf(odesc, "PORT %u%c", port, term) <= 0) {
c477e3ba
 	    logg("!ScanStream: error transmitting port.\n");
a9d3aa14
 	    closesocket(sockfd);
c477e3ba
 	    return -1;
 	}
e3aaff8e
     }
 
5f6edb22
     retval = poll_fd(sockfd, firsttimeout, 0);
949c6fe5
     if (!retval || retval == -1) {
 	const char *reason = !retval ? "timeout" : "poll";
 	mdprintf(odesc, "Accept %s. ERROR%c", reason, term);
 	logg("!ScanStream %u: accept %s.\n", port, reason);
 	closesocket(sockfd);
 	return -1;
c238ac42
     }
 
47b68859
     addrlen = sizeof(peer);
     if((acceptd = accept(sockfd, (struct sockaddr *) &peer, &addrlen)) == -1) {
a9d3aa14
 	closesocket(sockfd);
949c6fe5
 	mdprintf(odesc, "accept() ERROR%c", term);
1095156a
 	logg("!ScanStream %u: accept() failed.\n", port);
e3aaff8e
 	return -1;
     }
 
47b68859
     snprintf(peer_addr, sizeof(peer_addr), "%s", inet_ntoa(peer.sin_addr));
     logg("*Accepted connection from %s on port %u, fd %d\n", peer_addr, port, acceptd);
8139fd99
 
064b4a0c
     if(cli_gentempfd(optget(opts, "TemporaryDirectory")->strarg, &tmpname, &tmpd)) {
32c970f8
 	shutdown(sockfd, 2);
a9d3aa14
 	closesocket(sockfd);
 	closesocket(acceptd);
949c6fe5
 	mdprintf(odesc, "cli_gentempfd() failed. ERROR%c", term);
47b68859
 	logg("!ScanStream(%s@%u): Can't create temporary file.\n", peer_addr, port);
32c970f8
 	return -1;
     }
 
949c6fe5
     quota = maxsize = optget(opts, "StreamMaxLength")->numarg;
32c970f8
 
f22f13d9
     while((retval = poll_fd(acceptd, timeout, 0)) == 1) {
949c6fe5
 	/* only read up to max */
 	btread = (maxsize && (quota < sizeof(buff))) ? quota : sizeof(buff);
 	if (!btread) {
 		logg("^ScanStream(%s@%u): Size limit reached (max: %lu)\n", peer_addr, port, maxsize);
 		break; /* Scan what we have */
 	}
67118e92
 	bread = recv(acceptd, buff, btread, 0);
32c970f8
 	if(bread <= 0)
 	    break;
949c6fe5
 
 	quota -= bread;
32c970f8
 
 	if(writen(tmpd, buff, bread) != bread) {
e3aaff8e
 	    shutdown(sockfd, 2);
67118e92
 	    closesocket(sockfd);
 	    closesocket(acceptd);
949c6fe5
 	    mdprintf(odesc, "Temporary file -> write ERROR%c", term);
47b68859
 	    logg("!ScanStream(%s@%u): Can't write to temporary file.\n", peer_addr, port);
0e621e7d
 	    close(tmpd);
064b4a0c
 	    if(!optget(opts, "LeaveTemporaryFiles")->enabled)
0e621e7d
 		unlink(tmpname);
 	    free(tmpname);
e3aaff8e
 	    return -1;
 	}
32c970f8
     }
e3aaff8e
 
32c970f8
     switch(retval) {
 	case 0: /* timeout */
949c6fe5
 	    mdprintf(odesc, "read timeout ERROR%c", term);
47b68859
 	    logg("!ScanStream(%s@%u): read timeout.\n", peer_addr, port);
0e621e7d
 	    break;
32c970f8
 	case -1:
949c6fe5
 	    mdprintf(odesc, "read poll ERROR%c", term);
47b68859
 	    logg("!ScanStream(%s@%u): read poll failed.\n", peer_addr, port);
0e621e7d
 	    break;
32c970f8
     }
e3aaff8e
 
6d5c43a1
     if(retval == 1) {
 	lseek(tmpd, 0, SEEK_SET);
aa22174b
 	thrmgr_setactivetask(peer_addr, NULL);
370892d0
 	ret = cl_scandesc(tmpd, &virname, scanned, engine, options);
aa22174b
 	thrmgr_setactivetask(NULL, NULL);
6d5c43a1
     } else {
     	ret = -1;
     }
0e621e7d
     close(tmpd);
064b4a0c
     if(!optget(opts, "LeaveTemporaryFiles")->enabled)
0e621e7d
 	unlink(tmpname);
     free(tmpname);
e3aaff8e
 
67118e92
     closesocket(acceptd);
     closesocket(sockfd);
e3aaff8e
 
     if(ret == CL_VIRUS) {
949c6fe5
 	mdprintf(odesc, "stream: %s FOUND%c", virname, term);
47b68859
 	logg("stream(%s@%u): %s FOUND\n", peer_addr, port, virname);
064b4a0c
 	virusaction("stream", virname, opts);
e3aaff8e
     } else if(ret != CL_CLEAN) {
6d5c43a1
     	if(retval == 1) {
949c6fe5
 	    mdprintf(odesc, "stream: %s ERROR%c", cl_strerror(ret), term);
47b68859
 	    logg("stream(%s@%u): %s ERROR\n", peer_addr, port, cl_strerror(ret));
6d5c43a1
 	}
ee039e40
     } else {
949c6fe5
 	mdprintf(odesc, "stream: OK%c", term);
7708ddfc
         if(logok)
47b68859
 	    logg("stream(%s@%u): OK\n", peer_addr, port); 
ee039e40
     }
e3aaff8e
 
     return ret;
 }