clamd/session.c
ace125c7
 /*
4bed6861
  *  Copyright (C) 2002 - 2005 Tomasz Kojm <tkojm@clamav.net>
ace125c7
  *
  *  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
30738099
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
ace125c7
  */
 
b3483842
 #ifdef	_MSC_VER
 #include <winsock.h>
 #endif
 
24791abc
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
ace125c7
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
b3483842
 #ifdef	HAVE_UNISTD_H
ace125c7
 #include <unistd.h>
b3483842
 #endif
ace125c7
 #include <sys/types.h>
b3483842
 #ifndef	C_WINDOWS
a94c6905
 #include <dirent.h>
ace125c7
 #include <sys/socket.h>
e1c43c9b
 #include <sys/time.h>
b3483842
 #endif
ace125c7
 #include <pthread.h>
 #include <time.h>
 #include <signal.h>
4775d04e
 #include <errno.h>
b3483842
 #include <stddef.h>
ace125c7
 
e303cf29
 #include "libclamav/clamav.h"
 #include "libclamav/str.h"
 
 #include "shared/cfgparser.h"
 #include "shared/memory.h"
 #include "shared/output.h"
dcba96cb
 #include "shared/misc.h"
e303cf29
 
ace125c7
 #include "others.h"
 #include "scanner.h"
 #include "server.h"
 #include "clamuko.h"
 #include "session.h"
a94c6905
 #include "thrmgr.h"
 #include "shared.h"
ace125c7
 
10e2e41f
 static pthread_mutex_t ctime_mutex = PTHREAD_MUTEX_INITIALIZER;
