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((opt = optget(opts, "MaxPartitions"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_PARTITIONS, opt->numarg))) { logg("!cli_engine_set_num(MaxPartitions) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_PARTITIONS, NULL); logg("Limits: MaxPartitions limit set to %llu.\n", val); if((opt = optget(opts, "MaxIconsPE"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_ICONSPE, opt->numarg))) { logg("!cli_engine_set_num(MaxIconsPE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_MAX_ICONSPE, NULL); logg("Limits: MaxIconsPE limit set to %llu.\n", val); if((opt = optget(opts, "PCREMatchLimit"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_MATCH_LIMIT, opt->numarg))) { logg("!cli_engine_set_num(PCREMatchLimit) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_PCRE_MATCH_LIMIT, NULL); logg("Limits: PCREMatchLimit limit set to %llu.\n", val); if((opt = optget(opts, "PCRERecMatchLimit"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_RECMATCH_LIMIT, opt->numarg))) { logg("!cli_engine_set_num(PCRERecMatchLimit) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_PCRE_RECMATCH_LIMIT, NULL); logg("Limits: PCRERecMatchLimit limit set to %llu.\n", val); if((opt = optget(opts, "PCREMaxFileSize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_MAX_FILESIZE, opt->numarg))) { logg("!cli_engine_set_num(PCREMaxFileSize) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 1; } } val = cl_engine_get_num(engine, CL_ENGINE_PCRE_MAX_FILESIZE, NULL); logg("Limits: PCREMaxFileSize limit set to %llu.\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, "ScanSWF")->enabled) { logg("SWF support enabled.\n"); options |= CL_SCAN_SWF; } else { logg("SWF 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,"PartitionIntersection")->enabled) { options |= CL_SCAN_PARTITION_INTXN; logg("Raw DMG: Always checking for partitons intersections\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); } /* 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\n", (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, onas_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.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)) { if (buf->fd < 0) { logg("$Skipping shutdown of bad socket after error (FD %d)\n", buf->fd); } else { 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); 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; } #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 && tharg) { logg("Stopping on-access scan\n"); pthread_mutex_lock(&logg_mutex); pthread_kill(fan_pid, SIGUSR1); pthread_mutex_unlock(&logg_mutex); pthread_join(fan_pid, NULL); free(tharg); } #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; }
/* TODO: handle ReadTimeout */ int fds_poll_recv (struct fd_data *data, int timeout, int check_signals, void *event) { unsigned fdsok = data->nfds; size_t i; int retval; time_t now, closest_timeout; UNUSEDPARAM(event); /* we must have at least one fd, the control fd! */ fds_cleanup (data); #ifndef _WIN32 if (!data->nfds) return 0; #endif for (i = 0; i < data->nfds; i++) { data->buf[i].got_newdata = 0; } time (&now); if (timeout > 0) closest_timeout = now + timeout; else closest_timeout = 0; for (i = 0; i < data->nfds; i++) { time_t timeout_at = data->buf[i].timeout_at; if (timeout_at && timeout_at < now) { /* timed out */ data->buf[i].got_newdata = -2; /* we must return immediately from poll/select, we have a timeout! */ closest_timeout = now; } else { if (!closest_timeout) closest_timeout = timeout_at; else if (timeout_at && timeout_at < closest_timeout) closest_timeout = timeout_at; } } if (closest_timeout) timeout = closest_timeout - now; else timeout = -1; if (timeout > 0) logg ("$fds_poll_recv: timeout after %d seconds\n", timeout); #ifdef HAVE_POLL /* Use poll() if available, preferred because: * - can poll any number of FDs * - can notify of both data available / socket disconnected events * - when it says POLLIN it is guaranteed that a following recv() won't * block (select may say that data is available to read, but a following * recv() may still block according to the manpage */ if (realloc_polldata (data) == -1) return -1; if (timeout > 0) { /* seconds to ms */ timeout *= 1000; } for (i = 0; i < data->nfds; i++) { data->poll_data[i].fd = data->buf[i].fd; data->poll_data[i].events = POLLIN; data->poll_data[i].revents = 0; } do { int n = data->nfds; fds_unlock (data); #ifdef _WIN32 retval = poll_with_event (data->poll_data, n, timeout, event); #else retval = poll (data->poll_data, n, timeout); #endif fds_lock (data); if (retval > 0) { fdsok = 0; /* nfds may change during poll, but not * poll_data_nfds */ for (i = 0; i < data->poll_data_nfds; i++) { short revents; if (data->buf[i].fd < 0) continue; if (data->buf[i].fd != data->poll_data[i].fd) { /* should never happen */ logg ("!poll_recv_fds FD mismatch\n"); continue; } revents = data->poll_data[i].revents; if (revents & (POLLIN | POLLHUP)) { logg ("$Received POLLIN|POLLHUP on fd %d\n", data->poll_data[i].fd); } #ifndef _WIN32 if (revents & POLLHUP) { /* avoid SHUT_WR problem on Mac OS X */ int ret = send (data->poll_data[i].fd, &n, 0, 0); if (!ret || (ret == -1 && errno == EINTR)) revents &= ~POLLHUP; } #endif if (revents & POLLIN) { int ret = read_fd_data (&data->buf[i]); /* Data available to be read */ if (ret == -1) revents |= POLLERR; else if (!ret) revents = POLLHUP; } if (revents & (POLLHUP | POLLERR | POLLNVAL)) { if (revents & (POLLHUP | POLLNVAL)) { /* remote disconnected */ logg ("*Client disconnected (FD %d)\n", data->poll_data[i].fd); } else { /* error on file descriptor */ logg ("^Error condition on fd %d\n", data->poll_data[i].fd); } data->buf[i].got_newdata = -1; } else { fdsok++; } } } } while (retval == -1 && !check_signals && errno == EINTR); #else { fd_set rfds; struct timeval tv; int maxfd = -1; for (i = 0; i < data->nfds; i++) { int fd = data->buf[i].fd; if (fd >= FD_SETSIZE) { logg ("!File descriptor is too high for FD_SET\n"); return -1; } maxfd = MAX (maxfd, fd); } do { FD_ZERO (&rfds); for (i = 0; i < data->nfds; i++) { int fd = data->buf[i].fd; if (fd >= 0) FD_SET (fd, &rfds); } tv.tv_sec = timeout; tv.tv_usec = 0; fds_unlock (data); retval = select (maxfd + 1, &rfds, NULL, NULL, timeout >= 0 ? &tv : NULL); fds_lock (data); if (retval > 0) { fdsok = data->nfds; for (i = 0; i < data->nfds; i++) { if (data->buf[i].fd < 0) { fdsok--; continue; } if (FD_ISSET (data->buf[i].fd, &rfds)) { int ret = read_fd_data (&data->buf[i]); if (ret == -1 || !ret) { if (ret == -1) logg ("!Error condition on fd %d\n", data->buf[i].fd); else { /* avoid SHUT_WR problem on Mac OS X */ int ret = send (data->buf[i].fd, &i, 0, 0); if (!ret || (ret == -1 && errno == EINTR)) continue; logg ("*Client disconnected\n"); } data->buf[i].got_newdata = -1; } } } } if (retval < 0 && errno == EBADF) { /* unlike poll(), select() won't tell us which FD is bad, so * we have to check them one by one. */ tv.tv_sec = 0; tv.tv_usec = 0; /* with tv == 0 it doesn't check for EBADF */ FD_ZERO (&rfds); for (i = 0; i < data->nfds; i++) { if (data->buf[i].fd == -1) continue; FD_SET (data->buf[i].fd, &rfds); do { retval = select (data->buf[i].fd + 1, &rfds, NULL, NULL, &tv); } while (retval == -1 && errno == EINTR); if (retval == -1) { data->buf[i].fd = -1; } else { FD_CLR (data->buf[i].fd, &rfds); } } retval = -1; errno = EINTR; continue; } } while (retval == -1 && !check_signals && errno == EINTR); } #endif if (retval == -1 && errno != EINTR) { char err[128]; #ifdef HAVE_POLL logg ("!poll_recv_fds: poll failed: %s\n", cli_strerror (errno, err, sizeof (err))); #else logg ("!poll_recv_fds: select failed: %s\n", cli_strerror (errno, err, sizeof (err))); #endif } return retval; }