ClamAV is an open source (GPLv2) anti-virus toolkit.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
clamav/clamav-devel/clamd/server-th.c

683 lines
17 KiB

/*
* Copyright (C) 2002 - 2004 Tomasz Kojm <tkojm@clamav.net>
*
* 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.
*/
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <time.h>
#include <signal.h>
#include "cfgfile.h"
#include "others.h"
#include "defaults.h"
#include "scanner.h"
#include "server.h"
#include "clamuko.h"
#include "tests.h"
#include "session.h"
#include "../target.h"
#ifdef CLAMUKO
pthread_t clamukoid;
#endif
#ifdef TARGET_OS_DARWIN5_5
#define pthread_sigmask(A, B, C) sigprocmask((A), (B), (C))
#define pthread_kill(A, B) { }
#endif
void *threadscanner(void *arg)
{
struct thrarg *tharg = (struct thrarg *) arg;
sigset_t sigset;
int ret;
/* ignore all signals */
sigfillset(&sigset);
pthread_sigmask(SIG_SETMASK, &sigset, NULL);
ret = command(ths[tharg->sid].desc, tharg->root, tharg->limits, tharg->options, tharg->copt);
switch(ret) {
case COMMAND_QUIT:
progexit = 1;
break;
case COMMAND_RELOAD:
reload = 1;
break;
}
close(ths[tharg->sid].desc);
ths[tharg->sid].active = 0;
/* this mutex is rather useless */
/* pthread_mutex_unlock(&ths[tharg->sid].mutex); */
free(tharg);
return NULL;
}
/* this function takes care for threads, exit and various checks */
void *threadwatcher(void *arg)
{
struct thrwarg *thwarg = (struct thrwarg *) arg;
struct cfgstruct *cpt;
sigset_t sigset;
int i, j, ret, virnum;
unsigned long int timer = 0;
unsigned int timeout, threads, selfchk;
short int need_wait = 0, do_loop = 0, db_problem = 0;
const char *dbdir;
struct cl_stat dbstat;
#ifdef CLAMUKO
struct thrarg *tharg;
pthread_attr_t thattr;
int maxwait;
#endif
/* ignore all signals (except for SIGSEGV) */
sigfillset(&sigset);
sigdelset(&sigset, SIGSEGV);
pthread_sigmask(SIG_SETMASK, &sigset, NULL);
#ifdef C_LINUX
logg("*ThreadWatcher: Started in process %d\n", getpid());
#endif
if((cpt = cfgopt(thwarg->copt, "MaxThreads")))
threads = cpt->numarg;
else
threads = CL_DEFAULT_MAXTHREADS;
if((cpt = cfgopt(thwarg->copt, "SelfCheck")))
selfchk = cpt->numarg;
else
selfchk = CL_DEFAULT_SELFCHECK;
if(!selfchk) {
logg("^Self checking disabled.\n");
} else
logg("Self checking every %d seconds.\n", selfchk);
if((cpt = cfgopt(thwarg->copt, "ThreadTimeout")))
timeout = cpt->numarg;
else
timeout = CL_DEFAULT_SCANTIMEOUT;
if(!timeout) {
logg("^Timeout disabled.\n");
} else
logg("Timeout set to %d seconds.\n", timeout);
if((cpt = cfgopt(thwarg->copt, "DatabaseDirectory")) || (cpt = cfgopt(thwarg->copt, "DataDirectory")))
dbdir = cpt->strarg;
else
dbdir = cl_retdbdir();
memset(&dbstat, 0, sizeof(struct cl_stat));
cl_statinidir(dbdir, &dbstat);
for(i = 0; ; i++) {
if(i == threads)
i = 0;
/* check time */
if(timeout && ths[i].active) /* races are harmless here (timeout is re-set) */
if(time(NULL) - ths[i].start > timeout) {
pthread_cancel(ths[i].id);
mdprintf(ths[i].desc, "Session(%d): Time out ERROR\n", i);
close(ths[i].desc);
logg("Session %d stopped due to timeout.\n", i);
ths[i].active = 0;
/* pthread_mutex_unlock(&ths[i].mutex); */
}
/* cancel all threads in case of quit */
if(progexit == 1) {
#ifdef CLAMUKO
/* stop clamuko */
if(clamuko_running) {
logg("Stopping Clamuko...\n");
pthread_kill(clamukoid, SIGUSR1);
/* we must wait for Dazuko unregistration */
maxwait = CL_DEFAULT_MAXWHILEWAIT * 5;
while(clamuko_running && maxwait--)
usleep(200000);
if(!maxwait && clamuko_running)
logg("!Critical error: Can't stop Clamuko.\n");
}
#endif
for(j = 0; j < threads; j++)
if(ths[j].active) {
pthread_cancel(ths[j].id);
mdprintf(ths[j].desc, "Session(%d): Stopped (exiting)\n", j);
close(ths[j].desc);
logg("Session %d stopped (exiting).\n", j);
/* pthread_mutex_unlock(&ths[j].mutex); */
}
#ifndef C_BSD
logg("*Freeing trie structure.\n");
cl_freetrie(*thwarg->root);
#endif
logg("*Shutting down the main socket.\n");
shutdown(thwarg->socketd, 2);
logg("*Closing the main socket.\n");
close(thwarg->socketd);
if((cpt = cfgopt(thwarg->copt, "LocalSocket"))) {
if(unlink(cpt->strarg) == -1)
logg("!Can't unlink the socket file %s\n", cpt->strarg);
else
logg("Socket file removed.\n");
}
if((cpt = cfgopt(thwarg->copt, "PidFile"))) {
if(unlink(cpt->strarg) == -1)
logg("!Can't unlink the pid file %s\n", cpt->strarg);
else
logg("Pid file removed.\n");
}
logg("*Freeing stat structure.\n");
cl_statfree(&dbstat);
progexit = 2;
logg("*Exit level %d, ThreadWatcher termination.\n", progexit);
return NULL;
}
/* do self checks */
if(selfchk && (db_problem || !(timer % selfchk))) {
/* check the integrity of the database */
if(!reload) {
if(cl_statchkdir(&dbstat) == 1) {
logg("SelfCheck: Database modification detected. Forcing reload.\n");
reload = 1;
cl_statfree(&dbstat);
cl_statinidir(dbdir, &dbstat);
} else
logg("SelfCheck: Database status OK.\n");
if(!testsignature(*thwarg->root)) {
if(db_problem) {
logg("!SelfCheck: Unable to repair internal structure. Exiting.\n");
kill(progpid, SIGTERM);
continue;
}
/* oops */
logg("!SelfCheck: Unable to detect test signature, forcing database reload.\n");
db_problem = 1;
reload = 1;
} else {
logg("*SelfCheck: Integrity OK\n");
db_problem = 0;
}
}
}
timer++;
/* reload the database */
if(reload) {
/* make sure the main thread doesn't start new threads */
do {
usleep(200000);
} while(!main_accept && !main_reload);
/* wait until all working threads are finished */
do {
need_wait = 0;
for(j = 0; j < threads; j++)
if(ths[j].active) {
if(timeout && (time(NULL) - ths[j].start > timeout)) {
do_loop = 1;
break;
} else need_wait = 1;
}
#ifdef CLAMUKO
if(clamuko_running) {
logg("Stopping Clamuko...\n");
pthread_kill(clamukoid, SIGUSR1);
/* we must wait for Dazuko unregistration */
maxwait = CL_DEFAULT_MAXWHILEWAIT * 5;
while(clamuko_running && maxwait--)
usleep(200000);
if(!maxwait && clamuko_running)
logg("!Critical error: Can't stop Clamuko.\n");
/* should we stop here ? */
}
#endif
if(need_wait)
usleep(200000);
if(progexit == 1)
break;
} while(need_wait);
if(progexit == 1) {
reload = 0;
continue;
}
if(do_loop) {
/* some threads must be stopped in the next iteration,
* reload is still == 1
*/
logg("Database reload: some threads must be stopped in the next iteration.\n");
do_loop = 0;
continue;
}
/* relase old structure */
cl_freetrie(*thwarg->root);
*thwarg->root = NULL;
/* reload */
logg("Reading databases from %s\n", dbdir);
cl_statfree(&dbstat);
cl_statinidir(dbdir, &dbstat);
virnum = 0;
if((ret = cl_loaddbdir(dbdir, &*thwarg->root, &virnum))) {
logg("!%s\n", cl_strerror(ret));
kill(progpid, SIGTERM);
/* we stay in reload == 1, so all threads are waiting */
continue;
}
if(! *thwarg->root) {
logg("!Database initialization problem.\n");
kill(progpid, SIGTERM);
} else {
if((ret = cl_buildtrie(*thwarg->root)) != 0) {
logg("!Database initialization error: can't build the trie: %s\n", cl_strerror(ret));
kill(progpid, SIGTERM);
}
/* check integrity */
if(!testsignature(*thwarg->root)) {
logg("!Unable to detect test signature.\n");
kill(progpid, SIGTERM);
}
logg("Database correctly reloaded (%d viruses)\n", virnum);
}
/* start clamuko */
#ifdef CLAMUKO
if(cfgopt(thwarg->copt, "ClamukoScanOnLine")) {
logg("Starting Clamuko...\n");
tharg = (struct thrarg *) mcalloc(1, sizeof(struct thrarg));
tharg->copt = thwarg->copt;
tharg->root = *thwarg->root;
tharg->limits = thwarg->limits;
tharg->options = thwarg->options;
pthread_attr_init(&thattr);
pthread_attr_setdetachstate(&thattr, PTHREAD_CREATE_DETACHED);
pthread_create(&clamukoid, &thattr, clamukoth, tharg);
pthread_attr_destroy(&thattr);
}
#endif
reload = 0;
}
sleep(1);
}
return NULL;
}
int threads;
pthread_t watcherid;
int acceptloop_th(int socketd, struct cl_node *root, const struct cfgstruct *copt)
{
int acceptd, i, options = 0, maxwait;
struct cfgstruct *cpt;
struct thrarg *tharg;
struct thrwarg thwarg;
struct cl_limits limits;
pthread_attr_t thattr;
struct sigaction sigact;
sigset_t sigset;
mode_t old_umask;
#if defined(C_BIGSTACK) || defined(C_BSD)
size_t stacksize;
#endif
memset (&sigact, 0, sizeof(struct sigaction));
/* save the PID */
if((cpt = cfgopt(copt, "PidFile"))) {
FILE *fd;
old_umask = umask(0006);
if((fd = fopen(cpt->strarg, "w")) == NULL) {
logg("!Can't save PID in file %s\n", cpt->strarg);
} else {
fprintf(fd, "%d", getpid());
fclose(fd);
}
umask(old_umask);
}
logg("*Listening daemon: PID: %d\n", getpid());
if((cpt = cfgopt(copt, "MaxThreads")))
threads = cpt->numarg;
else
threads = CL_DEFAULT_MAXTHREADS;
logg("Maximal number of threads: %d\n", threads);
ths = (struct thrsession *) mcalloc(threads, sizeof(struct thrsession));
/*
for(i = 0; i < threads; i++)
pthread_mutex_init(&ths[i].mutex, NULL);
*/
if(cfgopt(copt, "ScanArchive") || cfgopt(copt, "ClamukoScanArchive")) {
/* set up limits */
memset(&limits, 0, sizeof(struct cl_limits));
if((cpt = cfgopt(copt, "ArchiveMaxFileSize"))) {
if((limits.maxfilesize = cpt->numarg))
logg("Archive: Archived file size limit set to %d bytes.\n", limits.maxfilesize);
else
logg("^Archive: File size limit protection disabled.\n");
} else {
limits.maxfilesize = 10485760;
logg("^USING HARDCODED LIMIT: Archive: Archived file size limit set to %d bytes.\n", limits.maxfilesize);
}
if((cpt = cfgopt(copt, "ArchiveMaxRecursion"))) {
if((limits.maxreclevel = cpt->numarg))
logg("Archive: Recursion level limit set to %d.\n", limits.maxreclevel);
else
logg("^Archive: Recursion level limit protection disabled.\n");
} else {
limits.maxreclevel = 5;
logg("^USING HARDCODED LIMIT: Archive: Recursion level set to %d.\n", limits.maxreclevel);
}
if((cpt = cfgopt(copt, "ArchiveMaxFiles"))) {
if((limits.maxfiles = cpt->numarg))
logg("Archive: Files limit set to %d.\n", limits.maxfiles);
else
logg("^Archive: Files limit protection disabled.\n");
} else {
limits.maxfiles = 1000;
logg("^USING HARDCODED LIMIT: Archive: Files limit set to %d.\n", limits.maxfiles);
}
if((cpt = cfgopt(copt, "ArchiveMaxCompressionRatio"))) {
if((limits.maxratio = cpt->numarg))
logg("Archive: Compression ratio limit set to %d.\n", limits.maxratio);
else
logg("^Archive: Compression ratio limit disabled.\n");
} else {
limits.maxratio = 200;
logg("^USING HARDCODED LIMIT: Archive: Compression ratio limit set to %d.\n", limits.maxratio);
}
if(cfgopt(copt, "ArchiveLimitMemoryUsage")) {
limits.archivememlim = 1;
logg("Archive: Limited memory usage.\n");
} else
limits.archivememlim = 0;
}
if(cfgopt(copt, "ScanArchive")) {
logg("Archive support enabled.\n");
options |= CL_ARCHIVE;
if(cfgopt(copt, "ScanRAR")) {
logg("RAR support enabled.\n");
} else {
logg("RAR support disabled.\n");
options |= CL_DISABLERAR;
}
} else {
logg("Archive support disabled.\n");
}
if(cfgopt(copt, "ScanMail")) {
logg("Mail files support enabled.\n");
options |= CL_MAIL;
} else {
logg("Mail files support disabled.\n");
}
if(cfgopt(copt, "ScanOLE2")) {
logg("OLE2 support enabled.\n");
options |= CL_OLE2;
} else {
logg("OLE2 support disabled.\n");
}
/* initialize important global variables */
progexit = 0;
progpid = 0;
reload = 0;
#ifdef CLAMUKO
clamuko_running = 0;
#endif
pthread_attr_init(&thattr);
pthread_attr_setdetachstate(&thattr, PTHREAD_CREATE_DETACHED);
/* run clamuko */
if(cfgopt(copt, "ClamukoScanOnLine"))
#ifdef CLAMUKO
{
tharg = (struct thrarg *) mcalloc(1, sizeof(struct thrarg));
tharg->copt = copt;
tharg->root = root;
tharg->limits = &limits;
tharg->options = options;
pthread_create(&clamukoid, &thattr, clamukoth, tharg);
}
#else
logg("!Clamuko is not available.\n");
#endif
/* start thread watcher */
thwarg.socketd = socketd;
thwarg.copt = copt;
thwarg.root = &root;
thwarg.limits = &limits;
thwarg.options = options;
pthread_create(&watcherid, &thattr, threadwatcher, &thwarg);
/* set up signal handling */
sigfillset(&sigset);
sigdelset(&sigset, SIGINT);
sigdelset(&sigset, SIGTERM);
sigdelset(&sigset, SIGSEGV);
sigdelset(&sigset, SIGHUP);
sigprocmask(SIG_SETMASK, &sigset, NULL);
/* SIGINT, SIGTERM, SIGSEGV */
sigact.sa_handler = sighandler_th;
sigemptyset(&sigact.sa_mask);
sigaddset(&sigact.sa_mask, SIGINT);
sigaddset(&sigact.sa_mask, SIGTERM);
sigaddset(&sigact.sa_mask, SIGHUP);
sigaction(SIGINT, &sigact, NULL);
sigaction(SIGTERM, &sigact, NULL);
#ifndef CL_DEBUG
sigaction(SIGSEGV, &sigact, NULL);
#endif
sigaction(SIGHUP, &sigact, NULL);
/* we need to save program's PID, because under Linux each thread
* has another PID, it works with other OSes as well
*/
progpid = getpid();
#if defined(C_BIGSTACK) || defined(C_BSD)
/*
* njh@bandsman.co.uk:
* libclamav/scanners.c uses a *huge* buffer
* (128K not BUFSIZ from stdio.h).
* We need to allow for that.
*/
pthread_attr_getstacksize(&thattr, &stacksize);
cli_dbgmsg("set stacksize to %u\n", stacksize + SCANBUFF + 64 * 1024);
pthread_attr_setstacksize(&thattr, stacksize + SCANBUFF + 64 * 1024);
#endif
while(progexit != 2) {
/* find a free session */
for(i = 0; ; i++) {
if(i == threads) {
i = 0;
usleep(50000);
}
if(!ths[i].active) {
/* logg("*Found free slot: %d\n", i); */
break;
}
}
main_accept = 1;
if((acceptd = accept(socketd, NULL, NULL)) == -1) {
logg("!accept() failed.\n");
/* exit ? */
continue;
}
main_accept = 0;
if(reload) { /* do not start new threads */
main_reload = 1;
logg("*Main thread: database reloading (waiting).\n");
maxwait = CL_DEFAULT_MAXWHILEWAIT;
while(reload && maxwait--)
sleep(1);
if(!maxwait && reload) {
logg("!Database reloading failed (time exceeded). Exit forced.\n");
progexit = 1;
sleep(10);
exit(1);
}
logg("*Main thread: database reloaded.\n");
main_reload = 0;
}
tharg = (struct thrarg *) mcalloc(1, sizeof(struct thrarg));
tharg->copt = copt;
tharg->sid = i;
tharg->root = root;
tharg->limits = &limits;
tharg->options = options;
ths[i].desc = acceptd;
ths[i].start = time(NULL);
ths[i].active = 1; /* the structure must be activated exactly here
* because we will surely create a race condition
* in other places (if activated in the new thread
* there * will be a race in the main thread (it
* may assign the same thread session once more);
* if activated after pthread_create() the new
* thread may be already finished).
*/
if(pthread_create(&ths[i].id, &thattr, threadscanner, tharg)) {
logg("!Session(%d) did not start. Dropping connection.", i);
close(acceptd);
ths[i].active = 0;
}
}
free(ths);
return 0;
}
void sighandler_th(int sig)
{
time_t currtime;
int maxwait = CL_DEFAULT_MAXWHILEWAIT * 5;
#ifndef CL_DEBUG
int i;
#endif
switch(sig) {
case SIGINT:
case SIGTERM:
progexit = 1;
logg("*Signal %d caught -> exiting.\n", sig);
while(progexit != 2 && maxwait--)
usleep(200000);
if(!maxwait && progexit != 2)
logg("!Critical error: Cannot reach exit level 2.\n");
time(&currtime);
logg("--- Stopped at %s", ctime(&currtime));
exit(0);
break; /* not reached */
#ifndef CL_DEBUG
case SIGSEGV:
logg("Segmentation fault :-( Bye..\n");
for(i = 0; i < threads; i++)
if(ths[i].active)
pthread_kill(ths[i].id, 9);
pthread_kill(watcherid, 9);
exit(11); /* probably not reached at all */
break; /* not reached */
#endif
case SIGHUP:
sighup = 1;
logg("SIGHUP catched: log file re-opened.\n");
break;
}
}