int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsigned int dboptions, const struct optstruct *opts) { int max_threads, max_queue, readtimeout, ret = 0; unsigned int options = 0; char timestr[32]; #ifndef _WIN32 struct sigaction sigact; sigset_t sigset; struct rlimit rlim; #endif mode_t old_umask; const struct optstruct *opt; char buff[BUFFSIZE + 1]; pid_t mainpid; int idletimeout; unsigned long long val; size_t i, j, rr_last = 0; pthread_t accept_th; pthread_mutex_t fds_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t recvfds_mutex = PTHREAD_MUTEX_INITIALIZER; struct acceptdata acceptdata = ACCEPTDATA_INIT(&fds_mutex, &recvfds_mutex); struct fd_data *fds = &acceptdata.recv_fds; time_t start_time, current_time; unsigned int selfchk; threadpool_t *thr_pool; #if defined(FANOTIFY) || defined(CLAMAUTH) pthread_t fan_pid; pthread_attr_t fan_attr; struct thrarg *tharg = NULL; /* shut up gcc */ #endif #ifndef _WIN32 memset(&sigact, 0, sizeof(struct sigaction)); #endif /* set up limits */ if((opt = optget(opts, "MaxScanSize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, opt->numarg))) { logg("!cl_engine_set_num(CL_ENGINE_MAX_SCANSIZE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_SCANSIZE, NULL); if(val) logg("Limits: Global size limit set to %llu bytes.\n", val); else logg("^Limits: Global size limit protection disabled.\n"); if((opt = optget(opts, "MaxFileSize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_FILESIZE, opt->numarg))) { logg("!cl_engine_set_num(CL_ENGINE_MAX_FILESIZE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_FILESIZE, NULL); if(val) logg("Limits: File size limit set to %llu bytes.\n", val); else logg("^Limits: File size limit protection disabled.\n"); #ifndef _WIN32 if(getrlimit(RLIMIT_FSIZE, &rlim) == 0) { if(rlim.rlim_cur < (rlim_t) cl_engine_get_num(engine, CL_ENGINE_MAX_FILESIZE, NULL)) logg("^System limit for file size is lower than engine->maxfilesize\n"); if(rlim.rlim_cur < (rlim_t) cl_engine_get_num(engine, CL_ENGINE_MAX_SCANSIZE, NULL)) logg("^System limit for file size is lower than engine->maxscansize\n"); } else { logg("^Cannot obtain resource limits for file size\n"); } #endif if((opt = optget(opts, "MaxRecursion"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_RECURSION, opt->numarg))) { logg("!cl_engine_set_num(CL_ENGINE_MAX_RECURSION) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_RECURSION, NULL); if(val) logg("Limits: Recursion level limit set to %u.\n", (unsigned int) val); else logg("^Limits: Recursion level limit protection disabled.\n"); if((opt = optget(opts, "MaxFiles"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_FILES, opt->numarg))) { logg("!cl_engine_set_num(CL_ENGINE_MAX_FILES) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_FILES, NULL); if(val) logg("Limits: Files limit set to %u.\n", (unsigned int) val); else logg("^Limits: Files limit protection disabled.\n"); #ifndef _WIN32 if (getrlimit(RLIMIT_CORE, &rlim) == 0) { logg("*Limits: Core-dump limit is %lu.\n", (unsigned long)rlim.rlim_cur); } #endif /* Engine max sizes */ if((opt = optget(opts, "MaxEmbeddedPE"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_EMBEDDEDPE, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_EMBEDDEDPE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_EMBEDDEDPE, NULL); logg("Limits: MaxEmbeddedPE limit set to %llu bytes.\n", val); if((opt = optget(opts, "MaxHTMLNormalize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_HTMLNORMALIZE, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_HTMLNORMALIZE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_HTMLNORMALIZE, NULL); logg("Limits: MaxHTMLNormalize limit set to %llu bytes.\n", val); if((opt = optget(opts, "MaxHTMLNoTags"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_HTMLNOTAGS, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_HTMLNOTAGS) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_HTMLNOTAGS, NULL); logg("Limits: MaxHTMLNoTags limit set to %llu bytes.\n", val); if((opt = optget(opts, "MaxScriptNormalize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCRIPTNORMALIZE, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_SCRIPTNORMALIZE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_SCRIPTNORMALIZE, NULL); logg("Limits: MaxScriptNormalize limit set to %llu bytes.\n", val); if((opt = optget(opts, "MaxZipTypeRcg"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_ZIPTYPERCG, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_ZIPTYPERCG) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_ZIPTYPERCG, NULL); logg("Limits: MaxZipTypeRcg limit set to %llu bytes.\n", val); if(optget(opts, "ScanArchive")->enabled) { logg("Archive support enabled.\n"); options |= CL_SCAN_ARCHIVE; if(optget(opts, "ArchiveBlockEncrypted")->enabled) { logg("Archive: Blocking encrypted archives.\n"); options |= CL_SCAN_BLOCKENCRYPTED; } } else { logg("Archive support disabled.\n"); } if(optget(opts, "AlgorithmicDetection")->enabled) { logg("Algorithmic detection enabled.\n"); options |= CL_SCAN_ALGORITHMIC; } else { logg("Algorithmic detection disabled.\n"); } if(optget(opts, "ScanPE")->enabled) { logg("Portable Executable support enabled.\n"); options |= CL_SCAN_PE; } else { logg("Portable Executable support disabled.\n"); } if(optget(opts, "ScanELF")->enabled) { logg("ELF support enabled.\n"); options |= CL_SCAN_ELF; } else { logg("ELF support disabled.\n"); } if(optget(opts, "ScanPE")->enabled || optget(opts, "ScanELF")->enabled) { if(optget(opts, "DetectBrokenExecutables")->enabled) { logg("Detection of broken executables enabled.\n"); options |= CL_SCAN_BLOCKBROKEN; } } if(optget(opts, "ScanMail")->enabled) { logg("Mail files support enabled.\n"); options |= CL_SCAN_MAIL; if(optget(opts, "ScanPartialMessages")->enabled) { logg("Mail: RFC1341 handling enabled.\n"); options |= CL_SCAN_PARTIAL_MESSAGE; } } else { logg("Mail files support disabled.\n"); } if(optget(opts, "ScanOLE2")->enabled) { logg("OLE2 support enabled.\n"); options |= CL_SCAN_OLE2; if(optget(opts, "OLE2BlockMacros")->enabled) { logg("OLE2: Blocking all VBA macros.\n"); options |= CL_SCAN_BLOCKMACROS; } } else { logg("OLE2 support disabled.\n"); } if(optget(opts, "ScanPDF")->enabled) { logg("PDF support enabled.\n"); options |= CL_SCAN_PDF; } else { logg("PDF support disabled.\n"); } if(optget(opts, "ScanHTML")->enabled) { logg("HTML support enabled.\n"); options |= CL_SCAN_HTML; } else { logg("HTML support disabled.\n"); } if(optget(opts,"PhishingScanURLs")->enabled) { if(optget(opts,"PhishingAlwaysBlockCloak")->enabled) { options |= CL_SCAN_PHISHING_BLOCKCLOAK; logg("Phishing: Always checking for cloaked urls\n"); } if(optget(opts,"PhishingAlwaysBlockSSLMismatch")->enabled) { options |= CL_SCAN_PHISHING_BLOCKSSL; logg("Phishing: Always checking for ssl mismatches\n"); } } if(optget(opts,"HeuristicScanPrecedence")->enabled) { options |= CL_SCAN_HEURISTIC_PRECEDENCE; logg("Heuristic: precedence enabled\n"); } if(optget(opts, "StructuredDataDetection")->enabled) { options |= CL_SCAN_STRUCTURED; if((opt = optget(opts, "StructuredMinCreditCardCount"))->enabled) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MIN_CC_COUNT, opt->numarg))) { logg("!cl_engine_set_num(CL_ENGINE_MIN_CC_COUNT) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MIN_CC_COUNT, NULL); logg("Structured: Minimum Credit Card Number Count set to %u\n", (unsigned int) val); if((opt = optget(opts, "StructuredMinSSNCount"))->enabled) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MIN_SSN_COUNT, opt->numarg))) { logg("!cl_engine_set_num(CL_ENGINE_MIN_SSN_COUNT) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MIN_SSN_COUNT, NULL); logg("Structured: Minimum Social Security Number Count set to %u\n", (unsigned int) val); if(optget(opts, "StructuredSSNFormatNormal")->enabled) options |= CL_SCAN_STRUCTURED_SSN_NORMAL; if(optget(opts, "StructuredSSNFormatStripped")->enabled) options |= CL_SCAN_STRUCTURED_SSN_STRIPPED; } #ifdef HAVE__INTERNAL__SHA_COLLECT if(optget(opts, "DevCollectHashes")->enabled) options |= CL_SCAN_INTERNAL_COLLECT_SHA; #endif selfchk = optget(opts, "SelfCheck")->numarg; if(!selfchk) { logg("Self checking disabled.\n"); } else { logg("Self checking every %u seconds.\n", selfchk); } memset(&dbstat, 0, sizeof(dbstat)); /* save the PID */ mainpid = getpid(); if((opt = optget(opts, "PidFile"))->enabled) { FILE *fd; old_umask = umask(0002); if((fd = fopen(opt->strarg, "w")) == NULL) { logg("!Can't save PID in file %s\n", opt->strarg); } else { if (fprintf(fd, "%u", (unsigned int) mainpid)<0) { logg("!Can't save PID in file %s\n", opt->strarg); } fclose(fd); } umask(old_umask); } logg("*Listening daemon: PID: %u\n", (unsigned int) mainpid); max_threads = optget(opts, "MaxThreads")->numarg; max_queue = optget(opts, "MaxQueue")->numarg; acceptdata.commandtimeout = optget(opts, "CommandReadTimeout")->numarg; readtimeout = optget(opts, "ReadTimeout")->numarg; #if !defined(_WIN32) && defined(RLIMIT_NOFILE) if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) { /* don't warn if default value is too high, silently fix it */ unsigned maxrec; int max_max_queue; unsigned warn = optget(opts, "MaxQueue")->active; const unsigned clamdfiles = 6; /* Condition to not run out of file descriptors: * MaxThreads * MaxRecursion + (MaxQueue - MaxThreads) + CLAMDFILES < RLIMIT_NOFILE * CLAMDFILES is 6: 3 standard FD + logfile + 2 FD for reloading the DB * */ #ifdef C_SOLARIS #ifdef HAVE_ENABLE_EXTENDED_FILE_STDIO if (enable_extended_FILE_stdio(-1, -1) == -1) { logg("^Unable to set extended FILE stdio, clamd will be limited to max 256 open files\n"); rlim.rlim_cur = rlim.rlim_cur > 255 ? 255 : rlim.rlim_cur; } #elif !defined(_LP64) if (rlim.rlim_cur > 255) { rlim.rlim_cur = 255; logg("^Solaris only supports 256 open files for 32-bit processes, you need at least Solaris 10u4, or compile as 64-bit to support more!\n"); } #endif #endif opt = optget(opts,"MaxRecursion"); maxrec = opt->numarg; max_max_queue = rlim.rlim_cur - maxrec * max_threads - clamdfiles + max_threads; if (max_queue < max_threads) { max_queue = max_threads; if (warn) logg("^MaxQueue value too low, increasing to: %d\n", max_queue); } if (max_max_queue < max_threads) { logg("^MaxThreads * MaxRecursion is too high: %d, open file descriptor limit is: %lu\n", maxrec*max_threads, (unsigned long)rlim.rlim_cur); max_max_queue = max_threads; } if (max_queue > max_max_queue) { max_queue = max_max_queue; if (warn) logg("^MaxQueue value too high, lowering to: %d\n", max_queue); } else if (max_queue < 2*max_threads && max_queue < max_max_queue) { max_queue = 2*max_threads; if (max_queue > max_max_queue) max_queue = max_max_queue; /* always warn here */ logg("^MaxQueue is lower than twice MaxThreads, increasing to: %d\n", max_queue); } } #endif logg("*MaxQueue set to: %d\n", max_queue); acceptdata.max_queue = max_queue; if(optget(opts, "ScanOnAccess")->enabled) #if defined(FANOTIFY) || defined(CLAMAUTH) { do { if(pthread_attr_init(&fan_attr)) break; pthread_attr_setdetachstate(&fan_attr, PTHREAD_CREATE_JOINABLE); if(!(tharg = (struct thrarg *) malloc(sizeof(struct thrarg)))) break; tharg->opts = opts; tharg->engine = engine; tharg->options = options; if(!pthread_create(&fan_pid, &fan_attr, fan_th, tharg)) break; free(tharg); tharg=NULL; } while(0); if (!tharg) logg("!Unable to start on-access scan\n"); } #else logg("!On-access scan is not available\n"); #endif #ifndef _WIN32 /* set up signal handling */ sigfillset(&sigset); sigdelset(&sigset, SIGINT); sigdelset(&sigset, SIGTERM); sigdelset(&sigset, SIGSEGV); sigdelset(&sigset, SIGHUP); sigdelset(&sigset, SIGPIPE); sigdelset(&sigset, SIGUSR2); /* The behavior of a process is undefined after it ignores a * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */ sigdelset(&sigset, SIGFPE); sigdelset(&sigset, SIGILL); sigdelset(&sigset, SIGSEGV); #ifdef SIGBUS sigdelset(&sigset, SIGBUS); #endif sigdelset(&sigset, SIGTSTP); sigdelset(&sigset, SIGCONT); 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); sigaddset(&sigact.sa_mask, SIGPIPE); sigaddset(&sigact.sa_mask, SIGUSR2); sigaction(SIGINT, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); sigaction(SIGHUP, &sigact, NULL); sigaction(SIGPIPE, &sigact, NULL); sigaction(SIGUSR2, &sigact, NULL); #endif idletimeout = optget(opts, "IdleTimeout")->numarg; for (i=0;i < nsockets;i++) if (fds_add(&acceptdata.fds, socketds[i], 1, 0) == -1) { logg("!fds_add failed\n"); cl_engine_free(engine); return 1; } #ifdef _WIN32 event_wake_accept = CreateEvent(NULL, TRUE, FALSE, NULL); event_wake_recv = CreateEvent(NULL, TRUE, FALSE, NULL); #else if (pipe(acceptdata.syncpipe_wake_recv) == -1 || (pipe(acceptdata.syncpipe_wake_accept) == -1)) { logg("!pipe failed\n"); exit(-1); } syncpipe_wake_recv_w = acceptdata.syncpipe_wake_recv[1]; if (fds_add(fds, acceptdata.syncpipe_wake_recv[0], 1, 0) == -1 || fds_add(&acceptdata.fds, acceptdata.syncpipe_wake_accept[0], 1, 0)) { logg("!failed to add pipe fd\n"); exit(-1); } #endif if ((thr_pool = thrmgr_new(max_threads, idletimeout, max_queue, scanner_thread)) == NULL) { logg("!thrmgr_new failed\n"); exit(-1); } if (pthread_create(&accept_th, NULL, acceptloop_th, &acceptdata)) { logg("!pthread_create failed\n"); exit(-1); } time(&start_time); for(;;) { int new_sd; /* Block waiting for connection on any of the sockets */ pthread_mutex_lock(fds->buf_mutex); fds_cleanup(fds); /* signal that we can accept more connections */ if (fds->nfds <= (unsigned)max_queue) pthread_cond_signal(&acceptdata.cond_nfds); new_sd = fds_poll_recv(fds, selfchk ? (int)selfchk : -1, 1, event_wake_recv); #ifdef _WIN32 ResetEvent(event_wake_recv); #else if (!fds->nfds) { /* at least the dummy/sync pipe should have remained */ logg("!All recv() descriptors gone: fatal\n"); pthread_mutex_lock(&exit_mutex); progexit = 1; pthread_mutex_unlock(&exit_mutex); pthread_mutex_unlock(fds->buf_mutex); break; } #endif if (new_sd == -1 && errno != EINTR) { logg("!Failed to poll sockets, fatal\n"); pthread_mutex_lock(&exit_mutex); progexit = 1; pthread_mutex_unlock(&exit_mutex); } if(fds->nfds) i = (rr_last + 1) % fds->nfds; for (j = 0; j < fds->nfds && new_sd >= 0; j++, i = (i+1) % fds->nfds) { size_t pos = 0; int error = 0; struct fd_buf *buf = &fds->buf[i]; if (!buf->got_newdata) continue; #ifndef _WIN32 if (buf->fd == acceptdata.syncpipe_wake_recv[0]) { /* dummy sync pipe, just to wake us */ if (read(buf->fd, buff, sizeof(buff)) < 0) { logg("^Syncpipe read failed\n"); } continue; } #endif if (buf->got_newdata == -1) { if (buf->mode == MODE_WAITREPLY) { logg("$mode WAIT_REPLY -> closed\n"); buf->fd = -1; thrmgr_group_terminate(buf->group); thrmgr_group_finished(buf->group, EXIT_ERROR); continue; } else { logg("$client read error or EOF on read\n"); error = 1; } } if (buf->fd != -1 && buf->got_newdata == -2) { logg("$Client read timed out\n"); mdprintf(buf->fd, "COMMAND READ TIMED OUT\n"); error = 1; } rr_last = i; if (buf->mode == MODE_WAITANCILL) { buf->mode = MODE_COMMAND; logg("$mode -> MODE_COMMAND\n"); } while (!error && buf->fd != -1 && buf->buffer && pos < buf->off && buf->mode != MODE_WAITANCILL) { client_conn_t conn; const char *cmd = NULL; int rc; /* New data available to read on socket. */ memset(&conn, 0, sizeof(conn)); conn.scanfd = buf->recvfd; buf->recvfd = -1; conn.sd = buf->fd; conn.options = options; conn.opts = opts; conn.thrpool = thr_pool; conn.engine = engine; conn.group = buf->group; conn.id = buf->id; conn.json = buf->json; conn.quota = buf->quota; conn.filename = buf->dumpname; conn.mode = buf->mode; conn.term = buf->term; /* Parse & dispatch command */ cmd = parse_dispatch_cmd(&conn, buf, &pos, &error, opts, readtimeout); if (conn.mode == MODE_COMMAND && !cmd) break; if (!error) { if (buf->mode == MODE_WAITREPLY && buf->off) { /* Client is not supposed to send anything more */ logg("^Client sent garbage after last command: %lu bytes\n", (unsigned long)buf->off); buf->buffer[buf->off] = '\0'; logg("$Garbage: %s\n", buf->buffer); error = 1; } else if (buf->mode == MODE_STREAM) { rc = handle_stream(&conn, buf, opts, &error, &pos, readtimeout); if (rc == -1) break; else continue; } } if (error && error != CL_ETIMEOUT) { conn_reply_error(&conn, "Error processing command."); } } if (error) { if (buf->dumpfd != -1) { close(buf->dumpfd); if (buf->dumpname) { cli_unlink(buf->dumpname); free(buf->dumpname); } buf->dumpfd = -1; } thrmgr_group_terminate(buf->group); if (thrmgr_group_finished(buf->group, EXIT_ERROR)) { logg("$Shutting down socket after error (FD %d)\n", buf->fd); shutdown(buf->fd, 2); closesocket(buf->fd); } else logg("$Socket not shut down due to active tasks\n"); buf->fd = -1; } } pthread_mutex_unlock(fds->buf_mutex); /* handle progexit */ pthread_mutex_lock(&exit_mutex); if (progexit) { pthread_mutex_unlock(&exit_mutex); pthread_mutex_lock(fds->buf_mutex); for (i=0;i < fds->nfds; i++) { if (fds->buf[i].fd == -1) continue; thrmgr_group_terminate(fds->buf[i].group); if (thrmgr_group_finished(fds->buf[i].group, EXIT_ERROR)) { logg("$Shutdown closed fd %d\n", fds->buf[i].fd); shutdown(fds->buf[i].fd, 2); closesocket(fds->buf[i].fd); fds->buf[i].fd = -1; } } pthread_mutex_unlock(fds->buf_mutex); break; } pthread_mutex_unlock(&exit_mutex); /* SIGHUP */ if (sighup) { logg("SIGHUP caught: re-opening log file.\n"); logg_close(); sighup = 0; if(!logg_file && (opt = optget(opts, "LogFile"))->enabled) logg_file = opt->strarg; } /* SelfCheck */ if(selfchk) { time(¤t_time); if((current_time - start_time) >= (time_t)selfchk) { if(reload_db(engine, dboptions, opts, TRUE, &ret)) { pthread_mutex_lock(&reload_mutex); reload = 1; pthread_mutex_unlock(&reload_mutex); } time(&start_time); } } /* DB reload */ pthread_mutex_lock(&reload_mutex); if(reload) { pthread_mutex_unlock(&reload_mutex); #if defined(FANOTIFY) || defined(CLAMAUTH) if(optget(opts, "ScanOnAccess")->enabled && tharg) { logg("Restarting on-access scan\n"); pthread_kill(fan_pid, SIGUSR1); pthread_join(fan_pid, NULL); } #endif engine = reload_db(engine, dboptions, opts, FALSE, &ret); if(ret) { logg("Terminating because of a fatal error.\n"); if(new_sd >= 0) closesocket(new_sd); break; } pthread_mutex_lock(&reload_mutex); reload = 0; time(&reloaded_time); pthread_mutex_unlock(&reload_mutex); #if defined(FANOTIFY) || defined(CLAMAUTH) if(optget(opts, "ScanOnAccess")->enabled && tharg) { tharg->engine = engine; pthread_create(&fan_pid, &fan_attr, fan_th, tharg); } #endif time(&start_time); } else { pthread_mutex_unlock(&reload_mutex); } } pthread_mutex_lock(&exit_mutex); progexit = 1; pthread_mutex_unlock(&exit_mutex); #ifdef _WIN32 SetEvent(event_wake_accept); #else if (write(acceptdata.syncpipe_wake_accept[1], "", 1) < 0) { logg("^Write to syncpipe failed\n"); } #endif /* Destroy the thread manager. * This waits for all current tasks to end */ logg("*Waiting for all threads to finish\n"); thrmgr_destroy(thr_pool); #if defined(FANOTIFY) || defined(CLAMAUTH) if(optget(opts, "ScanOnAccess")->enabled) { logg("Stopping on-access scan\n"); pthread_kill(fan_pid, SIGUSR1); pthread_join(fan_pid, NULL); } #endif if(engine) { thrmgr_setactiveengine(NULL); cl_engine_free(engine); } pthread_join(accept_th, NULL); fds_free(fds); pthread_mutex_destroy(fds->buf_mutex); pthread_cond_destroy(&acceptdata.cond_nfds); #ifdef _WIN32 CloseHandle(event_wake_accept); CloseHandle(event_wake_recv); #else close(acceptdata.syncpipe_wake_accept[1]); close(acceptdata.syncpipe_wake_recv[1]); #endif if(dbstat.entries) cl_statfree(&dbstat); logg("*Shutting down the main socket%s.\n", (nsockets > 1) ? "s" : ""); for (i = 0; i < nsockets; i++) shutdown(socketds[i], 2); if((opt = optget(opts, "PidFile"))->enabled) { if(unlink(opt->strarg) == -1) logg("!Can't unlink the pid file %s\n", opt->strarg); else logg("Pid file removed.\n"); } time(¤t_time); logg("--- Stopped at %s", cli_ctime(¤t_time, timestr, sizeof(timestr))); return ret; }
int scanmanager(const struct optstruct *opts) { int ret = 0, i; unsigned int options = 0, dboptions = 0, dirlnk = 1, filelnk = 1; struct cl_engine *engine; STATBUF sb; char *file, cwd[1024], *pua_cats = NULL; const char *filename; const struct optstruct *opt; #ifndef _WIN32 struct rlimit rlim; #endif dirlnk = optget(opts, "follow-dir-symlinks")->numarg; if(dirlnk > 2) { logg("!--follow-dir-symlinks: Invalid argument\n"); return 2; } filelnk = optget(opts, "follow-file-symlinks")->numarg; if(filelnk > 2) { logg("!--follow-file-symlinks: Invalid argument\n"); return 2; } if(optget(opts, "phishing-sigs")->enabled) dboptions |= CL_DB_PHISHING; if(optget(opts, "official-db-only")->enabled) dboptions |= CL_DB_OFFICIAL_ONLY; if(optget(opts,"phishing-scan-urls")->enabled) dboptions |= CL_DB_PHISHING_URLS; if(optget(opts,"bytecode")->enabled) dboptions |= CL_DB_BYTECODE; if((ret = cl_init(CL_INIT_DEFAULT))) { logg("!Can't initialize libclamav: %s\n", cl_strerror(ret)); return 2; } if(!(engine = cl_engine_new())) { logg("!Can't initialize antivirus engine\n"); return 2; } if(optget(opts, "detect-pua")->enabled) { dboptions |= CL_DB_PUA; if((opt = optget(opts, "exclude-pua"))->enabled) { dboptions |= CL_DB_PUA_EXCLUDE; i = 0; while(opt) { if(!(pua_cats = realloc(pua_cats, i + strlen(opt->strarg) + 3))) { logg("!Can't allocate memory for pua_cats\n"); cl_engine_free(engine); return 2; } sprintf(pua_cats + i, ".%s", opt->strarg); i += strlen(opt->strarg) + 1; pua_cats[i] = 0; opt = opt->nextarg; } pua_cats[i] = '.'; pua_cats[i + 1] = 0; } if((opt = optget(opts, "include-pua"))->enabled) { if(pua_cats) { logg("!--exclude-pua and --include-pua cannot be used at the same time\n"); cl_engine_free(engine); free(pua_cats); return 2; } dboptions |= CL_DB_PUA_INCLUDE; i = 0; while(opt) { if(!(pua_cats = realloc(pua_cats, i + strlen(opt->strarg) + 3))) { logg("!Can't allocate memory for pua_cats\n"); cl_engine_free(engine); return 2; } sprintf(pua_cats + i, ".%s", opt->strarg); i += strlen(opt->strarg) + 1; pua_cats[i] = 0; opt = opt->nextarg; } pua_cats[i] = '.'; pua_cats[i + 1] = 0; } if(pua_cats) { if((ret = cl_engine_set_str(engine, CL_ENGINE_PUA_CATEGORIES, pua_cats))) { logg("!cli_engine_set_str(CL_ENGINE_PUA_CATEGORIES) failed: %s\n", cl_strerror(ret)); free(pua_cats); cl_engine_free(engine); return 2; } free(pua_cats); } } if(optget(opts, "dev-ac-only")->enabled) cl_engine_set_num(engine, CL_ENGINE_AC_ONLY, 1); if(optget(opts, "dev-ac-depth")->enabled) cl_engine_set_num(engine, CL_ENGINE_AC_MAXDEPTH, optget(opts, "dev-ac-depth")->numarg); if(optget(opts, "leave-temps")->enabled) cl_engine_set_num(engine, CL_ENGINE_KEEPTMP, 1); if(optget(opts, "bytecode-unsigned")->enabled) dboptions |= CL_DB_BYTECODE_UNSIGNED; if((opt = optget(opts,"bytecode-timeout"))->enabled) cl_engine_set_num(engine, CL_ENGINE_BYTECODE_TIMEOUT, opt->numarg); if((opt = optget(opts,"bytecode-mode"))->enabled) { enum bytecode_mode mode; if (!strcmp(opt->strarg, "ForceJIT")) mode = CL_BYTECODE_MODE_JIT; else if(!strcmp(opt->strarg, "ForceInterpreter")) mode = CL_BYTECODE_MODE_INTERPRETER; else if(!strcmp(opt->strarg, "Test")) mode = CL_BYTECODE_MODE_TEST; else mode = CL_BYTECODE_MODE_AUTO; cl_engine_set_num(engine, CL_ENGINE_BYTECODE_MODE, mode); } if((opt = optget(opts, "tempdir"))->enabled) { if((ret = cl_engine_set_str(engine, CL_ENGINE_TMPDIR, opt->strarg))) { logg("!cli_engine_set_str(CL_ENGINE_TMPDIR) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if((opt = optget(opts, "database"))->active) { while(opt) { if((ret = cl_load(opt->strarg, engine, &info.sigs, dboptions))) { logg("!%s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } opt = opt->nextarg; } } else { char *dbdir = freshdbdir(); if((ret = cl_load(dbdir, engine, &info.sigs, dboptions))) { logg("!%s\n", cl_strerror(ret)); free(dbdir); cl_engine_free(engine); return 2; } free(dbdir); } if((ret = cl_engine_compile(engine)) != 0) { logg("!Database initialization error: %s\n", cl_strerror(ret));; cl_engine_free(engine); return 2; } if(optget(opts, "archive-verbose")->enabled) { cl_engine_set_clcb_meta(engine, meta); cl_engine_set_clcb_pre_cache(engine, pre); cl_engine_set_clcb_post_scan(engine, post); } /* set limits */ if((opt = optget(opts, "max-scansize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANSIZE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if((opt = optget(opts, "max-filesize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_FILESIZE, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_FILESIZE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } #ifndef _WIN32 if(getrlimit(RLIMIT_FSIZE, &rlim) == 0) { if(rlim.rlim_cur < (rlim_t) cl_engine_get_num(engine, CL_ENGINE_MAX_FILESIZE, NULL)) logg("^System limit for file size is lower than engine->maxfilesize\n"); if(rlim.rlim_cur < (rlim_t) cl_engine_get_num(engine, CL_ENGINE_MAX_SCANSIZE, NULL)) logg("^System limit for file size is lower than engine->maxscansize\n"); } else { logg("^Cannot obtain resource limits for file size\n"); } #endif if((opt = optget(opts, "max-files"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_FILES, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_FILES) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if((opt = optget(opts, "max-recursion"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_RECURSION, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_RECURSION) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } /* set scan options */ if(optget(opts,"phishing-ssl")->enabled) options |= CL_SCAN_PHISHING_BLOCKSSL; if(optget(opts,"phishing-cloak")->enabled) options |= CL_SCAN_PHISHING_BLOCKCLOAK; if(optget(opts,"heuristic-scan-precedence")->enabled) options |= CL_SCAN_HEURISTIC_PRECEDENCE; if(optget(opts, "scan-archive")->enabled) options |= CL_SCAN_ARCHIVE; if(optget(opts, "detect-broken")->enabled) options |= CL_SCAN_BLOCKBROKEN; if(optget(opts, "block-encrypted")->enabled) options |= CL_SCAN_BLOCKENCRYPTED; if(optget(opts, "scan-pe")->enabled) options |= CL_SCAN_PE; if(optget(opts, "scan-elf")->enabled) options |= CL_SCAN_ELF; if(optget(opts, "scan-ole2")->enabled) options |= CL_SCAN_OLE2; if(optget(opts, "scan-pdf")->enabled) options |= CL_SCAN_PDF; if(optget(opts, "scan-html")->enabled) options |= CL_SCAN_HTML; if(optget(opts, "scan-mail")->enabled) options |= CL_SCAN_MAIL; if(optget(opts, "algorithmic-detection")->enabled) options |= CL_SCAN_ALGORITHMIC; #ifdef HAVE__INTERNAL__SHA_COLLECT if(optget(opts, "dev-collect-hashes")->enabled) options |= CL_SCAN_INTERNAL_COLLECT_SHA; #endif if(optget(opts, "dev-performance")->enabled) options |= CL_SCAN_PERFORMANCE_INFO; if(optget(opts, "detect-structured")->enabled) { options |= CL_SCAN_STRUCTURED; if((opt = optget(opts, "structured-ssn-format"))->enabled) { switch(opt->numarg) { case 0: options |= CL_SCAN_STRUCTURED_SSN_NORMAL; break; case 1: options |= CL_SCAN_STRUCTURED_SSN_STRIPPED; break; case 2: options |= (CL_SCAN_STRUCTURED_SSN_NORMAL | CL_SCAN_STRUCTURED_SSN_STRIPPED); break; default: logg("!Invalid argument for --structured-ssn-format\n"); return 2; } } else { options |= CL_SCAN_STRUCTURED_SSN_NORMAL; } if((opt = optget(opts, "structured-ssn-count"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MIN_SSN_COUNT, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MIN_SSN_COUNT) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if((opt = optget(opts, "structured-cc-count"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MIN_CC_COUNT, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MIN_CC_COUNT) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } } else { options &= ~CL_SCAN_STRUCTURED; } #ifdef C_LINUX procdev = (dev_t) 0; if(STAT("/proc", &sb) != -1 && !sb.st_size) procdev = sb.st_dev; #endif /* check filetype */ if(!opts->filename && !optget(opts, "file-list")->enabled) { /* we need full path for some reasons (eg. archive handling) */ if(!getcwd(cwd, sizeof(cwd))) { logg("!Can't get absolute pathname of current working directory\n"); ret = 2; } else { STAT(cwd, &sb); scandirs(cwd, engine, opts, options, 1, sb.st_dev); } } else if(opts->filename && !optget(opts, "file-list")->enabled && !strcmp(opts->filename[0], "-")) { /* read data from stdin */ ret = scanstdin(engine, opts, options); } else { if(opts->filename && optget(opts, "file-list")->enabled) logg("^Only scanning files from --file-list (files passed at cmdline are ignored)\n"); while((filename = filelist(opts, &ret)) && (file = strdup(filename))) { if(LSTAT(file, &sb) == -1) { logg("^%s: Can't access file\n", file); perror(file); ret = 2; } else { for(i = strlen(file) - 1; i > 0; i--) { if(file[i] == *PATHSEP) file[i] = 0; else break; } if(S_ISLNK(sb.st_mode)) { if(dirlnk == 0 && filelnk == 0) { if(!printinfected) logg("%s: Symbolic link\n", file); } else if(STAT(file, &sb) != -1) { if(S_ISREG(sb.st_mode) && filelnk) { scanfile(file, engine, opts, options); } else if(S_ISDIR(sb.st_mode) && dirlnk) { scandirs(file, engine, opts, options, 1, sb.st_dev); } else { if(!printinfected) logg("%s: Symbolic link\n", file); } } } else if(S_ISREG(sb.st_mode)) { scanfile(file, engine, opts, options); } else if(S_ISDIR(sb.st_mode)) { scandirs(file, engine, opts, options, 1, sb.st_dev); } else { logg("^%s: Not supported file type\n", file); ret = 2; } } free(file); } } /* free the engine */ cl_engine_free(engine); /* overwrite return code - infection takes priority */ if(info.ifiles) ret = 1; else if(info.errors) ret = 2; return ret; }