a94c6905
 extern int progexit;
 
 struct multi_tag {
     int sd;
     int options;
     const struct cfgstruct *copt;
     char *fname;
     const struct cl_node *root;
     const struct cl_limits *limits;
 };
 
 void multiscanfile(void *arg)
 {
 	struct multi_tag *tag = (struct multi_tag *) arg;
 	const char *virname;
b3483842
 #ifndef	C_WINDOWS
a94c6905
         sigset_t sigset;
b3483842
 #endif
a94c6905
 	int ret;
 
 
b3483842
 #ifndef	C_WINDOWS
a94c6905
     /* ignore all signals */
     sigfillset(&sigset);
     pthread_sigmask(SIG_SETMASK, &sigset, NULL);
b3483842
 #endif
a94c6905
 
     ret = cl_scanfile(tag->fname, &virname, NULL, tag->root, 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;
 }
 
 static int multiscan(const char *dirname, const struct cl_node *root, const struct cl_limits *limits, int options, const struct cfgstruct *copt, int odesc, unsigned int *reclev, threadpool_t *multi_pool)
 {
 	DIR *dd;
 	struct dirent *dent;
 #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
 	union {
 	    struct dirent d;
 	    char b[offsetof(struct dirent, d_name) + NAME_MAX + 1];
 	} result;
 #endif
 	struct stat statbuf;
 	char *fname;
 	int scanret = 0;
 	unsigned int maxdirrec = 0;
 	struct multi_tag *scandata;
 
 
     maxdirrec = cfgopt(copt, "MaxDirectoryRecursion")->numarg;
     if(maxdirrec) {
 	if(*reclev > maxdirrec) {
 	    logg("*multiscan: Directory recursion limit exceeded at %s\n", dirname);
 	    return 0;
 	}
 	(*reclev)++;
     }
 
     if((dd = opendir(dirname)) != NULL) {
 #ifdef HAVE_READDIR_R_3
 	while(!readdir_r(dd, &result.d, &dent) && dent) {
 #elif defined(HAVE_READDIR_R_2)
 	while((dent = (struct dirent *) readdir_r(dd, &result.d))) {
 #else
 	while((dent = readdir(dd))) {
 #endif
 	    if (!is_fd_connected(odesc)) {
 		logg("multiscan: Client disconnected\n");
 		closedir(dd);
 		return -1;
 	    }
 
 	    if(progexit) {
 		closedir(dd);
 		return -1;
 	    }
 
b3483842
 #if	(!defined(C_INTERIX)) && (!defined(C_WINDOWS)) && (!defined(C_CYGWIN))
a94c6905
 	    if(dent->d_ino)
 #endif
 	    {
 		if(strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) {
 		    /* build the full name */
 		    fname = (char *) mcalloc(strlen(dirname) + strlen(dent->d_name) + 2, sizeof(char));
 		    if(!fname) {
 			logg("!multiscan: Can't allocate memory for fname\n");
 			closedir(dd);
 			return -1;
 		    }
 		    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")->enabled)) {
 			    if(multiscan(fname, root, limits, options, copt, odesc, reclev, multi_pool) == -1) {
 				free(fname);
 				closedir(dd);
 				return -1;
 			    }
3e185e70
 			    free(fname);
a94c6905
 			} else {
 			    if(S_ISREG(statbuf.st_mode) || (S_ISLNK(statbuf.st_mode) && (checksymlink(fname) == 2) && cfgopt(copt, "FollowFileSymlinks")->enabled)) {
 
 #ifdef C_LINUX
 				if(procdev && (statbuf.st_dev == procdev))
 				    scanret = CL_CLEAN;
 				else
 #endif
 				{
 				    scandata = (struct multi_tag *) mmalloc(sizeof(struct multi_tag));
 				    if(!scandata) {
 					logg("!multiscan: Can't allocate memory for scandata\n");
 					free(fname);
 					closedir(dd);
 					return -1;
 				    }
 				    scandata->sd = odesc;
 				    scandata->options = options;
 				    scandata->copt = copt;
 				    scandata->fname = fname;
 				    scandata->root = root;
 				    scandata->limits = limits;
 				    if(!thrmgr_dispatch(multi_pool, scandata)) {
 					logg("!multiscan: 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 */
b07d2a54
 #ifdef C_WINDOWS
 					Sleep(1);
 #else
a94c6905
 					usleep(200);
b3483842
 #endif
a94c6905
 				}
 			    }
 			}
3e185e70
 		    } else {
 			free(fname);
a94c6905
 		    }
 		}
 	    }
 	}
 	closedir(dd);
     } else {
7e997888
 	return -2;
a94c6905
     }
 
     (*reclev)--;
     return 0;
 }
ace125c7
 
6fab5766
 int command(int desc, const struct cl_node *root, const struct cl_limits *limits, int options, const struct cfgstruct *copt, int timeout)
ace125c7
 {
 	char buff[1025];
e303cf29
 	int bread, opt;
e1c43c9b
 
6fab5766
 
ce246dd4
     bread = readsock(desc, buff, sizeof(buff)-1, '\n', timeout, 0, 1);
     if(bread == -2) /* timeout */
ca43a1fd
 	return -2;
ce246dd4
     if(bread == 0) /* Connection closed */
85c4356a
 	return -1;
fe82c669
     if(bread < 0) {
ce246dd4
 	mdprintf(desc, "ERROR\n");
 	logg("!Command: readsock() failed.\n");
ace125c7
 	return -1;
     }
 
     buff[bread] = 0;
36f2038b
     cli_chomp(buff);
ace125c7
 
     if(!strncmp(buff, CMD1, strlen(CMD1))) { /* SCAN */
ee1b32cc
 	if(scan(buff + strlen(CMD1) + 1, NULL, root, limits, options, copt, desc, 0) == -2)
5c4d94a9
 	    if(cfgopt(copt, "ExitOnOOM")->enabled)
df4a42fe
 		return COMMAND_SHUTDOWN;
ace125c7
 
     } else if(!strncmp(buff, CMD2, strlen(CMD2))) { /* RAWSCAN */
06d4e856
 	opt = options & ~CL_SCAN_ARCHIVE;
ee1b32cc
 	if(scan(buff + strlen(CMD2) + 1, NULL, root, NULL, opt, copt, desc, 0) == -2)
5c4d94a9
 	    if(cfgopt(copt, "ExitOnOOM")->enabled)
df4a42fe
 		return COMMAND_SHUTDOWN;
ace125c7
 
     } else if(!strncmp(buff, CMD3, strlen(CMD3))) { /* QUIT */
24b71d1e
 	return COMMAND_SHUTDOWN;
ace125c7
 
     } else if(!strncmp(buff, CMD4, strlen(CMD4))) { /* RELOAD */
 	mdprintf(desc, "RELOADING\n");
 	return COMMAND_RELOAD;
 
     } else if(!strncmp(buff, CMD5, strlen(CMD5))) { /* PING */
 	mdprintf(desc, "PONG\n");
 
     } else if(!strncmp(buff, CMD6, strlen(CMD6))) { /* CONTSCAN */
ee1b32cc
 	if(scan(buff + strlen(CMD6) + 1, NULL, root, limits, options, copt, desc, 1) == -2)
5c4d94a9
 	    if(cfgopt(copt, "ExitOnOOM")->enabled)
df4a42fe
 		return COMMAND_SHUTDOWN;
ace125c7
 
     } else if(!strncmp(buff, CMD7, strlen(CMD7))) { /* VERSION */
5c4d94a9
 	    const char *dbdir = cfgopt(copt, "DatabaseDirectory")->strarg;
24b71d1e
 	    char *path;
 	    struct cl_cvd *daily;
277fd9a6
 	    struct stat foo;
24b71d1e
 
277fd9a6
 
 	if(!(path = mmalloc(strlen(dbdir) + 30))) {
24b71d1e
 	    mdprintf(desc, "Memory allocation error - SHUTDOWN forced\n");
 	    return COMMAND_SHUTDOWN;
 	}
 
 	sprintf(path, "%s/daily.cvd", dbdir);
277fd9a6
 	if(stat(path, &foo) == -1)
 	    sprintf(path, "%s/daily.inc/daily.info", dbdir);
24b71d1e
 
 	if((daily = cl_cvdhead(path))) {
 		time_t t = (time_t) daily->stime;
 
 	    pthread_mutex_lock(&ctime_mutex);
 	    mdprintf(desc, "ClamAV "VERSION"/%d/%s", daily->version, ctime(&t));
 	    pthread_mutex_unlock(&ctime_mutex);
 	    cl_cvdfree(daily);
 	} else {
 	    mdprintf(desc, "ClamAV "VERSION"\n");
 	}
 
 	free(path);
ace125c7
 
     } else if(!strncmp(buff, CMD8, strlen(CMD8))) { /* STREAM */
ee1b32cc
 	if(scanstream(desc, NULL, root, limits, options, copt) == CL_EMEM)
5c4d94a9
 	    if(cfgopt(copt, "ExitOnOOM")->enabled)
df4a42fe
 		return COMMAND_SHUTDOWN;
ace125c7
 
     } else if(!strncmp(buff, CMD9, strlen(CMD9))) { /* SESSION */
85c4356a
 	return COMMAND_SESSION;
ace125c7
 
     } else if(!strncmp(buff, CMD10, strlen(CMD10))) { /* END */
 	return COMMAND_END;
 
     } else if(!strncmp(buff, CMD11, strlen(CMD11))) { /* SHUTDOWN */
24b71d1e
 	return COMMAND_SHUTDOWN;
ace125c7
 
ee218f69
     } else if(!strncmp(buff, CMD12, strlen(CMD12))) { /* FD */
 	    int fd = atoi(buff + strlen(CMD12) + 1);
 
e303cf29
 	scanfd(fd, NULL, root, limits, options, copt, desc);
ee218f69
 	close(fd); /* FIXME: should we close it here? */
 
a94c6905
     } else if(!strncmp(buff, CMD13, strlen(CMD13))) { /* MULTISCAN */
 	    threadpool_t *multi_pool;
 	    int idletimeout = cfgopt(copt, "IdleTimeout")->numarg;
 	    int max_threads = cfgopt(copt, "MaxThreads")->numarg;
 	    int ret;
 	    unsigned int reclev = 0;
 	    const char *path = buff + strlen(CMD13) + 1;
 	    const char *virname;
 	    struct stat sb;
 
 	if(stat(path, &sb) == -1) {
 	    mdprintf(desc, "Can't stat file %s\n", path);
 	    return -1;
 	}
 
 	if(S_ISDIR(sb.st_mode)) {
 	    if((multi_pool = thrmgr_new(max_threads, idletimeout, multiscanfile)) == NULL) {
 		logg("!thrmgr_new failed for multi_pool\n");
 		mdprintf(desc, "ERROR: thrmgr_new failed for multi_pool\n");
 		return -1;
 	    }
 
 	    ret = multiscan(path, root, limits, options, copt, desc, &reclev, multi_pool);
 	    thrmgr_destroy(multi_pool);
 
 	    if(ret < 0)
 		return -1;
 
 	} else {
 	    ret = cl_scanfile(path, &virname, NULL, root, limits, options);
 
 	    if(ret == CL_VIRUS) {
 		mdprintf(desc, "%s: %s FOUND\n", path, virname);
 		logg("%s: %s FOUND\n", path, virname);
 		virusaction(path, virname, copt);
 	    } else if(ret != CL_CLEAN) {
 		mdprintf(desc, "%s: %s ERROR\n", path, cl_strerror(ret));
 		logg("%s: %s ERROR\n", path, cl_strerror(ret));
 	    } else {
 		mdprintf(desc, "%s: OK\n", path); 
 		if(logok)
 		    logg("%s: OK\n", path);
 	    }
 	}
 
ace125c7
     } else {
 	mdprintf(desc, "UNKNOWN COMMAND\n");
     }
 
     return 0; /* no error and no 'special' command executed */
 }